3179 lines
142 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

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

/*
* @Author: 白哉
* @Description: 行为逻辑类
* @Date: 2025年04月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 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,
}
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<CommonActionId, ActionLogicBase> ActionLogicDict;
private static Dictionary<uint, ActionLogicBase> _actionLogicIdDict;
public static Dictionary<CommonActionId, ActionLogicBase> GetActionLogicDict()
{
Refresh();
return ActionLogicDict;
}
public static Dictionary<uint, ActionLogicBase> GetActionLogicIDDict()
{
Refresh();
return _actionLogicIdDict;
}
public static void RefreshPlayerAction()
{
CommonActionId commonActionId;
//填加外交行为
foreach (PlayerActionType playerActionType in System.Enum.GetValues(typeof(PlayerActionType)))
{
if (playerActionType == PlayerActionType.None) continue;
commonActionId = new CommonActionId
{
ActionType = CommonActionType.PlayerAction,
PlayerActionType = playerActionType
};
ActionLogicDict[commonActionId] = new PlayerActionDiplomacy(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, ActionLogicBase>();
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);
}
for (int i = (int)PlayerActionType.OfferAlly; i < (int)PlayerActionType.Max; i++)
{
commonActionId = new CommonActionId
{
ActionType = CommonActionType.PlayerAction,
PlayerActionType = (PlayerActionType)i,
};
ActionLogicDict[commonActionId] = new PlayerActionDiplomacy(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);
}
//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.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<uint, ActionLogicBase>();
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<ActionLogicBase> actionList)
{
param.MainObjectType = MainObjectType.Player;
actionList = new List<ActionLogicBase>();
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<ActionLogicBase> actionList)
{
param.MainObjectType = MainObjectType.Grid;
actionList = new List<ActionLogicBase>();
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<ActionLogicBase> 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<ActionLogicBase> actionList)
{
param.MainObjectType = MainObjectType.City;
actionList = new List<ActionLogicBase>();
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<ActionLogicBase> actionList)
{
param.MainObjectType = MainObjectType.Unit;
actionList = new List<ActionLogicBase>();
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) continue;
}
actionList.Add(action);
}
return actionList.Count > 0;
}
// 返回一个 unit 的移动和攻击行为
public static bool UnitHasMoveAndAttackAction(MapData map, UnitData unit, out List<AIActionBase> 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<AIActionBase>();
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<ActionLogicBase> actionList,out List<ShowType> actionCantTypeList)
{
//注意要将所有cityLevelUp直接屏蔽掉 在这里
actionList = new List<ActionLogicBase>();
actionCantTypeList = new List<ShowType>();
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<ActionLogicBase> GetActionLogicByType(CommonActionType type)
{
var actionList = new List<ActionLogicBase>();
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.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 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<GridData> _sharedAroundBuf;
private static HashSet<uint> _sharedDataUnitIdSet;
private static List<uint> _sharedRendererOnlyUnitIds;
private static List<uint> _sharedDataOnlyUnitIds;
private static List<UnitData> _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)
{
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<UnitData>();
_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<uint>();
_sharedRendererOnlyUnitIds ??= new List<uint>();
_sharedDataOnlyUnitIds ??= new List<uint>();
_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 = MemoryPack.MemoryPackSerializer.Serialize(Main.MapData);
Main.Instance.CheckMapData = MemoryPack.MemoryPackSerializer.Deserialize<MapData>(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;
//找到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 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 = new UnitFullType(UnitType.BigGuy, GiantType.None, 0);
// 注意:多人允许相同 Empire 后,下列硬编码 (Civ,Force) 分支会让多个同 Empire 玩家共享同一特殊巨人单位,符合预期保留逻辑。
if(paramPlayer.PlayerCivId == 1 && paramPlayer.PlayerForceId == 1)
fullType = new UnitFullType(UnitType.KaguyaFrenchWolf, GiantType.None, 0);
if(paramPlayer.PlayerCivId == 0 && paramPlayer.PlayerForceId == 0)
fullType = new UnitFullType(UnitType.RemiliaEgyptianKoakuma, GiantType.None, 0);
if(paramPlayer.PlayerCivId == 2 && paramPlayer.PlayerForceId == 2)
fullType = new UnitFullType(UnitType.MoriyaHebi, GiantType.None, 3);
if(paramPlayer.PlayerCivId == 3 && paramPlayer.PlayerForceId == 3)
fullType = new UnitFullType(UnitType.KomeijiIndianBigGuy, GiantType.None, 0);
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<Vector2Int>();
//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);
MoveType moveType = moveAttackType switch
{
MoveAttackType.MoveTeleport => 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;
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<Main>();
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<Vector2Int>();
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);
var originKilledByAttack = originAliveBeforeAttack && !actionParams.UnitData.IsAlive();
var targetKilledByAttack = targetAliveBeforeAttack && !actionParams.TargetUnitData.IsAlive();
//Step #3 处理攻击动画
//如果是AI预测行为returnscope.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<Vector2Int> { 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<Vector2Int> { 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在targetGridtarget在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
{
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;
}
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<Main>();
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(Main.MapData);
GridData targetGrid = actionParams.GridData;
UnitRenderer originUnitRenderer = null;
GridRenderer targetGridRenderer = 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);
}
//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 _);
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;
}
}
//处理SUWAKO的地面攻击
if (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 fullType = new UnitFullType()
{ UnitType = UnitType.MoriyaHebi, GiantType = GiantType.None, UnitLevel = 1 };
// TODO 这里的延迟产生单位形象后续要加入presentationlist
if (!actionParams.MapData.AddUnitData(targetGrid.Id, city1.Id, fullType, out suwakoUnit, 0.2f))
return false;
}
else
{
ActiveAttackActionRecorder.MarkStarted(actionParams.MapData, unit1);
}
//处理 INFILTRATE 的地面攻击:渗透单位攻击敌方空城心 → 直接偷金 + 自杀 + 生叛军跳过远程攻击Bomb动画
bool infiltrateConsumed = false;
if (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)))
{
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
if (actionParams.UnitData.GetActionPoint(ActionPointType.Attack) <= 0) 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<Main>();
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();
}
}
if(actionParams.PlayerData.IsSelfPlayer())
{
PresentationManager.EnqueuePendingCityLevelUpChoices(actionParams.MapData, actionParams.PlayerData);
EventManager.Publish(new ShowUIBottomBottomBarNextTurn(){});
}
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;
actionParams.MapData.OnTurnEnd(actionParams.PlayerData.Id);
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);
}
}
}