2684 lines
132 KiB
C#
2684 lines
132 KiB
C#
/*
|
||
* @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 GridData SuikaFallingSplashFinalGrid;
|
||
public CityData SuikaFallingSplashFinalCity;
|
||
public CityData SuikaFallingSplashOriginCity;
|
||
public List<GridData> SuikaFallingSplashSightRefreshGrids;
|
||
public bool IsSuikaFallingSplash;
|
||
}
|
||
|
||
|
||
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 GridData DamageBearerGrid;
|
||
public CityData DamageBearerCity;
|
||
public bool DamageBearerKilled;
|
||
//是否击杀
|
||
public bool IsKill;
|
||
//是否已有死亡替换技能触发(防止多个死亡替换技能同时生成单位)
|
||
public bool IsDeathReplaced;
|
||
public bool IsFinished;
|
||
// 血量减少值
|
||
public int HealthReduceValue;
|
||
|
||
|
||
public SettlementInfo()
|
||
{
|
||
IsFinished = false;
|
||
}
|
||
}
|
||
|
||
|
||
public class UnitLogic : IUnitLogic
|
||
{
|
||
private const int HakureiRuneDeathHealValue = 6;
|
||
|
||
float[,] MovementCostMap;
|
||
float[,] MovementRemainMap;
|
||
float[,] MoveRealCostMap;//最优路线下,从别处移动过来的实机cost花费多少
|
||
float[,] MomijiHunterMovementRemainMap;
|
||
float[,] MomijiHunterMoveRealCostMap;
|
||
//ReachMap组,常规移动能抵达的map
|
||
|
||
bool[,] NormalReachMap;//常规计算
|
||
bool[,] MomijiHunterReachMap; //专门给momijiHunter 用的追击移动力可达图
|
||
bool[,] MomijiHunterTargetMap; //实际允许使用momijiHunter额外移动力作为目标的格子
|
||
bool[,] FinalReachMap;//最终整合reachMap,将常规计算和momijiHunter整合在一起,判定在常规移动下,哪些格子可以抵达,哪些不可以抵达
|
||
bool[,] MoveSlowMap;//最优移动路径进入该格时是否经过慢速地形
|
||
bool[,] MomijiHunterMoveSlowMap;
|
||
bool[,] MomijiMap;//通过MomijiHunter额外移动力保留的格子
|
||
bool[,] YuugiMap;//通过YuugiDash额外延伸的格子
|
||
|
||
// 供外部查询FinalReachMap(需在CalcUnitMoveInfo之后调用)
|
||
public bool IsFinalReachable(int x, int y) => FinalReachMap[x, y];
|
||
public bool IsKasenBeastGuideActiveMoveTarget(int x, int y)
|
||
{
|
||
return x >= 0 && y >= 0
|
||
&& x < Table.Instance.MaxMapSize
|
||
&& y < Table.Instance.MaxMapSize
|
||
&& KasenBeastGuideActiveMoveMap[x, y];
|
||
}
|
||
|
||
|
||
bool[,] TransMap;//在常规移动之外,额外赋予的特殊可传送格子
|
||
bool[,] KasenBeastGuideActiveMoveMap;//兽引额外赋予的可抵达格子,按普通主动移动结算
|
||
bool[,] SanaeMap;//在常规移动之外,额外赋予的特殊可抵达格子
|
||
bool[,] SanaeFromMomijiHunterMap;
|
||
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];
|
||
MomijiHunterMovementRemainMap = new float[Table.Instance.MaxMapSize,Table.Instance.MaxMapSize];
|
||
MomijiHunterMoveRealCostMap = 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];
|
||
MomijiHunterTargetMap = 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];
|
||
MomijiHunterMoveSlowMap = 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];
|
||
KasenBeastGuideActiveMoveMap = new bool[Table.Instance.MaxMapSize,Table.Instance.MaxMapSize];
|
||
SanaeMap = new bool[Table.Instance.MaxMapSize,Table.Instance.MaxMapSize];
|
||
SanaeFromMomijiHunterMap = 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;
|
||
}
|
||
|
||
bool shouldApplyAunnPairDistanceLimit = moveType is MoveType.ActiveMove or MoveType.SkillMove;
|
||
if (shouldApplyAunnPairDistanceLimit
|
||
&& !HakureiNorwayHeroSkillUtil.IsAunnPairWithinDistanceLimit(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);
|
||
HakureiNorwayHeroSkillUtil.RefreshKasenBeastGuideStatBuffs(mapData);
|
||
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 (!HasBerserkDamageEffect(origin))
|
||
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;
|
||
}
|
||
}
|
||
|
||
private static bool HasBerserkDamageEffect(UnitData unit)
|
||
{
|
||
return unit != null
|
||
&& (unit.HasEffectiveSkill(SkillType.Berserk, out _) ||
|
||
unit.HasEffectiveSkill(SkillType.KasenBeastGuideBerserkBuff, out _) ||
|
||
unit.HasEffectiveSkill(SkillType.KasenPermanentBerserk, out _));
|
||
}
|
||
|
||
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)
|
||
{
|
||
Attack(mapData, unit1, unit2, out attackDmg, out counterDmg, out fragmentType, out yuugiPushResult,
|
||
out _);
|
||
}
|
||
|
||
public void Attack(MapData mapData, UnitData unit1, UnitData unit2, out int attackDmg,out int counterDmg,out FragmentType fragmentType, out YuugiPushResult yuugiPushResult, out AttackInfo attackInfo)
|
||
{
|
||
attackDmg = 0;
|
||
counterDmg = 0;
|
||
fragmentType = FragmentType.Attack;
|
||
yuugiPushResult = null;
|
||
attackInfo = 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;
|
||
}
|
||
}
|
||
|
||
attackInfo = new AttackInfo();
|
||
attackInfo.DamageOrigin = unit1;
|
||
attackInfo.OriginPlayer = player1;
|
||
attackInfo.DamageTarget = unit2;
|
||
attackInfo.TargetPlayer = player2;
|
||
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();
|
||
attackInfo.IsKill = true;
|
||
if (unit1.IsAlive()) unit1.AfterActiveAttackOther(mapData, attackInfo);
|
||
}
|
||
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.TargetPlayer = player2;
|
||
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 _))
|
||
{
|
||
if (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.DamageBearerGrid = bearer.Grid(mapData);
|
||
settlement.DamageBearerCity = bearer.City(mapData);
|
||
var bearerRenderer = bearer.Renderer(mapData);
|
||
var bearerCanBeKilled = bearer.CanBeKilled(mapData);
|
||
settlement.HealthReduceValue = Mathf.Min(bearer.Health, settlement.DamageValue);
|
||
NotifyAunnPetrifiedDamageTakenTask(mapData, bearer.Player(mapData), bearer, settlement, dmg);
|
||
NotifyAunnGuardedDamageTakenTask(mapData, bearer, settlement, dmg);
|
||
bearer.Health -= settlement.DamageValue;
|
||
if (bearerCanBeKilled && !bearer.IsAlive())
|
||
{
|
||
if (TryConsumeHakureiRuneDeathHeal(mapData, bearer, settlement.DamageBearerGrid))
|
||
{
|
||
settlement.DamageBearerKilled = false;
|
||
}
|
||
else
|
||
{
|
||
UnitDie(mapData, bearer, settlement.DamageValue);
|
||
settlement.DamageBearerKilled = true;
|
||
}
|
||
}
|
||
settlement.IsKill = false;
|
||
if (mapData == Main.MapData)
|
||
{
|
||
void RefreshBearerVisual()
|
||
{
|
||
var bearerGridRenderer = settlement.DamageBearerGrid?.Renderer(mapData);
|
||
bearerGridRenderer?.PlayVFXInSight(new GridVFXParams(GridVFXType.Hurt));
|
||
bearerGridRenderer?.PlayVFXInSight(new GridVFXParams(GridVFXType.Damage, settlement.DamageValue));
|
||
|
||
if (settlement.DamageBearerKilled)
|
||
{
|
||
bearerGridRenderer?.PlayVFXInSight(new GridVFXParams(GridVFXType.Fog));
|
||
if (bearerCanBeKilled)
|
||
bearerGridRenderer?.PlayVFXInSight(new GridVFXParams(GridVFXType.Die));
|
||
bearerRenderer?.Die();
|
||
settlement.DamageBearerCity?.SetCityRenderer(mapData);
|
||
if (settlement.DamageBearerGrid != null)
|
||
MapRenderer.Instance?.UpdateAroundHighlight(mapData, settlement.DamageBearerGrid);
|
||
}
|
||
else
|
||
{
|
||
bearerRenderer?.InstantUpdateUnit(false);
|
||
}
|
||
|
||
bearerGridRenderer?.InstantUpdateGrid();
|
||
}
|
||
|
||
var scope = PresentationManager.CurrentScope;
|
||
if (scope != null)
|
||
{
|
||
scope.Add(new FragmentStep
|
||
{
|
||
Phase = type == DamageType.CounterAttack
|
||
? AnimPhase.CounterImpact + 50
|
||
: AnimPhase.AttackImpact + 50,
|
||
Duration = 0f,
|
||
Execute = RefreshBearerVisual
|
||
});
|
||
}
|
||
else
|
||
{
|
||
RefreshBearerVisual();
|
||
}
|
||
}
|
||
}
|
||
else
|
||
{
|
||
settlement.HealthReduceValue = Mathf.Min(target.Health, settlement.DamageValue);
|
||
NotifyAunnPetrifiedDamageTakenTask(mapData, player2, target, settlement, dmg);
|
||
NotifyAunnGuardedDamageTakenTask(mapData, target, settlement, dmg);
|
||
target.Health -= settlement.DamageValue;
|
||
if (type == DamageType.KillSelf && !target.IsAlive())
|
||
{
|
||
if (TryConsumeHakureiRuneDeathHeal(mapData, target, targetGrid))
|
||
{
|
||
settlement.IsKill = false;
|
||
}
|
||
else
|
||
{
|
||
UnitDie(mapData, target, settlement.DamageValue);
|
||
settlement.IsKill = true;
|
||
}
|
||
}
|
||
else if (target.CanBeKilled(mapData) && !target.IsAlive())
|
||
{
|
||
if (TryConsumeHakureiRuneDeathHeal(mapData, target, targetGrid))
|
||
{
|
||
settlement.IsKill = false;
|
||
}
|
||
else
|
||
{
|
||
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;
|
||
}
|
||
}
|
||
|
||
HakureiNorwayHeroSkillUtil.SyncAunnSharedHealthAfterDamage(mapData, bearer ?? target, type);
|
||
|
||
// 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;
|
||
}
|
||
|
||
private static void NotifyAunnPetrifiedDamageTakenTask(MapData mapData, PlayerData player, UnitData unit,
|
||
SettlementInfo settlement, int originalDamageValue)
|
||
{
|
||
if (settlement == null) return;
|
||
if (originalDamageValue <= 0 && settlement.DamageValue <= 0 && settlement.HealthReduceValue <= 0) return;
|
||
if (!HakureiNorwayHeroSkillUtil.IsAunnBody(unit)) return;
|
||
if (!HakureiNorwayHeroSkillUtil.IsAunnPetrified(unit)) return;
|
||
if (player?.PlayerHeroData == null) return;
|
||
if (!player.PlayerHeroData.GetHeroTask(GiantType.NorwayAunn, out var task)) return;
|
||
task.OnSkillActivation(mapData, SkillType.AunnPetrification, 1);
|
||
}
|
||
|
||
private static void NotifyAunnGuardedDamageTakenTask(MapData mapData, UnitData unit, SettlementInfo settlement,
|
||
int originalDamageValue)
|
||
{
|
||
if (settlement == null) return;
|
||
if (originalDamageValue <= 0 && settlement.DamageValue <= 0 && settlement.HealthReduceValue <= 0) return;
|
||
PlayerData player = null;
|
||
if (HakureiNorwayHeroSkillUtil.IsAunnBody(unit))
|
||
{
|
||
player = unit.Player(mapData);
|
||
}
|
||
else if (HakureiNorwayHeroSkillUtil.TryFindAunnPetrifiedDefenseAuraSource(mapData, unit, out var sourceAunn))
|
||
{
|
||
player = sourceAunn.Player(mapData);
|
||
}
|
||
|
||
if (player?.PlayerHeroData == null) return;
|
||
if (!player.PlayerHeroData.GetHeroTask(GiantType.NorwayAunn, out var task)) return;
|
||
task.OnSkillActivation(mapData, SkillType.AunnPetrifiedDefenseAura, 1);
|
||
}
|
||
|
||
private bool TryConsumeHakureiRuneDeathHeal(MapData map, UnitData unit, GridData grid)
|
||
{
|
||
if (map == null || unit == null || grid == null) return false;
|
||
if (!grid.HasSpType(GridSpType.HakureiRune)) return false;
|
||
if (!map.GetPlayerDataByUnitId(unit.Id, out var player)) return false;
|
||
if (player.CivEnum != CivEnum.Norway || player.ForceEnum != ForceEnum.Reimu) return false;
|
||
|
||
grid.RemoveSpType(GridSpType.HakureiRune, map);
|
||
unit.Health = Mathf.Min(unit.GetMaxHealth(), HakureiRuneDeathHealValue);
|
||
|
||
if (map == Main.MapData && grid.InMainSight())
|
||
{
|
||
grid.Renderer(map)?.PlayVFXInSight(new GridVFXParams(GridVFXType.Fog));
|
||
grid.Renderer(map)?.PlayVFXInSight(new GridVFXParams(GridVFXType.Heal));
|
||
grid.Renderer(map)?.InstantUpdateGrid();
|
||
unit.Renderer(map)?.InstantUpdateUnit(true);
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
|
||
//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);
|
||
target.OnHealedSelf(map, origin, HealType.SelfHeal, realRecover);
|
||
if (realRecover > 0)
|
||
HakureiNorwayHeroSkillUtil.SyncAunnSharedHealthAfterHeal(map, target);
|
||
|
||
//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 消耗本回合层数)会全部失效。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);
|
||
target.OnHealedSelf(map, origin, healType, realRecover);
|
||
if (realRecover > 0)
|
||
HakureiNorwayHeroSkillUtil.SyncAunnSharedHealthAfterHeal(map, target);
|
||
|
||
//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;
|
||
unit.BeforeDisappear(map);
|
||
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;
|
||
unit.BeforeDisappear(map);
|
||
|
||
// 如果是伟人死亡,对应的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, targetGridData))
|
||
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, targetGridData))
|
||
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, targetGridData))
|
||
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 初始化各类特殊移动标记
|
||
for (int i = 0; i < width; i++)
|
||
for (int j = 0; j < height; j++)
|
||
{
|
||
MomijiMap[i, j] = false;
|
||
YuugiMap[i, j] = false;
|
||
TransMap[i, j] = false;
|
||
KasenBeastGuideActiveMoveMap[i, j] = false;
|
||
SanaeMap[i, j] = false;
|
||
SanaeFromMomijiHunterMap[i, j] = false;
|
||
MomijiHunterTargetMap[i, j] = false;
|
||
}
|
||
|
||
//Step #6然后进行SPFA更新最短路。椛追击不能直接混进基础行动力,否则被堇子宝珠等移动力覆盖后会出现“未加2却扣2”。
|
||
var hasMomijiHunter = unitData.HasEffectiveSkill(SkillType.MOMIJIHUNTER, out var momijiHunterSkill);
|
||
var hasFinalMoveRangeOverride = unitData.TryGetFinalMoveRangeIgnoringSkill(null, out _);
|
||
var baseMoveRange = hasMomijiHunter && !hasFinalMoveRangeOverride
|
||
? unitData.FinalMoveRangeIgnoringSkill(mapData, SkillType.MOMIJIHUNTER)
|
||
: unitData.FinalMoveRange(mapData);
|
||
CalculateMoveSearchMap(mapData, unitData, gridData, baseMoveRange, MovementRemainMap, MoveRealCostMap,
|
||
NormalReachMap, MoveSlowMap);
|
||
|
||
//Step #7 -------------------------------------------先处理常规移动的最终情况-------------------------------
|
||
|
||
for (int i = 0; i < width; i++)
|
||
for (int j = 0; j < height; j++)
|
||
FinalReachMap[i,j] = NormalReachMap[i, j];
|
||
// MOMIJIHUNTER 技能拓展:只对靠近猎物的目标格使用额外移动图,不再对普通移动图事后扣移动力
|
||
if (!hasFinalMoveRangeOverride)
|
||
MOMIJIHUNTER_SkillHandler(mapData, unitData, gridData, momijiHunterSkill, baseMoveRange);
|
||
|
||
// 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;
|
||
}}
|
||
}
|
||
}
|
||
|
||
if (HakureiNorwayHeroSkillUtil.IsSumireko(unitData)
|
||
&& unitData.HasEffectiveSkill(SkillType.SumirekoOccultOrbOwner, out _))
|
||
{
|
||
foreach (var orb in mapData.UnitMap.UnitList)
|
||
{
|
||
if (!orb.IsAlive()) continue;
|
||
if (!HakureiNorwayHeroSkillUtil.CanUseOccultOrbAsMoveAnchor(mapData, unitData, orb)) continue;
|
||
if (!orb.Grid(mapData, out var orbGrid)) continue;
|
||
|
||
_aroundBuf2 ??= new List<GridData>();
|
||
_aroundBuf2.Clear();
|
||
mapData.GridMap.GetAroundGridDataSet_NOCENTER(1, 1, orbGrid, _aroundBuf2);
|
||
foreach (var targetGrid in _aroundBuf2)
|
||
{
|
||
if (!CheckUnitAbleForGrid_RealTimeStatus(mapData, unitData, targetGrid)) continue;
|
||
if (targetGrid.VisibleUnit(mapData, playerData, out _)) continue;
|
||
TransMap[targetGrid.Pos.X, targetGrid.Pos.Y] = true;
|
||
}
|
||
}
|
||
}
|
||
|
||
if (HakureiNorwayHeroSkillUtil.HasAunnPortalState(mapData, unitData)
|
||
&& unitData.Grid(mapData, out var aunnPortalUnitGrid))
|
||
{
|
||
foreach (var sourceAunn in mapData.UnitMap.UnitList)
|
||
{
|
||
if (!HakureiNorwayHeroSkillUtil.CanAunnProvidePortal(mapData, sourceAunn, unitData,
|
||
aunnPortalUnitGrid, out var pairAunn)) continue;
|
||
AddAunnPortalAnchorTargets(mapData, unitData, playerData, sourceAunn);
|
||
AddAunnPortalAnchorTargets(mapData, unitData, playerData, pairAunn);
|
||
}
|
||
}
|
||
|
||
_aroundBuf2 ??= new List<GridData>();
|
||
_aroundBuf2.Clear();
|
||
HakureiNorwayHeroSkillUtil.CollectKasenBeastGuideTeleportTargets(mapData, unitData, playerData, gridData,
|
||
_aroundBuf2);
|
||
foreach (var targetGrid in _aroundBuf2)
|
||
{
|
||
if (!CheckUnitAbleForGrid_RealTimeStatus(mapData, unitData, targetGrid)) continue;
|
||
if (targetGrid.VisibleUnit(mapData, playerData, out _)) continue;
|
||
KasenBeastGuideActiveMoveMap[targetGrid.Pos.X, targetGrid.Pos.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)
|
||
{
|
||
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] && !MomijiHunterTargetMap[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 =
|
||
NormalReachMap[newVec.x, newVec.y] || MomijiHunterTargetMap[newVec.x, newVec.y];
|
||
if (alreadyReachableWithoutSanae) continue;
|
||
SanaeMap[newVec.x, newVec.y] = true;
|
||
if (MomijiHunterTargetMap[vec.x, vec.y])
|
||
SanaeFromMomijiHunterMap[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;
|
||
SanaeFromMomijiHunterMap[i, j] = false;
|
||
}
|
||
|
||
}
|
||
}
|
||
|
||
private void AddAunnPortalAnchorTargets(MapData mapData, UnitData unitData, PlayerData playerData,
|
||
UnitData anchor)
|
||
{
|
||
if (anchor == null || !anchor.Grid(mapData, out var anchorGrid)) return;
|
||
_aroundBuf2 ??= new List<GridData>();
|
||
_aroundBuf2.Clear();
|
||
mapData.GridMap.GetAroundGridData(1, 1, anchorGrid, _aroundBuf2);
|
||
foreach (var targetGrid in _aroundBuf2)
|
||
{
|
||
if (!HakureiNorwayHeroSkillUtil.IsValidAunnPortalTarget(mapData, unitData, playerData,
|
||
targetGrid)) continue;
|
||
int x = targetGrid.Pos.X, y = targetGrid.Pos.Y;
|
||
if (FinalReachMap[x, y]) continue;
|
||
TransMap[x, y] = true;
|
||
}
|
||
}
|
||
|
||
private void CalculateMoveSearchMap(MapData mapData, UnitData unitData, GridData startGrid, int moveRange,
|
||
float[,] remainMap, float[,] realCostMap, bool[,] reachMap, bool[,] slowMap)
|
||
{
|
||
int width = (int)mapData.MapConfig.Width;
|
||
int height = (int)mapData.MapConfig.Height;
|
||
|
||
for (int i = 0; i < width; i++)
|
||
for (int j = 0; j < height; j++)
|
||
{
|
||
remainMap[i, j] = -999;
|
||
realCostMap[i, j] = 999;
|
||
reachMap[i, j] = false;
|
||
slowMap[i, j] = false;
|
||
}
|
||
|
||
remainMap[startGrid.Pos.X, startGrid.Pos.Y] = moveRange;
|
||
reachMap[startGrid.Pos.X, startGrid.Pos.Y] = true;
|
||
realCostMap[startGrid.Pos.X, startGrid.Pos.Y] = 0;
|
||
slowMap[startGrid.Pos.X, startGrid.Pos.Y] = false;
|
||
|
||
Queue<uint> q = new Queue<uint>();
|
||
int breakTime = Mathf.Min(10000000, width * height * width * height);
|
||
q.Enqueue(startGrid.Id);
|
||
while (breakTime > 0 && q.Count > 0)
|
||
{
|
||
breakTime--;
|
||
if (!mapData.GridMap.GetGridDataByGid(q.Dequeue(), out var gridDataX)) continue;
|
||
if (remainMap[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;
|
||
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 (remainMap[gridDataX.Pos.X, gridDataX.Pos.Y] - cost > remainMap[gridDataY.Pos.X, gridDataY.Pos.Y])
|
||
{
|
||
remainMap[gridDataY.Pos.X, gridDataY.Pos.Y] = remainMap[gridDataX.Pos.X, gridDataX.Pos.Y] - cost;
|
||
reachMap[gridDataY.Pos.X, gridDataY.Pos.Y] = true;
|
||
realCostMap[gridDataY.Pos.X, gridDataY.Pos.Y] = cost;
|
||
slowMap[gridDataY.Pos.X, gridDataY.Pos.Y] = isSlowMove;
|
||
if (!q.Contains(gridDataY.Id) && remainMap[gridDataY.Pos.X, gridDataY.Pos.Y] > 0.1f)
|
||
q.Enqueue(gridDataY.Id);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
private bool MOMIJIHUNTER_SkillHandler(MapData mapData, UnitData unitData, GridData startGrid,
|
||
SkillBase skillHunter, int baseMoveRange)
|
||
{
|
||
int width = (int)mapData.MapConfig.Width;
|
||
int height = (int)mapData.MapConfig.Height;
|
||
if (skillHunter == null) return false;
|
||
|
||
var hunterExtraMoveRange = skillHunter.GetExtraMoveRange(mapData, unitData);
|
||
if (hunterExtraMoveRange <= 0) return false;
|
||
|
||
CalculateMoveSearchMap(mapData, unitData, startGrid, baseMoveRange + hunterExtraMoveRange,
|
||
MomijiHunterMovementRemainMap, MomijiHunterMoveRealCostMap, MomijiHunterReachMap,
|
||
MomijiHunterMoveSlowMap);
|
||
|
||
for (int i = 0; i < width; i++)
|
||
for (int j = 0; j < height; j++)
|
||
{
|
||
if (!MomijiHunterReachMap[i, j]) continue;
|
||
if (!mapData.GridMap.GetGridDataByV2(new Vector2(i, j), out var tmpGrid)) continue;
|
||
|
||
var floor = unitData.GetGridMoveFloor(mapData, unitData, tmpGrid);
|
||
if (floor > 0.1f) continue;
|
||
|
||
FinalReachMap[i, j] = true;
|
||
MomijiHunterTargetMap[i, j] = true;
|
||
MomijiMap[i, j] = true;
|
||
}
|
||
|
||
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 (HakureiNorwayHeroSkillUtil.CanSuikaFlyAttackTarget(mapData, unitDataA, unitDataB))
|
||
return MoveAttackType.Attack;
|
||
|
||
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;
|
||
|
||
if (!HakureiNorwayHeroSkillUtil.IsAunnPairWithinDistanceLimit(mapData, unitDataA, gridDataB))
|
||
return MoveAttackType.None;
|
||
|
||
|
||
//如果是传送或者sanae,最优先判断
|
||
if (KasenBeastGuideActiveMoveMap[gridDataB.Pos.X, gridDataB.Pos.Y]) return MoveAttackType.MoveTeleport;
|
||
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;
|
||
var moveSlowMap = MomijiHunterTargetMap[x, y] ? MomijiHunterMoveSlowMap : MoveSlowMap;
|
||
if (moveAttackType == MoveAttackType.Move && moveSlowMap[x, y])
|
||
types |= MoveHighlightType.Slow;
|
||
if (moveAttackType is MoveAttackType.Move or MoveAttackType.MoveToPort or MoveAttackType.MoveAshore
|
||
&& mapData.GetGridDataByUnitId(unitData.Id, out var originGridData)
|
||
&& Main.PlayerLogic.HasRoadForUnit(mapData, originGridData, 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] && !KasenBeastGuideActiveMoveMap[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] && !KasenBeastGuideActiveMoveMap[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 (unitData != null
|
||
&& unitData.GetActionPoint(ActionPointType.Attack) > 0
|
||
&& HakureiNorwayHeroSkillUtil.TryGetSumirekoOccultOrbReady(unitData, out _))
|
||
return 3;
|
||
if (unitData != null
|
||
&& unitData.GetSkill(SkillType.SuikaThrowReady, out _))
|
||
return 3;
|
||
if (unitData != null
|
||
&& unitData.GetActionPoint(ActionPointType.Attack) > 0
|
||
&& unitData.GetSkill(SkillType.SuikaFallingSplash, out _))
|
||
return 3;
|
||
if (unitData != null
|
||
&& unitData.GetActionPoint(ActionPointType.Attack) > 0
|
||
&& HakureiNorwayHeroSkillUtil.TryGetKasenBeastGuideReady(unitData, out _))
|
||
return HakureiNorwayHeroSkillUtil.KasenBeastGuidePlaceDistance;
|
||
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 (KasenBeastGuideActiveMoveMap[end.x, end.y])
|
||
{
|
||
path = new List<Vector2Int> { start, end };
|
||
return MoveAttackType.MoveTeleport;
|
||
}
|
||
|
||
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;
|
||
var pathRemainMap = MomijiHunterTargetMap[end.x, end.y] || SanaeFromMomijiHunterMap[end.x, end.y]
|
||
? MomijiHunterMovementRemainMap
|
||
: MovementRemainMap;
|
||
queue.Enqueue(start);
|
||
cameFrom[start] = start; // 标记起点已访问
|
||
while (queue.Count > 0)
|
||
{
|
||
var current = queue.Dequeue();
|
||
float currentHeight = pathRemainMap[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 (pathRemainMap[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 (TryGetDefaultLandToBoatTarget(unitData, out var defaultTargetFullType))
|
||
Main.UnitLogic.UnitTypeTransform(mapData, unitData, defaultTargetFullType);
|
||
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 if (unitData.TryGetLandToBoatTarget(mapData, targetGrid, out var targetFullType))
|
||
Main.UnitLogic.UnitTypeTransform(mapData, unitData, targetFullType);
|
||
else
|
||
Main.UnitLogic.UnitTypeTransform(mapData, unitData, UnitType.Boat);
|
||
// 变身已自带回合结束语义,直接清 AP;不通过 ClearActionPoint 派发 OnClearActionPoint,避免误移除 Cloak 的 HideState
|
||
unitData.ActionPoint.Clear();
|
||
}
|
||
|
||
private static bool TryGetDefaultLandToBoatTarget(UnitData unitData, out UnitFullType targetFullType)
|
||
{
|
||
targetFullType = new UnitFullType();
|
||
if (unitData == null) return false;
|
||
|
||
targetFullType = unitData.UnitType switch
|
||
{
|
||
UnitType.KomeijiIndianBigGuy => new UnitFullType(UnitType.KomeijiIndianJuggernaut, GiantType.None, 0),
|
||
UnitType.BigGuy => new UnitFullType(UnitType.Juggernaut, GiantType.None, 0),
|
||
UnitType.KaguyaFrenchWolf => new UnitFullType(UnitType.WolfJuggernaut, GiantType.None, 0),
|
||
UnitType.Giant => new UnitFullType(UnitType.GiantJuggernaut, GiantType.None, unitData.UnitLevel),
|
||
_ => targetFullType
|
||
};
|
||
|
||
return targetFullType.UnitType != UnitType.None;
|
||
}
|
||
|
||
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) 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)
|
||
{
|
||
var isAunnCarryBody = HakureiNorwayHeroSkillUtil.IsAunnCarryBody(unitData);
|
||
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();
|
||
if (isAunnCarryBody)
|
||
HakureiNorwayHeroSkillUtil.SyncAunnBodiesToHeroLevel(mapData, unitData);
|
||
}
|
||
|
||
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;
|
||
|
||
mapData.NormalizeKasenPermanentBerserkSkill(unitData);
|
||
|
||
if (unitData.HasEffectiveSkill(SkillType.Item, out _))
|
||
{
|
||
unitData.ActionPoint.Clear();
|
||
return;
|
||
}
|
||
|
||
unitData.SetFullActionPoint_AllSkillRefresh();
|
||
}
|
||
|
||
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.CheckUnitCondition(mapData, unitData)) continue;
|
||
unitData.AddInitSkill(info.AddSkillType, mapData);
|
||
unitData.GetSkill(info.AddSkillType, out var newSkill);
|
||
newSkill?.NewSkillOnTransform(unitData.SkillCache);
|
||
}
|
||
}
|
||
mapData.NormalizeKasenPermanentBerserkSkill(unitData);
|
||
|
||
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;
|
||
}
|
||
}
|
||
}
|