1198 lines
55 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

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

/*
* @Author: 白哉
* @Description: 小兵逻辑
* @Date: 2025年04月01日 星期二 11:04:28
* @Modify:
*/
using System;
using System.Collections.Generic;
using System.Linq;
using Animancer;
using Logic.Action;
using Logic.Audio;
using Logic.CrashSight;
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 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 SettlementInfo()
{
IsFinished = false;
}
}
public class UnitLogic : IUnitLogic
{
float[,] MoveInfoCostMap;
float[,] MoveInfoMap;
public UnitLogic()
{
MoveInfoCostMap = new float[Table.Instance.MaxMapSize,Table.Instance.MaxMapSize];
MoveInfoMap = new float[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 (!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 bool AttackAlly(MapData mapData, UnitData unit1, UnitData unit2)
{
//只有白名单才能使用AttackAlly
//处理Eirin的友军攻击
if (unit1.GetSkill(SkillType.EIRINFRENCHATTACK, out var _))
{
MapRenderer.Instance.ProjectileManager.CreateProjectile(unit1.GetPosition(mapData) + Vector3.up * 3f,unit2.GetPosition(mapData) + Vector3.up * 3f,ProjectileType.Arrow,ProjectileMoveType.Parabola);
unit1.ClearAPMPCP();
unit1.Renderer(mapData)?.InstantUpdateUnit(false);
int recover = 5;
if (unit1.GetSkill(SkillType.EIRINFRENCHBUFF, out var _) && unit2.GetUnitFullType.UnitType == UnitType.Giant)
recover = 15;
Timer.Instance.TimerRegister(this, () =>
{
var before = unit1.Health;
RecoverHealth(mapData, unit1, unit2, recover);
unit2.AddSkill(SkillType.KAGUYAFRENCHSYNERGY,1);
//如果溢出
if (unit1.Health - before < recover)
{
unit2.AddSkill(SkillType.MOVERANGEUP,1);
}
},0.3f,"UnitLogic AttackAlly");
return true;
}
//处理Kaguya的友军攻击
if (unit1.GetSkill(SkillType.KAGUYAFRENCHATTACK, out var _) || unit1.GetSkill(SkillType.KAGUYAFRENCHATTACKPRO, out var _) )
{
var lv = unit1.GetSkill(SkillType.KAGUYAFRENCHATTACKPRO, out var _) ? 9 : 1;
MapRenderer.Instance.ProjectileManager.CreateProjectile(unit1.GetPosition(mapData) + Vector3.up * 2f,unit2.GetPosition(mapData) + Vector3.up * 2f,ProjectileType.KaguyaAttack,ProjectileMoveType.Straight);
unit1.ClearAPMPCP();
unit1.Renderer(mapData)?.InstantUpdateUnit(false);
unit2.AddSkill(SkillType.KAGUYAFRENCHFOREVERBUFF);
if(unit2.GetSkill(SkillType.KAGUYAFRENCHFOREVERBUFF, out var skill))
skill.AddLevel(mapData,unit1,lv);
Timer.Instance.TimerRegister(this, () =>
{
unit2.Grid(mapData)?.Renderer(mapData)?.PlayVFX(new GridVFXParams(GridVFXType.KaguyaFrenchBuff));
},0.3f,"UnitLogic AttackAlly");
return true;
}
return false;
}
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.GetGridDataByUnitId(unit2.Id, out var grid2)) return;
if (unit1.IsLimitSelfAttack(mapData)) return;
var attackDistance = Table.Instance.CalcDistance(unit1.Grid(mapData), unit2.Grid(mapData));
foreach (var skill in unit1.Skills)
{
skill.BeforeActiveAttackOther(mapData, unit1, unit2, out var tmpAddDmg);
attackDmg += tmpAddDmg;
}
// 计算攻击伤害
int dmg1 = Table.Instance.CalcDamage(mapData, unit1, unit2);
attackDmg += dmg1;
int dmg2 = 0;
//判断对方能否反击的参数
bool canCounter = CanCounter(mapData,unit1,unit2);
if (canCounter) dmg2 = Table.Instance.CalcCounterDamage(mapData, unit1,unit2 );
counterDmg += dmg2;
//攻击会消耗所有类别的行动点数
unit1.AP = unit1.CP = unit1.MP = 0;
var info1 = Main.UnitLogic.DamageSettlement(mapData, unit1, unit2, dmg1, DamageType.ActiveAttack);
if (info1.IsKill)
{
fragmentType = FragmentType.NotMoveKill;
//处理MoveKill的情况
if (unit1.IsCanMoveKill(mapData) && attackDistance <= 1 && CheckUnitCanMoveToGrid(mapData, player1, unit1, grid2))
{
Main.UnitLogic.MoveToLogic(mapData, unit1, grid2, MoveType.AttackMove);
fragmentType = FragmentType.MoveKill;
}
}
else
{
if (unit1.Health == 0 && unit2.Health == 0) return;
if (mapData.SameUnionByUnitId(unit1.Id, unit2.Id)) return;
if (!canCounter) return;
var info2 = Main.UnitLogic.DamageSettlement(mapData, unit2, unit1, dmg2, DamageType.CounterAttack);
if(!info2.IsKill)
fragmentType = FragmentType.AttackAndCounter;
else
fragmentType = FragmentType.AttackAndCounterDie;
}
foreach (var skill in unit2.Skills) skill.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;
}
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.BeforeDamaged(mapData, settlement);
if (settlement.IsFinished) return null;
target.Health -= settlement.DamageValue;
if (type == DamageType.KillSelf && target.Health <= 0)
{
UnitDie(mapData, target, settlement.DamageValue);
settlement.IsKill = true;
}
else if (target.CanBeKilled(mapData) && target.Health <= 0)
{
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);
if (type is DamageType.Splash or 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;
Main.UnitLogic.Die(map, 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);
}
// 判断我方单位是否可以站在某个格子上, 不考虑视野信息,不考虑可达
public bool CheckUnitCanStandOnGrid(MapData map, PlayerData self,UnitData unit ,GridData grid)
{
//如果unit不是null那么就是判断具体的某个unit能否站在grid上
if (unit != null)
{
if(!unit.IsCanMoveOnTerrain(map,grid.Terrain))return false;
if(!unit.IsCanMoveOnFeature(map, grid.Feature))return false;
if (grid.Terrain == TerrainType.ShallowSea && grid.Resource != ResourceType.Port)
return false;
if (grid.Terrain == TerrainType.ShallowSea && grid.Resource == ResourceType.Port)
{
if (!map.GetPlayerDataByUnitId(unit.Id, out var playerA)) return false;
if (!map.GetPlayerDataByTerritoryGridId(grid.Id,out var playerB)) return false;
if (!map.SameUnion(playerA.Id, playerB.Id)) return false;
}
}
//否则可能是判断一个玩家的任意unit能否站在上面(也就是玩家整个科技树是否支持站在上面)
else
{
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;
}
return true;
}
// 仅从feature和terrain角度判断我方单位是否可以移动到某个格子上 不考虑视野信息
//TODO 整理这一块代码,后续用这个来判断
public bool CheckUnitCanMoveToGrid(MapData map, PlayerData self, UnitData unit, GridData grid )
{
if (!map.CheckLandTypeForGrid(unit.GetUnitFullType, grid))
return false;
var landType = unit.GetLandType();
//step #1 排除科技情况
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;
}
//TODO 这个是不是要更新一下,用上面的函数
public bool CheckUnitCanMoveToGrid(MapData map, PlayerData self, 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;
if (endGrid.Feature == TerrainFeature.Mountain && !self.TechTree.CheckIfHasTech(TechType.Climbing)) return false;
if (endGrid.Terrain == TerrainType.DeepSea && !self.TechTree.CheckIfHasTech(TechType.Sailing)) return false;
//这条没懂
if (endGrid.Terrain == TerrainType.DeepSea && startGrid.Terrain != TerrainType.ShallowSea) return false;
if (endGrid.Terrain == TerrainType.ShallowSea && startGrid.Terrain == TerrainType.Land && endGrid.Resource != ResourceType.Port) return false;
return true;
}
//计算单位移动或者攻击的网格数组存储在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;
//根据地形先设置好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;
}
//将所有敌人的控制区的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;
MoveInfoMap[gridData.Pos.X,gridData.Pos.Y] = unitData.GetMoveRange();
//然后进行SPFA更新最短路
Queue<uint> q = new Queue<uint>();
//breakTime用来放置负边死循环情况
int breakTime = Mathf.Min(10000000, width * height * width * height);
q.Enqueue(gridData.Id);
while (breakTime > 0 && q.Count > 0)
{
breakTime--;
if (!mapData.GridMap.GetGridDataByGid(q.Dequeue(), out var gridDataX))
continue;
if (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格的移动距离");
//处理ALLTRANSPORT技能可以移动到己方伟人的身周
if (unitData.IsCanMoveGiantNearbyGrid(mapData))
{
//遍历所有单位,找到己方的伟人
foreach (var unitA in mapData.UnitMap.UnitList)
if(unitA.UnitType == UnitType.Giant || unitA.CarryUnitType == UnitType.Giant
&& 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(CheckUnitCanStandOnGrid(mapData,playerData,unitData,gridC)
&& !mapData.GetUnitDataByGid(gridC.Id,out var tt))
{
int x = gridC.Pos.X, y = gridC.Pos.Y;
MoveInfoMap[x, y] = 0;
}}
}
}
//接入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<CityData>();
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;
MoveInfoMap[gridB.Pos.X, gridB.Pos.Y] = 0;
}
}
}
}
// 检查单位是否能攻击目标点
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
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))
{
//处理Eirin技能 必须是盟友,不能是刚解除结盟的友军
if (mapData.SameUnion(playerDataA.Id, playerDataB.Id))
{
if (unitDataA.GetSkill(SkillType.EIRINFRENCHATTACK, out var _))
return MoveAttackType.Ally;
}
//处理Kaguya技能 必须是盟友,不能是刚解除结盟的友军
if (mapData.SameUnion(playerDataA.Id, playerDataB.Id))
{
if (unitDataA.GetSkill(SkillType.KAGUYAFRENCHATTACK, out var _) || unitDataA.GetSkill(SkillType.KAGUYAFRENCHATTACKPRO, out var _))
return MoveAttackType.Ally;
}
return MoveAttackType.None;
}
//如果目标位置上面有敌军单位,那不可移动,但是有可能可以攻击
else if (mapData.GridMap.CalcDistance(gridDataA, gridDataB) <= unitDataA.GetAttackRange() &&
!unitDataA.IsLimitSelfAttack(mapData)) //判断距离在攻击距离范围内,且具备攻击能力
{
if(mapData.GridMap.CalcDistance(gridDataA, gridDataB) > 3)
Debug.Log("异常的攻击距离");
return MoveAttackType.Attack; //返回可以攻击
}
else //不在攻击范围内,不可以攻击
return MoveAttackType.None;
}
//如果目标位置没有任何单位
else
{
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 bool GetMovePath(MapData map, Vector2Int start, Vector2Int end,out List<Vector2Int> path)
{
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 true;
}
var queue = new Queue<Vector2Int>();
// 用字典记录路径来源,同时兼作“已访问”集合
var cameFrom = new Dictionary<Vector2Int, Vector2Int>();
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 false;
// 从终点回溯路径
path = new List<Vector2Int>();
for (var at = end; at != start; at = cameFrom[at])
{
path.Add(at);
}
path.Add(start);
path.Reverse(); // 翻转为从起点到终点的顺序
return true;
}
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;
//unitData.MoveRange = Table.Instance.UnitInfoDict[UnitType.Boat].MoveRange;
//unitData.AttackRange = Table.Instance.UnitInfoDict[UnitType.Boat].AttackRange;
//unitData.RenderMark = true;
}
//unit[uid]从港口进入陆地,变为原来的单位
public void BoatToLand(MapData mapData, UnitData unitData)
{
if (!mapData.GetPlayerDataByUnitId(unitData.Id, out var player)
||!mapData.GetGridDataByUnitId(unitData.Id,out var grid)) return;
var count = Main.PlayerLogic.UpdateSight(mapData,player,mapData.GridMap.GetAroundGridIdList(unitData.GetSightRange(mapData),grid));
unitData.HeroTask(mapData)?.OnExploredGrids(mapData, count);
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;
//unitData.RenderMark = true;
}
public bool HeroUpgrade(MapData map,UnitData unit)
{
var type = unit.GetUnitFullType;
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(unit.GetUnitFullType) ?? 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)
{
//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;
foreach (var skilltype in originInfo.Skills)
{
//某些skill被Remove了要做特殊处理情况
if (unitData.GetSkill(skilltype, out var skill))
skill.OnTransformRemoved(unitData,mapData);
unitData.RemoveSkill(skilltype);
}
foreach (var skilltype in targetInfo.Skills)
{
//某些skill被Add了要做特殊处理
if (unitData.GetSkill(skilltype, out var ski))
ski.OnTransformRemoved(unitData,mapData);
unitData.AddSkill(skilltype);
}
unitData.UnitType = targetType;
unitData.GiantType = giantType;
unitData.UnitLevel = unitLevel;
//处理AddSkill的生命周期
foreach(var skill in unitData.Skills)
skill.OnSkillAdd(unitData,mapData);
if(nopop)
unitData.AddSkill(SkillType.NOPOPULATION);
}
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 (!CheckUnitCanGoOnGrid(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.CompleteExecute(param);
}
else
{
var data = new FragmentUnitData(FragmentType.Die, unitData.Renderer(mapData),unitGrid,city);
PresentationManager.EnqueueTask(new FragmentSequencerTask(new FragmentDie(data)));
}
}
public bool CheckUnitCanGoOnGrid(MapData mapData, UnitData unitData, GridData gridData)
{
//TODO 这里一定要改了用把landType通过unitSkill改过来 ,或者至少完成一套完备的方案
if (!mapData.CheckLandTypeForGrid(unitData.GetUnitFullType, 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 (gridData.Terrain != TerrainType.Land)
{
//如果本身就是水生单位
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.Bridge)
{
return true;
}
//如果有港口但不能下港口
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;
}
}
}