/* * @Author: 白哉 * @Description: 行为逻辑类 * @Date: 2025年04月10日 星期四 11:04:44 * @Modify: */ using System; using System.Collections.Generic; using System.Text; using Logic.AI; using Logic.Audio; using Logic.CrashSight; using Logic.Skill; using MemoryPack; using RuntimeData; using TH1_Anim; using TH1_Anim.Fragments; using TH1_Core.Events; using TH1_Core.Managers; using TH1_Logic.Action; using TH1_Logic.Collect; using TH1_Logic.Core; using TH1_Logic.MatchConfig; using TH1_Presentation.Sequencer.Task; using TH1_Renderer; using TH1_Logic.Net; using TH1_Logic.Steam; using TH1_Logic.Tools; using UnityEngine; using TH1Renderer; //记录ActionCheckCan失败时的原因 public enum ShowType { None, Locked, Cost, Done, Send, Cold, } namespace Logic.Action { // 行为类型枚举 public enum CommonActionType { Gain, Build, StartWonder, BuildWonder, TrainUnit, GridMisc, UnitAction, CityLevelUpAction, UnitSkill, LearnTech, UnitMove, UnitAttack, PlayerAction, TurnStart, TurnEnd, UnitPassiveMove, AIParamControl, UnitAttackAlly, UnitAttackGround, PlayerSurrender, BuyCultureCard, CityAction, HakureiEinherjarCityDevelopment, } public enum ActionShowState { None, Available, Unavailable, Expensive, Finished } public enum AIParamControlType { AllClear, APClear, MPClear, CPClear, AIMoney, Max, } // 通用行为参数类 [MemoryPackable] public partial class CommonActionParams { [MemoryPackIgnore] public MapData MapData; [MemoryPackIgnore] public PlayerData PlayerData; [MemoryPackIgnore] public UnitData UnitData; [MemoryPackIgnore] public CityData CityData; [MemoryPackIgnore] public GridData GridData; [MemoryPackIgnore] public UnitData TargetUnitData; [MemoryPackIgnore] public GridData TargetGridData; [MemoryPackIgnore] public PlayerData TargetPlayerData; public MainObjectType MainObjectType; public uint PlayerId; public uint UnitId; public uint CityId; public uint GridId; public uint TargetUnitId; public uint TargetGridId; public uint TargetPlayerId; [MemoryPackConstructor] public CommonActionParams() { } //必须走这个函数新建,否则需要调用下方的OnParamChanged刷新属性状态 public CommonActionParams(MapData mapData=null, PlayerData playerData=null, UnitData unitData=null, CityData cityData=null, GridData gridData=null, UnitData targetUnit=null, GridData targetGrid=null, PlayerData targetPlayer = null,MainObjectType mainObjectType=MainObjectType.None) { MapData = mapData; PlayerData = playerData; UnitData = unitData; CityData = cityData; GridData = gridData; TargetUnitData = targetUnit; TargetGridData = targetGrid; TargetPlayerData = targetPlayer; MainObjectType = mainObjectType; PlayerId = 0; UnitId = 0; CityId = 0; GridId = 0; TargetUnitId = 0; TargetGridId = 0; TargetPlayerId = 0; if (PlayerData != null) PlayerId = PlayerData.Id; if (UnitData != null) UnitId = UnitData.Id; if (CityData != null) CityId = CityData.Id; if (GridData != null) GridId = GridData.Id; if (TargetUnitData != null) TargetUnitId = TargetUnitData.Id; if (TargetGridData != null) TargetGridId = TargetGridData.Id; if (TargetPlayerData != null) TargetPlayerId = TargetPlayerData.Id; } public void RefreshParams() { if (PlayerId != 0 && MapData.PlayerMap.GetPlayerDataByPlayerID(PlayerId, out var player)) { PlayerData = player; } if (UnitId != 0 && MapData.UnitMap.GetUnitDataByUnitId(UnitId, out var unit)) { UnitData = unit; } if (CityId != 0 && MapData.CityMap.GetCityById(CityId, out var city)) { CityData = city; } if (GridId != 0 && MapData.GridMap.GetGridDataByGid(GridId, out var grid)) { GridData = grid; } if (TargetUnitId != 0 && MapData.UnitMap.GetUnitDataByUnitId(TargetUnitId, out var target)) { TargetUnitData = target; } if (TargetGridId != 0 && MapData.GridMap.GetGridDataByGid(TargetGridId, out var targetGrid)) { TargetGridData = targetGrid; } if (TargetPlayerId != 0 && MapData.PlayerMap.GetPlayerDataByPlayerID(TargetPlayerId, out var targetPlayer)) { TargetPlayerData = targetPlayer; } } public void OnParamChanged() { if (PlayerData != null) PlayerId = PlayerData.Id; else PlayerId = 0; if (UnitData != null) UnitId = UnitData.Id; else UnitId = 0; if (CityData != null) CityId = CityData.Id; else CityId = 0; if (GridData != null) GridId = GridData.Id; else GridId = 0; if (TargetUnitData != null) TargetUnitId = TargetUnitData.Id; else TargetUnitId = 0; if (TargetGridData != null) TargetGridId = TargetGridData.Id; else TargetGridId = 0; if (TargetPlayerData != null) TargetPlayerId = TargetPlayerData.Id; else TargetPlayerId = 0; } public CommonActionParams GetCopyParam() { var param = new CommonActionParams(); param.MapData = MapData; param.PlayerData = PlayerData; param.UnitData = UnitData; param.CityData = CityData; param.GridData = GridData; param.TargetUnitData = TargetUnitData; param.TargetGridData = TargetGridData; param.TargetPlayerData = TargetPlayerData; param.MainObjectType = MainObjectType; param.PlayerId = PlayerId; param.UnitId = UnitId; param.CityId = CityId; param.GridId = GridId; param.TargetUnitId = TargetUnitId; param.TargetGridId = TargetGridId; param.TargetPlayerId = TargetPlayerId; return param; } public string GetStringLog() { var log = $""; log += $"MainObjectType : {MainObjectType}\n"; log += $"PlayerId : {PlayerId}\n"; log += $"UnitId : {UnitId}\n"; log += $"CityId : {CityId}\n"; log += $"GridId : {GridId}\n"; log += $"TargetUnitId : {TargetUnitId}\n"; log += $"TargetGridId : {TargetGridId}\n"; log += $"TargetPlayerId : {TargetPlayerId}\n"; return log; } public string GetNotSameLog(CommonActionParams other) { var log = $""; if (MainObjectType != other.MainObjectType) log += $"MainObjectType : {MainObjectType}\n"; if (PlayerId != other.PlayerId) log += $"PlayerId : {PlayerId}\n"; if (UnitId != other.UnitId) log += $"UnitId : {UnitId}\n"; if (CityId != other.CityId) log += $"CityId : {CityId}\n"; if (GridId != other.GridId) log += $"GridId : {GridId}\n"; if (TargetUnitId != other.TargetUnitId) log += $"TargetUnitId : {TargetUnitId}\n"; if (TargetGridId != other.TargetGridId) log += $"TargetGridId : {TargetGridId}\n"; if (TargetPlayerId != other.TargetPlayerId) log += $"TargetPlayerId : {TargetPlayerId}\n"; return log; } } // 通用行为ID类 [Serializable] [MemoryPackable] public partial class CommonActionId { public CommonActionType ActionType; public WonderTypeEnum WonderType; public ResourceType ResourceType; public TerrainFeature FeatureType; public TerrainType TerrainType; public UnitType UnitType; public GiantType GiantType; public uint UnitLevel; public Vegetation Vegetation; public UnitActionType UnitActionType; public CityLevelUpActionType CityLevelUpActionType; public CityActionType CityActionType; public GridMiscActionType GridMiscActionType; public SkillType SkillType; public TechType TechType; public PlayerActionType PlayerActionType; public AIParamControlType AIParamType; public CultureCardType CultureCardType; [MemoryPackConstructor] public CommonActionId() { } //自动生成唯一Id Hash public uint Id => ComputeId(); private uint ComputeId() { unchecked // 溢出安全 { int hash = 17; hash = hash * 31 + ActionType.GetHashCode(); hash = hash * 31 + WonderType.GetHashCode(); hash = hash * 31 + ResourceType.GetHashCode(); hash = hash * 31 + FeatureType.GetHashCode(); hash = hash * 31 + TerrainType.GetHashCode(); hash = hash * 31 + UnitType.GetHashCode(); hash = hash * 31 + GiantType.GetHashCode(); hash = hash * 31 + UnitLevel.GetHashCode(); hash = hash * 31 + Vegetation.GetHashCode(); hash = hash * 31 + UnitActionType.GetHashCode(); hash = hash * 31 + CityLevelUpActionType.GetHashCode(); hash = hash * 31 + CityActionType.GetHashCode(); hash = hash * 31 + GridMiscActionType.GetHashCode(); hash = hash * 31 + SkillType.GetHashCode(); hash = hash * 31 + TechType.GetHashCode(); hash = hash * 31 + PlayerActionType.GetHashCode(); hash = hash * 31 + AIParamType.GetHashCode(); hash = hash * 31 + CultureCardType.GetHashCode(); return (uint)hash; } } // 重载 == 运算符 当属性被修改时需要修改运算符重载方法 public static bool operator ==(CommonActionId a, CommonActionId b) { if (a is null) return b is null; if (b is null) return false; if (a.ActionType != b.ActionType) return false; if (a.WonderType != b.WonderType) return false; if (a.ResourceType != b.ResourceType) return false; if (a.TerrainType != b.TerrainType) return false; if (a.FeatureType != b.FeatureType) return false; if (a.UnitType != b.UnitType) return false; if (a.GiantType != b.GiantType) return false; if (a.UnitLevel != b.UnitLevel) return false; if (a.Vegetation != b.Vegetation) return false; if (a.UnitActionType != b.UnitActionType) return false; if (a.CityLevelUpActionType != b.CityLevelUpActionType) return false; if (a.CityActionType != b.CityActionType) return false; if (a.GridMiscActionType != b.GridMiscActionType) return false; if (a.SkillType != b.SkillType) return false; if (a.TechType != b.TechType) return false; if (a.PlayerActionType != b.PlayerActionType) return false; if (a.AIParamType != b.AIParamType) return false; if (a.CultureCardType != b.CultureCardType) return false; return true; } // 必须同时重载 != 运算符 public static bool operator !=(CommonActionId a, CommonActionId b) { return !(a == b); } public override int GetHashCode() { // 直接调用你的哈希计算逻辑,并转换为int return (int)ComputeId(); } public override bool Equals(object obj) { // 调用类型安全的Equals方法 return Equals(obj as CommonActionId); } public bool Equals(CommonActionId other) { // 如果另一个对象是null,它们不相等 if (other is null) { return false; } // 如果它们是同一个内存中的对象,它们肯定相等 if (ReferenceEquals(this, other)) { return true; } // 调用你已经写好的比较逻辑! // 我们可以直接使用你重载的 `==` 运算符,这样可以重用代码。 return this == other; } public string GetStringLog() { var log = $""; log += $"Action : {ActionType}\n"; log += $"Wonder : {WonderType}\n"; log += $"Resource : {ResourceType}\n"; log += $"Feature : {FeatureType}\n"; log += $"Terrain : {TerrainType}\n"; log += $"Unit : {UnitType}\n"; log += $"Giant : {GiantType}\n"; log += $"Vegetation : {Vegetation}\n"; log += $"UnitAction : {UnitActionType}\n"; log += $"CityLevelUpAction : {CityLevelUpActionType}\n"; log += $"CityAction : {CityActionType}\n"; log += $"GridMiscAction : {GridMiscActionType}\n"; log += $"Skill : {SkillType}\n"; log += $"Tech : {TechType}\n"; log += $"PlayerAction : {PlayerActionType}\n"; log += $"AIParam : {AIParamType}\n"; log += $"Tech : {TechType}\n"; log += $"CultureCardType : {CultureCardType}\n"; return log; } } // 行为逻辑工厂类 public static class ActionLogicFactory { private static Dictionary ActionLogicDict; private static Dictionary _actionLogicIdDict; public static Dictionary GetActionLogicDict() { Refresh(); return ActionLogicDict; } public static Dictionary GetActionLogicIDDict() { Refresh(); return _actionLogicIdDict; } public static void RefreshPlayerAction() { CommonActionId commonActionId; //填加外交行为 var diplomacyActionTypes = new[] { PlayerActionType.OfferAlly, PlayerActionType.AcceptAlly, PlayerActionType.RefuseAlly, PlayerActionType.BreakAlly, PlayerActionType.Embassy, PlayerActionType.BreakEmbassy, PlayerActionType.DanegeldDemand, PlayerActionType.DanegeldPay, PlayerActionType.DanegeldReject, }; foreach (var playerActionType in diplomacyActionTypes) { commonActionId = new CommonActionId { ActionType = CommonActionType.PlayerAction, PlayerActionType = playerActionType }; ActionLogicDict[commonActionId] = new PlayerActionDiplomacy(commonActionId); } foreach (var playerActionType in new[] { PlayerActionType.TreasureGainCoin, PlayerActionType.TreasureGainCulture, PlayerActionType.TreasureGainCityExp, }) { commonActionId = new CommonActionId { ActionType = CommonActionType.PlayerAction, PlayerActionType = playerActionType, }; ActionLogicDict[commonActionId] = new PlayerActionTreasureReward(commonActionId); } foreach (var playerActionType in new[] { PlayerActionType.TreasureGainUnit, PlayerActionType.TreasureGainTech, }) { commonActionId = new CommonActionId { ActionType = CommonActionType.PlayerAction, PlayerActionType = playerActionType, }; ActionLogicDict[commonActionId] = new PlayerActionTreasureReward(commonActionId); } //填加英雄相关行为 foreach (GiantType giantType in System.Enum.GetValues(typeof(GiantType))) { if (giantType == GiantType.None) continue; //选择英雄 commonActionId = new CommonActionId { ActionType = CommonActionType.PlayerAction, PlayerActionType = PlayerActionType.SelectHero, GiantType = giantType }; ActionLogicDict[commonActionId] = new PlayerActionSelectHero(commonActionId); //强制完成英雄的任务 commonActionId = new CommonActionId { ActionType = CommonActionType.PlayerAction, PlayerActionType = PlayerActionType.FinishHeroTask, GiantType = giantType }; ActionLogicDict[commonActionId] = new PlayerActionFinishHeroTask(commonActionId); } } public static void Refresh() { if (ActionLogicDict != null) return; ActionLogicDict = new Dictionary(); CommonActionId commonActionId; // 移动和攻击 Action 入库 commonActionId = new CommonActionId { ActionType = CommonActionType.UnitMove }; ActionLogicDict[commonActionId] = new UnitMoveAction(commonActionId); commonActionId = new CommonActionId { ActionType = CommonActionType.UnitAttack }; ActionLogicDict[commonActionId] = new UnitAttackAction(commonActionId); commonActionId = new CommonActionId { ActionType = CommonActionType.UnitAttackGround }; ActionLogicDict[commonActionId] = new UnitAttackGroundAction(commonActionId); commonActionId = new CommonActionId { ActionType = CommonActionType.UnitPassiveMove }; ActionLogicDict[commonActionId] = new UnitPassiveMoveAction(commonActionId); commonActionId = new CommonActionId { ActionType = CommonActionType.UnitAttackAlly }; ActionLogicDict[commonActionId] = new UnitAttackAllyAction(commonActionId); // 回合开始和结束 Action 入库 commonActionId = new CommonActionId { ActionType = CommonActionType.TurnStart }; ActionLogicDict[commonActionId] = new PlayerTurnStartAction(commonActionId); commonActionId = new CommonActionId { ActionType = CommonActionType.TurnEnd }; ActionLogicDict[commonActionId] = new PlayerTurnEndAction(commonActionId); commonActionId = new CommonActionId { ActionType = CommonActionType.PlayerSurrender }; ActionLogicDict[commonActionId] = new SurrenderAction(commonActionId); for (int i = (int)CultureCardType.SecondHero; i < (int)CultureCardType.Max; i++) { commonActionId = new CommonActionId { ActionType = CommonActionType.BuyCultureCard, CultureCardType = (CultureCardType)i, }; ActionLogicDict[commonActionId] = new BuyCultureCardAction(commonActionId); } for (int i = (int)AIParamControlType.AllClear; i < (int)AIParamControlType.Max; i++) { commonActionId = new CommonActionId { ActionType = CommonActionType.AIParamControl, AIParamType = (AIParamControlType)i, }; ActionLogicDict[commonActionId] = new AIParamControlAction(commonActionId); } foreach (ResourceType resourceType in System.Enum.GetValues(typeof(ResourceType))) { if (resourceType == ResourceType.None) continue; // 先登记所有Gain 收获一次性资源的行为逻辑 if (resourceType == ResourceType.Animal || resourceType == ResourceType.Fish || resourceType == ResourceType.Fruit) { commonActionId = new CommonActionId { ActionType = CommonActionType.Gain, ResourceType = resourceType, }; ActionLogicDict[commonActionId] = new GainResourceAction(commonActionId); continue; } // 登记Build 建设建筑的行为逻辑 commonActionId = new CommonActionId { ActionType = CommonActionType.Build, ResourceType = resourceType, }; switch (resourceType) { case ResourceType.ForestTemple: case ResourceType.WaterTemple: case ResourceType.Temple: case ResourceType.MountainTemple: case ResourceType.KingTemple: ActionLogicDict[commonActionId] = new BuildActionBuildTemple(commonActionId); continue; case ResourceType.Preserve: ActionLogicDict[commonActionId] = new BuildActionBuildPreserve(commonActionId); continue; case ResourceType.NavalBase: ActionLogicDict[commonActionId] = new BuildActionBuildNavalBase(commonActionId); continue; case ResourceType.KaguyaFrenchYard: ActionLogicDict[commonActionId] = new BuildActionBuildKaguyaFrenchYard(commonActionId); continue; case ResourceType.EgyptianIrrigation: ActionLogicDict[commonActionId] = new BuildActionBuildEgyptianIrrigation(commonActionId); continue; case ResourceType.RemiliaMilitary: ActionLogicDict[commonActionId] = new BuildActionBuildRemiliaMilitary(commonActionId); continue; case ResourceType.MetalStation: ActionLogicDict[commonActionId] = new BuildActionBuildMetalStation(commonActionId); continue; case ResourceType.MoriyaMilitary: ActionLogicDict[commonActionId] = new BuildActionBuildMoriyaMilitary(commonActionId); continue; default: ActionLogicDict[commonActionId] = new BuildAction(commonActionId); continue; } } //登记build road这种特殊情况 commonActionId = new CommonActionId { ActionType = CommonActionType.Build, FeatureType = TerrainFeature.Road }; ActionLogicDict[commonActionId] = new BuildAction(commonActionId); //GridMisc grid的杂类行为逻辑 foreach (GridMiscActionType gridMiscActionType in System.Enum.GetValues(typeof(GridMiscActionType))) { if (gridMiscActionType == GridMiscActionType.None) continue; commonActionId = new CommonActionId { ActionType = CommonActionType.GridMisc, GridMiscActionType = gridMiscActionType }; switch (gridMiscActionType) { case GridMiscActionType.GrowForestOutside: ActionLogicDict[commonActionId] = new GridMiscActionGrowTreeOutside(commonActionId); continue; case GridMiscActionType.CreateMountain: ActionLogicDict[commonActionId] = new GridMiscActionCreateMountain(commonActionId); continue; case GridMiscActionType.SellMetal: ActionLogicDict[commonActionId] = new GridMiscActionSellMetal(commonActionId); continue; default: ActionLogicDict[commonActionId] = new GridMiscAction(commonActionId); continue; }; } //登记BuildWonder建造奇观的行为逻辑 foreach (WonderTypeEnum wonderType in System.Enum.GetValues(typeof(WonderTypeEnum))) { if(wonderType == WonderTypeEnum.None) continue; //登记建造奇观的action commonActionId = new CommonActionId { ActionType = CommonActionType.BuildWonder, WonderType = wonderType, }; ActionLogicDict[commonActionId] = new BuildWonderAction(commonActionId); //登记建造开启奇观任务的action commonActionId = new CommonActionId { ActionType = CommonActionType.StartWonder, WonderType = wonderType, }; ActionLogicDict[commonActionId] = new StartWonderAction(commonActionId); } // TrainUnit 建造unit的行为逻辑 foreach (UnitType unitType in Enum.GetValues(typeof(UnitType))) { if(unitType == UnitType.None) continue; if (unitType == UnitType.Giant) { foreach (GiantType giantType in Enum.GetValues(typeof(GiantType))) { if(giantType == GiantType.None) continue; commonActionId = new CommonActionId { ActionType = CommonActionType.TrainUnit, UnitType = unitType, GiantType = giantType }; //Debug.Log(ActionId.GiantType); ActionLogicDict[commonActionId] = new TrainUnitActionTrainHero(commonActionId); } continue; } commonActionId = new CommonActionId { ActionType = CommonActionType.TrainUnit, UnitType = unitType, }; if (unitType == UnitType.KaguyaFrenchAnimalWarrior) ActionLogicDict[commonActionId] = new TrainUnitActionTrainKaguyaFrenchAnimalWarrior(commonActionId); else ActionLogicDict[commonActionId] = new TrainUnitAction(commonActionId); // MoriyaHebi Level3 额外注册 if (unitType == UnitType.MoriyaHebi) { commonActionId = new CommonActionId { ActionType = CommonActionType.TrainUnit, UnitType = unitType, UnitLevel = 3, }; ActionLogicDict[commonActionId] = new TrainUnitAction(commonActionId); } } //cityLevelUp的各个行为逻辑 foreach (CityLevelUpActionType cityLevelUpActionType in System.Enum.GetValues(typeof(CityLevelUpActionType))) { if(cityLevelUpActionType == CityLevelUpActionType.None) continue; commonActionId = new CommonActionId { ActionType = CommonActionType.CityLevelUpAction, CityLevelUpActionType = cityLevelUpActionType }; ActionLogicDict[commonActionId] = new CityLevelUpActionAction(commonActionId); } foreach (CityActionType cityActionType in System.Enum.GetValues(typeof(CityActionType))) { if (cityActionType == CityActionType.None) continue; commonActionId = new CommonActionId { ActionType = CommonActionType.CityAction, CityActionType = cityActionType }; ActionLogicDict[commonActionId] = new CityAction(commonActionId); } commonActionId = new CommonActionId { ActionType = CommonActionType.HakureiEinherjarCityDevelopment }; ActionLogicDict[commonActionId] = new HakureiEinherjarCityDevelopmentAction(commonActionId); //unitAction自身行为的各个行为逻辑 foreach (UnitActionType unitActionType in System.Enum.GetValues(typeof(UnitActionType))) { commonActionId = new CommonActionId { ActionType = CommonActionType.UnitAction, UnitActionType = unitActionType }; switch (unitActionType) { case UnitActionType.None: continue; case UnitActionType.HeroUpgrade: ActionLogicDict[commonActionId] = new UnitActionHeroUpgrade(commonActionId); continue; case UnitActionType.CultureUnitUpgrade: ActionLogicDict[commonActionId] = new UnitActionCultureUnitUpgrade(commonActionId); continue; case UnitActionType.ReservedReimuProtectionAttackAlly: continue; case UnitActionType.ReimuPayClearExtermination: ActionLogicDict[commonActionId] = new UnitActionReimuPayClearExtermination(commonActionId); continue; case UnitActionType.Raid: ActionLogicDict[commonActionId] = new UnitActionRaid(commonActionId); continue; case UnitActionType.WarCry: ActionLogicDict[commonActionId] = new UnitActionWarCry(commonActionId); continue; case UnitActionType.KasenToggleOniForm: ActionLogicDict[commonActionId] = new UnitActionKasenToggleOniForm(commonActionId); continue; case UnitActionType.SuikaShakeOffMinis: ActionLogicDict[commonActionId] = new UnitActionSuikaShakeOffMinis(commonActionId); continue; case UnitActionType.SuikaCreateMiniByHp: ActionLogicDict[commonActionId] = new UnitActionSuikaCreateMiniByHp(commonActionId); continue; case UnitActionType.SuikaThrowUnit: ActionLogicDict[commonActionId] = new UnitActionSuikaThrowUnit(commonActionId); continue; case UnitActionType.AunnPetrify: ActionLogicDict[commonActionId] = new UnitActionAunnPetrify(commonActionId); continue; case UnitActionType.HakureiAbsorbRune: ActionLogicDict[commonActionId] = new UnitActionHakureiAbsorbRune(commonActionId); continue; case UnitActionType.ClearHakureiRune: ActionLogicDict[commonActionId] = new UnitActionClearHakureiRune(commonActionId); continue; case UnitActionType.KasenRecallBeastGuide: ActionLogicDict[commonActionId] = new UnitActionKasenRecallBeastGuide(commonActionId); continue; case UnitActionType.KasenClearBeastGuide: ActionLogicDict[commonActionId] = new UnitActionKasenClearBeastGuide(commonActionId); continue; case UnitActionType.Demolish: ActionLogicDict[commonActionId] = new UnitActionAction(commonActionId); continue; case UnitActionType.Disperse: ActionLogicDict[commonActionId] = new UnitActionAction(commonActionId); continue; case UnitActionType.ROYALFLAMESPRO: ActionLogicDict[commonActionId] = new UnitActionROYALFLAMESPRO(commonActionId); continue; case UnitActionType.HEAL: ActionLogicDict[commonActionId] = new UnitActionHEAL(commonActionId); continue; case UnitActionType.TEWIFRENCHBUFF: ActionLogicDict[commonActionId] = new UnitActionTEWIFRENCHBUFF(commonActionId); continue; case UnitActionType.MOKOUFRENCHBOOM: ActionLogicDict [commonActionId] = new UnitActionMOKOUFRENCHBOOM(commonActionId); continue; case UnitActionType.KAGUYAFRENCHAROUND: ActionLogicDict [commonActionId] = new UnitActionKAGUYAFRENCHAROUND(commonActionId); continue; case UnitActionType.ForceDisband: ActionLogicDict [commonActionId] = new UnitActionForceDisband(commonActionId); continue; case UnitActionType.RemoveRedMist: ActionLogicDict [commonActionId] = new UnitActionRemoveRedMist(commonActionId); continue; case UnitActionType.REMILIABUFF: ActionLogicDict [commonActionId] = new UnitActionREMILIABUFF(commonActionId); continue; case UnitActionType.AbsorbRedMist: ActionLogicDict [commonActionId] = new UnitActionAbsorbRedMist(commonActionId); continue; case UnitActionType.REMILIAABSORB: ActionLogicDict [commonActionId] = new UnitActionRemiliaAbsorb(commonActionId); continue; case UnitActionType.KANAKOSIT: ActionLogicDict [commonActionId] = new UnitActionKanakoSit(commonActionId); continue; case UnitActionType.KANAKOUNSIT: ActionLogicDict [commonActionId] = new UnitActionKanakoUnSit(commonActionId); continue; case UnitActionType.AYAMOVEAGAIN: ActionLogicDict [commonActionId] = new UnitActionAyaMoveAgain(commonActionId); continue; case UnitActionType.KomeijiRinFire: ActionLogicDict [commonActionId] = new UnitActionKomeijiRinFire(commonActionId); continue; case UnitActionType.KomeijiRinCityExp: ActionLogicDict [commonActionId] = new UnitActionKomeijiRinCityExp(commonActionId); continue; case UnitActionType.KomeijiBonePileBoom: ActionLogicDict [commonActionId] = new UnitActionKomeijiBonePileBoom(commonActionId); continue; case UnitActionType.ReisenFrenchBoom: ActionLogicDict [commonActionId] = new UnitActionReisenFrenchBoom(commonActionId); continue; default: ActionLogicDict[commonActionId] = new UnitActionAction(commonActionId); continue; } } foreach (UnitType unitType in Enum.GetValues(typeof(UnitType))) if(!(unitType is UnitType.None or UnitType.Giant) && Table.Instance.UnitTypeDataAssets.GetUnitTypeInfo(unitType,GiantType.None,0,out var info) && info.LandType == LandType.WaterAndAshore) { if (unitType == UnitType.Boat) continue; commonActionId = new CommonActionId { ActionType = CommonActionType.UnitAction, UnitType = unitType }; ActionLogicDict[commonActionId] = new UnitActionAction(commonActionId); } //登记所有学习科技的action foreach (TechType techType in System.Enum.GetValues(typeof(TechType))) { commonActionId = new CommonActionId { ActionType = CommonActionType.LearnTech, TechType = techType }; ActionLogicDict[commonActionId] = new LearnTechAction(commonActionId); } //登记6种unitskill类的科技,该科技会让该玩家当前的和未来的所有unit都拥有某个skill foreach (SkillType skillType in System.Enum.GetValues(typeof(SkillType))) { if (skillType == SkillType.MOUNTAINMOVE || skillType == SkillType.MOUNTAINDEFENSE || skillType == SkillType.WATERMOVE || skillType == SkillType.WATERDEFENSE || skillType == SkillType.OCEANMOVE || skillType == SkillType.OCEANDEFENSE || skillType == SkillType.FORESTDEFENSE) { commonActionId = new CommonActionId { ActionType = CommonActionType.UnitSkill, SkillType = skillType }; ActionLogicDict[commonActionId] = new UnitSkillAction(commonActionId); } } //登记playerAction RefreshPlayerAction(); if (_actionLogicIdDict == null) { _actionLogicIdDict = new Dictionary(); foreach (var kv in ActionLogicDict) { _actionLogicIdDict[kv.Key.Id] = kv.Value; } } } public static ActionLogicBase GetActionLogic(CommonActionId actionId) { Refresh(); foreach (var kv in ActionLogicDict) { if (kv.Key != actionId) continue; return kv.Value; } return null; } // 返回一个玩家有什么 Action public static bool PlayerHasAction(CommonActionParams param, out List actionList) { param.MainObjectType = MainObjectType.Player; actionList = new List(); var actionDict = GetActionLogicDict(); foreach (var action in actionDict.Values) { if(action.CheckCan(param)) actionList.Add(action); } return actionList.Count > 0; } //返回一个 grid 对于 player data 而言有什么 action public static bool GridHasAction(CommonActionParams param, out List actionList) { param.MainObjectType = MainObjectType.Grid; actionList = new List(); var actionDict = GetActionLogicDict(); foreach (var action in actionDict.Values) { if (!action.CheckCan(param)) continue; actionList.Add(action); } return actionList.Count > 0; } //返回一个 grid 对于 player data 而言有什么 action public static bool GridHasAction(CommonActionParams param, List actionList) { param.MainObjectType = MainObjectType.Grid; var actionDict = GetActionLogicDict(); bool hasAction = false; foreach (var action in actionDict.Values) { if (!action.CheckCan(param)) continue; actionList.Add(action); hasAction = true; } return hasAction; } public static bool CityHasAction(CommonActionParams param, out List actionList) { param.MainObjectType = MainObjectType.City; actionList = new List(); var actionDict = GetActionLogicDict(); foreach (var action in actionDict.Values) { if(action.CheckCan(param)) actionList.Add(action); } return actionList.Count > 0; } //返回一个 unit 对于 player data 而言有什么 action public static bool UnitHasAction(CommonActionParams param, out List actionList) { param.MainObjectType = MainObjectType.Unit; actionList = new List(); bool hasCP = param.UnitData.GetActionPoint(ActionPointType.Capture) > 0; var actionDict = GetActionLogicDict(); foreach (var action in actionDict.Values) { if (!action.CheckCan(param)) continue; // 无行动点时,仅保留不需要行动点的action(自身CheckCan已通过即可) if (!hasCP && action.ActionId.ActionType == CommonActionType.UnitAction) { var unitActionType = action.ActionId.UnitActionType; if (unitActionType != UnitActionType.KomeijiBonePileBoom && unitActionType != UnitActionType.REMILIAABSORB && unitActionType != UnitActionType.ReimuPayClearExtermination && unitActionType != UnitActionType.SuikaCreateMiniByHp && unitActionType != UnitActionType.AunnPetrify && unitActionType != UnitActionType.HakureiAbsorbRune && unitActionType != UnitActionType.KasenRecallBeastGuide && unitActionType != UnitActionType.Demolish && unitActionType != UnitActionType.Disperse && unitActionType != UnitActionType.Raid && unitActionType != UnitActionType.WarCry && unitActionType != UnitActionType.SkipUnitTurn) continue; } actionList.Add(action); } return actionList.Count > 0; } // 返回一个 unit 的移动和攻击行为 public static bool UnitHasMoveAndAttackAction(MapData map, UnitData unit, out List actionList) { if (!unit.IsAlive()) { actionList = null; return false; } var attackID = new CommonActionId { ActionType = CommonActionType.UnitAttack }; var moveId = new CommonActionId { ActionType = CommonActionType.UnitMove }; var attackAction = new UnitAttackAction(attackID); var moveAction = new UnitMoveAction(moveId); actionList = new List(); Main.UnitLogic.CalcUnitMoveInfo(map, unit.Id); if (!unit.Player(map, out var player)) return false; foreach (var grid in map.GridMap.GridList) { var result = Main.UnitLogic.CheckUnitCanMoveOrAttack(map, unit, grid); if (result == MoveAttackType.None) continue; if (result == MoveAttackType.Attack) { if (unit.GetActionPoint(ActionPointType.Attack) <= 0 || !grid.VisibleUnit(map,player, out var targetUnit)) continue; var param = new CommonActionParams(map, unitData:unit, targetUnit:targetUnit); param.RefreshParams(); actionList.Add(new AIActionBase(param, attackAction)); if (map.GetGridDataByUnitId(unit.Id, out var unitGrid)) { //Debug.Log($"生成Action 小兵攻击 {unit.Id}, 攻击范围{unit.GetAttackRange()}" +$"位置{unitGrid.Pos.X}, {unitGrid.Pos.Y}, " + $"目标 {targetUnit.Id}, " + $"目标位置{grid.Pos.X}, {grid.Pos.Y}"); } continue; } if (unit.GetActionPoint(ActionPointType.Move) > 0) { var param = new CommonActionParams(map, unitData:unit, gridData:grid); param.RefreshParams(); actionList.Add(new AIActionBase(param, moveAction)); } } return actionList.Count > 0; } //返回一个主体能够显示的action(注意,并非能“做”的action,有些行为没钱或者没科技,他也可以显示) public static bool MainObjectCanShowAction(CommonActionParams param, out List actionList,out List actionCantTypeList) { //注意,要将所有cityLevelUp直接屏蔽掉 在这里 actionList = new List(); actionCantTypeList = new List(); var actionDict = GetActionLogicDict(); foreach (var action in actionDict.Values) { if (!action.CheckShow(param,out var actionCantType)) continue; if (action.ActionId.ActionType == CommonActionType.CityLevelUpAction) continue; actionList.Add(action); actionCantTypeList.Add(actionCantType); } return actionList.Count > 0; } public static List GetActionLogicByType(CommonActionType type) { var actionList = new List(); foreach (var action in GetActionLogicDict().Values) { if (action.ActionId.ActionType != type) continue; actionList.Add(action); } return actionList; } public static MainObjectType GetMainObjectType(CommonActionType actionType) { if (actionType == CommonActionType.Gain) return MainObjectType.Grid; if (actionType == CommonActionType.Build) return MainObjectType.Grid; if (actionType == CommonActionType.StartWonder) return MainObjectType.Player; if (actionType == CommonActionType.BuildWonder) return MainObjectType.Grid; if (actionType == CommonActionType.TrainUnit) return MainObjectType.City; if (actionType == CommonActionType.GridMisc) return MainObjectType.Grid; if (actionType == CommonActionType.UnitAction) return MainObjectType.Unit; if (actionType == CommonActionType.CityLevelUpAction) return MainObjectType.City; if (actionType == CommonActionType.CityAction) return MainObjectType.City; if (actionType == CommonActionType.HakureiEinherjarCityDevelopment) return MainObjectType.City; if (actionType == CommonActionType.UnitSkill) return MainObjectType.Unit; if (actionType == CommonActionType.LearnTech) return MainObjectType.Player; if (actionType == CommonActionType.UnitMove) return MainObjectType.Unit; if (actionType == CommonActionType.UnitAttack) return MainObjectType.Unit; if (actionType == CommonActionType.PlayerAction) return MainObjectType.Player; if (actionType == CommonActionType.AIParamControl) return MainObjectType.Player; if (actionType == CommonActionType.UnitAttackAlly) return MainObjectType.Unit; if (actionType == CommonActionType.UnitAttackGround) return MainObjectType.Unit; if (actionType == CommonActionType.BuyCultureCard) return MainObjectType.Player; return MainObjectType.Player; } } public class CityAction : ActionLogicBase { public CityAction(CommonActionId id) : base(id) { } protected override bool Execute(CommonActionParams actionParams) { if (_actionId.CityActionType != CityActionType.BuildCityWall) return false; if (!CheckCan(actionParams)) return false; actionParams.PlayerData.SpendCoin(GetCost(actionParams)); actionParams.CityData.CityWall = true; var cityGrid = actionParams.CityData.Grid(actionParams.MapData); actionParams.CityData.SetCityRenderer(actionParams.MapData); cityGrid?.Renderer(actionParams.MapData)?.InstantUpdateGrid(true); if (actionParams.MapData == Main.MapData && (cityGrid?.InMainSight() ?? false) && cityGrid.MainSelfPlayerVisibleUnit(out var unit)) { unit.Renderer(actionParams.MapData)?.RenderUpdateUnitDefense(); } return true; } public override bool CheckCan(CommonActionParams actionParams) { if (_actionId.CityActionType != CityActionType.BuildCityWall) return false; if (actionParams?.MapData == null) return false; if (actionParams.PlayerData == null) return false; if (actionParams.CityData == null) return false; if (actionParams.MainObjectType != MainObjectType.City) return false; if (actionParams.CityData.CityWall) return false; if (!actionParams.MapData.CheckCityIdBelongPlayerId(actionParams.CityData.Id, actionParams.PlayerData.Id)) return false; if (!actionParams.PlayerData.TechTree.CheckIfHasTechAtom(TechAtom.BuildCityWall)) return false; if (actionParams.PlayerData.PlayerCoin < GetCost(actionParams)) return false; return true; } public override bool CheckShow(CommonActionParams actionParams, out ShowType showType) { showType = ShowType.None; if (_actionId.CityActionType != CityActionType.BuildCityWall) return false; if (actionParams?.MapData == null) return false; if (actionParams.PlayerData == null) return false; if (actionParams.CityData == null) return false; if (actionParams.MainObjectType != MainObjectType.City) return false; if (actionParams.CityData.CityWall) return false; if (!actionParams.MapData.CheckCityIdBelongPlayerId(actionParams.CityData.Id, actionParams.PlayerData.Id)) return false; if (!actionParams.PlayerData.TechTree.CheckIfHasTechAtom(TechAtom.BuildCityWall)) { return false; } if (actionParams.PlayerData.PlayerCoin < GetCost(actionParams)) showType = ShowType.Cost; return true; } } public class HakureiEinherjarCityDevelopmentAction : ActionLogicBase { public HakureiEinherjarCityDevelopmentAction(CommonActionId id) : base(id) { } protected override bool Execute(CommonActionParams actionParams) { if (!CheckCan(actionParams)) return false; if (!actionParams.CityData.Grid(actionParams.MapData, out var cityGrid)) return false; var cost = GetEinherjarCost(actionParams); if (!HakureiEinherjarCounter.TrySpendPlayerPoints(actionParams.MapData, actionParams.PlayerData, cost)) return false; if (!Main.CityLogic.GridGiveCityExp_LogicView(actionParams.MapData, actionParams.PlayerData, cityGrid, actionParams.CityData, 1)) return false; HakureiEinherjarCounter.AddCityOfferingLevel(actionParams.MapData, actionParams.CityData, 1); actionParams.CityData.SetCityRenderer(actionParams.MapData); cityGrid.Renderer(actionParams.MapData)?.InstantUpdateGrid(true); return true; } public int GetEinherjarCost(CommonActionParams actionParams) { return HakureiEinherjarCounter.GetCityDevelopmentCost(actionParams?.CityData); } public override int GetCost(CommonActionParams actionParams) { return 0; } public override bool CheckCan(CommonActionParams actionParams) { if (!CheckShow(actionParams, out _)) return false; return HakureiEinherjarCounter.GetPlayerPoints(actionParams.PlayerData) >= GetEinherjarCost(actionParams); } public override bool CheckShow(CommonActionParams actionParams, out ShowType showType) { showType = ShowType.None; if (actionParams?.MapData == null) return false; if (actionParams.PlayerData == null) return false; if (actionParams.CityData == null) return false; if (actionParams.MainObjectType != MainObjectType.City) return false; if (!IsHakureiEmpirePlayer(actionParams.PlayerData)) return false; if (!actionParams.MapData.CheckCityIdBelongPlayerId(actionParams.CityData.Id, actionParams.PlayerData.Id)) return false; if (!actionParams.CityData.Grid(actionParams.MapData, out var cityGrid)) return false; if (cityGrid.Resource != ResourceType.CityCenter) return false; if (HakureiEinherjarCounter.GetPlayerPoints(actionParams.PlayerData) < GetEinherjarCost(actionParams)) showType = ShowType.Cost; return true; } private static bool IsHakureiEmpirePlayer(PlayerData player) { return player != null && player.CivEnum == CivEnum.Norway && player.ForceEnum == ForceEnum.Reimu; } } // 行为基类 public abstract class ActionLogicBase { protected CommonActionId _actionId; public CommonActionId ActionId => _actionId; protected float _duration; public float Duration { get => _duration; set => _duration = value; } // 所有ActionLogic共享的临时集合(行为是顺序执行的,不会并发) protected static List _sharedAroundBuf; private static HashSet _sharedDataUnitIdSet; private static List _sharedRendererOnlyUnitIds; private static List _sharedDataOnlyUnitIds; private static List _sharedBoatUnitsOnLand; private static StringBuilder _sharedRendererUnitMismatchLogBuilder; private static bool _boatUnitOnLandDiagnosticFailedLogged; private static bool _unitRendererMismatchDiagnosticFailedLogged; private static bool _unitAttackRendererMissingAfterKillDiagnosticFailedLogged; private static bool _unitAttackAllyRendererMissingAfterKillDiagnosticFailedLogged; private const int MaxRendererUnitMismatchLogCount = 12; public ActionLogicBase(CommonActionId id) { _actionId = id; _duration = 0; } public bool CheckEqualActionId(CommonActionId actionId) { return _actionId == actionId; } //湖区特殊费用计算逻辑 public bool GetPreserveCost(MapData map, GridData grid, out int cost) { cost = 20; if (map == null || grid == null) return false; var city = grid.BelongButNotOnGridCity(map); if(city == null)return false; int count = 0; foreach (var gid in city.Territory.TerritoryArea) { if (map.GridMap.GetGridDataByGid(gid, out var targetGrid) && targetGrid.Resource == ResourceType.Preserve) count++; } cost = count * 5; return true; } public virtual int GetCost(CommonActionParams actionParams) { if (!Table.Instance.ActionDataAssets.GetActionInfo(_actionId, out var info)) return 0; //preserve特别的计算cost方式 if (_actionId.ResourceType == ResourceType.Preserve) { if(GetPreserveCost(actionParams.MapData,actionParams.GridData,out var cost))return cost; } return info.Cost; } public virtual float GetAnimTime(CommonActionParams actionParams) { //TODO 这里通用返回值要读表,暂时先直接返回0.1f return 0.05f; } public virtual bool CameraControl(CommonActionParams actionParams) { return false; } public float GetValue() { return 1f; } // 完整的执行调用, 供外部使用 public virtual bool CompleteExecute(CommonActionParams actionParams) { // 此处限制了只有在当前 Player 回合内才能执行对应Player的Action if (actionParams.PlayerId != 0 && ActionId.ActionType != CommonActionType.TurnStart && ActionId.ActionType != CommonActionType.PlayerSurrender && actionParams.MapData.CurPlayer != null && actionParams.MapData.CurPlayer.Id != actionParams.PlayerId) { LogSystem.LogError($"CompleteExecute Player 不一致 {ActionId.GetStringLog()}"); NetworkPlayerTipManager.Instance.Request(NetworkPlayerTipType.ActionSyncMismatch); return false; } if (Main.Instance.IsNetActionExecuting) { return ExecuteWithoutFullActionPeriod(actionParams); } if (actionParams.MapData == Main.MapData) { if (actionParams.MapData.Net.Mode == NetMode.Multi) { if (LobbyManager.Instance.Lobby.IsLobbyOwner()) { if (!GameNetSender.Instance.ActionExecute(_actionId, actionParams)) { LogSystem.LogError($"ActionExecute broadcast failed, abort owner execute: {ActionId.GetStringLog()}"); NetworkPlayerTipManager.Instance.Request(NetworkPlayerTipType.ActionSendFailed); return false; } } else { if (!GameNetSender.Instance.ActionConfirm(_actionId, actionParams)) { LogSystem.LogError($"ActionConfirm send failed, abort local execute: {ActionId.GetStringLog()}"); NetworkPlayerTipManager.Instance.Request(NetworkPlayerTipType.ActionSendFailed); return false; } if (ActionId.ActionType == CommonActionType.TurnEnd) return false; } } Main.Instance.IsNetActionExecuting = true; } // LogSystem.LogWarning($"CompleteExecute : {NetData.GetMapDataHash(Main.MapData)}"); BeforeExecute(actionParams); var ret = Execute(actionParams); AfterExecute(actionParams); if (actionParams.MapData == Main.MapData) Main.Instance.IsNetActionExecuting = false; return ret; } //仅仅复用Action的execute逻辑,但是不被视作一个完整的completeExecute,不会走网络同步和时序等等,通常是嵌套在另一个主体Action之中的一段逻辑 public virtual bool ExecuteWithoutFullActionPeriod(CommonActionParams actionParams) { return Execute(actionParams); } // 网络调用 public virtual bool NetCompleteExecute(CommonActionParams actionParams) { BeforeExecute(actionParams); var wasNetActionExecuting = Main.Instance.IsNetActionExecuting; Main.Instance.IsNetActionExecuting = true; try { var ret = Execute(actionParams); AfterExecute(actionParams); return ret; } finally { Main.Instance.IsNetActionExecuting = wasNetActionExecuting; } } // 实际的执行逻辑 protected abstract bool Execute(CommonActionParams actionParams); // 执行前逻辑 protected virtual void BeforeExecute(CommonActionParams actionParams) { // 必须在 action 执行之前记录 if (actionParams.MapData != Main.MapData) return; ReportBeforeActionDiagnostics(actionParams); var actionData = new ActionNetData(); actionData.Version = Main.MapData.Net.GetActionVersion(); actionData.MapHash = NetData.GetMapDataHash(Main.MapData); actionData.Param = actionParams; actionData.ActionId = _actionId; actionData.Time = Time.time; #if CHECK_ACTIONDEFFERENCE if (actionParams.MapData == Main.MapData && Main.Instance.CheckMapData != null && NetData.GetMapDataHash(Main.MapData) != NetData.GetMapDataHash(Main.Instance.CheckMapData)) { var allStr = ""; var diff = Main.MapData.GetCompareEqual(Main.Instance.CheckMapData); foreach (var str in diff) allStr += str + "\n"; diff = MapData.FindDifferences(Main.MapData, Main.Instance.CheckMapData); foreach (var str in diff) allStr += str + "\n"; if (Main.MapData.Net.Actions.Count == 0) { LogSystem.LogError($" Mapdata 不一致,数据:{allStr}\n " + $" 前Action:无,后Action:{_actionId.GetStringLog()} \n"); } else { LogSystem.LogError($" Mapdata 不一致,数据:{allStr}\n " + $" 前Action:{Main.MapData.Net.Actions[^1].ActionId.GetStringLog()},后Action:{_actionId.GetStringLog()} \n"); } } #endif Main.MapData.Net.Actions.Add(actionData); } private void ReportBeforeActionDiagnostics(CommonActionParams actionParams) { #if UNITY_EDITOR if (Logic.AI.AIDirectorBatchRuntime.SkipPresentationWait) return; #endif try { ReportBoatUnitOnLandBeforeAction(actionParams); } catch (Exception e) { if (!_boatUnitOnLandDiagnosticFailedLogged) { _boatUnitOnLandDiagnosticFailedLogged = true; LogSystem.LogWarning($"BoatUnitOnLandBeforeAction diagnostic failed: {e}"); } } try { ReportUnitRendererMismatchBeforeAction(actionParams); } catch (Exception e) { if (!_unitRendererMismatchDiagnosticFailedLogged) { _unitRendererMismatchDiagnosticFailedLogged = true; LogSystem.LogWarning($"UnitRendererMismatchBeforeAction diagnostic failed: {e}"); } } } private void ReportBoatUnitOnLandBeforeAction(CommonActionParams actionParams) { var mapData = actionParams.MapData; if (mapData?.UnitMap?.UnitList == null) return; if (Table.Instance?.UnitTypeDataAssets == null) return; _sharedBoatUnitsOnLand ??= new List(); _sharedRendererUnitMismatchLogBuilder ??= new StringBuilder(2048); _sharedBoatUnitsOnLand.Clear(); foreach (var unitData in mapData.UnitMap.UnitList) { if (unitData == null) continue; if (!mapData.GetGridDataByUnitId(unitData.Id, out var gridData) || gridData == null) continue; if (gridData.Terrain != TerrainType.Land) continue; var landType = unitData.GetLandType(); if (landType is LandType.WaterAndAshore or LandType.WaterOnly) _sharedBoatUnitsOnLand.Add(unitData); } if (_sharedBoatUnitsOnLand.Count == 0) return; var sb = _sharedRendererUnitMismatchLogBuilder; sb.Clear(); sb.AppendLine("[BoatUnitOnLandBeforeAction] 船形态Unit停留在陆地格"); sb.Append("MapId=").Append(mapData.MapID) .Append(", ActionIndex=").Append(mapData.Net?.Actions?.Count ?? 0) .Append(", Count=").Append(_sharedBoatUnitsOnLand.Count) .AppendLine(); AppendPreviousActionsLog(sb, mapData); AppendBoatUnitsOnLandLog(sb, mapData); LogSystem.LogError(sb.ToString()); } private void ReportUnitRendererMismatchBeforeAction(CommonActionParams actionParams) { var mapData = actionParams.MapData; if (mapData?.UnitMap?.UnitList == null) return; var mapRenderer = MapRenderer.Current; if (mapRenderer?.ROUnitMap == null) return; _sharedDataUnitIdSet ??= new HashSet(); _sharedRendererOnlyUnitIds ??= new List(); _sharedDataOnlyUnitIds ??= new List(); _sharedRendererUnitMismatchLogBuilder ??= new StringBuilder(2048); _sharedDataUnitIdSet.Clear(); _sharedRendererOnlyUnitIds.Clear(); _sharedDataOnlyUnitIds.Clear(); foreach (var unitData in mapData.UnitMap.UnitList) { if (unitData == null) continue; _sharedDataUnitIdSet.Add(unitData.Id); if (!mapRenderer.ROUnitMap.TryGetValue(unitData.Id, out var renderer) || renderer == null || !renderer.IsValid) _sharedDataOnlyUnitIds.Add(unitData.Id); } foreach (var kv in mapRenderer.ROUnitMap) { if (!_sharedDataUnitIdSet.Contains(kv.Key)) _sharedRendererOnlyUnitIds.Add(kv.Key); } if (_sharedRendererOnlyUnitIds.Count == 0 && _sharedDataOnlyUnitIds.Count == 0) return; var sb = _sharedRendererUnitMismatchLogBuilder; sb.Clear(); sb.AppendLine("[UnitRendererMismatchBeforeAction] 数据层Unit与渲染层Unit不一致"); sb.Append("MapId=").Append(mapData.MapID) .Append(", ActionIndex=").Append(mapData.Net?.Actions?.Count ?? 0) .Append(", DataUnits=").Append(_sharedDataUnitIdSet.Count) .Append(", RenderUnits=").Append(mapRenderer.ROUnitMap.Count) .Append(", RenderOnly=").Append(_sharedRendererOnlyUnitIds.Count) .Append(", DataOnly=").Append(_sharedDataOnlyUnitIds.Count) .AppendLine(); AppendPreviousActionsLog(sb, mapData); AppendRendererOnlyUnitsLog(sb, mapData, mapRenderer); AppendDataOnlyUnitsLog(sb, mapData, mapRenderer); LogSystem.LogError(sb.ToString()); mapRenderer.RepairUnitRenderersToData(mapData, _sharedRendererOnlyUnitIds, _sharedDataOnlyUnitIds); } private static void AppendPreviousActionsLog(StringBuilder sb, MapData mapData) { var actions = mapData.Net?.Actions; if (actions == null || actions.Count == 0) { sb.AppendLine("PrevActions: 无"); return; } var count = Math.Min(actions.Count, 2); sb.Append("PrevActions(count=").Append(count).AppendLine("):"); for (var i = 0; i < count; i++) { var actionIndex = actions.Count - 1 - i; sb.Append("PrevAction[").Append(actionIndex).AppendLine("]:"); AppendActionNetDataLog(sb, actions[actionIndex]); } } private static void AppendRecentActionsLog(StringBuilder sb, MapData mapData, int maxCount) { var actions = mapData.Net?.Actions; if (actions == null || actions.Count == 0) { sb.AppendLine("RecentActions: 无"); return; } var count = Math.Min(actions.Count, maxCount); sb.Append("RecentActions(count=").Append(count).AppendLine("):"); for (var i = 0; i < count; i++) { var actionIndex = actions.Count - 1 - i; sb.Append("RecentAction[").Append(actionIndex).AppendLine("]:"); AppendActionNetDataLog(sb, actions[actionIndex]); } } private static void AppendActionNetDataLog(StringBuilder sb, ActionNetData actionData) { if (actionData == null) { sb.AppendLine("null"); return; } sb.Append("Version=").Append(actionData.Version) .Append(", MapHash=").Append(actionData.MapHash) .AppendLine(); if (actionData.ActionId != null) sb.AppendLine(actionData.ActionId.GetStringLog()); if (actionData.Param != null) { sb.AppendLine("Param:"); sb.AppendLine(actionData.Param.GetStringLog()); } } private static void AppendBoatUnitsOnLandLog(StringBuilder sb, MapData mapData) { sb.Append("BoatUnitsOnLand(count=").Append(_sharedBoatUnitsOnLand.Count).AppendLine("):"); var count = Math.Min(_sharedBoatUnitsOnLand.Count, MaxRendererUnitMismatchLogCount); for (var i = 0; i < count; i++) { var unitData = _sharedBoatUnitsOnLand[i]; sb.Append(" ").Append(i + 1).Append(". "); AppendUnitDataLog(sb, mapData, unitData); if (mapData.GetGridDataByUnitId(unitData.Id, out var gridData) && gridData != null) { sb.Append(", gridTerrain=").Append(gridData.Terrain) .Append(", gridResource=").Append(gridData.Resource) .Append(", gridPos=(").Append(gridData.Pos.X).Append(",").Append(gridData.Pos.Y).Append(")"); } sb.Append(", landType=").Append(unitData.GetLandType()) .Append(", carry=").Append(unitData.CarryUnitType) .Append("/") .Append(unitData.CarryGiantType) .Append("/") .Append(unitData.CarryUnitLevel) .AppendLine(); } if (_sharedBoatUnitsOnLand.Count > count) sb.Append(" ... more=").Append(_sharedBoatUnitsOnLand.Count - count).AppendLine(); } private static void AppendRendererOnlyUnitsLog(StringBuilder sb, MapData mapData, MapRenderer mapRenderer) { sb.Append("RendererOnlyUnits(count=").Append(_sharedRendererOnlyUnitIds.Count).AppendLine("):"); if (_sharedRendererOnlyUnitIds.Count == 0) return; var count = Math.Min(_sharedRendererOnlyUnitIds.Count, MaxRendererUnitMismatchLogCount); for (var i = 0; i < count; i++) { var unitId = _sharedRendererOnlyUnitIds[i]; sb.Append(" ").Append(i + 1).Append(". "); if (mapRenderer.ROUnitMap.TryGetValue(unitId, out var renderer) && renderer != null) sb.AppendLine(renderer.GetDiagnosticString(mapData)); else sb.Append("uid=").Append(unitId).AppendLine(", renderer=null"); } if (_sharedRendererOnlyUnitIds.Count > count) sb.Append(" ... more=").Append(_sharedRendererOnlyUnitIds.Count - count).AppendLine(); } private static void AppendDataOnlyUnitsLog(StringBuilder sb, MapData mapData, MapRenderer mapRenderer) { sb.Append("DataOnlyUnits(count=").Append(_sharedDataOnlyUnitIds.Count).AppendLine("):"); if (_sharedDataOnlyUnitIds.Count == 0) return; var count = Math.Min(_sharedDataOnlyUnitIds.Count, MaxRendererUnitMismatchLogCount); for (var i = 0; i < count; i++) { var unitId = _sharedDataOnlyUnitIds[i]; sb.Append(" ").Append(i + 1).Append(". "); if (mapData.UnitMap.GetUnitDataByUnitId(unitId, out var unitData) && unitData != null) AppendUnitDataLog(sb, mapData, unitData); else sb.Append("uid=").Append(unitId).Append(", dataMissingAfterCollect=true"); if (!mapRenderer.ROUnitMap.TryGetValue(unitId, out var renderer) || renderer == null) sb.AppendLine(", rendererMissing=true"); else sb.Append(", renderer=").AppendLine(renderer.GetDiagnosticString(mapData)); } if (_sharedDataOnlyUnitIds.Count > count) sb.Append(" ... more=").Append(_sharedDataOnlyUnitIds.Count - count).AppendLine(); } private static void AppendUnitDataLog(StringBuilder sb, MapData mapData, UnitData unitData) { mapData.GetGridIdByUnitId(unitData.Id, out var gridId); mapData.GetCityIdByUnitId(unitData.Id, out var cityId); mapData.GetPlayerIdByUnitId(unitData.Id, out var playerId); sb.Append("uid=").Append(unitData.Id) .Append(", type=").Append(unitData.UnitType) .Append("/") .Append(unitData.GiantType) .Append("/") .Append(unitData.UnitLevel) .Append(", hp=").Append(unitData.Health) .Append(", alive=").Append(unitData.IsAlive()) .Append(", grid=").Append(gridId) .Append(", city=").Append(cityId) .Append(", player=").Append(playerId); } protected static void ReportUnitAttackRendererMissingAfterKill( CommonActionParams actionParams, CommonActionId actionId, FragmentType fragmentType, int attackDmg, int counterDmg, bool originAliveBefore, bool targetAliveBefore, bool originKilled, bool targetKilled, GridData originGrid, GridData targetGrid, UnitRenderer originUnitRenderer, UnitRenderer targetUnitRenderer) { try { var mapData = actionParams.MapData; if (mapData == null) return; _sharedRendererUnitMismatchLogBuilder ??= new StringBuilder(2048); var sb = _sharedRendererUnitMismatchLogBuilder; sb.Clear(); sb.AppendLine("[UnitAttackRendererMissingAfterKill] 攻击击杀后Renderer缺失导致死亡动画无法入队"); sb.Append("MapId=").Append(mapData.MapID) .Append(", ActionIndex=").Append(mapData.Net?.Actions?.Count ?? 0) .Append(", FragmentType=").Append(fragmentType) .Append(", AttackDmg=").Append(attackDmg) .Append(", CounterDmg=").Append(counterDmg) .Append(", OriginRendererMissing=").Append(originUnitRenderer == null) .Append(", TargetRendererMissing=").Append(targetUnitRenderer == null) .Append(", OriginGridMissing=").Append(originGrid == null) .Append(", TargetGridMissing=").Append(targetGrid == null) .Append(", OriginKilled=").Append(originKilled) .Append(", TargetKilled=").Append(targetKilled) .Append(", PresentationBusy=").Append(PresentationManager.Busy) .Append(", BusyType=").Append(PresentationManager.BusyType ?? "null") .AppendLine(); sb.AppendLine("CurrentAction:"); if (actionId != null) sb.AppendLine(actionId.GetStringLog()); if (actionParams != null) { sb.AppendLine("CurrentParam:"); sb.AppendLine(actionParams.GetStringLog()); } AppendRecentActionsLog(sb, mapData, 3); sb.Append("Origin: aliveBefore=").Append(originAliveBefore) .Append(", aliveAfter=").Append(actionParams.UnitData?.IsAlive()) .Append(", validAfter=").Append(actionParams.UnitData?.IsValidOnMap(mapData)) .Append(", renderer="); if (originUnitRenderer != null) sb.AppendLine(originUnitRenderer.GetDiagnosticString(mapData)); else sb.AppendLine("null"); if (actionParams.UnitData != null) { sb.Append("OriginData: "); AppendUnitDataLog(sb, mapData, actionParams.UnitData); sb.Append(", landType=").Append(actionParams.UnitData.GetLandType()) .Append(", APAttack=").Append(actionParams.UnitData.GetActionPoint(ActionPointType.Attack)) .Append(", APMove=").Append(actionParams.UnitData.GetActionPoint(ActionPointType.Move)) .AppendLine(); } sb.Append("Target: aliveBefore=").Append(targetAliveBefore) .Append(", aliveAfter=").Append(actionParams.TargetUnitData?.IsAlive()) .Append(", validAfter=").Append(actionParams.TargetUnitData?.IsValidOnMap(mapData)) .Append(", renderer="); if (targetUnitRenderer != null) sb.AppendLine(targetUnitRenderer.GetDiagnosticString(mapData)); else sb.AppendLine("null"); if (actionParams.TargetUnitData != null) { sb.Append("TargetData: "); AppendUnitDataLog(sb, mapData, actionParams.TargetUnitData); sb.Append(", landType=").Append(actionParams.TargetUnitData.GetLandType()) .AppendLine(); } LogSystem.LogError(sb.ToString()); } catch (Exception e) { if (!_unitAttackRendererMissingAfterKillDiagnosticFailedLogged) { _unitAttackRendererMissingAfterKillDiagnosticFailedLogged = true; LogSystem.LogWarning($"UnitAttackRendererMissingAfterKill diagnostic failed: {e}"); } } } protected static void ReportUnitAttackAllyRendererMissingAfterKill( CommonActionParams actionParams, CommonActionId actionId, bool originAliveBefore, bool targetAliveBefore, bool originKilled, bool targetKilled, bool handledByLifecycle, bool notProjectile, bool unit1Die, GridData originGrid, GridData targetGrid, UnitRenderer originUnitRenderer, UnitRenderer targetUnitRenderer) { try { var mapData = actionParams?.MapData; if (mapData == null) return; _sharedRendererUnitMismatchLogBuilder ??= new StringBuilder(2048); var sb = _sharedRendererUnitMismatchLogBuilder; sb.Clear(); sb.AppendLine("[UnitAttackAllyRendererMissingAfterKill] 友军攻击击杀后Renderer缺失导致死亡表现无法入队"); sb.Append("MapId=").Append(mapData.MapID) .Append(", ActionIndex=").Append(mapData.Net?.Actions?.Count ?? 0) .Append(", OriginRendererMissing=").Append(originUnitRenderer == null) .Append(", TargetRendererMissing=").Append(targetUnitRenderer == null) .Append(", OriginGridMissing=").Append(originGrid == null) .Append(", TargetGridMissing=").Append(targetGrid == null) .Append(", OriginKilled=").Append(originKilled) .Append(", TargetKilled=").Append(targetKilled) .Append(", HandledByLifecycle=").Append(handledByLifecycle) .Append(", NotProjectile=").Append(notProjectile) .Append(", Unit1DieFlag=").Append(unit1Die) .Append(", PresentationBusy=").Append(PresentationManager.Busy) .Append(", BusyType=").Append(PresentationManager.BusyType ?? "null") .AppendLine(); sb.AppendLine("CurrentAction:"); if (actionId != null) sb.AppendLine(actionId.GetStringLog()); if (actionParams != null) { sb.AppendLine("CurrentParam:"); sb.AppendLine(actionParams.GetStringLog()); } AppendRecentActionsLog(sb, mapData, 3); sb.Append("Origin: aliveBefore=").Append(originAliveBefore) .Append(", aliveAfter=").Append(actionParams?.UnitData?.IsAlive()) .Append(", validAfter=").Append(actionParams?.UnitData?.IsValidOnMap(mapData)) .Append(", renderer="); if (originUnitRenderer != null) sb.AppendLine(originUnitRenderer.GetDiagnosticString(mapData)); else sb.AppendLine("null"); if (actionParams?.UnitData != null) { sb.Append("OriginData: "); AppendUnitDataLog(sb, mapData, actionParams.UnitData); sb.Append(", landType=").Append(actionParams.UnitData.GetLandType()) .Append(", APAttack=").Append(actionParams.UnitData.GetActionPoint(ActionPointType.Attack)) .Append(", APMove=").Append(actionParams.UnitData.GetActionPoint(ActionPointType.Move)) .AppendLine(); } sb.Append("Target: aliveBefore=").Append(targetAliveBefore) .Append(", aliveAfter=").Append(actionParams?.TargetUnitData?.IsAlive()) .Append(", validAfter=").Append(actionParams?.TargetUnitData?.IsValidOnMap(mapData)) .Append(", renderer="); if (targetUnitRenderer != null) sb.AppendLine(targetUnitRenderer.GetDiagnosticString(mapData)); else sb.AppendLine("null"); if (actionParams?.TargetUnitData != null) { sb.Append("TargetData: "); AppendUnitDataLog(sb, mapData, actionParams.TargetUnitData); sb.Append(", landType=").Append(actionParams.TargetUnitData.GetLandType()) .AppendLine(); } LogSystem.LogError(sb.ToString()); } catch (Exception e) { if (!_unitAttackAllyRendererMissingAfterKillDiagnosticFailedLogged) { _unitAttackAllyRendererMissingAfterKillDiagnosticFailedLogged = true; LogSystem.LogWarning($"UnitAttackAllyRendererMissingAfterKill diagnostic failed: {e}"); } } } // 执行后逻辑 protected virtual void AfterExecute(CommonActionParams actionParams) { // 行为执行后触发技能生命周期 actionParams.MapData.OnActionExecuted(this, actionParams); // 这里限制住了部分逻辑执行后的好感度刷新,尽量控制在每帧最多执行一次,17人局平均执行时长为 3ms if (actionParams.MapData == Main.MapData && _actionId.ActionType != CommonActionType.TurnEnd && _actionId.ActionType != CommonActionType.TurnStart && _actionId.ActionType != CommonActionType.AIParamControl) { foreach (var pData in actionParams.MapData.PlayerMap.PlayerDataList) { pData.RefreshFeelingValue(actionParams.MapData); pData.RefreshDiplomacyState(actionParams.MapData); } } Main.PlayerLogic.CalcAllPlayerScore(actionParams.MapData); //刷新当前玩家的每回合获取金币数 if (actionParams.MapData == Main.MapData && Main.MapData.CurPlayer == Main.MapData.PlayerMap.SelfPlayerData) { EventManager.Publish(new UpdateUITopTopBar() { UpdateType = UpdateTopBarType.UpdateCoinPerTurn }); EventManager.Publish(new UpdateUITopTopBar() { UpdateType = UpdateTopBarType.UpdateTechPerTurn }); EventManager.Publish(new UpdateUITopTopBar() { UpdateType = UpdateTopBarType.UpdateCulturePerTurn }); EventManager.Publish(new UpdateUITopTopBar() { UpdateType = UpdateTopBarType.UpdateFaith }); } if (actionParams.MapData == Main.MapData) { // AI 演算不调用 PlayerLogic.Update,节省性能 Main.PlayerLogic.Update(actionParams.MapData); MatchSettlementLogicFactory.RefreshMatchSettlementInfo(actionParams.MapData); foreach (var item in actionParams.PlayerData.MomentData.Items) { item.OnActionExecuted(actionParams.MapData, this, actionParams); } } //如果是教程 通知教程面板刷新任务完成情况 if (Main.MapData.MatchSettlement.SettlementType == MatchSettlementType.Tutor && actionParams.MapData == Main.MapData) { EventManager.Publish(new UpdateUIBottomTutor() { }); } //更新Ranking界面的显示 if (actionParams.MapData == Main.MapData && Main.MapData.CurPlayer == Main.MapData.PlayerMap.SelfPlayerData) { EventManager.Publish(new UpdateUIBottomRanking() { }); } #if CHECK_ACTIONDEFFERENCE if (actionParams.MapData == Main.MapData) { byte[] bt = TH1Serialization.Serialize(Main.MapData); Main.Instance.CheckMapData = TH1Serialization.Deserialize(bt); } #endif } public abstract bool CheckCan(CommonActionParams actionParams); public virtual bool CheckShow(CommonActionParams actionParams,out ShowType showType) { showType = ShowType.None; return CheckCan(actionParams); } public virtual ActionShowState CheckShowState(CommonActionParams actionParams) { return ActionShowState.None; } public void TH1Debug() { Debug.Log($"{_actionId.ActionType} : {_actionId.ResourceType}/{_actionId.UnitType}/{_actionId.WonderType}"); } } // 建造奇观逻辑类 #buildwonder public class BuildWonderAction : ActionLogicBase { public BuildWonderAction(CommonActionId id) : base(id) { } protected override bool Execute(CommonActionParams actionParams) { //Step #0 鲁棒性处理 if(!actionParams.MapData.GetCityDataByTerritoryGid(actionParams.GridData.Id,out var city)) return false; if (!Table.Instance.GridAndResourceDataAssets.GetWonderInfoByType(_actionId.WonderType,actionParams.PlayerData, out var wonderInfo)) return false; if (actionParams.PlayerData == null) return false; //Step #1 逻辑层:更新gridData相关信息,设置奇观完成情况 var player = actionParams.PlayerData; actionParams.GridData.ResourceUnderBuilding = actionParams.GridData.Resource; actionParams.GridData.Resource = ResourceType.Wonder; actionParams.GridData.Wonder = wonderInfo.Wonder; player.Wonder.SetWonderState(wonderInfo.WonderType,WonderState.FINISH_BUILD); //Step #2 逻辑层+视觉层:城市获得3点经验 Main.CityLogic.UpdateGrid_ViewOnly(actionParams.MapData,actionParams.GridData); Main.CityLogic.GridGiveCityExp_LogicView(actionParams.MapData,actionParams.PlayerData,actionParams.GridData,city,wonderInfo.Exp); // moment foreach (var item in player.MomentData.Items) { item.OnWonderBuild(actionParams.MapData, player, _actionId.WonderType); } //Step #3 更新成就完成情况 AchievementDataManager.Instance.OnBuildWonder(actionParams.MapData, player, city, actionParams.GridData); try { if (actionParams.GridData.RealUnit(actionParams.MapData, out var wonderGridUnit)) BoatUnitOnLandDiagnostic.ReportIfNeeded(actionParams.MapData, wonderGridUnit, actionParams.GridData, $"BuildWonderAction:{_actionId.WonderType}"); } catch (Exception e) { LogSystem.LogWarning($"BuildWonderAction boat-on-land diagnostic failed: {e}"); } return true; } public override bool CheckCan(CommonActionParams actionParam) { //鲁棒性+基础数据准备,必须准备player(谁来建设?)和grid(在哪里建设?) if (actionParam.PlayerData == null) return false; if (actionParam.GridData == null) return false; var player = actionParam.PlayerData; var grid = actionParam.GridData; var map = actionParam.MapData; if (!map.GetCityDataByTerritoryGid(grid.Id, out _)) return false; if (!Table.Instance.GridAndResourceDataAssets.GetWonderInfoByType(_actionId.WonderType, player, out _)) return false; //找到grid所属于的player,如果是无主领土就return if (!map.GetPlayerDataByTerritoryGridId(grid.Id, out var gridPlayer)) return false; //如果player操作的grid都不属于自己return if (player != gridPlayer) return false; //如果根本无法建设这个奇观 return if (player.Wonder.GetWonderState(_actionId.WonderType) != WonderState.FINISH_NOT_BUILD) return false; //如果是山地或者深海,无法建设这个奇观 return false(拥有NapoleonicCode可在森林建造) bool wonderAllowForest = player.TechTree.CheckIfHasTechAtom(TechAtom.KaguyaFrenchNapoleonicCode); if (grid.Feature == TerrainFeature.Mountain || grid.Terrain == TerrainType.DeepSea || (grid.Vegetation == Vegetation.Trees && !wonderAllowForest)) return false; //如果有非联盟的敌人站在上面 if (grid.VisibleUnit(map,player, out var unit) && !map.CheckUnitIdBelongPlayerIdUnion(unit.Id, player.Id)) return false; //如果上面是常规资源,那么就是可以建设的 return true if (grid.Resource == ResourceType.None || grid.Resource == ResourceType.Fish || grid.Resource == ResourceType.Starfish || grid.Resource == ResourceType.Crop || grid.Resource == ResourceType.Fruit ) return true; if (GetCost(actionParam) > actionParam.PlayerData.PlayerCoin) return false; return false; } public override bool CheckShow(CommonActionParams actionParam,out ShowType showType) { showType = ShowType.None; return CheckCan(actionParam); } public override ActionShowState CheckShowState(CommonActionParams actionParams) { return ActionShowState.None; } } // Gain 收获资源逻辑类 public class GainResourceAction : ActionLogicBase { public GainResourceAction(CommonActionId id) : base(id) { } protected override bool Execute(CommonActionParams actionParams) { if (!CheckCan(actionParams)) return false; CityData cityData = actionParams.CityData; if (cityData == null) actionParams.MapData.GetCityDataByTerritoryGid(actionParams.GridData.Id,out cityData); //Step #1 逻辑层 扣钱,消除资源 actionParams.PlayerData.SpendCoin(GetCost(actionParams)); actionParams.GridData.Resource = ResourceType.None; //Step #2 逻辑层 & 表现层 :gridupdate + city获得exp var exp = Table.Instance.QueryActionExp(_actionId); Main.CityLogic.UpdateGrid_ViewOnly(actionParams.MapData,actionParams.GridData); Main.CityLogic.GridGiveCityExp_LogicView(actionParams.MapData,actionParams.PlayerData,actionParams.GridData,cityData,exp); //更新所有建筑的buildingLevel Main.PlayerLogic.UpdateTerritoryAllBuildingLevel(actionParams.MapData,actionParams.PlayerData.Id); try { if (actionParams.GridData.RealUnit(actionParams.MapData, out var gainGridUnit)) BoatUnitOnLandDiagnostic.ReportIfNeeded(actionParams.MapData, gainGridUnit, actionParams.GridData, $"GainResourceAction:{_actionId.ResourceType}"); } catch (Exception e) { LogSystem.LogWarning($"GainResourceAction boat-on-land diagnostic failed: {e}"); } return true; } public override bool CheckCan(CommonActionParams actionParam) { //如果是unit或者city来执行gain操作,直接return false if (actionParam.MainObjectType != MainObjectType.Grid) return false; var grid = actionParam.GridData; var map = actionParam.MapData; var player = actionParam.PlayerData; if (grid == null || map == null || player == null) return false; //如果没有对应科技,return false if (!player.TechTree.CheckActionCan(_actionId)) return false; //确认grid实际属于的player是谁 map.GetPlayerDataByTerritoryGridId(actionParam.GridData.Id, out var gridPlayer); //如果不在我方领土,return false if (gridPlayer == null || player.Id != gridPlayer.Id) return false; //钱不够 return if (GetCost(actionParam) > actionParam.PlayerData.PlayerCoin) return false; //如果有非联盟的敌人站在上面 if (grid.VisibleUnit(map,player, out var unit) && !map.CheckUnitIdBelongPlayerIdUnion(unit.Id, player.Id)) return false; return _actionId.ResourceType == actionParam.GridData.Resource; } public override bool CheckShow(CommonActionParams actionParam,out ShowType showType) { showType = ShowType.None; if (actionParam.MainObjectType != MainObjectType.Grid) return false; var grid = actionParam.GridData; var map = actionParam.MapData; var player = actionParam.PlayerData; if (grid == null || map == null || player == null) return false; //确认grid实际属于的player是谁 map.GetPlayerDataByTerritoryGridId(actionParam.GridData.Id, out var gridPlayer); //如果不在我方领土,return false if (gridPlayer == null || player.Id != gridPlayer.Id) return false; //如果有非联盟的敌人站在上面 if (grid.VisibleUnit(map,player, out var unit) && !map.CheckUnitIdBelongPlayerIdUnion(unit.Id, player.Id)) return false; //如果techpool里就没有这个技能,不显示 if (!Table.Instance.PlayerDataAssets.CheckActionInTechPool(_actionId, actionParam.PlayerData)) return false; //资源类型不匹配,不显示 if (_actionId.ResourceType != actionParam.GridData.Resource) return false; //没有科技,显示为灰色锁定 if (!player.TechTree.CheckActionCan(_actionId)) showType = ShowType.Locked; //钱不够,显示为Cost else if (GetCost(actionParam) > player.PlayerCoin) showType = ShowType.Cost; return true; } public override ActionShowState CheckShowState(CommonActionParams actionParams) { return ActionShowState.None; } } //CityLevelUp 行动逻辑类 public class CityLevelUpActionAction : ActionLogicBase { public CityLevelUpActionAction(CommonActionId id) : base(id) { } public static UnitFullType GetBigGuyCityLevelUpUnitFullType(PlayerData playerData) { if (playerData == null) return new UnitFullType(UnitType.BigGuy, GiantType.None, 0); return GetBigGuyCityLevelUpUnitFullType(playerData.PlayerCivId, playerData.PlayerForceId); } public static UnitFullType GetBigGuyCityLevelUpUnitFullType(uint civId, uint forceId) { // Multiple players may share the same Empire; the spawned special BigGuy unit is keyed by Empire. if (civId == 1 && forceId == 1) return new UnitFullType(UnitType.KaguyaFrenchWolf, GiantType.None, 0); if (civId == 0 && forceId == 0) return new UnitFullType(UnitType.RemiliaEgyptianKoakuma, GiantType.None, 0); if (civId == 2 && forceId == 2) return new UnitFullType(UnitType.MoriyaHebi, GiantType.None, 3); if (civId == 3 && forceId == 3) return new UnitFullType(UnitType.KomeijiIndianBigGuy, GiantType.None, 0); if (civId == 4 && forceId == 4) return new UnitFullType(UnitType.HakureiValkyrie, GiantType.None, 0); return new UnitFullType(UnitType.BigGuy, GiantType.None, 0); } public override bool CompleteExecute(CommonActionParams actionParams) { if (!CheckCanBeforeExecute(actionParams, "CompleteExecute")) return false; return base.CompleteExecute(actionParams); } public override bool NetCompleteExecute(CommonActionParams actionParams) { if (!CheckCanBeforeExecute(actionParams, "NetCompleteExecute")) return false; return base.NetCompleteExecute(actionParams); } private bool CheckCanBeforeExecute(CommonActionParams actionParams, string context) { if (actionParams != null && CheckCan(actionParams)) return true; if (actionParams?.MapData == Main.MapData) { LogSystem.LogError($"CityLevelUpAction CheckCan failed before {context}: {ActionId.GetStringLog()}\nParam:\n{actionParams?.GetStringLog()}"); } return false; } protected override bool Execute(CommonActionParams actionParams) { //必须传入参数 paramMap paramCity //需要参数:paramMap paramCity paramGrid paramPlayer //鲁棒性判断 if (actionParams.CityData == null) return false; if (!actionParams.MapData.GetGridDataByCityId(actionParams.CityData.Id, out var paramGrid)) return false; if (actionParams.CityData.CityLevelUpPoint <= 0) return false; if (!actionParams.MapData.GetPlayerDataByCityId(actionParams.CityData.Id, out var paramPlayer)) return false; var paramMap = actionParams.MapData; var paramCity = actionParams.CityData; actionParams.CityData.CityLevelUpPoint --; var cityLevelUpSucceeded = false; //不管哪个选项,都播放音效 if (actionParams.CityData.Grid(actionParams.MapData)?.InMainSight() ?? false) { AudioManager.Instance.PlayAudio("SFX/UNIT_treasure"); } if (_actionId.CityLevelUpActionType == CityLevelUpActionType.Explorer) { // 1. 锁定玩家输入 //main.InputLogic.LockInput(); // 2. 创建探索者,然后一边处理逻辑一边处理视觉,这里暂时用了maprenderer 来负责 MapRenderer.Instance.CreateTemporaryExplorer(actionParams.MapData,paramGrid, 3f); cityLevelUpSucceeded = true; } else if (_actionId.CityLevelUpActionType == CityLevelUpActionType.Workshop) { paramCity.Workshop = true; paramCity.SetCityRenderer(actionParams.MapData); if((paramGrid?.InMainSight()??false) && paramPlayer == Main.MapData.PlayerMap.SelfPlayerData) paramGrid.Renderer(paramMap)?.PlayVFXInSight(new GridVFXParams(GridVFXType.Coin)); cityLevelUpSucceeded = true; } else if (_actionId.CityLevelUpActionType == CityLevelUpActionType.CityWealth) { if(PresentationManager.GetCityLevelupActionPos(_actionId.CityLevelUpActionType,out var pos)) paramPlayer.AddCoin(5,pos); else paramPlayer.AddCoin(5); paramCity.SetCityRenderer(paramMap); cityLevelUpSucceeded = true; } else if (_actionId.CityLevelUpActionType == CityLevelUpActionType.CityWall) { paramPlayer.AddCulturePoint(2); cityLevelUpSucceeded = true; } else if (_actionId.CityLevelUpActionType == CityLevelUpActionType.Expand) { Main.CityLogic.CityLevelUpActionExpand(actionParams.MapData,actionParams.CityData); actionParams.CityData.SetCityRenderer(actionParams.MapData); //要更新玩家所有城市所有格子的levelbuilding Main.PlayerLogic.UpdateTerritoryAllBuildingLevel(actionParams.MapData,paramPlayer.Id); //更新所有国家的联通情况 Main.PlayerLogic.UpdateAllPlayerConnected(actionParams.MapData); //更新特殊占领技能 if(paramPlayer.TechTree.CheckIfHasTechAtom(TechAtom.KaguyaFrenchNapoleonicCode)) Main.PlayerLogic.SetCityTerritoryGridSp(actionParams.MapData,actionParams.CityData,GridSpType.KaguyaGrid); cityLevelUpSucceeded = true; } else if (_actionId.CityLevelUpActionType == CityLevelUpActionType.Population) { //Step #2 逻辑层 & 表现层 :city获得exp var exp = 3; Main.CityLogic.GridGiveCityExp_LogicView(actionParams.MapData,paramPlayer,actionParams.CityData?.Grid(actionParams.MapData),actionParams.CityData,exp); cityLevelUpSucceeded = true; } else if (_actionId.CityLevelUpActionType == CityLevelUpActionType.Park) { actionParams.CityData.ParkCount++; actionParams.CityData.SetCityRenderer(actionParams.MapData); if((paramGrid?.InMainSight()??false) && paramPlayer == Main.MapData.PlayerMap.SelfPlayerData) paramGrid.Renderer(paramMap)?.PlayVFXInSight(new GridVFXParams(GridVFXType.Luxury)); cityLevelUpSucceeded = true; } else if (_actionId.CityLevelUpActionType == CityLevelUpActionType.BigGuy) { if(paramGrid.RealUnit(paramMap,out var unitData)) Main.UnitLogic.PassiveMoveAway(paramMap, unitData); var fullType = GetBigGuyCityLevelUpUnitFullType(paramPlayer); if (!actionParams.MapData.AddUnitData(paramGrid.Id, actionParams.CityData.Id,fullType ,out var _)) return false; paramCity.SetCityRenderer(paramMap); cityLevelUpSucceeded = true; } if (!cityLevelUpSucceeded) return false; PresentationManager.EnqueuePendingCityLevelUpChoice(actionParams.MapData, actionParams.CityData); return true; } public override bool CheckCan(CommonActionParams actionParams) { if (actionParams.CityData == null) return false; //如果没有升级行动的点数,退出 if (actionParams.CityData.CityLevelUpPoint <= 0) return false; //用于处理连续升级的情况 var addition = actionParams.CityData.CityLevelUpPoint - 1; if (_actionId.CityLevelUpActionType == CityLevelUpActionType.Explorer) return actionParams.CityData.Level == 3 + addition; if (_actionId.CityLevelUpActionType == CityLevelUpActionType.Workshop) return actionParams.CityData.Level == 3 + addition; if (_actionId.CityLevelUpActionType == CityLevelUpActionType.CityWall) return actionParams.CityData.Level == 4 + addition; if (_actionId.CityLevelUpActionType == CityLevelUpActionType.CityWealth) return actionParams.CityData.Level == 4 + addition; if (_actionId.CityLevelUpActionType == CityLevelUpActionType.Population) return actionParams.CityData.Level == 5 + addition; if (_actionId.CityLevelUpActionType == CityLevelUpActionType.Expand) return actionParams.CityData.Level == 5 + addition; if (_actionId.CityLevelUpActionType == CityLevelUpActionType.Park) return actionParams.CityData.Level > 5 + addition; if (_actionId.CityLevelUpActionType == CityLevelUpActionType.BigGuy) return actionParams.CityData.Level > 5 + addition; return false; } public override bool CheckShow(CommonActionParams actionParams,out ShowType showType) { showType = ShowType.None; if (actionParams.CityData == null) return false; //如果没有升级行动的点数,退出 if (actionParams.CityData.CityLevelUpPoint <= 0) return false; var addition = actionParams.CityData.CityLevelUpPoint - 1; if (_actionId.CityLevelUpActionType == CityLevelUpActionType.Explorer) return actionParams.CityData.Level == 3 + addition; if (_actionId.CityLevelUpActionType == CityLevelUpActionType.Workshop) return actionParams.CityData.Level == 3 + addition; if (_actionId.CityLevelUpActionType == CityLevelUpActionType.CityWall) return actionParams.CityData.Level == 4 + addition; if (_actionId.CityLevelUpActionType == CityLevelUpActionType.CityWealth) return actionParams.CityData.Level == 4 + addition; if (_actionId.CityLevelUpActionType == CityLevelUpActionType.Population) return actionParams.CityData.Level == 5 + addition; if (_actionId.CityLevelUpActionType == CityLevelUpActionType.Expand) return actionParams.CityData.Level == 5 + addition; if (_actionId.CityLevelUpActionType == CityLevelUpActionType.Park) return actionParams.CityData.Level > 5 + addition; if (_actionId.CityLevelUpActionType == CityLevelUpActionType.BigGuy) return actionParams.CityData.Level > 5 + addition; return false; } public override ActionShowState CheckShowState(CommonActionParams actionParams) { return ActionShowState.None; } } // 小兵移动 public class UnitMoveAction : ActionLogicBase { public UnitMoveAction(CommonActionId id) : base(id) { } protected override bool Execute(CommonActionParams actionParams) { //Step #0 存储服务于anim的数据 GridData originGrid = null; GridData targetGrid = null; UnitData unit = null; unit = actionParams.UnitData; originGrid = unit?.Grid(actionParams.MapData); targetGrid = actionParams.GridData; if (unit == null || originGrid == null || targetGrid == null) return false; if (!unit.IsAlive()) { LogSystem.LogError($"unit is dead , cannot move!"); return false; } //提前记录player,因为有可能移动后unit就死了 PlayerData player = unit.Player(actionParams.MapData); if (player == null) return false; //Step #1记录路径 确定MoveType var path1 = new List(); //TODO 这里有风险,movepath依赖一个在时序上完全无关的 movecostinfo的计算 Main.UnitLogic.CalcUnitMoveInfo(actionParams.MapData, unit.Id); MoveAttackType moveAttackType = Main.UnitLogic.GetMovePath(actionParams.MapData, originGrid.Pos.V2(), targetGrid.Pos.V2(), out path1); bool isKasenBeastGuideActiveMove = moveAttackType == MoveAttackType.MoveTeleport && Main.UnitLogic.IsKasenBeastGuideActiveMoveTarget( targetGrid.Pos.X, targetGrid.Pos.Y); MoveType moveType = moveAttackType switch { MoveAttackType.MoveTeleport when !isKasenBeastGuideActiveMove => MoveType.SkillMove, _ => MoveType.ActiveMove }; //Step #2 处理移动数据 bool isMove = Main.UnitLogic.MoveToLogic(actionParams.MapData, actionParams.UnitData, actionParams.GridData, moveType, path1); if (!isMove) return false; //Step #3 将移动动画加入队列 bool need = Main.MapData == actionParams.MapData; //如果数据出错 不播放动画 if (originGrid == null || targetGrid == null || unit == null) need = false; //如果整个路径都在视野外,不播放动画 if (!Main.MapData.PlayerMap.SelfPlayerData.Sight.CheckIsInSight(originGrid.Id) && !Main.MapData.PlayerMap.SelfPlayerData.Sight.CheckIsInSight(targetGrid.Id)) need = false; // UtsuhoBase的移动动画由UtsuhoReadyMoveSkill自行控制 if (unit.GetSkill(SkillType.UtsuhoBase, out var _)) need = false; if (need) { //Step #2-1 处理闪现类动画 if (moveAttackType == MoveAttackType.MoveTeleport) { MapRenderer.Instance.ROUnitMap.TryGetValue(unit.Id, out var uintRenderer); var fragment = FragmentFactory.Create(FragmentType.MoveTeleport,FragmentDataFactory.Create(FragmentType.Move,uintRenderer,originGrid,targetGrid,path1)); PresentationManager.EnqueueTask(new FragmentSequencerTask(fragment)); } //Step #2-2 将移动动画加入队列 if (moveAttackType == MoveAttackType.Move) { MapRenderer.Instance.ROUnitMap.TryGetValue(unit.Id, out var uintRenderer); var fragment = FragmentFactory.Create(FragmentType.Move,FragmentDataFactory.Create(FragmentType.Move,uintRenderer,originGrid,targetGrid,path1)); PresentationManager.EnqueueTask(new FragmentSequencerTask(fragment)); } } //Step #4 更新Sight的数据,同时将Sight相关动画加入演出队列 //更新路径视野 foreach (var pathnode in path1) //需要传入player,因为可能unitData已经死了 actionParams.PlayerData.Sight.UpdateSightByPath(actionParams.UnitData,player,pathnode,actionParams.MapData); //Step #5 Moment if (actionParams.MapData == Main.MapData && targetGrid.CityOnGrid(actionParams.MapData,out var targetCity) && targetCity.Player(actionParams.MapData) == Main.MapData.PlayerMap.SelfPlayerData && unit.Player(actionParams.MapData) != Main.MapData.PlayerMap.SelfPlayerData) { EventManager.Publish(new ShowUINotifyMoment() { MomentSubType = MomentSubType.BattleCityFire, Empire = Main.MapData.PlayerMap.SelfPlayerData.Empire }); } return true; } public override bool CheckCan(CommonActionParams actionParams) { // TODO 需要完整更新CheckCan var unit = actionParams.UnitData; var map = actionParams.MapData; var player = actionParams.PlayerData; var grid = actionParams.GridData; if (unit is null || map is null || grid is null || player is null) return false; //如果grid上有可见的敌人,returnfalse if (grid.VisibleUnit(map,player, out var _)) return false; var unitPlayer = unit.Player(map); if (unitPlayer != player) return false; //判断是否只能在我方领土上移动(目前仅用于suwako) if (unit.IsLimitMoveToSelfTerrain(map) && grid.Player(map) != unit.Player(map)) return false; if (!HakureiNorwayHeroSkillUtil.IsAunnPairWithinDistanceLimit(map, unit, grid)) return false; Main.UnitLogic.CalcUnitMoveInfo(map, unit.Id); var moveAttackType = Main.UnitLogic.CheckUnitCanMoveOrAttack(map, unit, grid); return moveAttackType is MoveAttackType.Move or MoveAttackType.MoveToPort or MoveAttackType.MoveAshore or MoveAttackType.MoveTeleport; } public override bool CheckShow(CommonActionParams actionParams,out ShowType showType) { showType = ShowType.None; return false; } public override ActionShowState CheckShowState(CommonActionParams actionParams) { return ActionShowState.None; } public override bool CameraControl(CommonActionParams actionParams) { var main = GameObject.Find("Main").GetComponent
(); if (actionParams.GridData != null && main != null && MapRenderer.Instance.CameraController != null && actionParams.MapData.PlayerMap.SelfPlayerData.Sight.CheckIsInSight(actionParams.GridData.Id)) MapRenderer.Instance.CameraController?.CameraFocusOnGrid(actionParams.GridData); return true; } } public class UnitPassiveMoveAction : ActionLogicBase { public UnitPassiveMoveAction(CommonActionId id) : base(id) { } protected override bool Execute(CommonActionParams actionParams) { //Step #0 存储服务于anim的数据 GridData originGrid = null; GridData targetGrid = null; UnitData unit = null; originGrid = actionParams.UnitData.Grid(actionParams.MapData); targetGrid = actionParams.GridData; unit = actionParams.UnitData; //提前记录player,因为unit可能在移动后直接死了 PlayerData player = unit.Player(actionParams.MapData); //Step #1 处理移动数据 Main.UnitLogic.MoveToLogic(actionParams.MapData, actionParams.UnitData, actionParams.GridData,MoveType.PassiveMove); //记录路径 var path1 = new List(); path1.Add(originGrid.Pos.V2()); path1.Add(targetGrid.Pos.V2()); //Main.UnitLogic.GetMovePath(actionParams.MapData, originGrid.Pos.V2(), targetGrid.Pos.V2(), out path1); //Step #2 将移动动画加入队列 bool need = Main.MapData == actionParams.MapData; //如果数据出错 不播放动画 if (originGrid == null || targetGrid == null || unit == null) need = false; //如果在迷雾 不播放动画 if (!Main.MapData.PlayerMap.SelfPlayerData.Sight.CheckIsInSight(originGrid.Id) && !Main.MapData.PlayerMap.SelfPlayerData.Sight.CheckIsInSight(targetGrid.Id)) need = false; //如果单位不可见 不播房动画 if(unit.IsHideAndCantSee(actionParams.MapData,Main.MapData.PlayerMap.SelfPlayerData)) need = false; if (need) { MapRenderer.Instance.ROUnitMap.TryGetValue(unit.Id, out var uintRenderer); var fragment = FragmentFactory.Create(FragmentType.Move,FragmentDataFactory.Create(FragmentType.Move,uintRenderer,originGrid,targetGrid,path1)); //FragmentManager.Instance.AddFragment(fragment); PresentationManager.EnqueueTask(new FragmentSequencerTask(fragment)); } //Step #3 更新Sight的数据,同时将Sight相关动画加入演出队列 //更新路径视野 foreach (var pathnode in path1) actionParams.PlayerData.Sight.UpdateSightByPath(actionParams.UnitData,player,pathnode,actionParams.MapData); return true; } public override bool CheckCan(CommonActionParams actionParams) { return true; } public override bool CheckShow(CommonActionParams actionParams,out ShowType showType) { showType = ShowType.None; return false; } } internal static class ActiveAttackActionRecorder { public static void MarkStarted(MapData mapData, PlayerData player) { if (mapData == null || player == null) return; player.TurnNoAttack = 0; } public static void MarkStarted(MapData mapData, UnitData unit) { if (mapData == null || unit == null) return; var player = unit.Player(mapData); MarkStarted(mapData, player); } } public class UnitAttackAction : ActionLogicBase { public UnitAttackAction(CommonActionId id) : base(id) { } protected override bool Execute(CommonActionParams actionParams) { //Step #0 鲁棒性保护 if (actionParams?.UnitData == null || actionParams.TargetUnitData == null || actionParams.MapData == null) return false; ActiveAttackActionRecorder.MarkStarted(actionParams.MapData, actionParams.UnitData); // 开启 PendingAnimScope,逻辑运算期间技能产生的动画步骤会收集到 scope 中 using var scope = PresentationManager.BeginScope(); using var visualCollector = ActionVisualEventCollector.Begin( actionParams.MapData, actionParams.UnitData.Id, actionParams.TargetUnitData.Id); //Step #0 为anim做数据准备 GridData originGrid = null; GridData targetGrid = null; CityData originCity = null; CityData targetCity = null; UnitRenderer originUnitRenderer = null; UnitRenderer targetUnitRenderer = null; ProjectileType originUnitProjectileType = ProjectileType.None; ProjectileType targetUnitProjectileType = ProjectileType.None; PlayerData originPlayer = null; //如果不是AI预测相关的行为 if (actionParams.MapData == Main.MapData) { originGrid = actionParams.UnitData.Grid(Main.MapData); targetGrid = actionParams.TargetUnitData.Grid(Main.MapData); originCity = actionParams.UnitData.City(Main.MapData); targetCity = actionParams.TargetUnitData.City(Main.MapData); originPlayer = actionParams.UnitData.Player(Main.MapData); MapRenderer.Instance?.ROUnitMap?.TryGetValue(actionParams.UnitData.Id, out originUnitRenderer); MapRenderer.Instance?.ROUnitMap?.TryGetValue(actionParams.TargetUnitData.Id, out targetUnitRenderer); if (targetGrid != null) { originUnitProjectileType = actionParams.UnitData.GetProjectileType(actionParams.MapData,targetGrid); targetUnitProjectileType = actionParams.TargetUnitData.GetProjectileType(actionParams.MapData,targetGrid); } } var originAliveBeforeAttack = actionParams.UnitData.IsAlive(); var targetAliveBeforeAttack = actionParams.TargetUnitData.IsAlive(); //Step #2 处理逻辑计算,提前存储anim使用到的数据 bool targetCanNotBeKilled = !actionParams.TargetUnitData.CanBeKilled(actionParams.MapData); Main.UnitLogic.Attack(actionParams.MapData, actionParams.UnitData, actionParams.TargetUnitData, out var attackDmg, out var counterDmg, out var fragmentType, out var yuugiPushResult, out var attackInfo); var originKilledByAttack = originAliveBeforeAttack && !actionParams.UnitData.IsAlive(); var targetKilledByAttack = targetAliveBeforeAttack && !actionParams.TargetUnitData.IsAlive(); //Step #3 处理攻击动画 //如果是AI预测行为,return(scope.Dispose会自动清理pending steps) if (actionParams.MapData != Main.MapData) return true; //如果数据出错,return if (originGrid == null || targetGrid == null || originUnitRenderer == null || targetUnitRenderer == null) { if (originKilledByAttack || targetKilledByAttack) { ReportUnitAttackRendererMissingAfterKill( actionParams, _actionId, fragmentType, attackDmg, counterDmg, originAliveBeforeAttack, targetAliveBeforeAttack, originKilledByAttack, targetKilledByAttack, originGrid, targetGrid, originUnitRenderer, targetUnitRenderer); try { if (targetKilledByAttack && targetUnitRenderer != null) targetUnitRenderer.Die(); if (originKilledByAttack && originUnitRenderer != null) originUnitRenderer.Die(); } catch (Exception e) { LogSystem.LogWarning($"UnitAttackRendererMissingAfterKill cleanup failed: {e}"); } } return false; } //Step #3-1 检测YuugiPush是否发生了推动。推击致死时 target 已经从数据层移除, //不能再靠 target 当前 grid 推断,必须使用 YuugiPushSkill 返回的移动结果。 var targetGridAfter = actionParams.TargetUnitData.Grid(Main.MapData); bool pushed = yuugiPushResult is { Pushed: true, TargetGridAfterPush: not null }; if (pushed && targetGridAfter == null) targetGridAfter = yuugiPushResult.TargetGridAfterPush; //如果在视野,播放动画 if (Main.MapData?.PlayerMap?.SelfPlayerData?.Sight != null && (Main.MapData.PlayerMap.SelfPlayerData.Sight.CheckIsInSight(originGrid.Id) || Main.MapData.PlayerMap.SelfPlayerData.Sight.CheckIsInSight(targetGrid.Id))) { //如果发生了推动,先播放双方的移动动画,再播放攻击动画。 //推击可能致死:targetGridAfter 可能来自存活单位的当前格,也可能来自 YuugiPushResult 中记录的死亡格。 if (pushed && targetGridAfter != null) { //target往后退一格的移动动画 var targetMovePath = new List { targetGrid.Pos.V2(), targetGridAfter.Pos.V2() }; var targetMoveData = FragmentDataFactory.Create(FragmentType.Move, targetUnitRenderer, targetGrid, targetGridAfter, targetMovePath); var targetMoveFragment = FragmentFactory.Create(FragmentType.Move, targetMoveData); PresentationManager.EnqueueTask(new FragmentSequencerTask(targetMoveFragment)); //origin往前进一格的移动动画 var originMovePath = new List { originGrid.Pos.V2(), targetGrid.Pos.V2() }; var originMoveData = FragmentDataFactory.Create(FragmentType.Move, originUnitRenderer, originGrid, targetGrid, originMovePath); var originMoveFragment = FragmentFactory.Create(FragmentType.Move, originMoveData); PresentationManager.EnqueueTask(new FragmentSequencerTask(originMoveFragment)); //攻击动画使用移动后的位置:origin在targetGrid,target在targetGridAfter var attackData = FragmentDataFactory.Create(fragmentType, originUnitRenderer, targetUnitRenderer, targetGrid, targetGridAfter, originUnitProjectileType, targetUnitProjectileType, attackDmg, counterDmg, originCity, targetCity, targetCanNotBeKilled); var attackFragment = FragmentFactory.Create(fragmentType, attackData); // 将技能产生的 pending steps 注入到攻击 fragment 中 scope.FlushTo(attackFragment); visualCollector?.FlushTo(attackFragment); PresentationManager.EnqueueTask(new FragmentSequencerTask(attackFragment)); _duration = attackFragment.Duration; } else if (TryCreateSuikaFallingSplashFragment( attackInfo, originUnitRenderer, targetUnitRenderer, originGrid, targetGrid, originCity, targetCity, attackDmg, targetCanNotBeKilled, out var suikaFallingSplashFragment)) { scope.FlushTo(suikaFallingSplashFragment); visualCollector?.FlushTo(suikaFallingSplashFragment); PresentationManager.EnqueueTask(new FragmentSequencerTask(suikaFallingSplashFragment)); _duration = suikaFallingSplashFragment.Duration; } else { var data = fragmentType switch { FragmentType.Attack or FragmentType.AttackAndCounter or FragmentType.MoveKill or FragmentType.NotMoveKill or FragmentType.AttackAndCounterDie => FragmentDataFactory.Create(fragmentType, originUnitRenderer, targetUnitRenderer, originGrid, targetGrid, originUnitProjectileType, targetUnitProjectileType, attackDmg, counterDmg,originCity,targetCity,targetCanNotBeKilled), _ => null }; var fragment = FragmentFactory.Create(fragmentType,data); // 将技能产生的 pending steps 注入到攻击 fragment 中 scope.FlushTo(fragment); visualCollector?.FlushTo(fragment); PresentationManager.EnqueueTask(new FragmentSequencerTask(fragment)); _duration = fragment.Duration; } } //Step #4 处理视野变化(move kill / push move) //如果MoveKill,要给unit所在的player更新sight,如果学者攻击,也是会更新sight if(fragmentType == FragmentType.MoveKill) //传入originPlayer, 避免unit已经死了,无法通过unit拿到player的情况 actionParams.UnitData.Player(actionParams.MapData)?.Sight.UpdateSightByPath(actionParams.UnitData,originPlayer,targetGrid.Pos.V2(),actionParams.MapData); //如果YuugiPush推动发生了,攻击者也移动了,需要更新视野 else if(pushed) originPlayer?.Sight.UpdateSightByPath(actionParams.UnitData,originPlayer,targetGrid.Pos.V2(),actionParams.MapData); //Step #5 更新攻击前后两个格子的显示,比如学者攻击了在城市里的角色,那么城市里会有fire //更正!这一步不能在这里做,要在fragment里做!! //originGrid.Renderer(actionParams.MapData)?.SetUpdateGrid(); //targetGrid.Renderer(actionParams.MapData)?.SetUpdateGrid(); return true; } private static bool TryCreateSuikaFallingSplashFragment( AttackInfo attackInfo, UnitRenderer originUnitRenderer, UnitRenderer targetUnitRenderer, GridData originGrid, GridData targetGrid, CityData originCity, CityData targetCity, int attackDmg, bool targetCanNotBeKilled, out FragmentBase fragment) { fragment = null; if (attackInfo == null || !attackInfo.IsSuikaFallingSplash) return false; var finalGrid = attackInfo.SuikaFallingSplashFinalGrid ?? targetGrid; if (originUnitRenderer == null || targetUnitRenderer == null || originGrid == null || targetGrid == null || finalGrid == null) return false; var data = new FragmentSuikaFallingSplashData( originUnitRenderer, targetUnitRenderer, originGrid, targetGrid, finalGrid, attackInfo.SuikaFallingSplashOriginCity ?? originCity, targetCity, attackInfo.SuikaFallingSplashFinalCity, attackDmg, attackInfo.IsKill, targetCanNotBeKilled, null, sightRefreshGrids: attackInfo.SuikaFallingSplashSightRefreshGrids); fragment = FragmentFactory.Create(FragmentType.SuikaFallingSplash, data); return fragment != null; } public override bool CheckCan(CommonActionParams actionParams) { if (!actionParams.UnitData.IsAlive() || !actionParams.TargetUnitData.IsAlive()) return false; if (actionParams.UnitData.Id == actionParams.TargetUnitData.Id) return false; if (!actionParams.UnitData.CanAttackAll(actionParams.MapData) && actionParams.MapData.IsLeagueUnitByUnit(actionParams.UnitData.Id, actionParams.TargetUnitData.Id)) return false; if (actionParams.UnitData.IsLimitSelfAttack(actionParams.MapData)) return false; if (!actionParams.MapData.GetPlayerDataByUnitId(actionParams.UnitData.Id, out _)) return false; if (!actionParams.MapData.GetPlayerDataByUnitId(actionParams.TargetUnitData.Id, out _)) return false; if (!actionParams.MapData.GetCityDataByUnitId(actionParams.UnitData.Id, out _)) return false; if (!actionParams.MapData.GetCityDataByUnitId(actionParams.TargetUnitData.Id, out _)) return false; //单独处理: 学者无法攻击英雄 if (actionParams.UnitData.GetSkill(SkillType.CONVERT, out var _) && actionParams.TargetUnitData.UnitFullType.UnitType == UnitType.Giant) return false; //技能层细粒度过滤(如 INFILTRATE 仅允许打城心上的敌人) if (!actionParams.UnitData.IsCanAttackTargetUnit(actionParams.MapData, actionParams.TargetUnitData)) return false; return true; } public override bool CheckShow(CommonActionParams actionParams,out ShowType showType) { showType = ShowType.None; return false; } public override ActionShowState CheckShowState(CommonActionParams actionParams) { return ActionShowState.None; } public override bool CameraControl(CommonActionParams actionParams) { var main = GameObject.Find("Main").GetComponent
(); if(actionParams.GridData != null && main != null && MapRenderer.Instance.CameraController != null && actionParams.MapData.PlayerMap.SelfPlayerData.Sight.CheckIsInSight(actionParams.GridData.Id)) MapRenderer.Instance.CameraController?.CameraFocusOnGrid(actionParams.GridData); return true; } } public class UnitAttackGroundAction : ActionLogicBase { public UnitAttackGroundAction(CommonActionId id) : base(id) { } protected override bool Execute(CommonActionParams actionParams) { //Step #0 为anim做数据准备 GridData originGrid = actionParams.UnitData.Grid(actionParams.MapData); GridData targetGrid = actionParams.GridData; UnitRenderer originUnitRenderer = null; GridRenderer targetGridRenderer = null; CityData targetCity = null; bool isTrueMap = actionParams.MapData == Main.MapData; //如果不是AI预测相关的行为,获取两个格子的Renderer if (isTrueMap) { MapRenderer.Instance.ROUnitMap.TryGetValue(actionParams.UnitData.Id, out originUnitRenderer); targetGridRenderer = targetGrid.Renderer(Main.MapData); targetCity = targetGrid.City(Main.MapData, out var city) ? city : null; } //Step #1 处理所有攻击友军的逻辑 UnitData unit1 = actionParams.UnitData; //UnitData unit2 = actionParams.TargetUnitData; CityData city1 = unit1.City(actionParams.MapData); //MapData mapData = actionParams.MapData; SkillType animSkillData = SkillType.NONE; UnitData suwakoUnit = null; bool hasSuwakoAttack = unit1.GetSkill(SkillType.SUWAKOATTACK, out var _); bool hadSuikaThrowReady = unit1.GetSkill(SkillType.SuikaThrowReady, out _); bool handledBySkillAttackGround = false; List suikaGroundSightRefreshGrids = null; if (unit1.GetSkill(SkillType.INFILTRATE, out _) && targetGrid.Resource == ResourceType.CityCenter && targetGrid.RealUnit(actionParams.MapData, out var hiddenTargetUnit)) { var actingPlayer = actionParams.PlayerData; if (actingPlayer == null) unit1.Player(actionParams.MapData, out actingPlayer); if (hiddenTargetUnit.IsHideAndCantSee(actionParams.MapData, actingPlayer)) { hiddenTargetUnit.OnBeInteractTarget(actionParams.MapData, unit1, targetGrid); return true; } } // Cache Suika sight before AttackGroundExecute mutates SightGidSet. if (!hasSuwakoAttack && !hadSuikaThrowReady && HakureiNorwayHeroSkillUtil.CanSuikaFlyToGrid(actionParams.MapData, unit1, targetGrid)) { var suikaSightPlayer = actionParams.PlayerData; if (suikaSightPlayer == null) unit1.Player(actionParams.MapData, out suikaSightPlayer); suikaGroundSightRefreshGrids = HakureiNorwayHeroSkillUtil.CollectSuikaFallingSplashNewSightGrids( actionParams.MapData, unit1, suikaSightPlayer, targetGrid); } //处理SUWAKO的地面攻击 if (!hasSuwakoAttack && unit1.AttackGroundExecute(actionParams.MapData, targetGrid, out animSkillData)) { handledBySkillAttackGround = true; ActiveAttackActionRecorder.MarkStarted(actionParams.MapData, unit1); } if (!handledBySkillAttackGround && hadSuikaThrowReady) return false; if (!handledBySkillAttackGround && hasSuwakoAttack) { animSkillData = SkillType.SUWAKOATTACK; if (targetGrid.RealUnit(actionParams.MapData, out var targetGridUnit)) { ActiveAttackActionRecorder.MarkStarted(actionParams.MapData, unit1); targetGridUnit.OnBeInteractTarget(actionParams.MapData, unit1, targetGrid); return true; } // Suwako 对空地生成白蛇不是攻击,不能重置和平奇观的无攻击回合计数。 unit1.ClearActionPoint(); var summonedHebiLevel = unit1.UnitLevel >= 4 ? 2u : 1u; var fullType = new UnitFullType() { UnitType = UnitType.MoriyaHebi, GiantType = GiantType.None, UnitLevel = summonedHebiLevel }; // TODO 这里的延迟产生单位形象,后续要加入presentationlist if (!actionParams.MapData.AddUnitData(targetGrid.Id, city1.Id, fullType, out suwakoUnit, 0.2f)) return false; } else if (!handledBySkillAttackGround) { ActiveAttackActionRecorder.MarkStarted(actionParams.MapData, unit1); } //处理 INFILTRATE 的地面攻击:渗透单位攻击敌方空城心 → 直接偷金 + 自杀 + 生叛军,跳过远程攻击(Bomb)动画 bool infiltrateConsumed = false; if (!handledBySkillAttackGround && unit1.GetSkill(SkillType.INFILTRATE, out var infSkillBase) && infSkillBase is InfiltrateSkill infSkill) { infiltrateConsumed = infSkill.PerformInfiltrateOnAttackGround(actionParams.MapData, unit1, targetGrid); if (infiltrateConsumed) ActiveAttackActionRecorder.MarkStarted(actionParams.MapData, actionParams.PlayerData); } //Step #3 处理动画 //如果是AI预测行为,return if (!isTrueMap) return true; //如果数据出错,return if (originGrid == null || targetGrid == null || originUnitRenderer == null || targetGridRenderer == null) return false; //如果在视野,更新相关视觉。INFILTRATE 渗透单位已自杀消失,不应播放远程攻击(Bomb)动画 if (!infiltrateConsumed && (Main.MapData.PlayerMap.SelfPlayerData.Sight.CheckIsInSight(originGrid.Id) || Main.MapData.PlayerMap.SelfPlayerData.Sight.CheckIsInSight(targetGrid.Id))) { if (animSkillData == SkillType.SuikaFallingSplash && !hadSuikaThrowReady) { var suikaData = new FragmentSuikaFallingSplashData( originUnitRenderer, null, originGrid, targetGrid, targetGrid, city1, null, targetCity, 0, false, false, null, hasAttackTarget: false, isGroundLanding: true, sightRefreshGrids: suikaGroundSightRefreshGrids); var suikaFragment = FragmentFactory.Create(FragmentType.SuikaFallingSplash, suikaData); PresentationManager.EnqueueTask(new FragmentSequencerTask(suikaFragment)); _duration = suikaFragment.Duration; return true; } var data = new FragmentAttackGroundData(FragmentType.AttackGround, originUnitRenderer, targetGridRenderer, originGrid, targetGrid, animSkillData,city1); var fragment = FragmentFactory.Create(FragmentType.AttackGround,data); PresentationManager.EnqueueTask(new FragmentSequencerTask(fragment)); _duration = fragment.Duration; suwakoUnit?.Grid(actionParams.MapData)?.Renderer(actionParams.MapData)?.InstantUpdateGrid(); } //处理SUWAKO的地面攻击 刷新白蛇周围的视野 if (unit1.GetSkill(SkillType.SUWAKOATTACK, out var _) && suwakoUnit != null) { actionParams.PlayerData.Sight.UpdateSightByPath(suwakoUnit,actionParams.PlayerData,suwakoUnit.Grid(actionParams.MapData)?.Pos.V2() ?? Vector2Int.zero,actionParams.MapData); } return true; } public override bool CheckCan(CommonActionParams actionParams) { //Step #1 确定数据正确性 if (actionParams.MapData == null || actionParams.PlayerData == null || actionParams.UnitData == null || actionParams.GridData == null) return false; //Step #2 player操作的unit 必须属于player if (actionParams.UnitData.Player(actionParams.MapData) != actionParams.PlayerData) return false; //Step #3 unit是否能够攻击targetGrid if (!actionParams.UnitData.IsCanAttackTargetGrid(actionParams.MapData,actionParams.GridData)) return false; //Step #4 判断是否有AP var hasSuikaThrowReady = actionParams.UnitData.GetSkill(SkillType.SuikaThrowReady, out _); if (actionParams.UnitData.GetActionPoint(ActionPointType.Attack) <= 0 && !hasSuikaThrowReady) return false; return true; } public override bool CheckShow(CommonActionParams actionParams,out ShowType showType) { showType = ShowType.None; return false; } public override ActionShowState CheckShowState(CommonActionParams actionParams) { return ActionShowState.None; } public override bool CameraControl(CommonActionParams actionParams) { var main = GameObject.Find("Main").GetComponent
(); if(actionParams.GridData != null && main != null && MapRenderer.Instance.CameraController != null && actionParams.MapData.PlayerMap.SelfPlayerData.Sight.CheckIsInSight(actionParams.GridData.Id)) MapRenderer.Instance.CameraController?.CameraFocusOnGrid(actionParams.GridData); return true; } } public class StartWonderAction : ActionLogicBase { public StartWonderAction(CommonActionId id) : base(id) { } protected override bool Execute(CommonActionParams actionParams) { return false; } public override bool CheckCan(CommonActionParams actionParams) { return false; } public override bool CheckShow(CommonActionParams actionParams,out ShowType showType) { showType = ShowType.None; return false; } } //TODO 没有填写Skill public class UnitSkillAction : ActionLogicBase { public UnitSkillAction(CommonActionId id) : base(id) { } protected override bool Execute(CommonActionParams actionParams) { return false; } public override bool CheckCan(CommonActionParams actionParams) { return false; } public override bool CheckShow(CommonActionParams actionParams,out ShowType showType) { showType = ShowType.None; return false; } } public class LearnTechAction : ActionLogicBase { public LearnTechAction(CommonActionId id) : base(id) { } protected override bool Execute(CommonActionParams actionParams) { if (actionParams.MainObjectType != MainObjectType.Player) return false; if (actionParams.PlayerData == null) return false; int cost = Main.PlayerLogic.GetTechCost(actionParams.MapData, actionParams.PlayerData, _actionId.TechType); Main.PlayerLogic.ResearchTech(actionParams.MapData,actionParams.PlayerData,_actionId.TechType,cost); //发出学习科技的通知 if(actionParams.PlayerData.IsSelfPlayer()) EventManager.Publish(new ShowUINotifyCommon(){UINotifyCommonType = UINotifyCommonType.Tech}); return true; } public override bool CheckCan(CommonActionParams actionParams) { //鲁棒性判断,不是玩家执行这个操作就退出 if (actionParams.MainObjectType != MainObjectType.Player) return false; if (actionParams.PlayerData == null) return false; if (actionParams.PlayerData.TechTree.CheckIfHasTech(_actionId.TechType)) return false; //如果科技不在该玩家的tech pool里,return if (!Table.Instance.PlayerDataAssets.GetPlayerInfo(actionParams.PlayerData, out var playerInfo)) return false; if (!playerInfo.TechPool.Contains(_actionId.TechType)) return false; //如果没有父科技 var techInfo = Table.Instance.TechDataAssets.GetTechInfo(_actionId.TechType); if (!actionParams.PlayerData.TechTree.CheckIfHasTech(techInfo.FatherTechList)) return false; //判断钱够不够 int cost = Main.PlayerLogic.GetTechCost(actionParams.MapData, actionParams.PlayerData, _actionId.TechType); if(cost > actionParams.PlayerData.PlayerCoin + actionParams.PlayerData.PlayerTechPoint) return false; return true; } public override bool CheckShow(CommonActionParams actionParams,out ShowType showType) { showType = ShowType.None; return false; } } public class PlayerTurnStartAction : ActionLogicBase { public PlayerTurnStartAction(CommonActionId id) : base(id) { } protected override bool Execute(CommonActionParams actionParams) { if (!CheckCan(actionParams)) return false; // collect 调用 CollectManager.Instance.OnTurnStartCollect(actionParams.MapData, actionParams.PlayerData); actionParams.MapData.OnTurnStart(actionParams.PlayerData.Id); //这里需要一个AfterTurnStart周期。 否则 onturnstart自动给周围人上buff的技能,会出问题 actionParams.MapData.OnAfterTurnStart(actionParams.PlayerData.Id); // 回合开始时刷新一次外交关系:LeagueRupture→Neutral、War→Neutral 等跨回合状态过渡依赖此处触发。 // AfterExecute 的同名刷新显式过滤了 TurnStart/TurnEnd,所以这里必须显式补上,否则新回合 UI 不会立刻刷新。 if (actionParams.MapData == Main.MapData) { foreach (var pData in actionParams.MapData.PlayerMap.PlayerDataList) { pData.RefreshFeelingValue(actionParams.MapData); pData.RefreshDiplomacyState(actionParams.MapData); } } // 回合开始后:同步所有 UnitMono 的 SkillStatusInfo,让 buff/debuff 图标与最新数据保持一致 if (actionParams.MapData == Main.MapData && MapRenderer.Instance?.ROUnitMap != null) { foreach (var unitRenderer in MapRenderer.Instance.ROUnitMap.Values) { unitRenderer?.SyncStatusWithUnitSkills(); } } var canLocalControlTurnPlayer = actionParams.MapData != null && actionParams.PlayerData != null && actionParams.MapData.CanLocalControlPlayer(actionParams.PlayerData.Id); if(canLocalControlTurnPlayer) { PresentationManager.EnqueuePendingFirstMeetNotices(actionParams.MapData, actionParams.PlayerData); PresentationManager.EnqueuePendingHakureiPaymentNotices(actionParams.MapData, actionParams.PlayerData); PresentationManager.EnqueuePendingDanegeldChoices(actionParams.MapData, actionParams.PlayerData); PresentationManager.EnqueuePendingCityLevelUpChoices(actionParams.MapData, actionParams.PlayerData); EventManager.Publish(new ShowUIBottomBottomBarNextTurn(){}); } PresentationManager.EnqueuePendingTreasureChoice(actionParams.MapData, actionParams.PlayerData); return true; } public override bool CheckCan(CommonActionParams actionParams) { if (actionParams.PlayerData == null) return false; if (actionParams.MapData.CurPlayer != null && actionParams.MapData.CurPlayer.Id == actionParams.PlayerData.Id) return false; return true; } public override bool CheckShow(CommonActionParams actionParams,out ShowType showType) { showType = ShowType.None; return false; } } public class PlayerTurnEndAction : ActionLogicBase { public PlayerTurnEndAction(CommonActionId id) : base(id) { } protected override bool Execute(CommonActionParams actionParams) { if (!CheckCan(actionParams)) return false; PlayerActionDiplomacy.RejectPendingDanegeldDemandsInTurnEnd(actionParams.MapData, actionParams.PlayerData); actionParams.MapData.OnTurnEnd(actionParams.PlayerData.Id); PlayerActionDiplomacy.ClearExpiredDanegeldRefusals(actionParams.MapData, actionParams.PlayerData); if (actionParams.MapData == Main.MapData) PresentationManager.DismissTurnBoundUIOnTurnEnd(); if(actionParams.PlayerData.IsSelfPlayer()) EventManager.Publish(new HideUIBottomBottomBarNextTurn(){}); return true; } public override bool CheckCan(CommonActionParams actionParams) { if (actionParams.PlayerData == null) return false; if (actionParams.MapData.CurPlayer == null) return false; if (actionParams.MapData.CurPlayer.Id != actionParams.PlayerData.Id) return false; return true; } public override bool CheckShow(CommonActionParams actionParams,out ShowType showType) { showType = ShowType.None; return false; } } public class AIParamControlAction : ActionLogicBase { public AIParamControlAction(CommonActionId id) : base(id) { } protected override bool Execute(CommonActionParams actionParams) { if (_actionId.AIParamType is AIParamControlType.APClear or AIParamControlType.AllClear) { actionParams.UnitData.ReduceActionPoint(ActionPointType.Attack); } if (_actionId.AIParamType is AIParamControlType.CPClear or AIParamControlType.AllClear) { actionParams.UnitData.ReduceActionPoint(ActionPointType.Capture); } if (_actionId.AIParamType is AIParamControlType.MPClear or AIParamControlType.AllClear) { actionParams.UnitData.ReduceActionPoint(ActionPointType.Move); } if (_actionId.AIParamType == AIParamControlType.AIMoney) { if (!CheckAIMoney(actionParams)) return false; // AI 难度加钱 int tt = 0; if (actionParams.MapData.MapConfig.AIDiff == AIDifficult.EASY) { actionParams.PlayerData.SpendCoin(actionParams.PlayerData.PlayerCoin / 2); } else if (actionParams.MapData.MapConfig.AIDiff == AIDifficult.NORMAL) { actionParams.PlayerData.SpendCoin(actionParams.PlayerData.PlayerCoin / 5); } else { if (actionParams.PlayerData.Turn < 10) tt = (int)actionParams.MapData.MapConfig.AIDiff; actionParams.PlayerData.AddCoin((int)actionParams.PlayerData.Turn / 5 * (int)Main.MapData.MapConfig.AIDiff + tt); actionParams.PlayerData.AddCulturePoint(tt); } return true; } return true; } public override bool CheckCan(CommonActionParams actionParams) { if (_actionId.AIParamType == AIParamControlType.AIMoney)return CheckAIMoney(actionParams); if (actionParams.UnitData == null) return false; if (_actionId.AIParamType == AIParamControlType.MPClear) return actionParams.UnitData.GetActionPoint(ActionPointType.Move) > 0; if (_actionId.AIParamType == AIParamControlType.CPClear) return actionParams.UnitData.GetActionPoint(ActionPointType.Capture) > 0; if (_actionId.AIParamType == AIParamControlType.APClear) return actionParams.UnitData.GetActionPoint(ActionPointType.Attack) > 0; if (_actionId.AIParamType == AIParamControlType.AllClear) return actionParams.UnitData.GetActionPoint(ActionPointType.Move) > 0 || actionParams.UnitData.GetActionPoint(ActionPointType.Capture) > 0 || actionParams.UnitData.GetActionPoint(ActionPointType.Attack) > 0; return false; } public override bool CheckShow(CommonActionParams actionParams,out ShowType showType) { showType = ShowType.None; return false; } private bool CheckAIMoney(CommonActionParams actionParams) { if (actionParams.PlayerData == null) return false; if (actionParams.MapData.Net.Mode == NetMode.Multi) { if (actionParams.MapData.CheckIsRealPlayer(actionParams.PlayerData.Id))return false; } else { if (actionParams.MapData.PlayerMap.SelfPlayerData.Id == actionParams.PlayerData.Id) return false; } return true; } } public class SurrenderAction : ActionLogicBase { public SurrenderAction(CommonActionId id) : base(id) { } protected override bool Execute(CommonActionParams actionParams) { actionParams.PlayerData.Surrender(actionParams.MapData); return true; } public override bool CheckCan(CommonActionParams actionParams) { if (actionParams.PlayerData == null) return false; if (!actionParams.PlayerData.IsSurvival) return false; return true; } public override bool CheckShow(CommonActionParams actionParams,out ShowType showType) { showType = ShowType.None; return false; } } public class BuyCultureCardAction : ActionLogicBase { public BuyCultureCardAction(CommonActionId id) : base(id) { } protected override bool Execute(CommonActionParams actionParams) { var map = actionParams.MapData; var player = actionParams.PlayerData; player.PlayerCultureInfo.TryBuyCultureCard(map, player, _actionId.CultureCardType); return true; } public override bool CheckCan(CommonActionParams actionParams) { if (actionParams.MainObjectType != MainObjectType.Player) return false; var map = actionParams.MapData; var player = actionParams.PlayerData; return player.PlayerCultureInfo.CheckCanBuyCultureCard(map, player, _actionId.CultureCardType); } public override bool CheckShow(CommonActionParams actionParams,out ShowType showType) { showType = ShowType.None; if (actionParams.MainObjectType != MainObjectType.Player) return false; var map = actionParams.MapData; var player = actionParams.PlayerData; return player.PlayerCultureInfo.CheckCanBuyCultureCard(map, player, _actionId.CultureCardType); } } }