2026-06-22 20:03:09 +08:00

614 lines
28 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* @Author: 白哉
* @Description:
* @Date: 2025年11月15日 星期五
* @Modify:
*/
using System;
using System.Collections.Generic;
using Logic.Action;
using MemoryPack;
using RuntimeData;
using TH1_Anim.Fragments;
using TH1_DataAssetsScript;
using TH1_Logic.Core;
using TH1Renderer;
using UnityEngine;
namespace Logic.Skill
{
// 大吉/吉/凶/大凶 四种等级
public enum SanaeDivineType
{
BigLucky,
Lucky,
Unlucky,
BigUnlucky
}
public partial class SanaeDivineSkill : SkillBase
{
private int _bigLuckyDmg = 4;
private int _bigLuckyHeal = 4;
private int _bigUnluckyEnemyDmg = 6;
private int _bigUnluckyFriendDmg = 3;
private class OmikujiActionContext
{
public bool ForceFirstExtreme;
public bool IsFirstRoll = true;
public bool Rolled;
public bool HasExtreme;
public void Record(SanaeDivineType divine)
{
Rolled = true;
if (IsExtreme(divine)) HasExtreme = true;
}
}
public SanaeDivineSkill()
{
IsPermanent = true;
TurnsLimit = 0;
Score = 2;
}
public override SkillType GetSkillType()
{
return SkillType.SANAEDIVINE;
}
public override void OnHealOther(MapData mapData, UnitData origin,UnitData target, HealType healType)
{
if (healType != HealType.AttackAllyHeal) return;
if (mapData == null || origin == null || target == null) return;
var originId = origin.Id;
var targetId = target.Id;
if (!TryGetLiveUnit(mapData, originId, out origin) || !TryGetLiveUnit(mapData, targetId, out target)) return;
var omikujiContext = CreateOmikujiActionContext(origin);
try
{
//处理三连发
if (origin.Skills != null && origin.GetSkill(SkillType.SANAENINE, out var _))
{
bool again = false;
for (int i = 0; i < 3; i++)
{
if (!TryGetLiveUnit(mapData, originId, out origin) || !TryGetLiveUnit(mapData, targetId, out target)) return;
var divine = GetBuff(mapData, origin,true, out var skill, omikujiContext);
if(divine is SanaeDivineType.BigUnlucky or SanaeDivineType.BigLucky)
again = true;
//Debug.Log(divine);
var timer = Timer.Instance;
System.Action playOmikuji = () =>
{
if (!TryGetLiveUnitGrid(mapData, originId, out _, out var originGrid)) return;
if (!TryGetLiveUnitGrid(mapData, targetId, out _, out var targetGrid)) return;
//处理投掷神签动画
OmikujiAnim(mapData, originGrid, targetGrid, divine);
};
if (timer == null)
playOmikuji();
else
timer.TimerRegister(this, playOmikuji,1.1f * i,"SANAEDIVINE - SANAENINE - OnHeal");
//处理大凶
if (divine == SanaeDivineType.BigUnlucky)
{
var targetGrid = target.Grid(mapData);
if (targetGrid != null)
BigUnlucky(mapData, origin,targetGrid,1.1f * i);
}
else
{
if (!TryGetLiveUnit(mapData, targetId, out target)) return;
target.AddSkill_Legacy(skill,mapData,false,1,false,-1,false,SpecialAddSkillType.AddTurnLimit,origin.Id);
if (divine == SanaeDivineType.BigLucky)
{
var targetGrid = target.Grid(mapData);
if (targetGrid != null)
BigLucky(mapData, origin,targetGrid,1.1f * i);
}
}
}
if(again)
{
if (!TryGetLiveUnit(mapData, originId, out origin)) return;
origin.SetFullActionPoint_AllSkillRefresh();
}
}
//处理单次效果
else
{
if (!TryGetLiveUnit(mapData, originId, out origin) || !TryGetLiveUnit(mapData, targetId, out target)) return;
var divine = GetBuff(mapData, origin, true, out var skill, omikujiContext);
//处理投掷神签动画
var originGrid = origin.Grid(mapData);
var targetGrid = target.Grid(mapData);
if (originGrid == null || targetGrid == null) return;
OmikujiAnim(mapData, originGrid, targetGrid, divine);
//处理大凶
if (divine == SanaeDivineType.BigUnlucky)
{
BigUnlucky(mapData, origin,targetGrid,0);
//origin.
if (!TryGetLiveUnit(mapData, originId, out origin)) return;
origin.SetFullActionPoint_AllSkillRefresh();
}
else
{
if (!TryGetLiveUnit(mapData, targetId, out target)) return;
target.AddSkill_Legacy(skill,mapData,false,1,false, -1,false,SpecialAddSkillType.AddTurnLimit,origin.Id);
if (divine == SanaeDivineType.BigLucky)
{
if (!TryGetLiveUnit(mapData, originId, out origin)) return;
origin.SetFullActionPoint_AllSkillRefresh();
BigLucky(mapData, origin,targetGrid,0f);
}
}
}
}
finally
{
FinishOmikujiAction(mapData, originId, omikujiContext);
}
}
public override void OnDamageOther(MapData mapData, SettlementInfo info)
{
if (mapData == null || info?.DamageOrigin == null || info.DamageTargetGrid == null) return;
if (info.DamageType != DamageType.ActiveAttack) return;
var originId = info.DamageOrigin.Id;
var targetGridId = info.DamageTargetGrid.Id;
var targetId = info.DamageTarget?.Id ?? 0;
if (!TryGetLiveUnitGrid(mapData, originId, out var origin, out _)) return;
if (!TryGetLiveGrid(mapData, targetGridId, out var targetGrid)) return;
var omikujiContext = CreateOmikujiActionContext(origin);
try
{
//如果是三连发
if (origin.Skills != null && origin.GetSkill(SkillType.SANAENINE,out var _))
{
bool again = false;
for (int i = 0; i < 3; i++)
{
if (!TryGetLiveUnitGrid(mapData, originId, out origin, out _)) return;
if (!TryGetLiveGrid(mapData, targetGridId, out targetGrid)) return;
var divine = GetBuff(mapData, origin,false, out var skill, omikujiContext);
if(divine is SanaeDivineType.BigUnlucky or SanaeDivineType.BigLucky)
again = true;
System.Action playOmikuji = () =>
{
if (!TryGetLiveUnitGrid(mapData, originId, out _, out var originGrid)) return;
if (!TryGetLiveGrid(mapData, targetGridId, out var liveTargetGrid)) return;
//处理投掷神签动画
OmikujiAnim(mapData, originGrid, liveTargetGrid, divine);
};
if (Timer.Instance == null)
playOmikuji();
else
Timer.Instance.TimerRegister(this, playOmikuji,1.1f * i,"SANAEDIVINE - SANAENINE");
//处理大凶
if (divine == SanaeDivineType.BigUnlucky)
BigUnlucky(mapData, origin,targetGrid,1.1f * i);
else
{
//skill = SkillType.DIVINE_E4_KILL;
if (targetId != 0 && TryGetLiveUnit(mapData, targetId, out var liveTarget))
liveTarget.AddSkill_Legacy(skill,mapData,false,1,false, -1,false, SpecialAddSkillType.AddTurnLimit,origin.Id);
if (divine == SanaeDivineType.BigLucky)
{
BigLucky(mapData, origin,targetGrid,1.1f * i);
}
}
}
if(again)
{
if (!TryGetLiveUnit(mapData, originId, out origin)) return;
origin.SetFullActionPoint_AllSkillRefresh();
}
}
//如果是单发
else
{
if (!TryGetLiveUnitGrid(mapData, originId, out origin, out var originGrid)) return;
if (!TryGetLiveGrid(mapData, targetGridId, out targetGrid)) return;
var divine = GetBuff(mapData, origin,false, out var skill, omikujiContext);
//处理投掷神签动画
if (originGrid == null) return;
OmikujiAnim(mapData, originGrid, targetGrid, divine);
//处理大凶
if (divine == SanaeDivineType.BigUnlucky)
{
BigUnlucky(mapData, origin,targetGrid,0f);
if (!TryGetLiveUnit(mapData, originId, out origin)) return;
origin.SetFullActionPoint_AllSkillRefresh();
}
else
{
//skill = SkillType.DIVINE_E4_KILL;
if (targetId != 0 && TryGetLiveUnit(mapData, targetId, out var liveTarget))
liveTarget.AddSkill_Legacy(skill,mapData,false,1,false,-1,false,SpecialAddSkillType.AddTurnLimit,origin.Id);
if (divine == SanaeDivineType.BigLucky)
{
if (!TryGetLiveUnit(mapData, originId, out origin)) return;
origin.SetFullActionPoint_AllSkillRefresh();
BigLucky(mapData, origin,targetGrid,0f);
}
}
}
}
finally
{
FinishOmikujiAction(mapData, originId, omikujiContext);
}
}
private void OmikujiAnim(MapData mapData,GridData origin,GridData target, SanaeDivineType divine)
{
if (mapData == null || origin == null || target == null) return;
var mainMap = Main.MapData;
if (mapData != mainMap || mainMap?.GridMap == null || mainMap.PlayerMap?.SelfPlayerData?.Sight == null) return;
if (origin.InMainSight() || target.InMainSight())
{
if (Table.Instance == null) return;
var startPos = Table.Instance.GridToWorld(origin);
var endPos = Table.Instance.GridToWorld(target);
ProjectileType omikuji = ProjectileType.SanaeOmikuji;
ProjectileTypeInfo projInfo = null;
if(Table.Instance.ProjectileTypeDataAssets != null &&
Table.Instance.ProjectileTypeDataAssets.GetProjectileTypeInfo(omikuji, out projInfo))
MapRenderer.Instance?.ProjectileManager?.CreateProjectile(startPos,endPos,omikuji);
var targetGridId = target.Id;
var map = mapData;
System.Action playOmikujiVfx = () =>
{
if (!TryGetLiveGrid(map, targetGridId, out var liveTargetGrid)) return;
var vfx1 = divine switch
{
SanaeDivineType.BigLucky => GridVFXType.BigLucky,
SanaeDivineType.Lucky => GridVFXType.Lucky,
SanaeDivineType.Unlucky => GridVFXType.Unlucky,
_ => GridVFXType.BigUnlucky
};
var vfx2 = divine switch
{
SanaeDivineType.BigLucky => GridVFXType.BigLuckyText,
SanaeDivineType.Lucky => GridVFXType.LuckyText,
SanaeDivineType.Unlucky => GridVFXType.UnluckyText,
_ => GridVFXType.BigUnluckyText
};
var renderer = liveTargetGrid.Renderer(map);
renderer?.PlayVFXInSight(new GridVFXParams(vfx1));
renderer?.PlayVFXInSight(new GridVFXParams(vfx2));
};
if (Timer.Instance == null)
playOmikujiVfx();
else
Timer.Instance.TimerRegister(this, playOmikujiVfx,projInfo?.AnimTime ?? 0f,"SANAEDIVINE OMIKUJI ANIM");
}
}
private void BigUnlucky(MapData mapData, UnitData originUnit,GridData targetGrid,float waitTime)
{
var originId = originUnit?.Id ?? 0;
var gridId = targetGrid?.Id ?? 0;
if (!TryGetLiveUnit(mapData, originId, out originUnit)) return;
if (!TryGetLiveGrid(mapData, gridId, out var grid)) return;
ProjectileTypeInfo projInfo = null;
if ((grid.InMainSight() || (originUnit.Grid(mapData)?.InMainSight()??false))
&& Table.Instance?.ProjectileTypeDataAssets != null)
Table.Instance.ProjectileTypeDataAssets.GetProjectileTypeInfo(ProjectileType.SanaeOmikuji, out projInfo);
var aroundBuf = RentAroundBuf();
mapData.GridMap.GetAroundGridData(1, 1, grid, aroundBuf);
foreach (var round in aroundBuf)
{
if (round == null) continue;
if (!TryGetLiveUnit(mapData, originId, out originUnit)) break;
if (!round.RealUnit(mapData, out var unit)) continue;
if (!unit.IsValidOnMap(mapData, round)) continue;
if (!unit.IsAlive()) continue;
bool sameUnion = mapData.SameUnionByUnitId(unit.Id, originUnit.Id);
var map = mapData;
var tmpGridId = round.Id;
var tmpUnitId = unit.Id;
var dmg = sameUnion ? _bigUnluckyFriendDmg : _bigUnluckyEnemyDmg;
var canBeKilled = unit.CanBeKilled(mapData);
var visualCollector = ActionVisualEventCollector.Current;
var targetRenderer = mapData == Main.MapData ? unit.Renderer(mapData) : null;
var dmgInfo = visualCollector != null && mapData == Main.MapData
? visualCollector.SettleDamageWithVisual(
originUnit,
unit,
dmg,
sameUnion ? DamageType.KillSelf : DamageType.Splash,
AnimPhase.AttackImpact + 50,
(projInfo?.AnimTime ?? 0f) + waitTime,
showoff: true)
: Main.UnitLogic.DamageSettlement(mapData, originUnit, unit, dmg , sameUnion ? DamageType.KillSelf : DamageType.Splash);
if (dmgInfo?.DamageTargetGrid == null) continue;
if (visualCollector != null && mapData == Main.MapData) continue;
//处理视觉
if (mapData == Main.MapData)
{
RegisterOrRunTimer(() =>
{
if (!TryGetLiveGrid(map, tmpGridId, out var tmpGrid)) return;
var gridRenderer = tmpGrid.Renderer(map);
if (tmpGrid.InMainSight())
{
gridRenderer?.PlayVFXInSight(new GridVFXParams(GridVFXType.Hurt));
gridRenderer?.PlayVFXInSight(new GridVFXParams(GridVFXType.Damage,dmg));
}
if (dmgInfo.IsKill)
{
if (tmpGrid.InMainSight())
{
gridRenderer?.PlayVFXInSight(new GridVFXParams(GridVFXType.Fog));
if (canBeKilled)
gridRenderer?.PlayVFXInSight(new GridVFXParams(GridVFXType.Die));
}
targetRenderer?.Die();
gridRenderer?.InstantUpdateGrid();
MapRenderer.Instance?.UpdateAroundHighlight(map, tmpGrid);
return;
}
if (TryGetLiveUnit(map, tmpUnitId, out var liveUnit))
(targetRenderer ?? liveUnit.Renderer(map))?.InstantUpdateUnit(showoff: true);
},(projInfo?.AnimTime ?? 0f )+ waitTime,"SANAEDIVINE OMIKUJI BigUnlucky Anim");
}
}
ReturnAroundBuf();
}
private void BigLucky(MapData mapData, UnitData originUnit,GridData targetGrid,float waitTime)
{
var originId = originUnit?.Id ?? 0;
var gridId = targetGrid?.Id ?? 0;
if (!TryGetLiveUnit(mapData, originId, out originUnit)) return;
if (!TryGetLiveGrid(mapData, gridId, out var grid)) return;
ProjectileTypeInfo projInfo = null;
if ((grid.InMainSight() || (originUnit.Grid(mapData)?.InMainSight()??false))
&& Table.Instance?.ProjectileTypeDataAssets != null)
Table.Instance.ProjectileTypeDataAssets.GetProjectileTypeInfo(ProjectileType.SanaeOmikuji, out projInfo);
var aroundBuf = RentAroundBuf();
mapData.GridMap.GetAroundGridData(1, 1, grid, aroundBuf);
foreach (var round in aroundBuf)
{
if (round == null) continue;
if (!TryGetLiveUnit(mapData, originId, out originUnit)) break;
if (!round.RealUnit(mapData, out var unit)) continue;
if (!unit.IsValidOnMap(mapData, round)) continue;
bool sameUnion = mapData.SameUnionByUnitId(unit.Id, originUnit.Id);
var map = mapData;
var tmpGridId = round.Id;
var tmpUnitId = unit.Id;
//处理敌人
if (!sameUnion)
{
var canBeKilled = unit.CanBeKilled(mapData);
var visualCollector = ActionVisualEventCollector.Current;
var targetRenderer = mapData == Main.MapData ? unit.Renderer(mapData) : null;
var dmgInfo = visualCollector != null && mapData == Main.MapData
? visualCollector.SettleDamageWithVisual(
originUnit,
unit,
_bigLuckyDmg,
DamageType.Splash,
AnimPhase.AttackImpact + 50,
(projInfo?.AnimTime ?? 0f) + waitTime,
showoff: true)
: Main.UnitLogic.DamageSettlement(mapData, originUnit, unit, _bigLuckyDmg , DamageType.Splash);
if (dmgInfo?.DamageTargetGrid == null) continue;
if (visualCollector != null && mapData == Main.MapData) continue;
//处理视觉
if (mapData == Main.MapData)
{
RegisterOrRunTimer(() =>
{
if (!TryGetLiveGrid(map, tmpGridId, out var tmpGrid)) return;
var gridRenderer = tmpGrid.Renderer(map);
if (tmpGrid.InMainSight())
{
gridRenderer?.PlayVFXInSight(new GridVFXParams(GridVFXType.Hurt));
gridRenderer?.PlayVFXInSight(new GridVFXParams(GridVFXType.Damage,4));
}
if (dmgInfo.IsKill)
{
if (tmpGrid.InMainSight())
{
gridRenderer?.PlayVFXInSight(new GridVFXParams(GridVFXType.Fog));
if (canBeKilled)
gridRenderer?.PlayVFXInSight(new GridVFXParams(GridVFXType.Die));
}
targetRenderer?.Die();
gridRenderer?.InstantUpdateGrid();
MapRenderer.Instance?.UpdateAroundHighlight(map, tmpGrid);
return;
}
if (TryGetLiveUnit(map, tmpUnitId, out var liveUnit))
(targetRenderer ?? liveUnit.Renderer(map))?.InstantUpdateUnit(showoff: true);
},(projInfo?.AnimTime ?? 0f) + waitTime,"SANAEDIVINE OMIKUJI BigUnlucky Anim");
}
}
else
{
//TODO 这里用timer 延迟了逻辑因为recoverHealth 逻辑和视觉绑定了。后续要拆开
Main.UnitLogic.RecoverHealth_Legacy(map,originUnit,unit,_bigLuckyHeal);
//处理视觉
if (unit.Grid(map)?.InMainSight()??false)
{
RegisterOrRunTimer(() =>
{
if (!TryGetLiveGrid(map, tmpGridId, out var tmpGrid)) return;
tmpGrid.Renderer(map)?.PlayVFXInSight(new GridVFXParams(GridVFXType.Heal));
if (TryGetLiveUnit(map, tmpUnitId, out var liveUnit))
liveUnit.Renderer(map)?.InstantUpdateUnit(showoff: true);
},projInfo?.AnimTime ?? 0f,"SANAEDIVINE OMIKUJI BigUnlucky Anim");
}
else
Main.UnitLogic.RecoverHealth_Legacy(map,originUnit,unit,_bigLuckyHeal);
}
}
ReturnAroundBuf();
}
private void RegisterOrRunTimer(System.Action action, float delay, string message)
{
if (action == null) return;
var timer = Timer.Instance;
if (timer == null)
{
action();
return;
}
timer.TimerRegister(this, action, delay, message);
}
private static bool TryGetLiveUnit(MapData mapData, uint unitId, out UnitData unit)
{
unit = null;
if (unitId == 0 || mapData?.UnitMap == null) return false;
return mapData.UnitMap.GetUnitDataByUnitId(unitId, out unit)
&& unit != null
&& unit.IsValidOnMap(mapData);
}
private static bool TryGetLiveGrid(MapData mapData, uint gridId, out GridData grid)
{
grid = null;
if (gridId == 0 || mapData?.GridMap == null) return false;
return mapData.GridMap.GetGridDataByGid(gridId, out grid) && grid != null;
}
private static bool TryGetLiveUnitGrid(MapData mapData, uint unitId, out UnitData unit, out GridData grid)
{
grid = null;
if (!TryGetLiveUnit(mapData, unitId, out unit)) return false;
return mapData.GetGridDataByUnitId(unit.Id, out grid) && grid != null;
}
public SanaeDivineType GetBuff(MapData map, UnitData self,bool IsHeal, out SkillType skill)
{
return GetBuff(map, self, IsHeal, out skill, null);
}
private SanaeDivineType GetBuff(MapData map, UnitData self,bool IsHeal, out SkillType skill, OmikujiActionContext context)
{
skill = SkillType.NONE;
if (map?.Net == null || self == null) return SanaeDivineType.Lucky;
var forceExtreme = context is { IsFirstRoll: true, ForceFirstExtreme: true };
if (context != null) context.IsFirstRoll = false;
var buff = forceExtreme
? GetForcedExtremeBuff(map, IsHeal, out skill)
: GetNormalBuff(map, IsHeal, out skill);
if (IsExtreme(buff))
ClearDivinePending(map, self);
context?.Record(buff);
if (map.GetPlayerDataByUnitId(self.Id, out var player))
{
foreach (var kv in player.PlayerHeroData.HeroTaskDict)
kv.Value.OnSanaeDevine(map, player, buff);
}
return buff;
}
private static OmikujiActionContext CreateOmikujiActionContext(UnitData self)
{
var context = new OmikujiActionContext();
if (self != null &&
self.GetSkill(SkillType.LUCK, out var pending) &&
pending.Level >= LuckSkill.DivinePendingLimit)
{
context.ForceFirstExtreme = true;
}
return context;
}
private static void FinishOmikujiAction(MapData map, uint originId, OmikujiActionContext context)
{
if (context == null || !context.Rolled || context.HasExtreme) return;
if (!TryGetLiveUnit(map, originId, out var origin)) return;
origin.AddSkill_Legacy(SkillType.LUCK, map, true, 0, true, 1, true, SpecialAddSkillType.AddLevel, origin.Id);
}
private static void ClearDivinePending(MapData map, UnitData self)
{
if (self == null) return;
if (self.GetSkill(SkillType.LUCK, out _))
self.RemoveSkill(SkillType.LUCK, map);
}
private static SanaeDivineType GetForcedExtremeBuff(MapData map, bool isHeal, out SkillType skill)
{
skill = SkillType.NONE;
var roll = map.Net.GetRandom(map).Next(1, 101);
if (roll <= 50) return SanaeDivineType.BigUnlucky;
skill = roll <= 75
? (isHeal ? SkillType.DIVINE_F4_ATK : SkillType.DIVINE_E4_KILL)
: (isHeal ? SkillType.DIVINE_F4_KILL : SkillType.DIVINE_E4_DEFENSE);
return SanaeDivineType.BigLucky;
}
private static SanaeDivineType GetNormalBuff(MapData map, bool isHeal, out SkillType skill)
{
skill = SkillType.NONE;
var roll = map.Net.GetRandom(map).Next(1, 101); // 生成1到100的随机数
if (roll <= 10) return SanaeDivineType.BigUnlucky; // 大凶
if (roll <= 45)
{
skill = (roll <= 27) ? (isHeal?SkillType.DIVINE_F2_DEFENSE:SkillType.DIVINE_E2_ATK) : (isHeal?SkillType.DIVINE_F2_RESIST:SkillType.DIVINE_E2_MOVE);
return SanaeDivineType.Unlucky; // 凶
}
if (roll <= 90)
{
skill = (roll <= 67) ? (isHeal ? SkillType.DIVINE_F3_MOVE:SkillType.DIVINE_E3_HP) : (isHeal ? SkillType.DIVINE_F3_MOVE:SkillType.DIVINE_E3_COUNTER);
return SanaeDivineType.Lucky; // 吉
}
skill = (roll <= 95) ? (isHeal ? SkillType.DIVINE_F4_ATK:SkillType.DIVINE_E4_KILL) : (isHeal ? SkillType.DIVINE_F4_KILL:SkillType.DIVINE_E4_DEFENSE);
return SanaeDivineType.BigLucky; // 大吉
}
private static bool IsExtreme(SanaeDivineType divine)
{
return divine is SanaeDivineType.BigUnlucky or SanaeDivineType.BigLucky;
}
}
}