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

2361 lines
116 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年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整合在一起判定在常规移动下哪些格子可以抵达哪些不可以抵达
bool[,] MoveSlowMap;//最优移动路径进入该格时是否经过慢速地形
bool[,] MomijiMap;//通过MomijiHunter额外移动力保留的格子
bool[,] YuugiMap;//通过YuugiDash额外延伸的格子
// 供外部查询FinalReachMap需在CalcUnitMoveInfo之后调用
public bool IsFinalReachable(int x, int y) => FinalReachMap[x, y];
bool[,] TransMap;//在常规移动之外,额外赋予的特殊可传送格子
bool[,] SanaeMap;//在常规移动之外,额外赋予的特殊可抵达格子
bool[,] EnemyControlMap;//敌方控制区标记,仅用于移动高亮表现
bool[,] MoveAttackCandidateMap;//移动/攻击高亮候选去重用避免展示入口重复分配HashSet
// 复用的临时集合
private List<GridData> _aroundBuf;
private List<GridData> _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];
MoveSlowMap = new bool[Table.Instance.MaxMapSize,Table.Instance.MaxMapSize];
MomijiMap = new bool[Table.Instance.MaxMapSize,Table.Instance.MaxMapSize];
YuugiMap = 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];
EnemyControlMap = new bool[Table.Instance.MaxMapSize,Table.Instance.MaxMapSize];
MoveAttackCandidateMap = 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<Vector2Int> 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, reportBoatOnLand: false);
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);
}
//如果陆地单位需要下水
if (ShouldLandToBoat(unitData, gridData))
{
//注意必须先Onmove变身之前的技能用掉再变身)
unitData.OnMove(mapData, gridData, moveType, path);
LandToBoat(mapData, unitData, gridData);
//变身后再触发一次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, moveType, path);
}
//如果sea to sea或者land to land
else
//处理DASH等等技能情况在Move结束时会触发的那些技能
unitData.OnMove(mapData, gridData, moveType, path);
mapData.OnAnyUnitMove(mapData, unitData, gridData, moveType);
BoatUnitOnLandDiagnostic.ReportIfNeeded(mapData, unitData, gridData, $"MoveToLogic:{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;
}
//如果是本方军港,且该城市本回合尚未触发过军港恢复,则恢复该单位所有行动点
if (gridData.Resource == ResourceType.NavalBase
&& unitData.IsBoatUnit()
&& mapData.GetPlayerDataByUnitId(unitData.Id, out var unitPlayer)
&& mapData.GetPlayerDataByTerritoryGridId(gridData.Id, out var gridPlayer)
&& unitPlayer.Id == gridPlayer.Id
&& mapData.GetCityDataByTerritoryGid(gridData.Id, out var navalBaseCity)
&& CityBuildingUsageMarkerSkill.TryConsumeSpecialEffect(mapData, navalBaseCity, ResourceType.NavalBase))
{
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 = CalcActiveAttackDamage(mapData, unit1, unit2);
if (dmg1 >= unit2.Health) //如果伤害直接够杀死对方
return false;
return true;
}
public int CalcActiveAttackDamage(MapData mapData, UnitData origin, UnitData target, float damagePara = 1f,
bool multiOrAdd = false)
{
if (origin == null || target == null) return 0;
if (!origin.HasEffectiveSkill(SkillType.Berserk, out _))
return Table.Instance.CalcDamage(mapData, origin, target, damagePara, multiOrAdd);
var oldHealth = origin.Health;
origin.Health = origin.GetMaxHealth();
try
{
return Table.Instance.CalcDamage(mapData, origin, target, damagePara, multiOrAdd);
}
finally
{
origin.Health = oldHealth;
}
}
public void Attack(MapData mapData, UnitData unit1, UnitData unit2, out int attackDmg,out int counterDmg,out FragmentType fragmentType)
{
Attack(mapData, unit1, unit2, out attackDmg, out counterDmg, out fragmentType, out _);
}
public void Attack(MapData mapData, UnitData unit1, UnitData unit2, out int attackDmg,out int counterDmg,out FragmentType fragmentType, out YuugiPushResult yuugiPushResult)
{
attackDmg = 0;
counterDmg = 0;
fragmentType = FragmentType.Attack;
yuugiPushResult = null;
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;
// Dragonship海上目标使用推击结算非海上目标继续走普通攻击。
if (unit1.GetSkill(SkillType.HakureiDragonshipRam, out var dragonshipRamSkill)
&& dragonshipRamSkill is HakureiDragonshipRamSkill dragonshipRam
&& dragonshipRam.TryDragonshipAttack(unit1, unit2, mapData, out attackDmg, out counterDmg, out fragmentType, out yuugiPushResult))
{
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 yuugiPushResult))
{
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 = CalcActiveAttackDamage(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的情况
var canMokouMoveKill =
unit1.GetSkill(SkillType.MokouFrenchMoveKill, out var mokouMoveKillSkill)
&& mokouMoveKillSkill is MokouFrenchMoveKillSkill mokouMoveKill
&& mokouMoveKill.CanMoveToKilledTarget(mapData, unit1, unit2, attackDistance);
if (((unit1.IsCanMoveKill(mapData) && attackDistance <= 1) || canMokouMoveKill)
&& CheckUnitAbleForGrid_RealTimeStatus(mapData, 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;
}
/// <summary>
/// 新版恢复生命值方法,包含完整的 Heal 生命周期
/// </summary>
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 触发 OnAttackAllyJustBeforeHealAttackAlly专用在此消耗层数等
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;
player.AddHeroRevivePenalty(giantType, (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;
EnemyControlMap[i, j] = false;
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;
else if (CanHakureiShallowSeaEmbark(unitData, targetGridData))
MovementCostMap[i, j] = 999;
//如果是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 == TerrainType.ShallowSea)
{
if (targetGridData.Resource == ResourceType.Bridge)
MovementCostMap[i, j] = 1;
else if (CanHakureiShallowSeaEmbark(unitData, targetGridData))
MovementCostMap[i, j] = 999;
else
MovementCostMap[i, j] = -1;
}
if (targetGridData.Terrain == TerrainType.DeepSea)
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
bool applyEnemyControlCost = !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<GridData>();
_aroundBuf.Clear();
mapData.GridMap.GetAroundGridDataSet_NOCENTER(1,1,gridDataB, _aroundBuf);
foreach (var targetGridData in _aroundBuf)
{
int x = targetGridData.Pos.X, y = targetGridData.Pos.Y;
EnemyControlMap[x, y] = true;
if (applyEnemyControlCost)
MovementCostMap[x, y] = (MovementCostMap[x, y] < 0) ? -1 : 999;
}
EnemyControlMap[gridDataB.Pos.X, gridDataB.Pos.Y] = true;
if (applyEnemyControlCost)
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;
MoveSlowMap[i, j] = false;
MomijiMap[i, j] = false;
YuugiMap[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;
MoveSlowMap[gridData.Pos.X, gridData.Pos.Y] = false;
MomijiMap[gridData.Pos.X, gridData.Pos.Y] = false;
YuugiMap[gridData.Pos.X, gridData.Pos.Y] = false;
//Step #6然后进行SPFA更新最短路
Queue<uint> q = new Queue<uint>();
//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<GridData>();
_aroundBuf.Clear();
mapData.GridMap.GetAroundGridDataSet_NOCENTER(1,1,gridDataX, _aroundBuf);
foreach (var gridDataY in _aroundBuf)
{
float cost = MovementCostMap[gridDataY.Pos.X, gridDataY.Pos.Y];
bool isSlowMove = cost > 1f && cost < 900f;
//如果不可达区
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;
isSlowMove = false;
}
//如果能够发生更新
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;
MoveSlowMap[gridDataY.Pos.X, gridDataY.Pos.Y] = isSlowMove;
//如果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;
}
if (!FinalReachMap[nx, ny])
YuugiMap[nx, ny] = true;
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<GridData>();
_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<CityData>(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<CityData>(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)
{
var hasMomijiHunter = unitData.GetSkill(SkillType.MOMIJIHUNTER, out var _);
using var pooledTargetVec2 = THCollectionPool.GetListHandle<Vector2Int>(out var targetVec2);
for (int i = 0; i < width; i++)
{
for (int j = 0; j < height; j++)
{
if (!NormalReachMap[i, j]) continue;
if (hasMomijiHunter && !MomijiHunterReachMap[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;
}
// 早苗乘风图标只标记普通移动本来不能抵达、由乘风额外给到的格子。
var alreadyReachableWithoutSanae = hasMomijiHunter
? NormalReachMap[newVec.x, newVec.y] && MomijiHunterReachMap[newVec.x, newVec.y]
: NormalReachMap[newVec.x, newVec.y];
if (alreadyReachableWithoutSanae) 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;
if (MomijiHunterReachMap[i, j] && floor <= 0.1f)
MomijiMap[i, j] = true;
}
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))
{
var distance = mapData.GridMap.CalcDistance(gridDataA, gridDataB);
if (distance <= unitDataA.GetAttackRange(mapData) &&
unitDataA.GetSkill(SkillType.FLANDREATTACK, out var _) )
return MoveAttackType.Attack;
//以下处理attackAlly友军的情况不能背盟
if (distance <= unitDataA.GetAttackAllyRange(mapData) &&
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<GridData>();
_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 (ShouldLandToBoat(unitDataA, gridDataB))
return MoveAttackType.MoveToPort;
//上岸
if (gridDataB.Terrain == TerrainType.Land && unitDataA.GetLandType() == LandType.WaterAndAshore)
return MoveAttackType.MoveAshore;
return MoveAttackType.Move;
}
return MoveAttackType.None;
}
public MoveHighlightType GetMoveHighlightTypes(MapData mapData, UnitData unitData, GridData targetGridData, MoveAttackType moveAttackType)
{
if (mapData == null || unitData == null || targetGridData == null)
return MoveHighlightType.None;
int x = targetGridData.Pos.X;
int y = targetGridData.Pos.Y;
if (x < 0 || y < 0 || x >= mapData.MapConfig.Width || y >= mapData.MapConfig.Height)
return MoveHighlightType.None;
MoveHighlightType types = MoveHighlightType.None;
switch (moveAttackType)
{
case MoveAttackType.MoveToPort:
types |= MoveHighlightType.Boat;
break;
case MoveAttackType.MoveAshore:
types |= MoveHighlightType.Ashore;
break;
case MoveAttackType.MoveTeleport:
types |= MoveHighlightType.Teleport;
break;
}
if (SanaeMap[x, y])
types |= MoveHighlightType.Sanae;
if (EnemyControlMap[x, y])
types |= MoveHighlightType.Enemy;
if (MomijiMap[x, y])
types |= MoveHighlightType.Momiji;
if (YuugiMap[x, y])
types |= MoveHighlightType.Yuugi;
if (moveAttackType == MoveAttackType.Move && MoveSlowMap[x, y])
types |= MoveHighlightType.Slow;
if (moveAttackType is MoveAttackType.Move or MoveAttackType.MoveToPort or MoveAttackType.MoveAshore
&& Main.PlayerLogic.HasRoadForUnitOnGrid(mapData, targetGridData, unitData)
&& (targetGridData.Terrain == TerrainType.Land || targetGridData.Resource == ResourceType.Bridge))
types |= MoveHighlightType.Road;
return types;
}
public void CollectMoveAttackCandidateGrids(MapData mapData, UnitData unitData, List<GridData> buffer)
{
buffer?.Clear();
if (mapData == null || unitData == null || buffer == null) return;
if (!mapData.GetGridDataByUnitId(unitData.Id, out var originGrid)) return;
if (!mapData.GetPlayerDataByUnitId(unitData.Id, out var playerData)) return;
ClearMoveAttackCandidateMap(mapData);
if (unitData.GetActionPoint(ActionPointType.Move) > 0)
{
foreach (var gridData in mapData.GridMap.GridList)
{
int x = gridData.Pos.X, y = gridData.Pos.Y;
if (!FinalReachMap[x, y] && !TransMap[x, y] && !SanaeMap[x, y]) continue;
AddMoveAttackCandidateGrid(mapData, originGrid, gridData, buffer);
}
}
var specialGroundAttackRange = GetSpecialGroundAttackReadyRange(unitData);
if (unitData.GetActionPoint(ActionPointType.Attack) <= 0 && specialGroundAttackRange <= 0) return;
int attackRange = unitData.GetAttackRange(mapData);
if (specialGroundAttackRange > 0)
attackRange = Mathf.Max(attackRange, specialGroundAttackRange);
foreach (var gridData in mapData.GridMap.GridList)
{
if (mapData.GridMap.CalcDistance(originGrid, gridData) > attackRange) continue;
AddMoveAttackCandidateGrid(mapData, originGrid, gridData, buffer);
}
if (!HasKanakoBattlefieldAttack(unitData)) return;
_aroundBuf2 ??= new List<GridData>();
foreach (var tmpUnit in mapData.UnitMap.UnitList)
{
if (!IsKanakoBattlefieldAnchor(mapData, playerData, tmpUnit)) continue;
_aroundBuf2.Clear();
mapData.GridMap.GetAroundGridDataSet_NOCENTER(1, 1, tmpUnit.Grid(mapData), _aroundBuf2);
foreach (var tmpGrid in _aroundBuf2)
AddMoveAttackCandidateGrid(mapData, originGrid, tmpGrid, buffer);
}
}
public bool HasMoveTargetFromMoveInfo(MapData mapData, UnitData unitData, PlayerData playerData)
{
if (mapData == null || unitData == null || playerData == null) return false;
if (unitData.GetActionPoint(ActionPointType.Move) <= 0) return false;
if (!mapData.GetGridDataByUnitId(unitData.Id, out var originGrid)) return false;
bool hasUtsuhoBase = unitData.GetSkill(SkillType.UtsuhoBase, out var _);
foreach (var targetGridData in mapData.GridMap.GridList)
{
if (targetGridData.Id == originGrid.Id) continue;
if (!hasUtsuhoBase && !playerData.Sight.CheckIsInSight(targetGridData.Id)) continue;
int x = targetGridData.Pos.X, y = targetGridData.Pos.Y;
if (!FinalReachMap[x, y] && !TransMap[x, y] && !SanaeMap[x, y]) continue;
var sig = CheckUnitCanMoveOrAttack(mapData, unitData, targetGridData);
if (sig is MoveAttackType.Move or MoveAttackType.MoveToPort or MoveAttackType.MoveAshore or MoveAttackType.MoveTeleport)
return true;
if (sig == MoveAttackType.None && hasUtsuhoBase && FinalReachMap[x, y])
return true;
}
return false;
}
public bool HasAttackTargetFromMoveInfo(MapData mapData, UnitData unitData, PlayerData playerData, GridData originGrid)
{
if (mapData == null || unitData == null || playerData == null || originGrid == null) return false;
var specialGroundAttackRange = GetSpecialGroundAttackReadyRange(unitData);
if (unitData.GetActionPoint(ActionPointType.Attack) <= 0 && specialGroundAttackRange <= 0) return false;
_aroundBuf ??= new List<GridData>();
_aroundBuf.Clear();
int attackRange = unitData.GetAttackRange(mapData);
if (specialGroundAttackRange > 0)
attackRange = Mathf.Max(attackRange, specialGroundAttackRange);
mapData.GridMap.GetAroundGridDataSet_NOCENTER(attackRange, attackRange, originGrid, _aroundBuf);
foreach (var targetGridData in _aroundBuf)
{
if (!playerData.Sight.CheckIsInSight(targetGridData.Id)) continue;
var sig = CheckUnitCanMoveOrAttack(mapData, unitData, targetGridData);
if (sig is MoveAttackType.Attack or MoveAttackType.Ally or MoveAttackType.AttackGround)
return true;
}
if (!HasKanakoBattlefieldAttack(unitData)) return false;
foreach (var targetGridData in mapData.GridMap.GridList)
{
if (!playerData.Sight.CheckIsInSight(targetGridData.Id)) continue;
if (!targetGridData.VisibleUnit(mapData, playerData, out var targetUnit)) continue;
if (!targetUnit.Player(mapData, out var targetPlayer)) continue;
if (mapData.SameUnionOrJustBreakUnion(playerData.Id, targetPlayer.Id)) continue;
if (CheckUnitCanMoveOrAttack(mapData, unitData, targetGridData) == MoveAttackType.Attack)
return true;
}
return false;
}
private void ClearMoveAttackCandidateMap(MapData mapData)
{
foreach (var gridData in mapData.GridMap.GridList)
MoveAttackCandidateMap[gridData.Pos.X, gridData.Pos.Y] = false;
}
private void AddMoveAttackCandidateGrid(MapData mapData, GridData originGrid, GridData gridData, List<GridData> buffer)
{
if (mapData == null || originGrid == null || gridData == null || buffer == null) return;
if (gridData.Id == originGrid.Id) return;
int x = gridData.Pos.X, y = gridData.Pos.Y;
if (x < 0 || y < 0 || x >= mapData.MapConfig.Width || y >= mapData.MapConfig.Height) return;
if (MoveAttackCandidateMap[x, y]) return;
MoveAttackCandidateMap[x, y] = true;
buffer.Add(gridData);
}
private static int GetSpecialGroundAttackReadyRange(UnitData unitData)
{
if (HakureiNorwayHeroSkillUtil.TryGetSumirekoOccultOrbReady(unitData, out _))
return 3;
if (HakureiNorwayHeroSkillUtil.TryGetKasenBeastGuideReady(unitData, out _))
return 4;
return 0;
}
private static bool HasKanakoBattlefieldAttack(UnitData unitData)
{
return unitData != null
&& unitData.GetSkill(SkillType.KANAKOBATTLEFIELDPRO, out var _)
&& unitData.GetSkill(SkillType.KANAKOSITTING, out var _);
}
private static bool IsKanakoBattlefieldAnchor(MapData mapData, PlayerData playerData, UnitData unitData)
{
if (mapData == null || playerData == null || unitData == null) return false;
if (unitData.Player(mapData) != playerData) return false;
if (unitData.UnitType == UnitType.MoriyaHebi && unitData.UnitLevel < 3) return false;
if (unitData.GiantType == GiantType.None && unitData.UnitType != UnitType.MoriyaHebi) return false;
if (!unitData.Grid(mapData, out var gridData)) return false;
return gridData.Feature == TerrainFeature.Mountain;
}
//默认在调用这个函数前已经调用了 CalcUnitMoveInfo(A),计算好了。不组合使用是不行的
public MoveAttackType GetMovePath(MapData map, Vector2Int start, Vector2Int end,out List<Vector2Int> path)
{
if (TransMap[end.x, end.y])
{
path = new List<Vector2Int> { start, end };
return MoveAttackType.MoveTeleport;
}
path = new List<Vector2Int>();
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<Vector2Int> { start };
return MoveAttackType.Move;
}
var queue = new Queue<Vector2Int>();
// 用字典记录路径来源,同时兼作“已访问”集合
using var pooledCameFrom = THCollectionPool.GetDictionaryHandle<Vector2Int, Vector2Int>(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<Vector2Int>();
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.Grid(mapData, out var gridData);
LandToBoat(mapData, unitData, gridData);
}
public void LandToBoat(MapData mapData, UnitData unitData, GridData targetGrid)
{
unitData.CarryUnitFullType = unitData.UnitFullType;
if (unitData.TryGetLandToBoatTarget(mapData, targetGrid, out var targetFullType))
Main.UnitLogic.UnitTypeTransform(mapData, unitData, targetFullType);
else 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();
}
private static bool ShouldLandToBoat(UnitData unitData, GridData gridData)
{
if (unitData == null || gridData == null) return false;
if (unitData.GetLandType() == LandType.LandAndPort && gridData.Resource == ResourceType.Port) return true;
return CanHakureiShallowSeaEmbark(unitData, gridData);
}
private static bool CanHakureiShallowSeaEmbark(UnitData unitData, GridData gridData)
{
if (unitData == null || gridData == null) return false;
if (gridData.Terrain != TerrainType.ShallowSea) return false;
if (gridData.Resource == ResourceType.Bridge) return false;
if (unitData.GetLandType() is not (LandType.LandAndPort or LandType.LandOnly)) return false;
return unitData.GetSkill(SkillType.HakureiKarviEmbark, out _);
}
//unit[uid]从港口进入陆地,变为原来的单位
public void BoatToLand(MapData mapData, UnitData unitData)
{
BoatToLand(mapData, unitData, MoveType.PassiveMove);
}
public void BoatToLand(MapData mapData, UnitData unitData, MoveType moveType, List<Vector2Int> path = null)
{
Main.UnitLogic.UnitTypeTransform(mapData,unitData, unitData.CarryUnitType, unitData.CarryGiantType,unitData.CarryUnitLevel);
unitData.ActionPoint.Clear();
unitData.Grid(mapData, out var gridData);
unitData.AfterTransformFromBoat(mapData, gridData, moveType, path);
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 (!ContentGate.CanUseHeroForPlayer(player, type.GiantType)) return false;
if (!Table.Instance.HeroDataAssets.GetHeroInfo(type.GiantType, out var heroInfo) ||
heroInfo.TaskList == null ||
heroInfo.TaskList.Count <= type.UnitLevel - 1)
return false;
var targetType = new UnitFullType()
{ UnitType = unit.UnitType, GiantType = unit.GiantType, UnitLevel = unit.UnitLevel + 1 };
if (!Table.Instance.UnitTypeDataAssets.GetUnitTypeInfo(targetType, out _)) return false;
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;
//在进入下一个回合前,如果可以回血自动回血;恐惧会抵消本次回血,即使满血也应被清除
var hasKomeijiFear = unitData.GetSkill(SkillType.KomeijiFear, out _);
if ((unitData.Health < unitData.GetMaxHealth() || hasKomeijiFear) && unitData.GetActionPoint(ActionPointType.Capture) > 0)
{
if (mapData.GetGridDataByUnitId(unitData.Id, out var grid)
&& mapData.GetPlayerDataByUnitId(unitData.Id, out var player)
)
{
//如果有KomeijiFear消除恐惧替代本次回血
if (hasKomeijiFear)
{
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的时候要删掉之类的要不要触发情况等等
var originFullTypeBeforeTransform = unitData.UnitFullType;
var targetFullTypeBeforeTransform = new UnitFullType(targetType, giantType, unitLevel);
if (!Table.Instance.UnitTypeDataAssets.GetUnitTypeInfo(originFullTypeBeforeTransform, out var originInfo))
{
LogSystem.LogError($"UnitTypeTransform failed: origin unit type not found. unitId:{unitData.Id} origin:{FormatUnitFullType(originFullTypeBeforeTransform)} target:{FormatUnitFullType(targetFullTypeBeforeTransform)}");
return;
}
if (!Table.Instance.UnitTypeDataAssets.GetUnitTypeInfo(targetFullTypeBeforeTransform, out var targetInfo))
{
LogSystem.LogError($"UnitTypeTransform failed: target unit type not found. unitId:{unitData.Id} origin:{FormatUnitFullType(originFullTypeBeforeTransform)} target:{FormatUnitFullType(targetFullTypeBeforeTransform)}");
return;
}
var previousFullTypeForBoatOnLandLog = unitData.UnitFullType;
var requestedFullTypeForBoatOnLandLog = targetFullTypeBeforeTransform;
unitData.SkillCache ??= new List<SkillBase>();
unitData.Skills ??= new List<SkillBase>();
bool useCache = unitData.FullTypeCache == new UnitFullType(targetType, giantType, unitLevel);
using var pooledSkillCache = THCollectionPool.GetDictionaryHandle<SkillType, SkillBase>(out var skillCache);
using var pooledOriginSkills = THCollectionPool.GetListHandle<SkillBase>(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<SkillType>(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. unitId:{unitData.Id} origin:{FormatUnitFullType(originFullTypeBeforeTransform)} target:{FormatUnitFullType(targetFullTypeBeforeTransform)} skill:{skill.GetType().Name} skillType:{skill.GetSkillType()} ex:{e}");
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 = originFullTypeBeforeTransform;
var targetFullType = targetFullTypeBeforeTransform;
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);
if (mapData.GetGridDataByUnitId(unitData.Id, out var boatOnLandGrid))
BoatUnitOnLandDiagnostic.ReportIfNeeded(mapData, unitData, boatOnLandGrid, "UnitTypeTransform",
previousFullTypeForBoatOnLandLog, requestedFullTypeForBoatOnLandLog);
}
private static string FormatUnitFullType(UnitFullType fullType)
{
return $"{fullType.UnitType}/{fullType.GiantType}/{fullType.UnitLevel}";
}
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<GridData>();
_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 (!CheckUnitCanEnterGridByDiplomacyStatus(mapData, unitData, gridData))
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;
}
public bool CheckUnitCanEnterGridByDiplomacyStatus(MapData mapData, UnitData unitData, GridData gridData)
{
if (mapData == null || unitData == null || gridData == null) return false;
if (gridData.Resource != ResourceType.CityCenter) return true;
if (!gridData.CityOnGrid(mapData, out var cityData)) return true;
if (!mapData.GetPlayerDataByCityId(cityData.Id, out var cityPlayer)) return true;
if (!mapData.GetPlayerDataByUnitId(unitData.Id, out var unitPlayer)) return false;
if (cityPlayer.Id == unitPlayer.Id) return true;
return !mapData.SameUnionOrJustBreakUnion(cityPlayer.Id, unitPlayer.Id);
}
//判断一个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;
}
}
}