/* * @Author: 白哉 * @Description: 小兵逻辑 * @Date: 2025年04月01日 星期二 11:04:28 * @Modify: */ using System; using System.Collections.Generic; using System.Linq; using Animancer; using Logic.Action; using Logic.Audio; using Logic.CrashSight; using Logic.Skill; using UnityEngine; using RuntimeData; using TH1_Anim; using TH1_Anim.Fragments; using TH1_Core.Managers; using TH1_Logic.Core; using TH1_Presentation.Sequencer.Task; using TH1Renderer; using Unity.VisualScripting; namespace Logic { public enum DamageType { ActiveAttack, CounterAttack, FollowAttack, Splash, True, KillSelf, } public enum HealType { AttackAllyHeal, SelfHeal } public class SettlementInfo { //伤害类型 public DamageType DamageType; //伤害来源的unitId public UnitData DamageOrigin; //伤害目标的unitId public UnitData DamageTarget; public GridData DamageTargetGrid; public CityData DamageTargetCity; //伤害值 public int DamageValue; //是否击杀 public bool IsKill; public bool IsFinished; // 血量减少值 public int HealthReduceValue; public SettlementInfo() { IsFinished = false; } } public class UnitLogic : IUnitLogic { float[,] MoveInfoCostMap; float[,] MoveInfoMap; bool[,] TransMap; public UnitLogic() { MoveInfoCostMap = new float[Table.Instance.MaxMapSize,Table.Instance.MaxMapSize]; MoveInfoMap = new float[Table.Instance.MaxMapSize,Table.Instance.MaxMapSize]; TransMap = 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); } //-------- 执行类 --------// public void MoveToLogic(MapData mapData, UnitData unitData, GridData gridData,MoveType moveType) { //处理移动目标有单位的情况(return false) var targetGridUnit = gridData.Unit(mapData); if (targetGridUnit != null) return; mapData.SetUnitIdToGridId(unitData.Id,gridData.Id); if (mapData == Main.MapData) AchievementDataManager.Instance.OnUnitMove(mapData, unitData, gridData); //如果主动移动会消耗所有类别的行动点数,被动挤出去的移动不算 if(moveType != MoveType.PassiveMove && moveType != MoveType.AttackMove) unitData.MP = unitData.CP = unitData.AP = 0; //如果 land to port if (unitData.GetLandType() == LandType.LandAndPort && gridData.Resource == ResourceType.Port) { //注意,必须先Onmove(变身之前的技能用掉,再变身) unitData.OnMove(mapData, gridData,moveType); LandToBoat(mapData, unitData); } //如果sea to land else if (unitData.GetLandType() == LandType.WaterAndAshore && gridData.Terrain == TerrainType.Land) { //注意,必须先Onmove(变身之前的技能用掉,再变身) unitData.OnMove(mapData, gridData,moveType); BoatToLand(mapData, unitData); } //如果sea to sea或者land to land else //处理DASH等等技能情况,在Move结束时会触发的那些技能 unitData.OnMove(mapData, gridData,moveType); mapData.OnAnyUnitMove(mapData, unitData, gridData, moveType); if (!mapData.GetPlayerDataByUnitId(unitData.Id, out var playerData)) { LogSystem.LogError("unit找不到player"); return; } //如果是盟友的navalbase,且对方有恢复力,则消耗恢复力,恢复该单位所有行动点 if (gridData.Resource == ResourceType.NavalBase && mapData.GetPlayerDataByUnitId(unitData.Id, out var unitPlayer) && mapData.GetPlayerDataByTerritoryGridId(gridData.Id, out var gridPlayer) && mapData.SameUnion(unitPlayer.Id, gridPlayer.Id) && gridData.NavalBasePoint > 0) { gridData.NavalBasePoint--; unitData.AP = 1; unitData.CP = 1; unitData.MP = 1; } //更新所有人的外交好感情况。临时用的。必须真map才做,AI预测的不做,性能爆炸了 if (mapData == Main.MapData) { foreach(var pData in mapData.PlayerMap.PlayerDataList) pData.RefreshFeelingValue(mapData); } //判断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); } } //返回unit2是否能反击unit1 public bool CanCounter(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; // 计算攻击伤害 int dmg1 = Table.Instance.CalcDamage(mapData, unit1, unit2); //设置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()) canCounter = false; if (dmg1 >= unit2.Health) //如果伤害直接够杀死对方 canCounter = false; return canCounter; } public void Attack(MapData mapData, UnitData unit1, UnitData unit2, out int attackDmg,out int counterDmg,out FragmentType fragmentType) { attackDmg = 0; counterDmg = 0; fragmentType = FragmentType.Attack; if (!mapData.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; // 攻击前生命周期 unit1.BeforeActiveAttackOther(mapData, unit1, unit2, out var tmpAddDmg); attackDmg += tmpAddDmg; var attackDistance = Table.Instance.CalcDistance(grid1, grid2); // 计算攻击伤害 int dmg1 = Table.Instance.CalcDamage(mapData, unit1, unit2); int dmg2 = Table.Instance.CalcCounterDamage(mapData, unit1,unit2 ); //判断对方能否反击的参数 //TODO 之后还是要把这些放到后面逻辑里去写,不要在最开头判断,一定判断不出来的 bool canCounter = CanCounter(mapData,unit1,unit2); if(unit1.UnitFullType.UnitType == UnitType.Minder)canCounter = false; attackDmg += dmg1; //攻击会消耗所有类别的行动点数 unit1.AP = unit1.CP = unit1.MP = 0; Main.UnitLogic.DamageSettlement(mapData, unit1, unit2, dmg1, DamageType.ActiveAttack); if (!unit2.IsAlive()) { fragmentType = FragmentType.NotMoveKill; //处理MoveKill的情况 if (unit1.IsCanMoveKill(mapData) && attackDistance <= 1 && CheckUnitAbleForGrid(mapData, player1, unit1, grid2) && grid2.Unit(mapData) == null) { 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, unit1, unit2); if (unit2.IsAlive()) unit2.AfterActiveAttacked(mapData, unit1, unit2); } // 伤害新结算方法,所有的掉血都要走此方法,不允许直接修改UnitData.Health // TODO 自杀或者杀队友要不要加 player.TotalKill public SettlementInfo DamageSettlement(MapData mapData, UnitData origin, UnitData target, int dmg, DamageType type) { if (origin == null || target == null) { LogSystem.LogError($"DamageSettlement !!! DamageSettlement origin:{origin} or target{target} is null"); return null; } if (!mapData.GetGridDataByUnitId(target.Id, out var targetGrid)) { LogSystem.LogError($"DamageSettlement !!! Target Grid is null target.id:{target.Id}"); return null; } if (!mapData.GetCityDataByUnitId(target.Id, out var targetCity)) { LogSystem.LogError($"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.LogError($"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.CanBeDamaged(mapData, dmg)) { LogSystem.LogError($"DamageSettlement !!! if (!target.CanBeDamaged(mapData, dmg)) )"); return null; } var settlement = new SettlementInfo(); settlement.DamageType = type; settlement.DamageOrigin = origin; settlement.DamageTarget = target; settlement.DamageValue = dmg; 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; settlement.HealthReduceValue = Mathf.Min(target.Health, settlement.DamageValue); target.Health -= settlement.DamageValue; if (type == DamageType.KillSelf && !target.IsAlive()) { UnitDie(mapData, target, settlement.DamageValue); settlement.IsKill = true; } else if (target.CanBeKilled(mapData) && !target.IsAlive()) { if (Main.MapData == mapData) AchievementDataManager.Instance.OnKillUnit(mapData, origin, target); UnitDie(mapData, target, settlement.DamageValue); settlement.IsKill = true; if (!origin.IsExpLock(mapData)) origin.Exp++; mapData.GetPlayerDataByUnitId(origin.Id, out var player); if (player != null) player.TotalKill++; } else { settlement.IsKill = false; } // unit 受伤和伤害他人时回调 origin.OnDamageOther(mapData, settlement); target.OnDamaged(mapData, settlement); mapData.OnUnitDamaged(settlement); player1.PlayerHeroData.HeroTaskDict.TryGetValue(origin.UnitFullType, out var task1); task1?.OnDamageOther(mapData, settlement); player2.PlayerHeroData.HeroTaskDict.TryGetValue(target.UnitFullType, out var task2); task2?.OnDamaged(mapData, settlement); // unit 受伤和伤害他人后回调 origin.AfterDamageOther(mapData, settlement); if (type is DamageType.ActiveAttack or DamageType.FollowAttack) { 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; //更新所有人的外交好感情况。临时用的 foreach(var pData in mapData.PlayerMap.PlayerDataList) pData.RefreshFeelingValue(mapData); } return settlement; } public int RecoverHealth(MapData map, UnitData origin, UnitData target, int recover) { //Step #0 鲁棒性处理,设置基本参数 if (origin == null || target == null) { LogSystem.LogError($"DamageSettlement origin:{origin} or target{target} is null"); return 0; } var originPlayer = target.Player(map); if (originPlayer == null) { LogSystem.LogError($"Origin Player is null target.id:{origin.Id}"); return 0; } //Step #1处理恢复生命 int realRecover = target.AddHealth(recover); //Step #2处理恢复技能的herotask生命周期 origin.HeroTask(map)?.OnHealthReturn(map,realRecover,recover); //Step #3 处理View var grid = target.Grid(map); if (grid != null) grid.Renderer(map)?.PlayVFX(new GridVFXParams(GridVFXType.Heal)); target.Renderer(map)?.InstantUpdateUnit(true); origin.Renderer(map)?.InstantUpdateUnit(true); return realRecover; } //自然死亡 public void UnitDie(MapData map, UnitData unit, int dmg) { unit.Health = 0; if (!map.GetGridDataByUnitId(unit.Id, out var targetGrid)) return; //如果是伟人死亡,对应的player受到惩罚 penalty=4 if (unit.UnitType == UnitType.Giant && map.GetPlayerDataByUnitId(unit.Id, out var player) && Table.Instance.UnitTypeDataAssets.GetUnitTypeInfo(unit.UnitType, unit.GiantType, unit.UnitLevel, out var info)) player.giantPenalty[(uint)info.ChessType] = 4; //如果是英雄战船死亡,对应的player受到惩罚 penalty=4 if (unit.UnitType == UnitType.GiantJuggernaut && unit.CarryUnitType == UnitType.Giant && map.GetPlayerDataByUnitId(unit.Id, out var player2) && Table.Instance.UnitTypeDataAssets.GetUnitTypeInfo(unit.CarryUnitType, unit.CarryGiantType, unit.CarryUnitLevel, out var info2)) player2.giantPenalty[(uint)info2.ChessType] = 4; map.SetUnitDataDie(unit); map.OnAnyUnitDie(map); } //unit非自然死亡,如背盟、国家灭亡、解散、被挤死等 public void UnitUnnaturalDie(MapData map, UnitData unit) { unit.Health = 0; //如果是伟人死亡,对应的player受到惩罚 penalty=4 if (unit.UnitType == UnitType.Giant && map.GetPlayerDataByUnitId(unit.Id, out var player) && Table.Instance.UnitTypeDataAssets.GetUnitTypeInfo(unit.UnitType, unit.GiantType, unit.UnitLevel, out var info)) player.giantPenalty[(uint)info.ChessType] = 4; map.SetUnitDataDie(unit); } public bool TestKillUnit(MapData mapData, UnitData origin, UnitData target, int dmg) { if (dmg < target.Health) return false; if (!target.CanBeKilled(mapData)) return false; return true; } 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 (MoveInfoMap[i, j] < 0) t += "@"; else t += ((int)MoveInfoMap[i, j]).ToString(); t += "\n"; } Debug.Log(t); } //计算单位移动或者攻击的网格数组,存储在InfoMap中,方便后续判断使用,是一个需要立刻使用的,非长久存储的临时infomap public void CalcUnitMoveInfo(MapData mapData, uint uid) { mapData.UnitMap.GetUnitDataByUnitId(uid, out var unitData); mapData.GetPlayerDataByUnitId(uid, out var playerData); mapData.GetGridDataByUnitId(uid, out var gridData); int width = (int)mapData.MapConfig.Width; int height = (int)mapData.MapConfig.Height; if (playerData == null) { Debug.Log("Fatal Error !!!CalcUnitMoveInfo PlayerData is Null"); return; } //根据地形先设置好costMap基础信息,MoveInfoCostMap = -1表示不可抵达 foreach (var targetGridData in mapData.GridMap.GridList) { int i = targetGridData.Pos.X, j = targetGridData.Pos.Y; if (playerData.Sight.CheckIsInSight(targetGridData.Id)) { //如果是陆地单位 if (unitData.GetLandType() is LandType.LandAndPort) { MoveInfoCostMap[i, j] = 1; if (targetGridData.Vegetation == Vegetation.Trees) { MoveInfoCostMap[i, j] = 3; if (unitData.GetSkill(SkillType.BAMBOOMOVE, out var _)) MoveInfoCostMap[i, j] = 1; } if (targetGridData.Feature == TerrainFeature.Mountain) MoveInfoCostMap[i, j] = playerData.TechTree.CheckIfHasTech(TechType.Climbing) ? 3 : -1; if (targetGridData.Terrain == TerrainType.ShallowSea) { if (targetGridData.Resource == ResourceType.Bridge) MoveInfoCostMap[i, j] = 1; //如果是port,要判断是盟军的port 还是其他的port else if (targetGridData.Resource == ResourceType.Port) { //如果没有port科技,不能去 if (!playerData.TechTree.CheckIfHasTech(TechType.Fishing)) MoveInfoCostMap[i, j] = -1; //如果是己方的port else if (mapData.GetPlayerDataByTerritoryGridId(targetGridData.Id, out var targetGridPlayer) && mapData.SameUnion(targetGridPlayer.Id, playerData.Id)) MoveInfoCostMap[i, j] = 999; else MoveInfoCostMap[i, j] = -1; } else MoveInfoCostMap[i, j] = -1; } if (targetGridData.Terrain == TerrainType.DeepSea) MoveInfoCostMap[i, j] = -1; } else if (unitData.GetLandType() is LandType.LandAndWater) { MoveInfoCostMap[i, j] = 1; if (targetGridData.Vegetation == Vegetation.Trees) { MoveInfoCostMap[i, j] = 3; if (unitData.GetSkill(SkillType.BAMBOOMOVE, out var _)) MoveInfoCostMap[i, j] = 1; } if (targetGridData.Feature == TerrainFeature.Mountain) MoveInfoCostMap[i, j] = playerData.TechTree.CheckIfHasTech(TechType.Climbing) ? 3 : -1; if (targetGridData.Terrain == TerrainType.ShallowSea) { MoveInfoCostMap[i, j] = 1; if (unitData.GiantType == GiantType.FrenchInaba) MoveInfoCostMap[i, j] = 2; //如果没有WATERMOVE if (!unitData.GetSkill(SkillType.WATERMOVE, out var _)) MoveInfoCostMap[i, j] = -1; } if (targetGridData.Terrain == TerrainType.DeepSea) { MoveInfoCostMap[i, j] = 1; if (unitData.GiantType == GiantType.FrenchInaba) MoveInfoCostMap[i, j] = 2; if (!unitData.GetSkill(SkillType.OCEANMOVE, out var _)) MoveInfoCostMap[i, j] = -1; } } else if (unitData.GetLandType() is LandType.LandOnly) { MoveInfoCostMap[i, j] = 1; if (targetGridData.Vegetation == Vegetation.Trees) { MoveInfoCostMap[i, j] = 3; if (unitData.GetSkill(SkillType.BAMBOOMOVE, out var _)) MoveInfoCostMap[i, j] = 1; } if (targetGridData.Feature == TerrainFeature.Mountain) MoveInfoCostMap[i, j] = playerData.TechTree.CheckIfHasTech(TechType.Climbing) ? 3 : -1; if (targetGridData.Terrain is TerrainType.ShallowSea or TerrainType.DeepSea) { if (targetGridData.Resource == ResourceType.Bridge) MoveInfoCostMap[i, j] = 1; else MoveInfoCostMap[i, j] = -1; } } //如果是水上单位 else if (unitData.GetLandType() is LandType.WaterAndAshore or LandType.WaterOnly) { if (targetGridData.Terrain == TerrainType.DeepSea) MoveInfoCostMap[i, j] = playerData.TechTree.CheckIfHasTech(TechType.Sailing) ? 1 : -1; else if (targetGridData.Terrain == TerrainType.ShallowSea) MoveInfoCostMap[i, j] = 1; else { MoveInfoCostMap[i, j] = 999; if (targetGridData.Feature == TerrainFeature.Mountain && !playerData.TechTree.CheckIfHasTech(TechType.Climbing)) MoveInfoCostMap[i, j] = -1; } } else if (unitData.GetLandType() is LandType.Fly) { MoveInfoCostMap[i, j] = 1; } } //没有视野,那就不可抵达 else MoveInfoCostMap[i, j] = -1; if (MoveInfoCostMap[i, j] == 0) { Debug.Log("Fatal Error !!!!"); } } //将所有敌人的控制区的costMap设置为999 foreach (UnitData B in mapData.UnitMap.UnitList) { if (!B.Alive) continue; mapData.GetPlayerDataByUnitId(B.Id, out var playerDataB); mapData.GetGridDataByUnitId(B.Id, out var gridDataB); //盟友则不会有控制区域 if (mapData.SameUnion(playerDataB.Id ,playerData.Id)) continue; var targetGridDataList = mapData.GridMap.GetAroundGridDataSet(1,1,gridDataB); foreach (var targetGridData in targetGridDataList) { int x = targetGridData.Pos.X, y = targetGridData.Pos.Y; MoveInfoCostMap[x, y] = (MoveInfoCostMap[x, y] < 0) ? -1 : 999; } MoveInfoCostMap[gridDataB.Pos.X,gridDataB.Pos.Y] = 999; } //将所有友军的城市中心设置为-1(不能进入友军的城市中心) foreach (var tcity in mapData.CityMap.CityList) { if (!mapData.GetPlayerDataByCityId(tcity.Id, out var tplayer))continue; if (tplayer.Id == playerData.Id) continue; if(!mapData.SameUnion(tplayer.Id,playerData.Id))continue; if(!mapData.GetGridDataByCityId(tcity.Id,out var tgrid))continue; MoveInfoCostMap[tgrid.Pos.X,tgrid.Pos.Y] = -1; } //初始化infomap,<0表示不可达,<0.1则不再扩展,>0.1表示可以向附近扩展 for (int i = 0; i < width; i++) for (int j = 0; j < height; j++) { MoveInfoMap[i, j] = -1; TransMap[i, j] = false; } MoveInfoMap[gridData.Pos.X,gridData.Pos.Y] = unitData.GetMoveRange(); //然后进行SPFA更新最短路 Queue q = new Queue(); //breakTime用来放置负边死循环情况 int breakTime = Mathf.Min(10000000, width * height * width * height); q.Enqueue(gridData.Id); while (breakTime > 0 && q.Count > 0) { breakTime--; if (!mapData.GridMap.GetGridDataByGid(q.Dequeue(), out var gridDataX)) continue; if (MoveInfoMap[gridDataX.Pos.X, gridDataX.Pos.Y] < 0.1f) continue; var nearby = mapData.GridMap.GetAroundGridDataSet(1,1,gridDataX); foreach (var gridDataY in nearby) { float cost = MoveInfoCostMap[gridDataY.Pos.X, gridDataY.Pos.Y]; //如果不可达区 if (cost <= 0)continue; //如果是陆地单位,看是否需要减去道路或者桥梁的行动力加成。道路是非敌人领地才行,并且目标不是耗光所有cost的港口或者敌占区 if ((unitData.GetLandType() is LandType.LandAndPort or LandType.LandOnly or LandType.LandAndWater) && Main.PlayerLogic.HasRoadForUnit(mapData, gridDataX, gridDataY, unitData) && cost < 900) cost = 0.5f; //cost -= 0.5f + (gridDataY.Vegetation == Vegetation.Trees ? 1f : 0f); //如果能够发生更新 if (Mathf.Max(MoveInfoMap[gridDataX.Pos.X, gridDataX.Pos.Y] - cost, 0f) > MoveInfoMap[gridDataY.Pos.X, gridDataY.Pos.Y]) { MoveInfoMap[gridDataY.Pos.X, gridDataY.Pos.Y] = Mathf.Max(MoveInfoMap[gridDataX.Pos.X, gridDataX.Pos.Y] - cost, 0f); //如果xy不再队列里,并且还剩余有行动力,就加入队列 if (!q.Contains(gridDataY.Id) && MoveInfoMap[gridDataY.Pos.X, gridDataY.Pos.Y] > 0.1f) q.Enqueue(gridDataY.Id); } } } //检测有没有特别离谱的MoveInfo for (int i = 0; i < width; i++) for (int j = 0; j < height; j++) if(MoveInfoMap[i, j] >= 0 && Table.Instance.CalcDistance(new Vector2Int(gridData.Pos.X,gridData.Pos.Y),new Vector2Int(i,j)) > 5) Debug.Log("moveinfo计算时出现了超过5格的移动距离"); //处理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); var nearby = mapData.GridMap.GetAroundGridDataSet(1,1,gridB); foreach (var gridC in nearby){ int xx = gridC.Pos.X, yy = gridC.Pos.Y; if(CheckUnitAbleForGrid(mapData,playerData,unitData,gridC) && !mapData.GetUnitDataByGid(gridC.Id,out var tt)) { int x = gridC.Pos.X, y = gridC.Pos.Y; TransMap[x, y] = true; }} } } //接入CITYTRANSPORT,在首都联通的城市之间传送 if (unitData.IsCanMoveNoUnitSelfCity(mapData)) { //当前在我方的首都或者首都联通的城市才可以 if (mapData.GetCityDataByGid(gridData.Id, out var cityA) && (cityA.IsCapital || cityA.IsConnectedCapital) && mapData.GetPlayerDataByCityId(cityA.Id,out var cityAsPlayer) && cityAsPlayer.Id == playerData.Id) { //遍历所有我方城市 如果位置没有人且联通,就可以移动过去 var cityDataList = new List(); 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 (mapData.GetUnitDataByGid(gridB.Id, out var _)) continue; TransMap[gridB.Pos.X, gridB.Pos.Y] = 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.GetUnitDataByGid(gridData.Id, out var targetUnit)) return false; if(!mapData.GetPlayerDataByUnitId(unitData.Id, out var player)) 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()) return false; return true; } // None, Move, Attack, Port, MoveAshore, Ally public MoveAttackType CheckUnitCanMoveOrAttack(MapData mapData, UnitData unitDataA, GridData gridDataB) { //默认在调用这个函数前已经调用了 CalcUnitMoveInfo(A),计算好了。不组合使用是不行的 mapData.GetGridDataByUnitId(unitDataA.Id,out var gridDataA); //如果目标位置有单位 if(mapData.GetUnitDataByGid(gridDataB.Id,out var unitDataB)) { mapData.GetPlayerDataByUnitId(unitDataA.Id, out var playerDataA); mapData.GetPlayerDataByUnitId(unitDataB.Id, out var playerDataB); //如果目标位置上面有友军单位,除非有特殊技能,否则是none if (mapData.SameUnionOrJustBreakUnion(playerDataA.Id, playerDataB.Id) && mapData.GridMap.CalcDistance(gridDataA, gridDataB) <= unitDataA.GetAttackRange()) { //处理Flandre技能,可以是刚解除结盟的友军 if (unitDataA.GetSkill(SkillType.FLANDREATTACK, out var _) ) return MoveAttackType.Attack; //以下处理不能是刚解除结盟的友军的情况 if (mapData.SameUnion(playerDataA.Id, playerDataB.Id)) { //处理Eirin技能 必须是盟友,不能是刚解除结盟的友军 if (unitDataA.GetSkill(SkillType.EIRINFRENCHATTACK, out var _)) return MoveAttackType.Ally; //处理Kaguya技能 必须是盟友,不能是刚解除结盟的友军 if (unitDataA.GetSkill(SkillType.KAGUYAFRENCHATTACK, out var _) || unitDataA.GetSkill(SkillType.KAGUYAFRENCHATTACKPRO, out var _)) return MoveAttackType.Ally; //处理Patchouli技能 必须是盟友,不能是刚解除结盟的友军 if (unitDataA.GetSkill(SkillType.PATCHOULIEARTH, out var _) ) return MoveAttackType.Ally; //处理Koakuma的友军释放,不能是刚解除结盟的友军 if (unitDataA.GetSkill(SkillType.KOAKUMADEVOTION, out var _) ) return MoveAttackType.Ally; } return MoveAttackType.None; } //如果目标位置上面有敌军单位,那不可移动,但是有可能可以攻击 else if (mapData.GridMap.CalcDistance(gridDataA, gridDataB) <= unitDataA.GetAttackRange() && !unitDataA.IsLimitSelfAttack(mapData)) //判断距离在攻击距离范围内,且具备攻击能力 { //特殊处理:学者不能攻击英雄 if (unitDataA.GetSkill(SkillType.CONVERT, out var _) && unitDataB.UnitFullType.UnitType == UnitType.Giant) return MoveAttackType.None; /*if(mapData.GridMap.CalcDistance(gridDataA, gridDataB) > 3) Debug.Log("异常的攻击距离");*/ return MoveAttackType.Attack; //返回可以攻击 } else //不在攻击范围内,不可以攻击 return MoveAttackType.None; } //如果目标位置没有任何单位 else { //如果是传送,最优先判断 if (TransMap[gridDataB.Pos.X, gridDataB.Pos.Y]) return MoveAttackType.MoveTeleport; if (MoveInfoMap[gridDataB.Pos.X, gridDataB.Pos.Y] < 0f) //去不了直接返回 return MoveAttackType.None; if (gridDataB.Terrain == TerrainType.ShallowSea && unitDataA.GetLandType() == LandType.LandAndPort) //下港口 return MoveAttackType.MoveToPort; if (gridDataB.Terrain == TerrainType.Land && unitDataA.GetLandType() == LandType.WaterAndAshore) //上岸 return MoveAttackType.MoveAshore; return MoveAttackType.Move; } return MoveAttackType.None; } //默认在调用这个函数前已经调用了 CalcUnitMoveInfo(A),计算好了。不组合使用是不行的 public MoveAttackType GetMovePath(MapData map, Vector2Int start, Vector2Int end,out List path) { if (TransMap[end.x, end.y]) { path = new List { start, end }; return MoveAttackType.MoveTeleport; } path = new List(); Vector2Int[] Dirs = { new(-1, 0), new(0, -1),new(0, 1),new(1, 0), new(1, -1), new(1, 1),new(-1, -1),new(-1, 1) }; if (start == end) { path = new List { start }; return MoveAttackType.Move; } var queue = new Queue(); // 用字典记录路径来源,同时兼作“已访问”集合 var cameFrom = new Dictionary(); uint width = map.MapConfig.Width; uint height = map.MapConfig.Height; queue.Enqueue(start); cameFrom[start] = start; // 标记起点已访问 while (queue.Count > 0) { var current = queue.Dequeue(); float currentHeight = MoveInfoMap[current.x, current.y]; if (current == end) break; // 首次到达终点,即为最短路径 if (currentHeight < 0) continue; 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 (MoveInfoMap[next.x, next.y] < currentHeight) { queue.Enqueue(next); cameFrom[next] = current; } } } // 如果终点不可达,返回空列表 if (!cameFrom.ContainsKey(end)) return MoveAttackType.None; // 从终点回溯路径 path = new List(); for (var at = end; at != start; at = cameFrom[at]) { path.Add(at); } path.Add(start); path.Reverse(); // 翻转为从起点到终点的顺序 return MoveAttackType.Move; } public void Upgrade(MapData mapData, UnitData u) { /* u.veteran = true; u.health = u.maxHealth + 5; RenderUpdateManager.Instance.AddUnitRUList(u.unitId); */ } public void AddMPAPCP(MapData mapData, UnitData unitData) { unitData.MP = 1; unitData.AP = 1; unitData.CP = 1; } //判断该unit是不是selfPlayer的unit //unit[uid]原地休息 public void Recover(MapData mapData, UnitData unitData) { } //unit[uid]从陆地进入港口,变为boat public void LandToBoat(MapData mapData, UnitData unitData) { unitData.CarryUnitType = unitData.UnitType; unitData.CarryGiantType = unitData.GiantType; unitData.CarryUnitLevel = unitData.UnitLevel; if (unitData.UnitType == UnitType.BigGuy) Main.UnitLogic.UnitTypeTransform(mapData, unitData, UnitType.Juggernaut); else if(unitData.UnitType == UnitType.Giant) Main.UnitLogic.UnitTypeTransform(mapData, unitData, UnitType.GiantJuggernaut,GiantType.None,unitData.UnitLevel); else Main.UnitLogic.UnitTypeTransform(mapData, unitData, UnitType.Boat); unitData.AP = 0; unitData.MP = 0; unitData.CP = 0; unitData.CarryExp = unitData.Exp; unitData.Exp = 0; unitData.CarryVeteran = unitData.Veteran; unitData.Veteran = false; } //unit[uid]从港口进入陆地,变为原来的单位 public void BoatToLand(MapData mapData, UnitData unitData) { Main.UnitLogic.UnitTypeTransform(mapData,unitData, unitData.CarryUnitType, unitData.CarryGiantType,unitData.CarryUnitLevel); unitData.AP = 0; unitData.MP = 0; unitData.CP = 0; unitData.Exp = unitData.CarryExp; unitData.Veteran = unitData.CarryVeteran; } public bool HeroUpgrade(MapData map,UnitData unit) { var type = unit.UnitFullType; if (type.UnitType != UnitType.Giant) return false; if (type.GiantType == GiantType.None) return false; if (type.UnitLevel >= 3) return false; var targetType = new UnitFullType() { UnitType = unit.UnitType, GiantType = unit.GiantType, UnitLevel = unit.UnitLevel + 1 }; UnitTypeTransform(map,unit,targetType); unit.Player(map)?.PlayerHeroData.UpdateHeroMaxCount(targetType.UnitLevel); return unit.Player(map)?.PlayerHeroData.UpdateHero(map,unit.Player(map),unit.UnitFullType) ?? false; } //unit[uid]死亡 /*public void Die(MapData mapData, UnitData unitData) { mapData.SetUnitDataDie(unitData); }*/ //unitData开启下一回合 public void StartNextTurn(MapData mapData, UnitData unitData) { if (!unitData.Alive) return; unitData.AP = 1; unitData.MP = 1; unitData.CP = 1; unitData.Renderer(mapData)?.InstantUpdateUnit(true); } public void UnitEndTurn(MapData mapData, UnitData unitData) { if (!unitData.Alive) return; //在进入下一个回合前,如果可以回血自动回血 if (unitData.Health < unitData.GetMaxHealth() && unitData.CP > 0) { if (mapData.GetGridDataByUnitId(unitData.Id, out var grid) && mapData.GetPlayerDataByUnitId(unitData.Id, out var player) ) { unitData.Health += 2; //如果是自家或者盟军领土 if (mapData.CheckGridIdBelongPlayerIdUnion(grid.Id, player.Id)) unitData.Health += 2; if (unitData.Health > unitData.GetMaxHealth()) unitData.Health = unitData.GetMaxHealth(); grid.Renderer(mapData)?.PlayVFX(new GridVFXParams(GridVFXType.Heal)); unitData.Renderer(mapData)?.InstantUpdateUnit(true); } } } 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) { //Debug.Log($"Check Transform Before APMPCP = {unitData.AP} , {unitData.MP} ,{unitData.CP} "); //重新处理一下: //生命周期: 1 . 卸载所有原来的skill //TODO 要迭代这块,主要是在skill那边要加标记,确认一个skill是否可以继承,是否transfrom的时候要删掉之类的,要不要触发情况等等 bool nopop = unitData.GetSkill(SkillType.NOPOPULATION,out var _); Table.Instance.UnitTypeDataAssets.GetUnitTypeInfo(unitData.UnitType, unitData.GiantType,unitData.UnitLevel, out var originInfo); Table.Instance.UnitTypeDataAssets.GetUnitTypeInfo(targetType, giantType,unitLevel, out var targetInfo); if (originInfo == null || targetInfo == null) return; var oldSkills = new List(); foreach (var skilltype in originInfo.Skills) { if (targetInfo.Skills.Contains(skilltype)) continue; unitData.GetSkill(skilltype, out var skill); if (skill == null) continue; var fullType = new UnitFullType(targetType, giantType, unitLevel); if (skill.ReservedOnTransform(unitData, fullType)) continue; oldSkills.Add(skill); unitData.RemoveSkill(skilltype, mapData); } foreach (var skillType in targetInfo.Skills) { unitData.AddSkill(skillType); unitData.GetSkill(skillType, out var newSkill); newSkill?.NewSkillOnTransform(oldSkills); } unitData.UnitType = targetType; unitData.GiantType = giantType; unitData.UnitLevel = unitLevel; //处理AddSkill的生命周期 var copy = new List(unitData.Skills); foreach (var skill in copy) skill.OnSkillAdd(unitData, mapData); //Debug.Log($"Check Transform After APMPCP = {unitData.AP} , {unitData.MP} ,{unitData.CP} "); } 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; GridData bestGrid = null; int score = -1; var gridList = mapData.GridMap.GetAroundGridData(1,1,gridData); foreach (var targetGrid in gridList) { if (targetGrid == gridData) continue; if (!CheckUnitCanStandOnGrid(mapData, unitData, targetGrid)) continue; if (score == -1 || mapData.GridMap.CalcManhattanDistance(targetGrid, cityGrid) > score) { bestGrid = targetGrid; score = mapData.GridMap.CalcManhattanDistance(targetGrid, cityGrid); } } 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 CheckUnitCanStandOnGrid(MapData mapData, UnitData unitData, GridData gridData) { //TODO 这里一定要改了,用把landType通过unitSkill改过来 ,或者至少完成一套完备的方案 if (!mapData.CheckLandTypeForGrid(unitData.UnitFullType, gridData)) return false; if (!mapData.GetPlayerDataByUnitId(unitData.Id, out var playerData)) return false; if (!Table.Instance.UnitTypeDataAssets.GetUnitTypeInfo(unitData.UnitType, unitData.GiantType,unitData.UnitLevel, out var unitInfo)) return false; //如果上面有人 if (mapData.GetUnitDataByGid(gridData.Id, out var tmp)) return false; if (unitData.GetLandType() == LandType.Fly) return true; //如果是水域 if (gridData.Terrain != TerrainType.Land) { if (gridData.Resource == ResourceType.Bridge) { return true; } //如果本身就是水生单位 if (unitData.GetLandType() == LandType.WaterOnly || unitData.GetLandType() == LandType.WaterAndAshore || unitData.GetLandType() == LandType.LandAndWater) { if(gridData.Terrain == TerrainType.ShallowSea) return unitData.GetSkill(SkillType.WATERMOVE,out var _); if(gridData.Terrain == TerrainType.DeepSea) return unitData.GetSkill(SkillType.OCEANMOVE,out var _); } //否则 //如果有港口但不能下港口 if (gridData.Resource == ResourceType.Port) { if (unitInfo.LandType != LandType.WaterAndAshore && unitInfo.LandType == LandType.WaterOnly) return false; var gridPlayer = gridData.Player(mapData); var unitPlayer = unitData.Player(mapData); if (gridPlayer == null || unitPlayer == null) return false; if(!mapData.SameUnion(gridPlayer.Id,unitPlayer.Id)) return false; } //如果没有港口,纯看能不能移动进入这片区域 if (gridData.Resource != ResourceType.Port) { //TODO 最好将LandType直接转为Skill ,后续要思考做法 //如果单位不支持进水,直接return false if (!(unitInfo.LandType is LandType.WaterOnly or LandType.LandAndWater)) return false; if(gridData.Terrain == TerrainType.ShallowSea) return unitData.GetSkill(SkillType.WATERMOVE,out var _); if(gridData.Terrain == TerrainType.DeepSea) return unitData.GetSkill(SkillType.OCEANMOVE,out var _); } } //如果是陆地 if (gridData.Terrain == TerrainType.Land) { //如果是山脉 if (gridData.Feature == TerrainFeature.Mountain) { return unitData.GetSkill(SkillType.MOUNTAINMOVE,out var _); } } return true; } //判断一个unit是否有能力去某个grid,但是和那个grid目前的状态无关 public bool CheckUnitAbleForGrid(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.CheckIfHasTech(TechType.Climbing)) return false; if (grid.Terrain == TerrainType.ShallowSea && !self.TechTree.CheckIfHasTech(TechType.Fishing)) return false; if (grid.Terrain == TerrainType.DeepSea && !self.TechTree.CheckIfHasTech(TechType.Sailing)) 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.WaterAndAshore) { } return true; } } }