/* * @Author: 白哉 * @Description: 小兵逻辑 * @Date: 2025年04月01日 星期二 11:04:28 * @Modify: */ using System; using System.Collections.Generic; using System.Linq; using System.Numerics; using Animancer; using Logic.Action; using Logic.Audio; using Logic.CrashSight; using Logic.Pool; using Logic.Skill; using ParadoxNotion; using UnityEngine; using RuntimeData; using TH1_Anim; using TH1_Anim.Fragments; using TH1_Core.Events; using TH1_Core.Managers; using TH1_Logic.Collect; using TH1_Logic.Core; using TH1_Presentation.Sequencer.Task; using TH1Renderer; using Unity.VisualScripting; using Vector2 = UnityEngine.Vector2; namespace Logic { public enum DamageType { ActiveAttack, CounterAttack, FollowAttack, Splash, True, KillSelf, DelayAttack, PushAttack, } public enum HealType { AttackAllyHeal, SelfHeal } public class AttackInfo { //伤害来源的unitId public UnitData DamageOrigin; public PlayerData OriginPlayer; //伤害目标的unitId public UnitData DamageTarget; public PlayerData TargetPlayer; public GridData DamageOriginGrid; public GridData DamageTargetGrid; //是否击杀 public bool IsKill; } public class SettlementInfo { //伤害类型 public DamageType DamageType; //伤害来源的unitId public UnitData DamageOrigin; public PlayerData OriginPlayer; //伤害目标的unitId public UnitData DamageTarget; public PlayerData TargetPlayer; public GridData DamageOriginGrid; public GridData DamageTargetGrid; public CityData DamageTargetCity; //伤害值 public int DamageValue; // 承伤者:非空时,扣血和死亡判定改针对此单位,但 OnDamaged/HeroTask/Moment 等被动回调仍走 DamageTarget。 // 用于 SakuyaGuard 这类"代为承受伤害"语义:原目标算被打过(被动正常触发)但不掉血,血由 Bearer 来扣。 // Bearer 死亡不计入攻击者的 TotalKill / killHero 等 moment。 public UnitData DamageBearer; //是否击杀 public bool IsKill; //是否已有死亡替换技能触发(防止多个死亡替换技能同时生成单位) public bool IsDeathReplaced; public bool IsFinished; // 血量减少值 public int HealthReduceValue; public SettlementInfo() { IsFinished = false; } } public class UnitLogic : IUnitLogic { float[,] MovementCostMap; float[,] MovementRemainMap; float[,] MoveRealCostMap;//最优路线下,从别处移动过来的实机cost花费多少 //ReachMap组,常规移动能抵达的map bool[,] NormalReachMap;//常规计算 bool[,] MomijiHunterReachMap; //专门给momijiHunter 用的 判断一个格子如果不是hunter周围,还是否有行动力抵达。如果一个角色没有momijihunter技能,这个就没用 bool[,] FinalReachMap;//最终整合reachMap,将常规计算和momijiHunter整合在一起,判定在常规移动下,哪些格子可以抵达,哪些不可以抵达 // 供外部查询FinalReachMap(需在CalcUnitMoveInfo之后调用) public bool IsFinalReachable(int x, int y) => FinalReachMap[x, y]; bool[,] TransMap;//在常规移动之外,额外赋予的特殊可传送格子 bool[,] SanaeMap;//在常规移动之外,额外赋予的特殊可抵达格子 // 复用的临时集合 private List _aroundBuf; private List _aroundBuf2; public UnitLogic() { MovementCostMap = new float[Table.Instance.MaxMapSize,Table.Instance.MaxMapSize]; MovementRemainMap = new float[Table.Instance.MaxMapSize,Table.Instance.MaxMapSize]; MoveRealCostMap = new float[Table.Instance.MaxMapSize,Table.Instance.MaxMapSize]; NormalReachMap = new bool[Table.Instance.MaxMapSize,Table.Instance.MaxMapSize]; MomijiHunterReachMap = new bool[Table.Instance.MaxMapSize,Table.Instance.MaxMapSize]; FinalReachMap = new bool[Table.Instance.MaxMapSize,Table.Instance.MaxMapSize]; TransMap = new bool[Table.Instance.MaxMapSize,Table.Instance.MaxMapSize]; SanaeMap = new bool[Table.Instance.MaxMapSize,Table.Instance.MaxMapSize]; } public void Update() { } //-------- 信息判断类 ---------// //判断uid是不是selfplayer的unit public bool CheckIsSelfPlayer(MapData mapData, uint uid) { mapData.GetPlayerDataByUnitId(uid, out var playerData); return playerData == mapData.PlayerMap.SelfPlayerData; } public void MoveTo(MapData mapData, PlayerData playerData, UnitData unitData, GridData gridData) { // 攻击行为切换成 Action 驱动 var moveId = new CommonActionId { ActionType = CommonActionType.UnitMove }; var moveAction = new UnitMoveAction(moveId); var param = new CommonActionParams(mapData, playerData:playerData, unitData:unitData, gridData:gridData); param.RefreshParams(); moveAction.CompleteExecute(param); } //-------- 执行类 --------// // TODO 调用处要处理失败情况 // 返回值代表是否成功移动 !!! public bool MoveToLogic(MapData mapData, UnitData unitData, GridData gridData, MoveType moveType, List path = null) { bool isPassiveDisplacement = moveType is MoveType.PassiveMove or MoveType.PushMove; //首先处理该单位如果有DelayAct的情况,阿空专属 //只有主动移动才触发延迟行动,被动移动(PassiveMove)不应触发 if (!isPassiveDisplacement && unitData.GetSkill(SkillType.UtsuhoDelayAct, out var skill) && skill is UtsuhoDelayActSkill delaySkill) { if (!unitData.GetSkill(SkillType.UtsuhoReadyMove, out _) && !unitData.GetSkill(SkillType.UtsuhoReadyMoveSuper, out _)) { delaySkill.DelayMove(unitData,gridData,mapData,moveType,path); return false; } } //处理移动目标有单位的情况(return false) 这里的移动失败如果有表现要处理下(比如格子上有隐身单位) ; if (gridData.RealUnit(mapData,out var targetGridUnit) && !unitData.GetSkill(SkillType.UtsuhoBase, out var _)) { targetGridUnit.OnBeInteractTarget(mapData, unitData, gridData); return false; } unitData.BeforeMove(mapData, gridData, moveType, path); mapData.SetUnitIdToGridId(unitData.Id, gridData.Id); if (mapData == Main.MapData) AchievementDataManager.Instance.OnUnitMove(mapData, unitData, gridData); // UtsuhoBase的单位移动不消耗行动点 //如果主动移动会消耗所有类别的行动点数,被动挤出去的移动不算 if (!isPassiveDisplacement && moveType != MoveType.AttackMove && !unitData.GetSkill(SkillType.UtsuhoBase, out _)) unitData.ClearActionPoint(); if (unitData.GetSkill(SkillType.UtsuhoReadyMove, out _)) unitData.RemoveSkill(SkillType.UtsuhoReadyMove,mapData); if (unitData.GetSkill(SkillType.UtsuhoReadyMoveSuper, out _)) unitData.RemoveSkill(SkillType.UtsuhoReadyMoveSuper,mapData); //被动移动取消阿空的延迟冲锋:清空路径并刷新视觉 if (isPassiveDisplacement && unitData.GetSkill(SkillType.UtsuhoDelayAct, out var delaySkill2) && delaySkill2 is UtsuhoDelayActSkill delayAct2 && delayAct2.Path is { Count: > 0 }) { delayAct2.Path.Clear(); unitData.Renderer(mapData)?.InstantUpdateUnit(false); } //如果 land to port if (unitData.GetLandType() == LandType.LandAndPort && gridData.Resource == ResourceType.Port) { //注意,必须先Onmove(变身之前的技能用掉,再变身) unitData.OnMove(mapData, gridData, moveType, path); LandToBoat(mapData, unitData); //变身后再触发一次OnMove,使变身后新获得的技能(如StompSkill)也能产生溅射效果 unitData.OnMove(mapData, gridData, MoveType.PassiveMove); } //如果sea to land else if (unitData.GetLandType() == LandType.WaterAndAshore && gridData.Terrain == TerrainType.Land) { //注意,必须先Onmove(变身之前的技能用掉,再变身) unitData.OnMove(mapData, gridData, moveType, path); BoatToLand(mapData, unitData); } //如果sea to sea或者land to land else //处理DASH等等技能情况,在Move结束时会触发的那些技能 unitData.OnMove(mapData, gridData, moveType, path); mapData.OnAnyUnitMove(mapData, unitData, gridData, moveType); if (!mapData.GetPlayerDataByUnitId(unitData.Id, out var playerData)) { // OnMove 中触发的自杀类技能(如 INFILTRATE)会让 unit 在此点之前已被 UnitUnnaturalDie 移除, // 此时找不到 player 是合法的:MoveToLogic 后续的 NavalBase 恢复、宣战等逻辑都依赖单位仍存活, // 因此直接静默 return;只有在单位"应该还活着却找不到 player"的真异常路径才打错误日志。 if (!unitData.IsAlive()) return true; LogSystem.LogError("unit找不到player"); return true; } //如果是盟友的navalbase,且对方有恢复力,则消耗恢复力,恢复该单位所有行动点 if (gridData.Resource == ResourceType.NavalBase && mapData.GetPlayerDataByUnitId(unitData.Id, out var unitPlayer) && mapData.GetPlayerDataByTerritoryGridId(gridData.Id, out var gridPlayer) && mapData.SameUnion(unitPlayer.Id, gridPlayer.Id) && gridData.NavalBasePoint > 0) { gridData.NavalBasePoint--; unitData.SetFullActionPoint(); } //判断unit是否走到了另一个玩家的城市里,如果是,要设置两国状态为战争 if (mapData.GetCityDataByGid(gridData.Id, out var city) && mapData.GetPlayerDataByCityId(city.Id, out var player2) && mapData.GetPlayerDataByUnitId(unitData.Id, out var player1) && player1.Id != player2.Id) { player2.AddAttacker(player1.Id); Main.PlayerLogic.SetDiplomacyLeague(mapData, player1, player2, DiplomacyState.War); } return true; } //返回unit2是否满足反击unit1的基础规则,不包含"本次攻击是否会杀死unit2"的预判。 public bool CanCounterByRules(MapData mapData, UnitData unit1, UnitData unit2) { if (!unit2.CanAttackAll(mapData) && mapData.IsLeagueUnitByUnit(unit1.Id, unit2.Id)) return false; if (!mapData.GetPlayerDataByUnitId(unit1.Id, out var player1)) return false; if (!mapData.GetPlayerDataByUnitId(unit2.Id, out var player2)) return false; if (!mapData.GetGridDataByUnitId(unit1.Id, out var grid1)) return false; if (!mapData.GetGridDataByUnitId(unit2.Id, out var grid2)) return false; //设置unit1 attackendermark和相关参数 //判断对方能否反击的参数 bool canCounter; canCounter = true; //是否限制敌方反击 if (unit1.IsLimitTargetCounterAttack(mapData)) canCounter = false; //敌方是否被限制反击 if (unit2.IsLimitSelfCounterAttack(mapData)) canCounter = false; //确认对方是否有我的视野,没有的话无法反击 if (!player2.Sight.CheckIsInSight(grid1.Id)) canCounter = false; //如果对方攻击范围无法覆盖我,则无法反击 if (Table.Instance.CalcDistance(new Vector2Int(grid1.Pos.X,grid1.Pos.Y),new Vector2Int(grid2.Pos.X,grid2.Pos.Y)) > unit2.GetAttackRange(mapData)) canCounter = false; return canCounter; } //返回unit2是否能反击unit1。对外查询保留致死预判,真实攻击执行会在结算后按存活状态决定。 public bool CanCounter(MapData mapData, UnitData unit1, UnitData unit2) { if (!CanCounterByRules(mapData, unit1, unit2)) return false; // 计算攻击伤害 int dmg1 = Table.Instance.CalcDamage(mapData, unit1, unit2); if (dmg1 >= unit2.Health) //如果伤害直接够杀死对方 return false; return true; } public void Attack(MapData mapData, UnitData unit1, UnitData unit2, out int attackDmg,out int counterDmg,out FragmentType fragmentType) { attackDmg = 0; counterDmg = 0; fragmentType = FragmentType.Attack; if (mapData == null || unit1 == null || unit2 == null) return; if (!unit1.IsValidOnMap(mapData) || !unit2.IsValidOnMap(mapData)) return; if (!mapData.GetPlayerDataByUnitId(unit1.Id, out var player1)) return; if (!mapData.GetPlayerDataByUnitId(unit2.Id, out var player2)) return; if (!mapData.GetGridDataByUnitId(unit1.Id, out var grid1)) return; if (!mapData.GetGridDataByUnitId(unit2.Id, out var grid2)) return; if (unit1.IsLimitSelfAttack(mapData)) return; // YuugiPush:推击替代普通攻击 if (unit1.GetSkill(SkillType.YuugiPush, out var yuugiPushSkill)) { var push = yuugiPushSkill as YuugiPushSkill; if (push != null && push.YuugiPushAttack(unit1, unit2, mapData, out attackDmg, out counterDmg, out fragmentType, out _)) { unit1.ClearActionPoint(); return; } } var attackInfo = new AttackInfo(); attackInfo.DamageOrigin = unit1; attackInfo.OriginPlayer = player1; attackInfo.DamageTarget = unit2; attackInfo.DamageOriginGrid = grid1; attackInfo.DamageTargetGrid = grid2; attackInfo.IsKill = false; // 攻击前生命周期 unit1.BeforeActiveAttackOther(mapData, unit1, unit2, out var tmpAddDmg); if (!unit1.IsValidOnMap(mapData)) return; if (!unit2.IsValidOnMap(mapData)) { var visualCollector = ActionVisualEventCollector.Current; if (visualCollector != null && visualCollector.MainTargetKilledBeforeAttack) { attackDmg = visualCollector.MainTargetPreAttackDamage; fragmentType = FragmentType.Attack; unit1.ClearActionPoint(); } return; } if (!mapData.GetPlayerDataByUnitId(unit1.Id, out player1)) return; if (!mapData.GetPlayerDataByUnitId(unit2.Id, out player2)) return; if (!mapData.GetGridDataByUnitId(unit1.Id, out grid1)) return; if (!mapData.GetGridDataByUnitId(unit2.Id, out grid2)) return; attackInfo.OriginPlayer = player1; attackInfo.DamageOriginGrid = grid1; attackInfo.DamageTargetGrid = grid2; var attackDistance = Table.Instance.CalcDistance(grid1, grid2); // 计算攻击伤害 int dmg1 = Table.Instance.CalcDamage(mapData, unit1, unit2) + tmpAddDmg; int dmg2 = Table.Instance.CalcCounterDamage(mapData, unit1,unit2 ); attackDmg += dmg1; //判断对方能否反击的参数 //TODO 之后还是要把这些放到后面逻辑里去写,不要在最开头判断,一定判断不出来的 bool canCounter = CanCounterByRules(mapData,unit1,unit2); if(unit1.UnitFullType.UnitType == UnitType.Minder)canCounter = false; //攻击会消耗所有类别的行动点数 unit1.ClearActionPoint(); var activeSettlement = Main.UnitLogic.DamageSettlement(mapData, unit1, unit2, dmg1, DamageType.ActiveAttack); if (activeSettlement == null) return; if (!unit2.IsAlive()) { attackInfo.IsKill = true; fragmentType = FragmentType.NotMoveKill; //处理MoveKill的情况 if (unit1.IsCanMoveKill(mapData) && attackDistance <= 1 && CheckUnitAbleForGrid_OfflineStatus(mapData, player1, unit1, grid2) && !grid2.RealUnit(mapData,out _)) { Main.UnitLogic. MoveToLogic(mapData, unit1, grid2, MoveType.AttackMove); //如果杀了人且可以移动过去,动画类型就是Movekill fragmentType = FragmentType.MoveKill; } else { //如果杀了人且可以移动过去,动画类型就是之前已经设置好的默认的Attack } } else { if (canCounter && unit1.IsAlive() && unit2.IsAlive()) { counterDmg += dmg2; Main.UnitLogic.DamageSettlement(mapData, unit2, unit1, dmg2, DamageType.CounterAttack); if(!unit1.IsAlive()) fragmentType = FragmentType.AttackAndCounterDie; else fragmentType = FragmentType.AttackAndCounter; } } if (unit1.IsAlive()) unit1.AfterActiveAttackOther(mapData, attackInfo); if (unit2.IsAlive()) unit2.AfterActiveAttacked(mapData, attackInfo); } // 伤害新结算方法,所有的掉血都要走此方法,不允许直接修改UnitData.Health // TODO 自杀或者杀队友要不要加 player.TotalKill public SettlementInfo DamageSettlement(MapData mapData, UnitData origin, UnitData target, int dmg, DamageType type) { if (origin == null || target == null) { LogSystem.LogInfo($"DamageSettlement !!! DamageSettlement origin:{origin} or target{target} is null"); return null; } if (!mapData.GetGridDataByUnitId(origin.Id, out var originGrid)) { LogSystem.LogInfo($"DamageSettlement !!! Target Grid is null target.id:{origin.Id}"); return null; } if (!mapData.GetGridDataByUnitId(target.Id, out var targetGrid)) { LogSystem.LogInfo($"DamageSettlement !!! Target Grid is null target.id:{target.Id}"); return null; } if (!mapData.GetCityDataByUnitId(target.Id, out var targetCity)) { LogSystem.LogInfo($"DamageSettlement !!! Target City is null target.id:{target.Id}"); return null; } //TODO 和白哉check下是不是这么改 if (!origin.CanAttackAll(mapData) && mapData.IsLeagueUnitByUnit(origin.Id, target.Id) && type != DamageType.KillSelf) { LogSystem.LogInfo($"DamageSettlement !!! mapData.IsLeagueUnitByUnit(origin.Id, target.Id) && type != DamageType.KillSelf)"); return null; } if (!mapData.GetPlayerDataByUnitId(origin.Id, out var player1)) return null; if (!mapData.GetPlayerDataByUnitId(target.Id, out var player2)) return null; if (target.IsLimitDamaged(mapData, dmg)) { LogSystem.LogInfo($"DamageSettlement !!! if (!target.CanBeDamaged(mapData, dmg)) )"); return null; } //这里是缓存区,要在运算前缓存一些数据 var targetPlayer = target.Player(mapData); var settlement = new SettlementInfo(); settlement.DamageType = type; settlement.DamageOrigin = origin; settlement.OriginPlayer = player1; settlement.DamageTarget = target; settlement.TargetPlayer = player2; settlement.DamageValue = dmg; settlement.DamageOriginGrid = originGrid; settlement.DamageTargetGrid = targetGrid; settlement.DamageTargetCity = targetCity; origin.BeforeDamageOther(mapData, settlement); target.BeforeDamagedSupportStage(mapData, settlement); target.BeforeDamagedTransformStage(mapData, settlement); mapData.BeforeUnitDamaged(settlement); if (settlement.IsFinished) return null; // 承伤者重定向:Bearer 非空时,扣血/击杀针对 Bearer,但 OnDamaged/Moment/HeroTask 仍走 target。 // Bearer 死亡走完整 UnitDie 流程,但不计为攻击者的击杀(IsKill 保持 false,不加 TotalKill)。 var bearer = settlement.DamageBearer; if (bearer != null) { settlement.HealthReduceValue = Mathf.Min(bearer.Health, settlement.DamageValue); bearer.Health -= settlement.DamageValue; if (bearer.CanBeKilled(mapData) && !bearer.IsAlive()) { UnitDie(mapData, bearer, settlement.DamageValue); } settlement.IsKill = false; } else { settlement.HealthReduceValue = Mathf.Min(target.Health, settlement.DamageValue); target.Health -= settlement.DamageValue; if (type == DamageType.KillSelf && !target.IsAlive()) { UnitDie(mapData, target, settlement.DamageValue); settlement.IsKill = true; } else if (target.CanBeKilled(mapData) && !target.IsAlive()) { if (Main.MapData == mapData) AchievementDataManager.Instance.OnKillUnit(mapData, origin, target); UnitDie(mapData, target, settlement.DamageValue); settlement.IsKill = true; //if (!origin.IsExpLock(mapData)) origin.Exp++; mapData.GetPlayerDataByUnitId(origin.Id, out var player); if (player != null) player.TotalKill++; } else { settlement.IsKill = false; } } // unit 受伤和伤害他人时回调 origin.OnDamageOther(mapData, settlement); target.OnDamaged(mapData, settlement); mapData.OnUnitDamaged(settlement); // collect 调用 CollectManager.Instance.OnDamageCollect(mapData, settlement); // moment 调用 foreach (var item in player1.MomentData.Items) item.OnDamageOther(mapData, player1, settlement); foreach (var item in player2.MomentData.Items) item.OnDamaged(mapData, player2, settlement); // 任务三层调用 player1.PlayerHeroData.HeroTaskDict.TryGetValue(origin.UnitFullType, out var task1); task1?.OnDamageOther(mapData, settlement); player1.PlayerHeroData.HeroTaskDict.TryGetValue(origin.OriginUnitFullType, out var task1_origin); task1_origin?.OnDamageOther(mapData, settlement); player1.PlayerHeroData.HeroTaskDict.TryGetValue(origin.CarryUnitFullType, out var task1_carry); task1_carry?.OnDamageOther(mapData, settlement); foreach (var kv in player1.PlayerHeroData.HeroTaskDict) kv.Value.OnPlayerDamageOther(mapData, settlement); foreach (var player in mapData.PlayerMap.PlayerDataList) { foreach (var kv in player.PlayerHeroData.HeroTaskDict) kv.Value.OnAnyDamageOther(mapData, settlement); } player2.PlayerHeroData.HeroTaskDict.TryGetValue(target.UnitFullType, out var task2); task2?.OnDamaged(mapData, settlement); foreach (var kv in player2.PlayerHeroData.HeroTaskDict) kv.Value.OnPlayerDamaged(mapData, settlement); foreach (var player in mapData.PlayerMap.PlayerDataList) { foreach (var kv in player.PlayerHeroData.HeroTaskDict) kv.Value.OnAnyDamaged(mapData, settlement); } // unit 受伤和伤害他人后回调 origin.AfterDamageOther(mapData, settlement); if (type is DamageType.ActiveAttack or DamageType.FollowAttack or DamageType.Splash or DamageType.True) { Main.PlayerLogic.SetDiplomacyLeague(mapData,player1, player2, DiplomacyState.War); player1.CurAttackPlayers.Add(player2.Id); player2.CurAttackPlayers.Add(player1.Id); player2.AddAttacker(player1.Id); player1.TurnNoAttack = 0; } //Step #4 Moment Process if (mapData == Main.MapData && settlement.IsKill) { //注意,这里只判断我方击杀的moment,我方被杀的moment要在unitDie里判断 bool killHero = false; bool killBigGuy = false; bool killTreasure = false; bool killVillage = false; bool killOnOurCity = false; bool killOnOtherCity = false; bool killFull = false; bool killWithLow = false; bool killOfficer = false; bool oneHP = false; bool counterLow = false; if (origin.Player(mapData) == mapData.PlayerMap.SelfPlayerData) { if(target.IsHero(true))killHero = true; if (settlement.HealthReduceValue >= target.GetMaxHealth()) killFull = true; if(target.IsBigGuy(true))killBigGuy = true; if(target.IsOfficer())killOfficer = true; if(targetGrid.Resource == ResourceType.Treasure) killTreasure = true; if (targetGrid.Resource == ResourceType.CityCenter) { var gridPlayer = targetGrid.Player(mapData); if(gridPlayer == null) killVillage = true; else if(gridPlayer == Main.MapData.PlayerMap.SelfPlayerData) killOnOurCity = true; else if(targetPlayer != gridPlayer)killOnOtherCity = true;//说明占领城市的单位和城市不是一个玩家的,说明是杀掉了第三方占领敌方城市 } if (origin.Health < 5 && origin.Health < origin.GetMaxHealth() * 0.3f && type == DamageType.ActiveAttack) killWithLow = true; if (origin.Health < 5 && origin.Health < origin.GetMaxHealth() * 0.3f && type == DamageType.CounterAttack) counterLow = true; } if (target.Player(mapData) == mapData.PlayerMap.SelfPlayerData) { if (target.Health == 1) oneHP = true; } var momentsubType = MomentSubType.None; if (killHero) momentsubType = MomentSubType.BattleKillHero; else if (killBigGuy)momentsubType = MomentSubType.BattleKillGiant; else if (killTreasure)momentsubType = MomentSubType.BattleKillTreasure; else if (killVillage)momentsubType = MomentSubType.BattleKillVillage; else if (killOnOurCity)momentsubType = MomentSubType.BattleKillOnOurCity; else if (killOnOtherCity)momentsubType = MomentSubType.BattleKillOnOtherCity; else if (killFull)momentsubType = MomentSubType.BattleKillFullHealth; else if (killWithLow)momentsubType = MomentSubType.BattleNarrowVictory; else if (killOfficer)momentsubType = MomentSubType.BattleKillOfficer; else if (oneHP)momentsubType = MomentSubType.BattleLastStand; else if (counterLow)momentsubType = MomentSubType.BattleCounterKill; if(momentsubType != MomentSubType.None) EventManager.Publish(new ShowUINotifyMoment() { MomentSubType = momentsubType, Empire = Main.MapData.PlayerMap.SelfPlayerData.Empire }); } // 兜底:单位 Health<=0 但 OnDamaged 阶段没把它 SetDie(僵尸态), // 例如 Undead/BonePile 让 IsCanBeKill 返回 false 跳过 UnitDie, // 而 UnitData.OnDamaged 又因 Frozen 过滤掉了它们的兜底 OnDamaged。 if (target.IsZombie()) { UnitUnnaturalDie(mapData, target); } return settlement; } //TODO 这里RecoverHealth即可处理了视觉,要改为视觉和逻辑分离 //所有恢复都走这个函数 public int RecoverHealth_Legacy(MapData map, UnitData origin, UnitData target, int recover) { //Step #0 鲁棒性处理,设置基本参数 if (target == null) { LogSystem.LogError($"DamageSettlement target:{target} is null"); return 0; } var originPlayer = target.Player(map); if (originPlayer == null) { LogSystem.LogError($"Origin Player is null target.id:{target.Id}"); return 0; } //Step #1 如果目标身上有KomeijiFear,消除恐惧替代本次回血 if (target.GetSkill(SkillType.KomeijiFear, out _)) { target.RemoveSkill(SkillType.KomeijiFear, map); //处理View if (map == Main.MapData && !target.IsHideAndCantSee(map, map.PlayerMap.SelfPlayerData)) { var fearGrid = target.Grid(map); if (fearGrid != null) fearGrid.Renderer(map)?.PlayVFXInSight(new GridVFXParams(GridVFXType.Heal)); target.Renderer(map)?.InstantUpdateUnit(true); origin?.Renderer(map)?.InstantUpdateUnit(true); } return 0; } //Step #2 治疗前允许被治疗者技能修改最终回复量 recover = target.BeforeHealSelf(map, origin, HealType.SelfHeal, recover); if (recover < 0) recover = 0; //Step #3处理恢复生命 int realRecover = target.AddHealth(recover); //Step #4处理恢复技能的herotask生命周期 origin?.HeroTask(map)?.OnHealthReturn(map,realRecover,recover); //Step #5 处理View(隐身且不可见的单位不播放特效) if (map == Main.MapData && target.IsHideAndCantSee(map, map.PlayerMap.SelfPlayerData)) return realRecover; var grid = target.Grid(map); if (grid != null) grid.Renderer(map)?.PlayVFXInSight(new GridVFXParams(GridVFXType.Heal)); target.Renderer(map)?.InstantUpdateUnit(true); origin?.Renderer(map)?.InstantUpdateUnit(true); return realRecover; } /// /// 新版恢复生命值方法,包含完整的 Heal 生命周期 /// public int RecoverHealth(MapData map, UnitData origin, UnitData target, int recover, HealType healType = HealType.SelfHeal) { //Step #0 鲁棒性处理,设置基本参数 if (target == null) { LogSystem.LogError($"RecoverHealth target:{target} is null"); return 0; } var originPlayer = target.Player(map); if (originPlayer == null) { LogSystem.LogError($"RecoverHealth Origin Player is null target.id:{target.Id}"); return 0; } //Step #1 触发 BeforeHeal 生命周期(治疗者技能) // 未来可以在这里添加 BeforeHeal 生命周期 //Step #2 触发 OnHealOther 生命周期(通用治疗生命周期) //注意:必须在 KomeijiFear 抵消检测之前触发。否则当被治疗者带 fear 时, //治疗者侧的副作用(如 SanaeDivine 神签 roll、Escape/EscapePro 补 Move AP、 //Dash 设 IsTrigger)会全部失效。Fear 是被治疗者身上的状态,理论上不应该 //影响治疗者侧的技能反应。 origin?.OnHealOther(map, target, healType); //Step #3 如果目标身上有KomeijiFear,消除恐惧替代本次回血 if (target.GetSkill(SkillType.KomeijiFear, out _)) { target.RemoveSkill(SkillType.KomeijiFear, map); //处理View if (map == Main.MapData && !target.IsHideAndCantSee(map, map.PlayerMap.SelfPlayerData)) { var fearGrid = target.Grid(map); if (fearGrid != null) fearGrid.Renderer(map)?.PlayVFXInSight(new GridVFXParams(GridVFXType.Heal)); target.Renderer(map)?.InstantUpdateUnit(true); origin?.Renderer(map)?.InstantUpdateUnit(true); } // 触发 AfterHeal,但 realHealed=0, overflow=0(被恐惧替代) origin?.OnAttackAllyAfterHeal(map, target, healType, 0, 0); return 0; } //Step #4 触发 OnAttackAllyJustBeforeHeal(AttackAlly专用,在此消耗层数等) origin?.OnAttackAllyJustBeforeHeal(map, target, healType); //Step #5 治疗前允许被治疗者技能修改最终回复量 recover = target.BeforeHealSelf(map, origin, healType, recover); if (recover < 0) recover = 0; //Step #6 计算溢出前的生命值(用于计算溢出) int healthBefore = target.Health; int maxHealth = target.GetMaxHealth(); //Step #7 执行恢复生命 int realRecover = target.AddHealth(recover); //Step #8 计算溢出点数 // 溢出 = 治疗量 - (治疗后的生命值 - 治疗前的生命值) // 或者直接:溢出 = 治疗量 - 实际恢复 int overflow = recover - realRecover; if (overflow < 0) overflow = 0; // 安全处理 //Step #9 触发 AfterHeal 生命周期(治疗者视角) // 传入:实际恢复值,溢出点数 origin?.OnAttackAllyAfterHeal(map, target, healType, realRecover, overflow); //Step #10 处理恢复技能的herotask生命周期 origin?.HeroTask(map)?.OnHealthReturn(map, realRecover, recover); //Step #11 处理View(隐身且不可见的单位不播放特效) if (map == Main.MapData && target.IsHideAndCantSee(map, map.PlayerMap.SelfPlayerData)) return realRecover; var grid = target.Grid(map); if (grid != null) grid.Renderer(map)?.PlayVFXInSight(new GridVFXParams(GridVFXType.Heal)); target.Renderer(map)?.InstantUpdateUnit(true); origin?.Renderer(map)?.InstantUpdateUnit(true); return realRecover; } //自然死亡 public void UnitDie(MapData map, UnitData unit, int dmg) { unit.Health = 0; if (!map.GetGridDataByUnitId(unit.Id, out var targetGrid)) { LogSystem.LogWarning($"UnitDie: unitId={unit.Id} missing grid relation, removing stale unit data."); map.SetUnitDataDie(unit); return; } bool notifymoment = false; // 如果是伟人死亡,对应的player受到复活冷却,冷却回合数等于死亡时等级+1 SetHeroRevivePenalty(map, unit, unit.UnitType, unit.GiantType, unit.UnitLevel); // 如果是英雄战船死亡,对应的player受到携带英雄等级+1的复活冷却 SetHeroRevivePenalty(map, unit, unit.CarryUnitType, unit.CarryGiantType, unit.CarryUnitLevel); if (map == Main.MapData && unit.Player(map) == map.PlayerMap.SelfPlayerData) { if (unit.UnitType == UnitType.Giant || unit.CarryUnitType == UnitType.Giant) { EventManager.Publish(new ShowUINotifyMoment() { MomentSubType = MomentSubType.BattleHeroDie, Empire = unit.Player(map).Empire, }); } if(unit.IsBigGuy(true)) { EventManager.Publish(new ShowUINotifyMoment() { MomentSubType = MomentSubType.BattleSacrify, Empire = unit.Player(map).Empire, }); } if (unit.Grid(map, out var grid) && grid.Resource == ResourceType.Treasure) { EventManager.Publish(new ShowUINotifyMoment() { MomentSubType = MomentSubType.BattleTreasure, Empire = unit.Player(map).Empire, }); } } if (notifymoment && map == Main.MapData && unit.Player(map) == map.PlayerMap.SelfPlayerData) { EventManager.Publish(new ShowUINotifyMoment() { MomentSubType = MomentSubType.BattleHeroDie, Empire = unit.Player(map).Empire, }); } map.OnAnyUnitDie(map, unit); map.SetUnitDataDie(unit); } //unit非自然死亡,如背盟、国家灭亡、解散、被挤死等 public void UnitUnnaturalDie(MapData map, UnitData unit) { unit.Health = 0; // 如果是伟人死亡,对应的player受到复活冷却,冷却回合数等于死亡时等级+1 SetHeroRevivePenalty(map, unit, unit.UnitType, unit.GiantType, unit.UnitLevel); map.SetUnitDataDie(unit); } private static void SetHeroRevivePenalty(MapData map, UnitData unit, UnitType unitType, GiantType giantType, uint unitLevel) { if (unitType != UnitType.Giant) return; if (!map.GetPlayerDataByUnitId(unit.Id, out var player)) return; if (!Table.Instance.UnitTypeDataAssets.GetUnitTypeInfo(unitType, giantType, unitLevel, out var info)) return; player.giantPenalty[(uint)info.ChessType] = (int)unitLevel + 1; } public void DebugOutputMoveInfo(MapData mapData) { string t = "\n"; for (int i = (int)mapData.MapConfig.Width - 1; i >= 0; i--) { for (int j = (int)mapData.MapConfig.Height - 1; j >= 0; j--) if (MovementRemainMap[i, j] < 0) t += "@"; else t += ((int)MovementRemainMap[i, j]).ToString(); t += "\n"; } Debug.Log(t); } //计算单位移动或者攻击的网格数组,存储在InfoMap中,方便后续判断使用,是一个需要立刻使用的,非长久存储的临时infomap public void CalcUnitMoveInfo(MapData mapData, uint uid) { //Step #1 设置好各种初始参数 mapData.UnitMap.GetUnitDataByUnitId(uid, out var unitData); mapData.GetPlayerDataByUnitId(uid, out var playerData); mapData.GetGridDataByUnitId(uid, out var gridData); if (unitData == null || playerData == null || gridData == null) { return; } int width = (int)mapData.MapConfig.Width; int height = (int)mapData.MapConfig.Height; //Step #2 根据地形先设置好costMap基础信息,MoveInfoCostMap = -1表示不可抵达 foreach (var targetGridData in mapData.GridMap.GridList) { int i = targetGridData.Pos.X, j = targetGridData.Pos.Y; if (playerData.Sight.CheckIsInSight(targetGridData.Id)) { //如果是陆地单位 if (unitData.GetLandType() is LandType.LandAndPort) { MovementCostMap[i, j] = 1; if (targetGridData.Vegetation == Vegetation.Trees) { MovementCostMap[i, j] = 3; // 走 IsIgnoreForestMoveDebuff 聚合:BAMBOOMOVE / CREEP 等任一持有都视作无视森林减损 if (unitData.IsIgnoreForestMoveDebuff(mapData)) MovementCostMap[i, j] = 1; } if (targetGridData.Feature == TerrainFeature.Mountain) MovementCostMap[i, j] = unitData.GetSkill(SkillType.MOUNTAINMOVE, out var _) ? 3 : -1; if (targetGridData.Terrain == TerrainType.ShallowSea) { if (targetGridData.Resource == ResourceType.Bridge) MovementCostMap[i, j] = 1; //如果是port,要判断是盟军的port 还是其他的port else if (targetGridData.Resource == ResourceType.Port) { //如果没有port科技,不能去 if (!unitData.GetSkill(SkillType.WATERMOVE, out var _)) MovementCostMap[i, j] = -1; //如果是己方的port else if (mapData.GetPlayerDataByTerritoryGridId(targetGridData.Id, out var targetGridPlayer) && mapData.SameUnion(targetGridPlayer.Id, playerData.Id)) MovementCostMap[i, j] = 999; else MovementCostMap[i, j] = -1; } else MovementCostMap[i, j] = -1; } if (targetGridData.Terrain == TerrainType.DeepSea) MovementCostMap[i, j] = -1; } else if (unitData.GetLandType() is LandType.LandAndWater) { MovementCostMap[i, j] = 1; if (targetGridData.Vegetation == Vegetation.Trees) { MovementCostMap[i, j] = 3; // 走 IsIgnoreForestMoveDebuff 聚合:BAMBOOMOVE / CREEP 等任一持有都视作无视森林减损 if (unitData.IsIgnoreForestMoveDebuff(mapData)) MovementCostMap[i, j] = 1; } if (targetGridData.Feature == TerrainFeature.Mountain){ MovementCostMap[i, j] = unitData.GetSkill(SkillType.MOUNTAINMOVE, out var _) ? 3 : -1; } if (targetGridData.Terrain == TerrainType.ShallowSea) { MovementCostMap[i, j] = 1; //如果没有WATERMOVE if (!unitData.GetSkill(SkillType.WATERMOVE, out var _)) MovementCostMap[i, j] = -1; } if (targetGridData.Terrain == TerrainType.DeepSea) { MovementCostMap[i, j] = 1; if (!unitData.GetSkill(SkillType.OCEANMOVE, out var _)) MovementCostMap[i, j] = -1; } } else if (unitData.GetLandType() is LandType.LandOnly) { MovementCostMap[i, j] = 1; if (targetGridData.Vegetation == Vegetation.Trees) { MovementCostMap[i, j] = 3; // 走 IsIgnoreForestMoveDebuff 聚合:BAMBOOMOVE / CREEP 等任一持有都视作无视森林减损 if (unitData.IsIgnoreForestMoveDebuff(mapData)) MovementCostMap[i, j] = 1; } if (targetGridData.Feature == TerrainFeature.Mountain) MovementCostMap[i, j] = unitData.GetSkill(SkillType.MOUNTAINMOVE, out var _) ? 3 : -1; if (targetGridData.Terrain is TerrainType.ShallowSea or TerrainType.DeepSea) { if (targetGridData.Resource == ResourceType.Bridge) MovementCostMap[i, j] = 1; else MovementCostMap[i, j] = -1; } } //如果是水上单位 else if (unitData.GetLandType() is LandType.WaterAndAshore or LandType.WaterOnly) { if (targetGridData.Terrain == TerrainType.DeepSea) MovementCostMap[i, j] = unitData.GetSkill(SkillType.OCEANMOVE, out var _) ? 1 : -1; else if (targetGridData.Terrain == TerrainType.ShallowSea) MovementCostMap[i, j] = 1; else { MovementCostMap[i, j] = 999; if (targetGridData.Feature == TerrainFeature.Mountain && !unitData.GetSkill(SkillType.MOUNTAINMOVE, out var _)) MovementCostMap[i, j] = -1; } } else if (unitData.GetLandType() is LandType.Fly) { MovementCostMap[i, j] = 1; } } //没有视野,那就不可抵达 else MovementCostMap[i, j] = -1; //特殊处理MORIYAKNIGHTMOVE,只允许在山脉、城市中心、军营之间移动 if (unitData.GetSkill(SkillType.MORIYAKNIGHTMOVE, out var _)) { bool isMountain = targetGridData.Feature == TerrainFeature.Mountain; bool isCity = targetGridData.CityOnGrid(mapData, out var _); bool isMoriyaMilitary = targetGridData.Resource == ResourceType.MoriyaMilitary; if (!(isMountain || isCity || isMoriyaMilitary)) { MovementCostMap[i, j] = -1; } } if (MovementCostMap[i, j] == 0) { Debug.Log("Fatal Error !!!!"); } } //Step #3 将所有敌人的控制区的costMap设置为999 if(!unitData.IsIgnoreZOC()) foreach (UnitData B in mapData.UnitMap.UnitList) { if (!B.IsAlive()) continue; //不具备ZOC能力的单位不产生控制区 if (!B.HasZOC()) continue; mapData.GetPlayerDataByUnitId(B.Id, out var playerDataB); mapData.GetGridDataByUnitId(B.Id, out var gridDataB); //盟友则不会有控制区域 if (mapData.SameUnion(playerDataB.Id ,playerData.Id)) continue; _aroundBuf ??= new List(); _aroundBuf.Clear(); mapData.GridMap.GetAroundGridDataSet_NOCENTER(1,1,gridDataB, _aroundBuf); foreach (var targetGridData in _aroundBuf) { int x = targetGridData.Pos.X, y = targetGridData.Pos.Y; MovementCostMap[x, y] = (MovementCostMap[x, y] < 0) ? -1 : 999; } MovementCostMap[gridDataB.Pos.X,gridDataB.Pos.Y] = 999; } //Step #4将所有友军/刚背盟盟友的城市中心costmap设置为-1(不能进入) //刚背盟(LeagueRupture)的城心同样要挡路,否则单位会直接走进城心触发自动宣战 foreach (var tcity in mapData.CityMap.CityList) { if (!mapData.GetPlayerDataByCityId(tcity.Id, out var tplayer))continue; if (tplayer.Id == playerData.Id) continue; if(!mapData.SameUnionOrJustBreakUnion(tplayer.Id,playerData.Id))continue; if(!mapData.GetGridDataByCityId(tcity.Id,out var tgrid))continue; MovementCostMap[tgrid.Pos.X,tgrid.Pos.Y] = -1; } //Step #5 初始化remainmap,表示剩余多少movement,< 0 则不再扩展。ReachMap则用来确定是否可达 for (int i = 0; i < width; i++) for (int j = 0; j < height; j++) { MovementRemainMap[i, j] = -999; MoveRealCostMap[i, j] = 999; NormalReachMap[i, j] = false; TransMap[i, j] = false; SanaeMap[i, j] = false; MomijiHunterReachMap[i, j] = false; } //设置初始行动力 //注意,这里使用的是FinalMoveRange,和MoveRange不一样,MoveRange是行动力,Fixed则是在这之上还有一层修改,目前主要用于Kanako MovementRemainMap[gridData.Pos.X,gridData.Pos.Y] = unitData.FinalMoveRange(mapData); NormalReachMap[gridData.Pos.X, gridData.Pos.Y] = true; MoveRealCostMap[gridData.Pos.X, gridData.Pos.Y] = 0; //Step #6然后进行SPFA更新最短路 Queue q = new Queue(); //breakTime用来放置负边死循环情况 int breakTime = Mathf.Min(10000000, width * height * width * height); q.Enqueue(gridData.Id); while (breakTime > 0 && q.Count > 0) { breakTime--; if (!mapData.GridMap.GetGridDataByGid(q.Dequeue(), out var gridDataX)) continue; if (MovementRemainMap[gridDataX.Pos.X, gridDataX.Pos.Y] < 0.1f) continue; _aroundBuf ??= new List(); _aroundBuf.Clear(); mapData.GridMap.GetAroundGridDataSet_NOCENTER(1,1,gridDataX, _aroundBuf); foreach (var gridDataY in _aroundBuf) { float cost = MovementCostMap[gridDataY.Pos.X, gridDataY.Pos.Y]; //如果不可达区 if (cost <= 0)continue; //如果是陆地单位,看是否需要减去道路或者桥梁的行动力加成。道路是非敌人领地才行,并且目标不是耗光所有cost的港口或者敌占区 if ((unitData.GetLandType() is LandType.LandAndPort or LandType.LandOnly or LandType.LandAndWater) && Main.PlayerLogic.HasRoadForUnit(mapData, gridDataX, gridDataY, unitData) && cost < 900) cost = 0.5f; //如果能够发生更新 if (MovementRemainMap[gridDataX.Pos.X, gridDataX.Pos.Y] - cost > MovementRemainMap[gridDataY.Pos.X, gridDataY.Pos.Y]) { MovementRemainMap[gridDataY.Pos.X, gridDataY.Pos.Y] = MovementRemainMap[gridDataX.Pos.X, gridDataX.Pos.Y] - cost; NormalReachMap[gridDataY.Pos.X, gridDataY.Pos.Y] = true; MoveRealCostMap[gridDataY.Pos.X, gridDataY.Pos.Y] = cost; //如果xy不再队列里,并且还剩余有行动力,就加入队列 if (!q.Contains(gridDataY.Id) && MovementRemainMap[gridDataY.Pos.X, gridDataY.Pos.Y] > 0.1f) q.Enqueue(gridDataY.Id); } } } //Step #7 -------------------------------------------先处理常规移动的最终情况------------------------------- for (int i = 0; i < width; i++) for (int j = 0; j < height; j++) FinalReachMap[i,j] = NormalReachMap[i, j]; // MOMIJIHUNTER 技能拓展,做法是让所有MOVEInfoMap -= MoveFloor MOMIJIHUNTER_SkillHandler(mapData,unitData); // UtsuhoBase 技能拓展:移动范围内所有格子都视为可达(无视视野) if (unitData.GetSkill(SkillType.UtsuhoBase, out var _)) { int moveRange = unitData.FinalMoveRange(mapData); for (int i = 0; i < width; i++) for (int j = 0; j < height; j++) { if (!mapData.GridMap.GetGridDataByPos(i, j, out var tmpGrid)) continue; if (mapData.GridMap.CalcDistance(gridData, tmpGrid) > moveRange) continue; FinalReachMap[i, j] = true; } } // YuugiDash 技能拓展:在直线方向上延伸3格作为可抵达格子 if (unitData.GetSkill(SkillType.YuugiDash, out var _)) { bool hasDashPro = unitData.GetSkill(SkillType.YuugiDashPro, out var _); bool hasMovePlus = unitData.GetSkill(SkillType.YuugiMovePlus, out var _); // 4方向或8方向 Vector2Int[] directions = hasMovePlus ? new[] { new Vector2Int(1,0), new Vector2Int(-1,0), new Vector2Int(0,1), new Vector2Int(0,-1), new Vector2Int(1,1), new Vector2Int(1,-1), new Vector2Int(-1,1), new Vector2Int(-1,-1) } : new[] { new Vector2Int(1,0), new Vector2Int(-1,0), new Vector2Int(0,1), new Vector2Int(0,-1) }; int gx = gridData.Pos.X, gy = gridData.Pos.Y; foreach (var dir in directions) { // 取该方向上起点邻格的remain值作为递减基准 float baseRemain = MovementRemainMap[gx, gy]; for (int step = 1; step <= 3; step++) { int nx = gx + dir.x * step; int ny = gy + dir.y * step; if (nx < 0 || nx >= width || ny < 0 || ny >= height) break; if (!mapData.GridMap.GetGridDataByPos(nx, ny, out var dashGrid)) break; // 必须是当前单位在当前局面下可进入的格子(与常规移动一致) if (MovementCostMap[nx, ny] <= 0) break; // LandAndPort单位不能dash经过敌方港口 if (unitData.GetLandType() == LandType.LandAndPort && dashGrid.Terrain == TerrainType.ShallowSea && dashGrid.Resource == ResourceType.Port && !(mapData.GetPlayerDataByTerritoryGridId(dashGrid.Id, out var dashPortPlayer) && mapData.SameUnion(dashPortPlayer.Id, playerData.Id))) break; // 检查是否有敌方单位 if (dashGrid.RealUnit(mapData, out var blockUnit) && blockUnit.Id != unitData.Id && !mapData.IsLeagueUnitByUnit(unitData.Id, blockUnit.Id)) { // 有敌方单位:没有DashPro就不能继续延伸 if (!hasDashPro) break; } FinalReachMap[nx, ny] = true; // 为Dash格子设置递减的remain值,保证路径搜索能形成递减序列 float dashRemain = baseRemain - step; if (dashRemain > MovementRemainMap[nx, ny]) MovementRemainMap[nx, ny] = dashRemain; } } } //Step #8 ----------------------------------------------------这里开始处理各种特殊情况-------------------------------------- //处理SAKUYAFLY技能,可以移动到己方伟人的身周 if (unitData.IsCanMoveGiantNearbyGrid(mapData)) { //遍历所有单位,找到己方的伟人或者小恶魔 foreach (var unitA in mapData.UnitMap.UnitList) if((unitA.TreatedAsHero(mapData,unitData)) && mapData.GetPlayerIdByUnitId(unitA.Id,out var pid) && pid == playerData.Id && unitA.Id != unitData.Id) { mapData.GetGridDataByUnitId(unitA.Id, out var gridB); _aroundBuf2 ??= new List(); _aroundBuf2.Clear(); mapData.GridMap.GetAroundGridDataSet_NOCENTER(1,1,gridB, _aroundBuf2); foreach (var gridC in _aroundBuf2){ int xx = gridC.Pos.X, yy = gridC.Pos.Y; if(CheckUnitAbleForGrid_OfflineStatus(mapData,playerData,unitData,gridC) && !gridC.VisibleUnit(mapData,playerData,out var tt)) { int x = gridC.Pos.X, y = gridC.Pos.Y; TransMap[x, y] = true; }} } } //接入CITYTRANSPORT,在首都联通的城市之间传送 if (unitData.IsCanMoveNoUnitSelfCity(mapData)) { //当前在我方的首都或者首都联通的城市才可以 if (mapData.GetCityDataByGid(gridData.Id, out var cityA) && (cityA.IsCapital || cityA.IsConnectedCapital) && mapData.GetPlayerDataByCityId(cityA.Id,out var cityAsPlayer) && cityAsPlayer.Id == playerData.Id) { //遍历所有我方城市 如果位置没有人且联通,就可以移动过去 using var pooledCityDataList = THCollectionPool.GetListHandle(out var cityDataList); mapData.GetCityDataListByPlayerId(playerData.Id, cityDataList); foreach (var cityB in cityDataList) { if(cityB.Id == cityA.Id)continue; if (!cityB.IsConnectedCapital && !cityB.IsCapital) continue; if (!mapData.GetGridDataByCityId(cityB.Id, out var gridB)) continue; if (gridB.VisibleUnit(mapData,playerData, out var _)) continue; TransMap[gridB.Pos.X, gridB.Pos.Y] = true; } } } //接入MORIYAKNIGHTMOVE,在城市中心和天狗酒馆之间传送 if (unitData.GetSkill(SkillType.MORIYAKNIGHTMOVE,out var _) && unitData.Grid(mapData,out var curGrid) && (curGrid.Resource is ResourceType.MoriyaMilitary or ResourceType.CityCenter)) { //遍历所有我方城市和军营 如果位置没有人就可以移动过去 using var pooledCityDataList = THCollectionPool.GetListHandle(out var cityDataList); mapData.GetCityDataListByPlayerId(playerData.Id, cityDataList); foreach (var targetCity in cityDataList) { if (!targetCity.Grid(mapData, out var targetGrid)) continue; if (targetGrid.Id == curGrid.Id) continue; if (targetGrid.VisibleUnit(mapData,playerData, out _)) continue; TransMap[targetGrid.Pos.X, targetGrid.Pos.Y] = true; } foreach (var targetCity in cityDataList) { //然后处理该城市的军营 var gidSet = targetCity.Territory.TerritoryArea; foreach (var targetGid in gidSet) { if (targetGid == curGrid.Id) continue; if (!mapData.GridMap.GetGridDataByGid(targetGid, out var targetGridB))continue; if (targetGridB.Resource != ResourceType.MoriyaMilitary) continue; if (targetGridB.VisibleUnit(mapData,playerData,out var _)) continue; TransMap[targetGridB.Pos.X, targetGridB.Pos.Y] = true; } } } // SANAEWIND{X} 技能拓展 if (unitData.GetSkill(SkillType.SANAEWINDX, out var skill)) { var sanaeWindXSkill = skill as SanaeWindXSkill; if (sanaeWindXSkill != null) { using var pooledTargetVec2 = THCollectionPool.GetListHandle(out var targetVec2); for (int i = 0; i < width; i++) { for (int j = 0; j < height; j++) { if (!NormalReachMap[i, j]) continue; targetVec2.Add(new Vector2Int(i, j)); } } foreach (var vec in targetVec2) { var newVec = vec + sanaeWindXSkill.GetOffset(); if (newVec.x < 0 || newVec.x >= width || newVec.y < 0 || newVec.y >= height) continue; if (!mapData.GridMap.GetGridDataByPos(vec.x, vec.y, out var oldGrid)) continue; if (!mapData.GridMap.GetGridDataByPos(newVec.x, newVec.y, out var tmpGrid)) continue; if (!CheckUnitAbleForGrid_RealTimeStatus(mapData, unitData, tmpGrid)) continue; //如果是陆地单位,然后经过了港口,那么不能再飞起来 if (oldGrid.Resource == ResourceType.Port && unitData.GetLandType() is LandType.LandAndPort or LandType.LandOnly) continue; //不能进入友方城市中心 if (tmpGrid.CityOnGrid(mapData, out var city)) { if (!city.Player(mapData, out var playerA)) continue; if (!unitData.Player(mapData, out var playerB)) continue; if (playerA.Id != playerB.Id && mapData.SameUnionOrJustBreakUnion(playerA.Id, playerB.Id)) continue; } SanaeMap[newVec.x, newVec.y] = true; } } } // MORIYAKNIGHTMOVE 技能拓展(主要是消除早苗的影响) if (unitData.GetSkill(SkillType.MORIYAKNIGHTMOVE, out var _)) { for (int i = 0; i < width; i++) for (int j = 0; j < height; j++) if (mapData.GridMap.GetGridDataByV2(new Vector2(i, j), out var grid) && grid.Terrain == TerrainType.Land && !(grid.Feature == TerrainFeature.Mountain || grid.CityOnGrid(mapData,out var _) )) { SanaeMap[i,j] = false; } } } private bool MOMIJIHUNTER_SkillHandler(MapData mapData,UnitData unitData) { int width = (int)mapData.MapConfig.Width; int height = (int)mapData.MapConfig.Height; if (!unitData.GetSkill(SkillType.MOMIJIHUNTER, out var skillHunter)) return false; for (int i = 0; i < width; i++) for (int j = 0; j < height; j++) { if (!NormalReachMap[i, j]) continue; //floor 算出亏欠几点移动力 var floor = unitData.GetGridMoveFloor(mapData, unitData, mapData.GridMap.GetGridDataByV2(new Vector2(i,j),out var tmpGrid) ? tmpGrid : null); //如果剩余行动力 减去亏欠的移动力,再加上上一次移动过来的realcost,仍然>=0,那么说明这个格子是仍然可以抵达的 MomijiHunterReachMap[i, j] = (MovementRemainMap[i, j] + MoveRealCostMap[i, j] - floor) > 0.1f; } for (int i = 0; i < width; i++) for (int j = 0; j < height; j++) FinalReachMap[i,j] = NormalReachMap[i, j] && MomijiHunterReachMap[i, j]; return true; } // 检查单位是否能攻击目标点 public bool CheckUnitCanAttackPos(MapData mapData, UnitData unitData, GridData gridData) { if (Table.Instance.UnitTypeDataAssets.GetUnitTypeInfo(unitData.UnitType,unitData.GiantType,unitData.UnitLevel,out var info) && info.Attack <= 0) return false; if (!mapData.GetGridDataByUnitId(unitData.Id, out var originGrid)) return false; if(!mapData.GetPlayerDataByUnitId(unitData.Id, out var player)) return false; if (gridData.VisibleUnit(mapData,player, out var targetUnit)) return false; if(!mapData.GetPlayerDataByUnitId(targetUnit.Id, out var targetPlayer)) return false; if (player == targetPlayer) return false; if (mapData.GridMap.CalcDistance(gridData, originGrid) > unitData.GetAttackRange(mapData)) return false; return true; } // None, Move, Attack, Port, MoveAshore, Ally,AttackGround public MoveAttackType CheckUnitCanMoveOrAttack(MapData mapData, UnitData unitDataA, GridData gridDataB) { //默认在调用这个函数前已经调用了 CalcUnitMoveInfo(A),计算好了。不组合使用是不行的 mapData.GetGridDataByUnitId(unitDataA.Id,out var gridDataA); if (!unitDataA.Player(mapData, out var playerDataA)) return MoveAttackType.None; //如果目标位置有单位且并非隐身不可见 if(gridDataB.VisibleUnit(mapData,playerDataA,out var unitDataB) ) { mapData.GetPlayerDataByUnitId(unitDataB.Id, out var playerDataB); //如果目标位置上面有友军单位,除非有特殊技能,否则是none if (mapData.SameUnionOrJustBreakUnion(playerDataA.Id, playerDataB.Id)) { if (mapData.GridMap.CalcDistance(gridDataA, gridDataB) <= unitDataA.GetAttackRange(mapData)) { //处理Flandre技能,可以是刚解除结盟的友军 if (unitDataA.GetSkill(SkillType.FLANDREATTACK, out var _) ) return MoveAttackType.Attack; //以下处理attackAlly友军的情况(不能背盟) if (mapData.SameUnion(playerDataA.Id, playerDataB.Id) && (unitDataA.IsCanAttackAlly() || unitDataA.CanFeedBonePile(mapData))) { if (unitDataA.CanFeedBonePile(mapData) && unitDataB.UnitType == UnitType.BonePile) return MoveAttackType.Ally; if(unitDataA.IsCanAttackAlly() && unitDataA.IsCanAttackTargetAlly(mapData,unitDataB)) return MoveAttackType.Ally; } } return MoveAttackType.None; } //如果目标位置上面有敌军单位,那不可移动,但是有可能可以攻击 else if (!unitDataA.IsLimitSelfAttack(mapData)) //具备攻击能力 { if (mapData.GridMap.CalcDistance(gridDataA, gridDataB) <= unitDataA.GetAttackRange(mapData)) //判断距离在攻击距离范围内, { //特殊处理:学者不能攻击英雄 if (unitDataA.GetSkill(SkillType.CONVERT, out var _) && unitDataB.UnitFullType.UnitType == UnitType.Giant) return MoveAttackType.None; //技能层细粒度过滤(如 INFILTRATE 仅允许打城心上的敌人) if (!unitDataA.IsCanAttackTargetUnit(mapData, unitDataB)) return MoveAttackType.None; /*if(mapData.GridMap.CalcDistance(gridDataA, gridDataB) > 3) Debug.Log("异常的攻击距离");*/ return MoveAttackType.Attack; //返回可以攻击 } else if(unitDataA.GetSkill(SkillType.KANAKOBATTLEFIELDPRO,out var _) && unitDataA.GetSkill(SkillType.KANAKOSITTING,out var _)) //不在攻击范围内,有一种可能 kanako { _aroundBuf2 ??= new List(); _aroundBuf2.Clear(); mapData.GridMap.GetAroundGridDataSet_NOCENTER(1,1,gridDataB, _aroundBuf2); bool can = false; //kanako 的3级技能,可以攻击任意远的我方英雄/hebi周围1格敌人 foreach(var tmpGrid in _aroundBuf2) if(tmpGrid.VisibleUnit(mapData,playerDataA,out var tmpUnit) && tmpUnit.Player(mapData) == gridDataA.Player(mapData) && (tmpUnit.GiantType != GiantType.None || (tmpUnit.UnitType == UnitType.MoriyaHebi && tmpUnit.UnitLevel >= 3)) && tmpUnit.Grid(mapData).Feature == TerrainFeature.Mountain) { can = true; break; } if (can) return MoveAttackType.Attack; } } return MoveAttackType.None; } //如果目标位置没有任何单位(或目标单位是隐身不可见状态) else { //如果满足AttackGround,优先处理 if (unitDataA.IsCanAttackTargetGrid(mapData, gridDataB)) return MoveAttackType.AttackGround; //如果不能在非我方领土移动,优先判断 if (unitDataA.IsLimitMoveToSelfTerrain(Main.MapData) && unitDataA.Player(Main.MapData) != gridDataB.Player(Main.MapData)) return MoveAttackType.None; //如果是传送或者sanae,最优先判断 if (TransMap[gridDataB.Pos.X, gridDataB.Pos.Y]) return MoveAttackType.MoveTeleport; if (SanaeMap[gridDataB.Pos.X, gridDataB.Pos.Y]) return MoveAttackType.Move; //如果常规移动不可达,直接return if (!FinalReachMap[gridDataB.Pos.X, gridDataB.Pos.Y]) return MoveAttackType.None; //下港口 if (gridDataB.Terrain == TerrainType.ShallowSea && unitDataA.GetLandType() == LandType.LandAndPort) return MoveAttackType.MoveToPort; //上岸 if (gridDataB.Terrain == TerrainType.Land && unitDataA.GetLandType() == LandType.WaterAndAshore) return MoveAttackType.MoveAshore; return MoveAttackType.Move; } return MoveAttackType.None; } //默认在调用这个函数前已经调用了 CalcUnitMoveInfo(A),计算好了。不组合使用是不行的 public MoveAttackType GetMovePath(MapData map, Vector2Int start, Vector2Int end,out List path) { if (TransMap[end.x, end.y]) { path = new List { start, end }; return MoveAttackType.MoveTeleport; } path = new List(); Vector2Int[] Dirs = { new(-1, 0), new(0, -1),new(0, 1),new(1, 0), new(1, -1), new(1, 1),new(-1, -1),new(-1, 1) }; if (start == end) { path = new List { start }; return MoveAttackType.Move; } var queue = new Queue(); // 用字典记录路径来源,同时兼作“已访问”集合 using var pooledCameFrom = THCollectionPool.GetDictionaryHandle(out var cameFrom); uint width = map.MapConfig.Width; uint height = map.MapConfig.Height; queue.Enqueue(start); cameFrom[start] = start; // 标记起点已访问 while (queue.Count > 0) { var current = queue.Dequeue(); float currentHeight = MovementRemainMap[current.x, current.y]; if (current == end) break; // 首次到达终点,即为最短路径 //if (currentHeight < 0) continue; 这里不能直接continue,因为有sanaeMap存在 即使<0也可以继续移动到sanaemap上 foreach (var dir in Dirs) { var next = current + dir; // 检查:1.越界 2.已访问 if (next.x < 0 || next.x >= width || next.y < 0 || next.y >= height || cameFrom.ContainsKey(next)) continue; // 检查:3.高度非增 if (MovementRemainMap[next.x, next.y] < currentHeight || SanaeMap[next.x, next.y]) { queue.Enqueue(next); cameFrom[next] = current; } } } // 如果终点不可达,返回空列表 if (!cameFrom.ContainsKey(end)) return MoveAttackType.None; // 从终点回溯路径 path = new List(); for (var at = end; at != start; at = cameFrom[at]) { path.Add(at); } path.Add(start); path.Reverse(); // 翻转为从起点到终点的顺序 return MoveAttackType.Move; } public void Upgrade(MapData mapData, UnitData u) { /* u.veteran = true; u.health = u.maxHealth + 5; RenderUpdateManager.Instance.AddUnitRUList(u.unitId); */ } //unit[uid]原地休息 public void Recover(MapData mapData, UnitData unitData) { } //unit[uid]从陆地进入港口,变为boat public void LandToBoat(MapData mapData, UnitData unitData) { unitData.CarryUnitFullType = unitData.UnitFullType; if (unitData.UnitType == UnitType.KomeijiIndianBigGuy) Main.UnitLogic.UnitTypeTransform(mapData, unitData, UnitType.KomeijiIndianJuggernaut); else if (unitData.UnitType == UnitType.BigGuy) Main.UnitLogic.UnitTypeTransform(mapData, unitData, UnitType.Juggernaut); else if (unitData.UnitType == UnitType.KaguyaFrenchWolf) Main.UnitLogic.UnitTypeTransform(mapData, unitData, UnitType.WolfJuggernaut); else if(unitData.UnitType == UnitType.Giant) Main.UnitLogic.UnitTypeTransform(mapData, unitData, UnitType.GiantJuggernaut,GiantType.None,unitData.UnitLevel); else if (unitData.UnitType == UnitType.Cloak) Main.UnitLogic.UnitTypeTransform(mapData, unitData, UnitType.Dinghy); else if (unitData.UnitType == UnitType.Dagger) Main.UnitLogic.UnitTypeTransform(mapData, unitData, UnitType.DaggerShip); else Main.UnitLogic.UnitTypeTransform(mapData, unitData, UnitType.Boat); // 变身已自带回合结束语义,直接清 AP;不通过 ClearActionPoint 派发 OnClearActionPoint,避免误移除 Cloak 的 HideState unitData.ActionPoint.Clear(); } //unit[uid]从港口进入陆地,变为原来的单位 public void BoatToLand(MapData mapData, UnitData unitData) { Main.UnitLogic.UnitTypeTransform(mapData,unitData, unitData.CarryUnitType, unitData.CarryGiantType,unitData.CarryUnitLevel); unitData.ActionPoint.Clear(); unitData.CarryUnitFullType = new UnitFullType(); } public bool HeroUpgrade(MapData map,UnitData unit) { var player = unit.Player(map); if (player == null) return false; var type = unit.UnitFullType; if (type.UnitType != UnitType.Giant) return false; if (type.GiantType == GiantType.None) return false; if (type.UnitLevel >= 4) return false; var targetType = new UnitFullType() { UnitType = unit.UnitType, GiantType = unit.GiantType, UnitLevel = unit.UnitLevel + 1 }; UnitTypeTransform(map,unit,targetType); // moment foreach (var item in player.MomentData.Items) item.OnUpgradeHero(map, player, targetType); return unit.Player(map)?.PlayerHeroData.UpdateHero(map,unit.Player(map),unit.UnitFullType) ?? false; } //unitData开启下一回合 public void StartNextTurn(MapData mapData, UnitData unitData) { if (!unitData.IsAlive()) return; unitData.SetFullActionPoint(); unitData.Renderer(mapData)?.InstantUpdateUnit(true); } public void UnitEndTurn(MapData mapData, UnitData unitData) { if (!unitData.IsAlive()) return; //在进入下一个回合前,如果可以回血自动回血 if (unitData.Health < unitData.GetMaxHealth() && unitData.GetActionPoint(ActionPointType.Capture) > 0) { if (mapData.GetGridDataByUnitId(unitData.Id, out var grid) && mapData.GetPlayerDataByUnitId(unitData.Id, out var player) ) { //如果有KomeijiFear,消除恐惧替代本次回血 if (unitData.GetSkill(SkillType.KomeijiFear, out _)) { unitData.RemoveSkill(SkillType.KomeijiFear, mapData); grid.Renderer(mapData)?.PlayVFXInSight(new GridVFXParams(GridVFXType.Heal)); unitData.Renderer(mapData)?.InstantUpdateUnit(true); } else { int recoverAmount = 2; if (mapData.CheckGridIdBelongPlayerIdUnion(grid.Id, player.Id)) recoverAmount += 2; RecoverHealth(mapData, null, unitData, recoverAmount, HealType.SelfHeal); } } } } public void UnitTypeTransform(MapData mapData, UnitData unitData, UnitFullType targetType) { UnitTypeTransform(mapData, unitData, targetType.UnitType, targetType.GiantType, targetType.UnitLevel); } //unitData转换成另一个UnitType public void UnitTypeTransform(MapData mapData, UnitData unitData, UnitType targetType, GiantType giantType = GiantType.None, uint unitLevel = 0) { if (mapData == null || unitData == null) { LogSystem.LogError($"UnitTypeTransform param is null. mapData:{mapData} unitData:{unitData}"); return; } if (Table.Instance?.UnitTypeDataAssets == null) { LogSystem.LogError("UnitTypeTransform failed: Table.Instance.UnitTypeDataAssets is null."); return; } //Debug.Log($"Check Transform Before APMPCP = {unitData.GetActionPoint(ActionPointType.Attack)} , {unitData.GetActionPoint(ActionPointType.Move)} ,{unitData.GetActionPoint(ActionPointType.Capture)} "); //重新处理一下: //生命周期: 1 . 卸载所有原来的skill //TODO 要迭代这块,主要是在skill那边要加标记,确认一个skill是否可以继承,是否transfrom的时候要删掉之类的,要不要触发情况等等 Table.Instance.UnitTypeDataAssets.GetUnitTypeInfo(unitData.UnitType, unitData.GiantType, unitData.UnitLevel, out var originInfo); Table.Instance.UnitTypeDataAssets.GetUnitTypeInfo(targetType, giantType, unitLevel, out var targetInfo); if (originInfo == null || targetInfo == null) return; unitData.SkillCache ??= new List(); unitData.Skills ??= new List(); bool useCache = unitData.FullTypeCache == new UnitFullType(targetType, giantType, unitLevel); using var pooledSkillCache = THCollectionPool.GetDictionaryHandle(out var skillCache); using var pooledOriginSkills = THCollectionPool.GetListHandle(out var originSkills); foreach (var t in unitData.Skills) originSkills.Add(t); foreach (var t in unitData.SkillCache) { if (t == null) continue; skillCache[t.GetSkillType()] = t; } unitData.SkillCache.Clear(); using var pooledReservedSkillTypes = THCollectionPool.GetHashSetHandle(out var reservedSkillTypes); foreach (var skill in originSkills) { // if (targetInfo.Skills.Contains(skillType)) continue; if (skill == null) continue; var fullType = new UnitFullType(targetType, giantType, unitLevel); bool reserved; try { reserved = skill.ReservedOnTransform(unitData, fullType); } catch (Exception e) { LogSystem.LogError($"UnitTypeTransform ReservedOnTransform failed. skill:{skill.GetType().Name} ex:{e.Message}"); reserved = false; } if (reserved) { reservedSkillTypes.Add(skill.GetSkillType()); continue; } unitData.RemoveSkill(skill.GetSkillType(), mapData); unitData.SkillCache.Add(skill); } if (targetInfo.Skills != null) { foreach (var skillType in targetInfo.Skills) { // 如果该技能已经作为Reserve保留在单位身上,跳过不重建 if (reservedSkillTypes.Contains(skillType)) continue; if (unitData.GetSkill(skillType, out var skill)) { unitData.RemoveSkill(skill.GetSkillType(), mapData); unitData.SkillCache.Add(skill); } if (useCache && skillCache.TryGetValue(skillType, out var value)) unitData.AddOrOverrideSkill(value, mapData, unitData.Id); else unitData.AddOrOverrideSkill(skillType, mapData, unitData.Id); unitData.GetSkill(skillType, out var newSkill); newSkill?.NewSkillOnTransform(unitData.SkillCache); } } unitData.FullTypeCache = unitData.UnitFullType; unitData.UnitFullType.UnitType = targetType; unitData.UnitFullType.GiantType = giantType; unitData.UnitFullType.UnitLevel = unitLevel; //Debug.Log($"Check Transform After APMPCP = {unitData.GetActionPoint(ActionPointType.Attack)} , {unitData.GetActionPoint(ActionPointType.Move)} ,{unitData.GetActionPoint(ActionPointType.Capture)} "); //遍历所有techAtom,填加科技技能 var unitPlayer = unitData.Player(mapData); var techAtoms = unitPlayer?.TechTree?.TechAtomCacheSet; if (techAtoms != null) { foreach (var atom in techAtoms) { if(!Table.Instance.TechDataAssets.GetTechAtomInfo(atom,out var info))continue; if (!info.IsAddSkill) continue; if (!info.CheckCondition(unitData.UnitFullType)) continue; unitData.AddInitSkill(info.AddSkillType, mapData); unitData.GetSkill(info.AddSkillType, out var newSkill); newSkill?.NewSkillOnTransform(unitData.SkillCache); } } var originFullType = new UnitFullType(unitData.UnitType, unitData.GiantType, unitData.UnitLevel); var targetFullType = new UnitFullType(targetType, giantType, unitLevel); if (mapData.GetPlayerDataByUnitId(unitData.Id, out var playerData)) { if (playerData.PlayerHeroData?.HeroTaskDict != null) foreach (var kv in playerData.PlayerHeroData.HeroTaskDict) kv.Value?.OnTransformUnit(mapData, playerData, unitData); if (playerData.MomentData?.Items != null) foreach (var item in playerData.MomentData.Items) item?.OnTransformUnit(mapData, playerData, unitData); // culture card 调用 playerData.PlayerCultureInfo?.OnUnitTransform(mapData, unitData, originFullType, targetFullType); } // Collect 调用 CollectManager.Instance?.TransformUnitCollect(mapData, unitData, originFullType, targetFullType); } public void PassiveMoveAway(MapData mapData,UnitData unitData) { if (!mapData.GetGridDataByUnitId(unitData.Id, out var gridData)) return; var unitGrid = unitData.Grid(mapData); if (unitGrid == null) return; //默认向远离最近城市的方向推出 if(!mapData.GetNearestCity(unitData,out var city))return; var cityGrid = city.Grid(mapData); if (cityGrid == null) return; //当前是否身处水域:海上单位被推开时优先挤向相邻水域,没有可用水域才退而求其次推到陆地 bool onWater = gridData.Terrain == TerrainType.ShallowSea || gridData.Terrain == TerrainType.DeepSea; GridData bestGrid = null; int score = -1; int bestTier = -1; //仅在 onWater 时生效:水域=1,陆地=0 _aroundBuf ??= new List(); _aroundBuf.Clear(); mapData.GridMap.GetAroundGridData(1,1,gridData, _aroundBuf); foreach (var targetGrid in _aroundBuf) { if (targetGrid == gridData) continue; if (!CheckUnitAbleForGrid_RealTimeStatus(mapData, unitData, targetGrid)) continue; //被动推移必须检查真实占位(包括隐身单位),不能推到有隐身单位的格子上 if (targetGrid.RealUnit(mapData, out _)) continue; var tmpScore = mapData.GridMap.CalcManhattanDistance(targetGrid, cityGrid); //TODO MORIYAKNIGHT 特判 if (unitData.UnitType == UnitType.MoriyaKnight && targetGrid.Feature == TerrainFeature.Mountain) { tmpScore += 100000; } if (onWater) { //水域优先于陆地;同 tier 内再按远离城市的曼哈顿距离择优 int tmpTier = (targetGrid.Terrain == TerrainType.ShallowSea || targetGrid.Terrain == TerrainType.DeepSea) ? 1 : 0; if (tmpTier > bestTier || (tmpTier == bestTier && tmpScore > score)) { bestTier = tmpTier; bestGrid = targetGrid; score = tmpScore; } } else { if (score == -1 || tmpScore > score) { bestGrid = targetGrid; score = tmpScore; } } } if (score != -1) { //MoveToLogic(mapData,unitData,bestGrid,MoveType.PassiveMove); var param = new CommonActionParams(mapData, playerData:unitData.Player(mapData), unitData:unitData, gridData:bestGrid); var moveId = new CommonActionId { ActionType = CommonActionType.UnitPassiveMove}; var moveAction = new UnitPassiveMoveAction(moveId); param.RefreshParams(); moveAction.ExecuteWithoutFullActionPeriod(param); } else { var data = new FragmentUnitData(FragmentType.Die, unitData.Renderer(mapData),unitGrid,city); PresentationManager.EnqueueTask(new FragmentSequencerTask(new FragmentDie(data))); Main.UnitLogic.UnitUnnaturalDie(mapData,unitData); } } //判断一个unit 目前是否能直接站在那个grid上,要考虑grid目前的状态(例如盟友的城市中心,有人的格子 等等),但是不考虑隐身! public bool CheckUnitAbleForGrid_RealTimeStatus(MapData mapData, UnitData unitData, GridData gridData) { //TODO 这里一定要改了,用把landType通过unitSkill改过来 ,或者至少完成一套完备的方案 if (!mapData.CheckLandTypeForGrid(unitData.UnitFullType, gridData)) return false; if (!mapData.GetPlayerDataByUnitId(unitData.Id, out var playerData)) return false; if (!Table.Instance.UnitTypeDataAssets.GetUnitTypeInfo(unitData.UnitType, unitData.GiantType,unitData.UnitLevel, out var unitInfo)) return false; //如果上面有人 if (gridData.VisibleUnit(mapData,playerData, out var tmp)) return false; if (unitData.GetLandType() == LandType.Fly) return true; //如果是水域 if (gridData.Terrain != TerrainType.Land) { if (gridData.Resource == ResourceType.Bridge) { return true; } //如果本身就是水生单位 if (unitData.GetLandType() == LandType.WaterOnly || unitData.GetLandType() == LandType.WaterAndAshore || unitData.GetLandType() == LandType.LandAndWater) { if(gridData.Terrain == TerrainType.ShallowSea) return unitData.GetSkill(SkillType.WATERMOVE,out var _); if(gridData.Terrain == TerrainType.DeepSea) return unitData.GetSkill(SkillType.OCEANMOVE,out var _); } //否则 //如果有港口但不能下港口 if (gridData.Resource == ResourceType.Port) { if (unitInfo.LandType != LandType.WaterAndAshore && unitInfo.LandType == LandType.WaterOnly) return false; var gridPlayer = gridData.Player(mapData); var unitPlayer = unitData.Player(mapData); if (gridPlayer == null || unitPlayer == null) return false; if(!mapData.SameUnion(gridPlayer.Id,unitPlayer.Id)) return false; } //如果没有港口,纯看能不能移动进入这片区域 if (gridData.Resource != ResourceType.Port) { //TODO 最好将LandType直接转为Skill ,后续要思考做法 //如果单位不支持进水,直接return false if (!(unitInfo.LandType is LandType.WaterOnly or LandType.LandAndWater)) return false; if(gridData.Terrain == TerrainType.ShallowSea) return unitData.GetSkill(SkillType.WATERMOVE,out var _); if(gridData.Terrain == TerrainType.DeepSea) return unitData.GetSkill(SkillType.OCEANMOVE,out var _); } } //如果是陆地 if (gridData.Terrain == TerrainType.Land) { //如果是山脉 if (gridData.Feature == TerrainFeature.Mountain) { return unitData.GetSkill(SkillType.MOUNTAINMOVE,out var _); } } return true; } //判断一个unit是否有能力去某个grid,但是和那个grid目前的状态无关 public bool CheckUnitAbleForGrid_OfflineStatus(MapData map, PlayerData self, UnitData unit, GridData grid ) { if (unit != null) { if (!map.CheckLandTypeForGrid(unit.UnitFullType, grid)) return false; } if (grid.Resource == ResourceType.Bridge) return true; var landType = unit?.GetLandType() ?? LandType.LandAndWater; //step #1 排除科技情况 if (landType != LandType.Fly) { if (grid.Feature == TerrainFeature.Mountain && !self.TechTree.TechAtomCacheSet.Contains(TechAtom.UnitSkillMOUNTAINMOVE)) return false; if (grid.Terrain == TerrainType.ShallowSea && !self.TechTree.TechAtomCacheSet.Contains(TechAtom.UnitSkillWATERMOVE)) return false; if (grid.Terrain == TerrainType.DeepSea && !self.TechTree.TechAtomCacheSet.Contains(TechAtom.UnitSkillOCEANMOVE)) return false; } //step #2 排除landandport单位的情况 if (landType == LandType.LandAndPort) { if (grid.Terrain == TerrainType.DeepSea) return false; if (grid.Terrain == TerrainType.ShallowSea && (grid.Resource != ResourceType.Port || !map.CheckIfGidBelongPidUnion(grid.Id,self.Id)))return false; } //step #3 if (landType == LandType.WaterOnly) { if (grid.Terrain == TerrainType.Land) return false; } if (landType == LandType.LandOnly) { if (grid.Terrain != TerrainType.Land) return false; } return true; } // 仅通过landType,判断一个unit是否有能力从gridA移动到相邻的gridB,不考虑grid上面是否有单位阻挡,如果unit==null 则用player的通用能力来判断 public bool CheckUnitCanMoveToNearbyGridOnlyByLandType(MapData map, PlayerData self, UnitData unit,Vector2Int start, Vector2Int end) { if (!map.GridMap.GetGridDataByPos(start.x, start.y, out var startGrid)) return false; if (!map.GridMap.GetGridDataByPos(end.x, end.y, out var endGrid)) return false; bool startCheck = CheckUnitAbleForGrid_OfflineStatus(map, self, unit, startGrid); bool endCheck = CheckUnitAbleForGrid_OfflineStatus(map, self, unit, endGrid); if (!startCheck || !endCheck) return false; //如果是判断一个具体的unit if (unit != null) { switch(unit.GetLandType()) { case LandType.Fly: return true; break; case LandType.LandAndPort: return true; break; case LandType.LandAndWater: return true; break; case LandType.LandOnly: return true; break; case LandType.WaterAndAshore: return true; break; case LandType.WaterOnly: return true; break; default: break; } } //如果是判断player else { return true; } return true; } } }