TH1/Unity/Assets/Scripts/TH1_Logic/AI/AIActionBase.cs

2555 lines
103 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: AI 行为类
* @Date: 2025年04月01日 星期二 14:04:35
* @Modify:
*/
using System;
using System.Collections.Generic;
using Logic.Action;
using Logic.CrashSight;
using Logic.Pool;
using MemoryPack;
using OPS.Obfuscator.Attribute;
using RuntimeData;
using TH1_Logic.Core;
using TH1_Logic.Net;
using TH1_Logic.Steam;
using UnityEngine;
using Vector2 = System.Numerics.Vector2;
namespace Logic.AI
{
public enum CountryAttackType
{
CounterattackInvasions,
BullyWeak,
ForcedExpansion,
None,
}
public enum LegionStrategyType
{
SelfDefend,
SelfAttack,
CountryDefendMatch,
CountryDefendMax,
CountryAttackMatch,
CountryAttackMax,
None,
}
public enum FreeUnitStrategyType
{
FreeDevelopment1,
Retreat,
FreeDevelopment2,
None,
}
public enum Strategy
{
Attack,
Defend,
Development,
Military,
EmergencyDefend,
Retreat,
Common,
DefendAttack,
None,
}
public enum CalculateType
{
PlayerTechDefend,
PlayerTechAttack,
PlayerTechScore,
CityLevelUpDefend,
CityTrainDefend,
CityTrainAttack,
CityDevelopment,
Ok,
UnitCollect,
UnitUpgrade,
UnitRecovery,
UnitAttackCityCenter,
UnitExplore,
UnitRetreat,
UnitMoveToTargetGrid,
UnitAuto,
UnitAttack,
LegionDefendKill,
LegionDefendMove,
UnitMoveForTrain,
LegionDefendAttack,
LegionAttackMoveInCity,
LegionAttackMoveToCity,
LegionAttackCityUnit,
LegionAttackUnit,
LegionDevelopmentMoveToCityTerritory,
LegionDevelopmentKill,
LegionDevelopmentMoveToCity,
LegionDevelopmentAttackUnit,
LegionDevelopmentMoveToOtherCity,
UnitAttackSelfCityEnemy,
UnitKill,
CityLevelUp,
TechGap,
TechResource,
CityTrainDefendAttack,
AroundGapScoreMax,
CityTrainMilitary,
MoveToNoUnitCity,
DiplomacyTech,
AroundAttackerMin,
AroundAttackerMax,
AroundAttackerHealthMin,
AroundHeroHealthMin,
UnitMoveForTrainGiant,
AroundAttackerDangerMin,
GridMiscCreateMountain,
GridMiscGrowTree,
}
[DoNotObfuscateClass]
public class AICalculatorData
{
// 核心数据
public MapData Map;
public PlayerData Player;
public Strategy CountryStrategy;
public HashSet<PlayerData> AttackPlayerSet;
// 策略集合
public Dictionary<CityData, Strategy> CityStrategy;
public Dictionary<UnitData, Strategy> FreeUnitStrategy;
public Dictionary<uint, Strategy> LegionStrategy;
public Dictionary<uint, LegionStrategyType> LegionStrategyTypes;
public Dictionary<uint, FreeUnitStrategyType> FreeUnitStrategyTypes;
// 玩家的军事分
public Dictionary<uint, float> MilitaryScore;
// 玩家的发展分
public Dictionary<uint, float> DevelopmentScore;
// 玩家对我的威胁分
public Dictionary<uint, float> ThreatScore;
// 我减去其他玩家的军事差距
public Dictionary<uint, float> MilitaryGapScore;
// 玩家对我的地缘距离
public Dictionary<uint, int> GeographicalDistance;
// 我的城市到对方城市的最近距离
public Dictionary<uint, int> AttackDistance;
// 城市3格内我方单位
public Dictionary<uint, List<UnitData>> CityDefendUnits;
// 城市3格内敌方单位
public Dictionary<uint, List<UnitData>> CityEnemyUnits;
// 城市守备分
public Dictionary<uint, float> CityDefendScore;
// 城市救援分
public Dictionary<uint, float> CityRescueScore;
// 城市敌军分
public Dictionary<uint, float> CityEnemyScore;
// 城市危险分
public Dictionary<uint, float> CityDangerScore;
// 城市加权危险分
public Dictionary<uint, float> CityRatioDangerScore;
// 别国任意城市对我城市的最近距离
public Dictionary<uint, int> PlayerBorderDistance;
// 别国城市对我任意城市的最近距离小于8的城市
public List<uint> CityBorder;
// 军团单位
public Dictionary<uint, List<UnitData>> LegionUnits;
// 自由人单位
public List<UnitData> FreeUnits;
// 军团分
public Dictionary<uint, float> LegionScore;
// 军团坐标
public Dictionary<uint, GridData> LegionGrid;
// 军团战力缺口
public Dictionary<uint, float> LegionGapScore;
// 军团不稳定值
public Dictionary<uint, float> LegionUnstableScore;
// 军团目标城市
public Dictionary<uint, uint> LegionTargetCity;
// 小兵目标格子
public Dictionary<uint, GridData> UnitTargetGrid;
// 小兵目标城市
public Dictionary<uint, uint> UnitTargetCity;
// 小兵攻击系数
public Dictionary<uint, float> UnitAttackRatio;
// 军团可达城市
public Dictionary<uint, HashSet<uint>> LegionCanMoveCities;
// 科技克制分缓存
public Dictionary<TechType, float> TechGapScore;
// 科技资源分缓存
public Dictionary<TechType, float> TechResourceScore;
public List<AIActionBase> AIActions;
public AIActionBase MaxAiAction;
public CommonActionParams TargetParam;
public Strategy TargetStrategy;
public List<uint> TargetList;
public bool UseTargetListAsMoveAnchors;
public int MaxMoveActionsPerUnit;
public HashSet<string> Marks;
// 迭代器
public List<UnitData> ForeachUnit;
public List<uint> ForeachLegion;
public List<CityData> ForeachCity;
// 复用的临时集合供静态Calculate方法使用
public List<GridData> AroundGridBuffer;
public HashSet<UnitData> TmpUnitSetBuffer;
public List<CityData> TmpCityListBuffer;
public HashSet<CityData> TmpCitySetBuffer;
// 复用临时集合减少频繁GC
private List<UnitData> _tmpUnitListBuffer1;
private List<UnitData> _tmpUnitListBuffer2;
private List<CityData> _tmpCityListBuffer2;
private List<uint> _tmpUIntListBuffer;
private List<MatchResult> _tmpMatchResultBuffer;
private List<LegionMergeResult> _tmpLegionMergeResultBuffer;
private List<GridData> _tmpGridListBuffer;
private HashSet<TechType> _tmpTechTypeSetBuffer;
private Dictionary<TechType, float> _tmpCountryTechResourceScoreCache;
private HashSet<uint> _tmpUIntSetBuffer;
private HashSet<uint> _tmpUIntSetBuffer2;
private HashSet<UnitData> _tmpUnitSetBuffer2;
private HashSet<CityData> _tmpCitySetBuffer2;
private HashSet<GridData> _tmpGridSetBuffer;
private HashSet<uint> _tmpCheckedUnitIdBuffer;
private int _reachabilityToken;
private bool _techGridResourceTemplateInit;
private Dictionary<TechType, TechGridResourceTemplate> _techGridResourceTemplateCache;
private readonly Stack<List<UnitData>> _unitListPool = new Stack<List<UnitData>>();
private readonly Stack<HashSet<uint>> _uintSetPool = new Stack<HashSet<uint>>();
private static readonly UnitType[] s_sortUnitTypes =
{
UnitType.KaguyaFrenchMokouEgg,
UnitType.KaguyaFrenchReisenIllusion
};
private struct TechGridResourceTemplate
{
public int GainBonus;
public int TreeBonus;
public int CropBonus;
public int FarmBonus;
public int MetalBonus;
public int MineBonus;
public int ShallowSeaBonus;
public bool IsEmpty =>
GainBonus == 0 &&
TreeBonus == 0 &&
CropBonus == 0 &&
FarmBonus == 0 &&
MetalBonus == 0 &&
MineBonus == 0 &&
ShallowSeaBonus == 0;
}
private static readonly GiantType[] s_sortGiantTypes =
{
GiantType.EgyptianRemilia,
GiantType.EgyptianFlandre,
GiantType.EgyptianPatchouli,
GiantType.EgyptianSakuya,
GiantType.EgyptianMeiling,
GiantType.FrenchKaguya,
GiantType.FrenchTewi,
GiantType.FrenchMokou,
GiantType.FrenchEirin,
GiantType.FrenchReisen,
GiantType.GermanySanae,
GiantType.GermanyMomiji,
GiantType.GermanyAya,
GiantType.GermanySuwako,
GiantType.GermanyKanako
};
public bool IsFinish;
public AIDiffInfo AiDiffInfo;
public AICalculatorData()
{
AttackPlayerSet = new HashSet<PlayerData>();
CityStrategy = new Dictionary<CityData, Strategy>();
FreeUnitStrategy = new Dictionary<UnitData, Strategy>();
LegionStrategy = new Dictionary<uint, Strategy>();
LegionStrategyTypes = new Dictionary<uint, LegionStrategyType>();
FreeUnitStrategyTypes = new Dictionary<uint, FreeUnitStrategyType>();
AIActions = new List<AIActionBase>();
TargetParam = new CommonActionParams();
Marks = new HashSet<string>();
ForeachUnit = new List<UnitData>();
ForeachLegion = new List<uint>();
ForeachCity = new List<CityData>();
TargetList = new List<uint>();
UseTargetListAsMoveAnchors = false;
MaxMoveActionsPerUnit = 0;
AroundGridBuffer = new List<GridData>();
TmpUnitSetBuffer = new HashSet<UnitData>();
TmpCityListBuffer = new List<CityData>();
TmpCitySetBuffer = new HashSet<CityData>();
_tmpUnitListBuffer1 = new List<UnitData>();
_tmpUnitListBuffer2 = new List<UnitData>();
_tmpCityListBuffer2 = new List<CityData>();
_tmpUIntListBuffer = new List<uint>();
_tmpMatchResultBuffer = new List<MatchResult>();
_tmpLegionMergeResultBuffer = new List<LegionMergeResult>();
_tmpGridListBuffer = new List<GridData>();
_tmpTechTypeSetBuffer = new HashSet<TechType>();
_tmpCountryTechResourceScoreCache = new Dictionary<TechType, float>();
_tmpUIntSetBuffer = new HashSet<uint>();
_tmpUIntSetBuffer2 = new HashSet<uint>();
_tmpUnitSetBuffer2 = new HashSet<UnitData>();
_tmpCitySetBuffer2 = new HashSet<CityData>();
_tmpGridSetBuffer = new HashSet<GridData>();
_tmpCheckedUnitIdBuffer = new HashSet<uint>();
_techGridResourceTemplateInit = false;
_techGridResourceTemplateCache = new Dictionary<TechType, TechGridResourceTemplate>();
MilitaryScore = new Dictionary<uint, float>();
DevelopmentScore = new Dictionary<uint, float>();
ThreatScore = new Dictionary<uint, float>();
MilitaryGapScore = new Dictionary<uint, float>();
GeographicalDistance = new Dictionary<uint, int>();
AttackDistance = new Dictionary<uint, int>();
CityDefendUnits = new Dictionary<uint, List<UnitData>>();
CityEnemyUnits = new Dictionary<uint, List<UnitData>>();
CityDefendScore = new Dictionary<uint, float>();
CityRescueScore = new Dictionary<uint, float>();
CityEnemyScore = new Dictionary<uint, float>();
CityDangerScore = new Dictionary<uint, float>();
CityRatioDangerScore = new Dictionary<uint, float>();
PlayerBorderDistance = new Dictionary<uint, int>();
CityBorder = new List<uint>();
LegionUnits = new Dictionary<uint, List<UnitData>>();
FreeUnits = new List<UnitData>();
LegionScore = new Dictionary<uint, float>();
LegionGrid = new Dictionary<uint, GridData>();
LegionGapScore = new Dictionary<uint, float>();
LegionUnstableScore = new Dictionary<uint, float>();
LegionTargetCity = new Dictionary<uint, uint>();
UnitTargetGrid = new Dictionary<uint, GridData>();
UnitTargetCity = new Dictionary<uint, uint>();
UnitAttackRatio = new Dictionary<uint, float>();
LegionCanMoveCities = new Dictionary<uint, HashSet<uint>>();
TechGapScore = new Dictionary<TechType, float>();
TechResourceScore = new Dictionary<TechType, float>();
IsFinish = false;
TargetStrategy = Strategy.None;
}
private List<UnitData> RentUnitList()
{
if (_unitListPool.Count == 0) return new List<UnitData>();
var list = _unitListPool.Pop();
list.Clear();
return list;
}
private void ReturnUnitList(List<UnitData> list)
{
if (list == null) return;
list.Clear();
_unitListPool.Push(list);
}
private HashSet<uint> RentUIntSet()
{
if (_uintSetPool.Count == 0) return new HashSet<uint>();
var set = _uintSetPool.Pop();
set.Clear();
return set;
}
private void ReturnUIntSet(HashSet<uint> set)
{
if (set == null) return;
set.Clear();
_uintSetPool.Push(set);
}
private void ClearAndRecycleUnitListDict(Dictionary<uint, List<UnitData>> dict)
{
foreach (var kv in dict) ReturnUnitList(kv.Value);
dict.Clear();
}
private void ClearAndRecycleUIntSetDict(Dictionary<uint, HashSet<uint>> dict)
{
foreach (var kv in dict) ReturnUIntSet(kv.Value);
dict.Clear();
}
public void Refresh(MapData map, PlayerData player)
{
Map = map;
Player = player;
PathFinder.ClearReachabilityCache();
_reachabilityToken = PathFinder.BuildReachabilityToken(Map, Player);
TargetStrategy = Strategy.None;
CountryStrategy = Strategy.None;
AttackPlayerSet.Clear();
IsFinish = false;
AIActions.Clear();
TargetParam = new CommonActionParams();
Marks.Clear();
ForeachUnit.Clear();
ForeachLegion.Clear();
ForeachCity.Clear();
TargetList.Clear();
UseTargetListAsMoveAnchors = false;
MaxMoveActionsPerUnit = 0;
CityStrategy.Clear();
FreeUnitStrategy.Clear();
LegionStrategy.Clear();
LegionStrategyTypes.Clear();
FreeUnitStrategyTypes.Clear();
MilitaryScore.Clear();
DevelopmentScore.Clear();
ThreatScore.Clear();
MilitaryGapScore.Clear();
GeographicalDistance.Clear();
AttackDistance.Clear();
ClearAndRecycleUnitListDict(CityDefendUnits);
ClearAndRecycleUnitListDict(CityEnemyUnits);
CityDefendScore.Clear();
CityRescueScore.Clear();
CityEnemyScore.Clear();
CityDangerScore.Clear();
CityRatioDangerScore.Clear();
PlayerBorderDistance.Clear();
CityBorder.Clear();
ClearAndRecycleUnitListDict(LegionUnits);
FreeUnits.Clear();
LegionScore.Clear();
LegionGrid.Clear();
LegionGapScore.Clear();
LegionUnstableScore.Clear();
LegionTargetCity.Clear();
UnitTargetGrid.Clear();
UnitTargetCity.Clear();
UnitAttackRatio.Clear();
ClearAndRecycleUIntSetDict(LegionCanMoveCities);
TechGapScore.Clear();
TechResourceScore.Clear();
// 国家策略
foreach (var playerData in map.PlayerMap.PlayerDataList)
{
MilitaryScore[playerData.Id] = CalMilitaryScore(playerData);
DevelopmentScore[playerData.Id] = playerData.PlayerScore;
}
foreach (var target in map.PlayerMap.PlayerDataList)
{
if (target == player) continue;
ThreatScore[target.Id] = CalThreatScore(target);
MilitaryGapScore[target.Id] = CalMilitaryGapScore(target);
GeographicalDistance[target.Id] = CalPlayerMinDistance(target);
AttackDistance[target.Id] = CalAttackDistance(target);
}
CalculateCountryStrategy();
// 城市策略
foreach (var city in Map.CityMap.CityList)
{
CalCityUnits(city);
CityDefendScore[city.Id] = CalCityDefendScore(city);
CityRescueScore[city.Id] = CalCityRescueScore(city);
CityEnemyScore[city.Id] = CalCityEnemyScore(city);
CityDangerScore[city.Id] = CalCityDangerScore(city);
CityRatioDangerScore[city.Id] = CalCityRatioDangerScore(city);
PlayerBorderDistance[city.Id] = CalPlayerBorderDistance(city);
}
CalCityBorderDistance();
CalculateCityStrategy();
var count = 0;
while (true)
{
if (CalculateLegionStrategy() && CalculateFreeUnitStrategy() && CalculateLegionMergeStrategy()) break;
count++;
if (count > 20)
{
LogSystem.LogError($"死循环了");
break;
}
}
CalculateUnitAttackRatio();
CalculateTechGapScore();
CalculateTechResourceScore();
CalculateUnitTargetCity();
TargetParam.OnParamChanged();
}
public void ClearCache()
{
IsFinish = false;
AIActions.Clear();
MaxAiAction = null;
TargetParam = new CommonActionParams();
TargetParam.MapData = Map;
TargetParam.PlayerData = Player;
TargetParam.OnParamChanged();
TargetList.Clear();
}
// 小兵目标城市统计
public void CalculateUnitTargetCity()
{
_tmpUnitListBuffer1.Clear();
Map.GetUnitDataListByPlayerId(Player.Id, _tmpUnitListBuffer1);
foreach (var unit in _tmpUnitListBuffer1)
{
if (unit.LegionId == 0) continue;
if (!LegionTargetCity.TryGetValue(unit.LegionId, out var value)) continue;
UnitTargetCity[unit.Id] = value;
}
}
// 科技资源分计算
public void CalculateTechResourceScore()
{
bool isSea = false;
_tmpGridSetBuffer.Clear();
foreach (var city in Map.CityMap.CityList)
{
foreach (var gridId in city.Territory.TerritoryArea)
{
if (!Map.GridMap.GetGridDataByGid(gridId, out var grid)) continue;
_tmpGridSetBuffer.Add(grid);
}
}
foreach (var grid in _tmpGridSetBuffer)
{
if (grid.Terrain != TerrainType.ShallowSea) continue;
isSea = true;
break;
}
_tmpUIntSetBuffer2.Clear();
Map.GetPlayerTerritoryGridIdSet(Player.Id, _tmpUIntSetBuffer2);
TmpCitySetBuffer.Clear();
Map.GetCityDataListByPlayerId(Player.Id, TmpCitySetBuffer);
var playerCityCount = TmpCitySetBuffer.Count;
_tmpCountryTechResourceScoreCache.Clear();
for (int i = (int)TechType.Climbing; i <= (int)TechType.RemiliaFreeSpirit; i++)
{
var techType = (TechType)i;
_tmpTechTypeSetBuffer.Clear();
Table.Instance.TechDataAssets.GetNextTechsNonAlloc(techType, _tmpTechTypeSetBuffer);
var score = 0f;
score += GetCountryTechResourceScoreCached(techType, _tmpGridSetBuffer, _tmpUIntSetBuffer2, playerCityCount) * 2;
foreach (var tech in _tmpTechTypeSetBuffer)
{
score += GetCountryTechResourceScoreCached(tech, _tmpGridSetBuffer, _tmpUIntSetBuffer2, playerCityCount);
}
score += CalForceTechResourceScore(techType);
TechResourceScore[techType] = score;
}
if (isSea) return;
_tmpTechTypeSetBuffer.Clear();
Table.Instance.TechDataAssets.GetNextTechsNonAlloc(TechType.Fishing, _tmpTechTypeSetBuffer);
TechResourceScore[TechType.Fishing] = 0;
foreach (var tech in _tmpTechTypeSetBuffer) TechResourceScore[tech] = 0;
Table.Instance.TechDataAssets.GetNextTechsNonAlloc(TechType.HakureiFishing, _tmpTechTypeSetBuffer);
TechResourceScore[TechType.HakureiFishing] = 0;
foreach (var tech in _tmpTechTypeSetBuffer) TechResourceScore[tech] = 0;
}
// 计算科技对阵营的资源分
public float CalForceTechResourceScore(TechType techType)
{
var forceEnum = Table.Instance.TransForceIdToForceEnum(Player.PlayerForceId);
if (forceEnum == ForceEnum.Kaguya)
{
if (techType == TechType.KaguyaHunting) return 100;
if (techType == TechType.KaguyaArcher) return 100;
if (techType == TechType.KaguyaConstruction) return 100;
if (techType == TechType.KaguyaForestry) return 100;
if (techType == TechType.KaguyaMath) return 100;
if (techType == TechType.KaguyaRoad) return 100;
if (techType == TechType.KaguyaSpiritual) return 100;
if (techType == TechType.KaguyaTrade) return 100;
}
if (forceEnum == ForceEnum.Remilia)
{
if (techType == TechType.RemiliaFarming) return 100;
if (techType == TechType.RemiliaRamming) return 100;
if (techType == TechType.RemiliaFreeSpirit) return 100;
}
if (forceEnum == ForceEnum.Kanako)
{
if (techType == TechType.KanakoMining) return 100;
if (techType == TechType.KanakoMeditation) return 100;
if (techType == TechType.KanakoPhilosophy) return 100;
if (techType == TechType.KanakoSmithery) return 100;
if (techType == TechType.KanakoRiding) return 100;
if (techType == TechType.KanakoFreeSpirit) return 100;
if (techType == TechType.KanakoRoads) return 100;
if (techType == TechType.KanakoChivalry) return 100;
if (techType == TechType.KanakoTrade) return 100;
if (techType == TechType.KanakoNavigation) return 100;
}
if (forceEnum == ForceEnum.Reimu)
{
if (techType == TechType.HakureiVikingStart) return 100;
if (techType == TechType.HakureiFishing) return 100;
if (techType == TechType.HakureiStrategy) return 100;
if (techType == TechType.HakureiRamming) return 100;
if (techType == TechType.HakureiSmithery) return 100;
if (techType == TechType.HakureiDiplomacy) return 100;
if (techType == TechType.HakureiTrade) return 100;
}
return 0;
}
// 计算科技对国家的资源分
public float CalCountryTechResourceScore(TechType techType)
{
_tmpGridSetBuffer.Clear();
foreach (var city in Map.CityMap.CityList)
{
foreach (var gridId in city.Territory.TerritoryArea)
{
if (!Map.GridMap.GetGridDataByGid(gridId, out var grid)) continue;
_tmpGridSetBuffer.Add(grid);
}
}
_tmpUIntSetBuffer2.Clear();
Map.GetPlayerTerritoryGridIdSet(Player.Id, _tmpUIntSetBuffer2);
TmpCitySetBuffer.Clear();
Map.GetCityDataListByPlayerId(Player.Id, TmpCitySetBuffer);
return CalCountryTechResourceScoreInternal(techType, _tmpGridSetBuffer, _tmpUIntSetBuffer2, TmpCitySetBuffer.Count);
}
private float GetCountryTechResourceScoreCached(TechType techType, HashSet<GridData> allCityTerritoryGridSet,
HashSet<uint> playerTerritoryGridIdSet, int playerCityCount)
{
if (_tmpCountryTechResourceScoreCache.TryGetValue(techType, out var cachedScore))
{
return cachedScore;
}
var score = CalCountryTechResourceScoreInternal(techType, allCityTerritoryGridSet, playerTerritoryGridIdSet,
playerCityCount);
_tmpCountryTechResourceScoreCache[techType] = score;
return score;
}
private float CalCountryTechResourceScoreInternal(TechType techType, HashSet<GridData> allCityTerritoryGridSet,
HashSet<uint> playerTerritoryGridIdSet, int playerCityCount)
{
float score = 0f;
if (techType == TechType.Climbing || techType == TechType.Fishing || techType == TechType.HakureiFishing || techType == TechType.Sailing)
{
foreach (var grid in allCityTerritoryGridSet)
{
if (grid.Feature == TerrainFeature.Mountain && techType == TechType.Climbing) score += 0.5f;
if (grid.Terrain == TerrainType.ShallowSea && (techType == TechType.Fishing || techType == TechType.HakureiFishing)) score += 0.5f;
if (grid.Terrain == TerrainType.DeepSea && techType == TechType.Sailing) score += 0.5f;
}
}
else if (techType == TechType.Roads)
{
score = playerCityCount * 2;
}
else if (techType == TechType.Trade || techType == TechType.HakureiTrade)
{
foreach (var id in playerTerritoryGridIdSet)
{
if (!Map.GridMap.GetGridDataByGid(id, out var grid)) continue;
if (grid.Resource is not (ResourceType.Windmill or ResourceType.Sawmill
or ResourceType.Forge or ResourceType.Preserve or ResourceType.EgyptianIrrigation)
&& (techType != TechType.HakureiTrade || grid.Resource != ResourceType.Port)) continue;
score += grid.buildingLevel;
if (techType == TechType.HakureiTrade && grid.Resource == ResourceType.Port) score += 1;
}
}
else
{
foreach (var gid in playerTerritoryGridIdSet)
{
if (!Map.GridMap.GetGridDataByGid(gid, out var grid)) continue;
//计算每一种techActions在grid下的资源分
score += CalcGridTechResourceScore(techType, grid);
}
}
return score;
}
// 计算科技对一个格子的资源分。该函数主要用于计算科技对于一个国家的资源分
public int CalcGridTechResourceScore(TechType techType, GridData gridData)
{
EnsureTechGridResourceTemplateCache();
if (!_techGridResourceTemplateCache.TryGetValue(techType, out var template))
{
return 0;
}
int score = template.GainBonus;
if (gridData.Vegetation == Vegetation.Trees) score += template.TreeBonus;
if (gridData.Resource == ResourceType.Crop) score += template.CropBonus;
if (gridData.Resource == ResourceType.Farm) score += template.FarmBonus;
if (gridData.Resource == ResourceType.Metal) score += template.MetalBonus;
if (gridData.Resource == ResourceType.Mine) score += template.MineBonus;
if (gridData.Terrain == TerrainType.ShallowSea) score += template.ShallowSeaBonus;
return score;
}
private void EnsureTechGridResourceTemplateCache()
{
if (_techGridResourceTemplateInit) return;
_techGridResourceTemplateCache.Clear();
foreach (var techInfo in Table.Instance.TechDataAssets.TechList)
{
var template = new TechGridResourceTemplate();
foreach (var atom in techInfo.TechAtomList)
{
if (!Table.Instance.TechDataAssets.GetTechAtomInfo(atom, out var techAtomInfo)) continue;
if (!techAtomInfo.EnableAction) continue;
foreach (var action in techAtomInfo.TechActions)
{
if (action.ActionType == CommonActionType.Gain)
{
if (!Table.Instance.GridAndResourceDataAssets.GetResourceInfo(action.ResourceType,
out var gainResourceInfo))
continue;
template.GainBonus += gainResourceInfo.Exp;
continue;
}
if (action.ActionType != CommonActionType.Build) continue;
if (!Table.Instance.GridAndResourceDataAssets.GetResourceInfo(action.ResourceType,
out _))
continue;
switch (action.ResourceType)
{
case ResourceType.LumberHut:
case ResourceType.Sawmill:
template.TreeBonus += 1;
break;
case ResourceType.Farm:
template.CropBonus += 2;
break;
case ResourceType.Mine:
template.MetalBonus += 2;
break;
case ResourceType.Windmill:
template.CropBonus += 1;
template.FarmBonus += 1;
break;
case ResourceType.Forge:
template.MetalBonus += 2;
template.MineBonus += 2;
break;
case ResourceType.Port:
template.ShallowSeaBonus += 1;
break;
}
}
}
if (!template.IsEmpty)
{
_techGridResourceTemplateCache[techInfo.TechType] = template;
}
}
_techGridResourceTemplateInit = true;
}
// 科技克制分计算
public void CalculateTechGapScore()
{
bool isSea = false;
_tmpGridSetBuffer.Clear();
foreach (var city in Map.CityMap.CityList)
{
foreach (var gridId in city.Territory.TerritoryArea)
{
if (!Map.GridMap.GetGridDataByGid(gridId, out var grid)) continue;
_tmpGridSetBuffer.Add(grid);
}
}
foreach (var grid in _tmpGridSetBuffer)
{
if (grid.Terrain != TerrainType.ShallowSea) continue;
isSea = true;
break;
}
for (int i = (int)TechType.Climbing; i <= (int)TechType.RemiliaFreeSpirit; i++)
{
var techType = (TechType)i;
_tmpTechTypeSetBuffer.Clear();
Table.Instance.TechDataAssets.GetNextTechsNonAlloc(techType, _tmpTechTypeSetBuffer);
var score = 0f;
foreach (var player in Map.PlayerMap.PlayerDataList)
{
if (player == Player) continue;
score += CalCountryTechGapScore(techType, player.Id) * 2;
}
foreach (var tech in _tmpTechTypeSetBuffer)
{
foreach (var player in Map.PlayerMap.PlayerDataList)
{
if (player == Player) continue;
score += CalCountryTechGapScore(tech, player.Id);
}
}
TechGapScore[techType] = score;
}
if (isSea) return;
_tmpTechTypeSetBuffer.Clear();
Table.Instance.TechDataAssets.GetNextTechsNonAlloc(TechType.Fishing, _tmpTechTypeSetBuffer);
TechResourceScore[TechType.Fishing] = 0;
foreach (var tech in _tmpTechTypeSetBuffer) TechResourceScore[tech] = 0;
}
// 计算科技对某个国家的克制分
public float CalCountryTechGapScore(TechType techType, uint playerId)
{
GetUnitTypeByTech(techType, out var unitType, out var giantType, out var unitLevel);
// 没有对应兵种的科技直接返回,避免 GetUnitTypeInfo 失败分支里的临时对象分配
if (unitType == UnitType.None) return 0;
if (!Table.Instance.UnitTypeDataAssets.GetUnitTypeInfo(unitType, giantType, unitLevel, out var info))
return 0;
var fullType = new UnitFullType();
fullType.UnitType = info.UnitType;
fullType.GiantType = info.GiantType;
fullType.UnitLevel = info.UnitLevel;
TmpCitySetBuffer.Clear();
Map.GetCityDataListByPlayerId(Player.Id, TmpCitySetBuffer);
if (TmpCitySetBuffer.Count == 0) return 0;
_tmpUnitListBuffer1.Clear();
Map.GetUnitDataListByPlayerId(playerId, _tmpUnitListBuffer1);
var score = 0f;
var targetFullType = new UnitFullType();
foreach (var targetUnit in _tmpUnitListBuffer1)
{
if (!Table.Instance.UnitTypeDataAssets.GetUnitTypeInfo(targetUnit.UnitType, targetUnit.GiantType,
targetUnit.UnitLevel, out var targetInfo))
continue;
targetFullType.UnitType = targetInfo.UnitType;
targetFullType.GiantType = targetInfo.GiantType;
targetFullType.UnitLevel = targetInfo.UnitLevel;
if (!Map.GetGridDataByUnitId(targetUnit.Id, out var unitGrid)) continue;
var minDis = int.MaxValue;
foreach (var city in TmpCitySetBuffer)
{
if (!Map.GetGridDataByCityId(city.Id, out var cityGrid)) continue;
var dis = Map.GridMap.CalcDistance(unitGrid, cityGrid);
if (dis < minDis) minDis = dis;
}
score += CalUnitCounterScore(fullType, targetFullType);
score -= CalUnitCounterScore(targetFullType, fullType);
score /= minDis;
}
return score;
}
// 计算军团战略
public bool CalculateLegionStrategy()
{
LegionStrategy.Clear();
LegionStrategyTypes.Clear();
FreeUnitStrategyTypes.Clear();
ClearAndRecycleUnitListDict(LegionUnits);
FreeUnits.Clear();
LegionScore.Clear();
LegionGrid.Clear();
LegionGapScore.Clear();
LegionUnstableScore.Clear();
LegionTargetCity.Clear();
UnitTargetGrid.Clear();
UnitTargetCity.Clear();
UnitAttackRatio.Clear();
ClearAndRecycleUIntSetDict(LegionCanMoveCities);
_tmpUnitListBuffer1.Clear();
Map.GetUnitDataListByPlayerId(Player.Id, _tmpUnitListBuffer1);
foreach (var unit in _tmpUnitListBuffer1)
{
if (unit.LegionId == 0) FreeUnits.Add(unit);
else
{
if (!LegionUnits.ContainsKey(unit.LegionId)) LegionUnits[unit.LegionId] = RentUnitList();
LegionUnits[unit.LegionId].Add(unit);
}
}
foreach (var kv in LegionUnits)
{
var score = 0f;
foreach (var unit in kv.Value)
{
score += unit.GetMilitary();
}
LegionScore[kv.Key] = score;
}
foreach (var kv in LegionUnits)
{
var score = LegionScore[kv.Key];
var vec = new Vector2();
foreach (var unit in kv.Value)
{
if (!Map.GetGridDataByUnitId(unit.Id, out var grid)) continue;
if (score == 0)
{
var ratio = 1f / kv.Value.Count;
vec += new Vector2(grid.Pos.X, grid.Pos.Y) * ratio;
}
else
{
var ratio = unit.GetMilitary() / score;
vec += new Vector2(grid.Pos.X, grid.Pos.Y) * ratio;
}
}
var center = new Vector2Int((int)Math.Round(vec.X), (int)Math.Round(vec.Y));
if (!Map.GridMap.GetGridDataByPos(center.x, center.y, out var centerGrid))
{
LogSystem.LogError($"Can not find center grid for legion {center.x},{center.y}");
}
LegionGrid[kv.Key] = centerGrid;
}
foreach (var kv in LegionUnits)
{
LegionCanMoveCities[kv.Key] = RentUIntSet();
foreach (var city in Map.CityMap.CityList)
{
if (!Map.GetGridDataByCityId(city.Id, out var cityGrid)) continue;
if (!LegionGrid.ContainsKey(kv.Key)) continue;
var canReach = PathFinder.CanReachFast((int)Map.MapConfig.Width, (int)Map.MapConfig.Height,
new(cityGrid.Pos.X, cityGrid.Pos.Y),
new(LegionGrid[kv.Key].Pos.X, LegionGrid[kv.Key].Pos.Y), Map, Player, null, _reachabilityToken);
if (!canReach) continue;
LegionCanMoveCities[kv.Key].Add(city.Id);
}
}
foreach (var kv in LegionUnits)
{
var sunDis = 0;
foreach (var unit in kv.Value)
{
if (!Map.GetGridDataByUnitId(unit.Id, out var grid)) continue;
sunDis += Map.GridMap.CalcDistance(grid, LegionGrid[kv.Key]);
}
LegionUnstableScore[kv.Key] = sunDis * kv.Value.Count / 5f;
}
// 自主战略
TmpCitySetBuffer.Clear();
Map.GetCityDataListByPlayerId(Player.Id, TmpCitySetBuffer);
foreach (var kv in LegionUnits)
{
_tmpCitySetBuffer2.Clear();
var centerGrid = LegionGrid[kv.Key];
AroundGridBuffer.Clear();
Map.GridMap.GetAroundGridData(4, 4, centerGrid, AroundGridBuffer);
foreach (var grid in AroundGridBuffer)
{
if (!Map.GetCityDataByGid(grid.Id, out var cityData)) continue;
_tmpCitySetBuffer2.Add(cityData);
}
// 防守-城市协防
var minDis = int.MaxValue;
CityData defendCity = null;
foreach (var cityPair in CityStrategy)
{
if (cityPair.Value != Strategy.EmergencyDefend) continue;
if (!LegionCanMoveCities[kv.Key].Contains(cityPair.Key.Id)) continue;
if (!Map.GetGridDataByCityId(cityPair.Key.Id, out var grid)) continue;
var dis = Map.GridMap.CalcDistance(grid, centerGrid);
if (dis >= minDis) continue;
minDis = dis;
defendCity = cityPair.Key;
}
if (defendCity != null)
{
LegionTargetCity[kv.Key] = defendCity.Id;
LegionStrategy[kv.Key] = Strategy.Defend;
LegionStrategyTypes[kv.Key] = LegionStrategyType.SelfDefend;
continue;
}
// 进攻-城市进攻
foreach (var targetCity in _tmpCitySetBuffer2)
{
if (TmpCitySetBuffer.Contains(targetCity)) continue;
if (!LegionCanMoveCities[kv.Key].Contains(targetCity.Id)) continue;
var score = LegionScore[kv.Key] - CityDefendScore[targetCity.Id];
foreach (var unit in kv.Value)
{
foreach (var targetUnit in CityDefendUnits[targetCity.Id])
{
score += CalUnitCounterScore(unit, targetUnit);
score -= CalUnitCounterScore(targetUnit, unit);
}
}
if (score >= 5)
{
LegionTargetCity[kv.Key] = targetCity.Id;
LegionStrategy[kv.Key] = Strategy.Attack;
LegionStrategyTypes[kv.Key] = LegionStrategyType.SelfAttack;
break;
}
}
}
// 遵循国家战略
if (LegionTargetCity.Count == LegionUnits.Count) return true;
_tmpUIntListBuffer.Clear();
foreach (var kv in LegionUnits)
{
if (LegionTargetCity.ContainsKey(kv.Key)) continue;
_tmpUIntListBuffer.Add(kv.Key);
}
// 国家防守
_tmpMatchResultBuffer.Clear();
if (CountryStrategy == Strategy.Defend)
{
foreach (var legionId in _tmpUIntListBuffer)
{
foreach (var city in TmpCitySetBuffer)
{
if (!LegionCanMoveCities[legionId].Contains(city.Id)) continue;
if (!Map.GetGridDataByCityId(city.Id, out var cityGrid)) continue;
var dis = Map.GridMap.CalcDistance(cityGrid, LegionGrid[legionId]) + 1;
var score = CityDangerScore[city.Id] / dis;
_tmpMatchResultBuffer.Add(new MatchResult(legionId, city.Id, score));
}
}
_tmpMatchResultBuffer.Sort((a, b) => b.Score.CompareTo(a.Score));
foreach (var result in _tmpMatchResultBuffer)
{
if (LegionTargetCity.ContainsKey(result.LegionId)) continue;
LegionTargetCity[result.LegionId] = result.CityId;
LegionStrategy[result.LegionId] = Strategy.Defend;
LegionStrategyTypes[result.LegionId] = LegionStrategyType.CountryDefendMatch;
}
}
// 国家进攻
if (CountryStrategy == Strategy.Attack)
{
_tmpMatchResultBuffer.Clear();
foreach (var legionId in _tmpUIntListBuffer)
{
foreach (var cityId in CityBorder)
{
if (!Map.GetPlayerIdByCityId(cityId, out var ownerId)) continue;
if (!Map.PlayerMap.GetPlayerDataByPlayerID(ownerId, out var owner)) continue;
if (!AttackPlayerSet.Contains(owner)) continue;
if (!LegionCanMoveCities[legionId].Contains(cityId)) continue;
if (!Map.GetGridDataByCityId(cityId, out var cityGrid)) continue;
var dis = Map.GridMap.CalcDistance(cityGrid, LegionGrid[legionId]) + 1;
var score = LegionScore[legionId] / dis * 2 - CityDefendScore[cityId] - CityRescueScore[cityId];
_tmpMatchResultBuffer.Add(new MatchResult(legionId, cityId, score));
}
}
_tmpMatchResultBuffer.Sort((a, b) => b.Score.CompareTo(a.Score));
foreach (var result in _tmpMatchResultBuffer)
{
if (LegionTargetCity.ContainsKey(result.LegionId)) continue;
LegionTargetCity[result.LegionId] = result.CityId;
LegionStrategy[result.LegionId] = Strategy.Attack;
LegionStrategyTypes[result.LegionId] = LegionStrategyType.CountryAttackMatch;
}
}
// 计算战力缺口
foreach (var kv in LegionUnits)
{
var combatGap = 0f;
if (LegionTargetCity.ContainsKey(kv.Key))
{
if (LegionStrategy[kv.Key] == Strategy.Attack)
{
combatGap = Mathf.Max(CityDefendScore[LegionTargetCity[kv.Key]] - LegionScore[kv.Key], 0);
}
if (LegionStrategy[kv.Key] == Strategy.Defend)
{
combatGap = Mathf.Max(CityEnemyScore[LegionTargetCity[kv.Key]] - LegionScore[kv.Key], 0);
}
}
else
{
combatGap = Mathf.Max(5 - kv.Value.Count, 15 - LegionScore[kv.Key]);
combatGap = Mathf.Max(0, combatGap);
}
LegionGapScore[kv.Key] = combatGap;
}
if (CountryStrategy == Strategy.Development)
{
foreach (var legionId in _tmpUIntListBuffer)
{
if (LegionUnits[legionId].Count == 1)
{
LegionUnits[legionId][0].LegionId = 0;
Debug.Log($"解散军团{legionId}");
return false;
}
LegionStrategy[legionId] = Strategy.Development;
if (LegionUnstableScore[legionId] >= 3)
{
var maxDis = 0;
UnitData maxUnit = null;
foreach (var unit in LegionUnits[legionId])
{
if (!Map.GetGridDataByUnitId(unit.Id, out var unitGrid)) continue;
var dis = Map.GridMap.CalcDistance(unitGrid, LegionGrid[legionId]);
if (dis > maxDis)
{
maxDis = dis;
maxUnit = unit;
}
}
maxUnit.LegionId = 0;
Debug.Log($"{legionId} 军团抛弃小兵 {maxUnit.Id}");
return false;
}
}
}
foreach (var kv in LegionUnits) LegionStrategy.TryAdd(kv.Key, Strategy.Development);
return true;
}
// 计算自由人策略
public bool CalculateFreeUnitStrategy()
{
if (FreeUnits.Count == 0) return true;
TmpCitySetBuffer.Clear();
_tmpUnitListBuffer1.Clear();
Map.GetUnitDataListByPlayerId(Player.Id, _tmpUnitListBuffer1);
Map.GetCityDataListByPlayerId(Player.Id, TmpCitySetBuffer);
foreach (var unit in FreeUnits)
{
// 如果当前在遗迹/村庄/敌方城市中心/可采集的海星上,则确定自由发展战略
if (!Map.GetGridDataByUnitId(unit.Id, out var unitGrid)) continue;
if (unitGrid.Resource == ResourceType.Treasure ||
(unitGrid.Resource == ResourceType.Starfish && Player.TechTree.CheckIfHasTech(TechType.Navigation)))
{
FreeUnitStrategy[unit] = Strategy.Development;
FreeUnitStrategyTypes[unit.Id] = FreeUnitStrategyType.FreeDevelopment1;
continue;
}
if (unitGrid.Resource == ResourceType.CityCenter)
{
if (!Map.GetCityDataByGid(unitGrid.Id, out var city))
{
FreeUnitStrategy[unit] = Strategy.Development;
FreeUnitStrategyTypes[unit.Id] = FreeUnitStrategyType.FreeDevelopment1;
continue;
}
if (city != null && !TmpCitySetBuffer.Contains(city))
{
FreeUnitStrategy[unit] = Strategy.Development;
FreeUnitStrategyTypes[unit.Id] = FreeUnitStrategyType.FreeDevelopment1;
continue;
}
}
// 如果3寻路距离内存在一个军团有战力缺口则加入该军团
uint legion = 0;
int dis = int.MaxValue;
foreach (var kv in LegionGapScore)
{
if (kv.Value <= 0) continue;
if (Map.GridMap.CalcDistance(LegionGrid[kv.Key], unitGrid) > 4) continue;
if (!PathFinder.FindPathLengthWithin((int)Map.MapConfig.Width, (int)Map.MapConfig.Height,
new(unitGrid.Pos.X, unitGrid.Pos.Y),
new(LegionGrid[kv.Key].Pos.X, LegionGrid[kv.Key].Pos.Y), 3, Map, Player, out var pathLength, unit, _reachabilityToken))
continue;
if (pathLength < dis)
{
legion = kv.Key;
dis = pathLength;
}
}
if (legion != 0)
{
unit.LegionId = legion;
return false;
}
// 如果2直线距离内交战国的敌军总分-友军总分>=6且自由人距离最近的我方城市中心距离>2则执行撤退战略
AroundGridBuffer.Clear();
Map.GridMap.GetAroundGridData(2, 2, unitGrid, AroundGridBuffer);
var score = 0f;
foreach (var aroundGrid in AroundGridBuffer)
{
if (!aroundGrid.VisibleUnit(Map,Player, out var attacker)) continue;
if (!_tmpUnitListBuffer1.Contains(attacker)) score += attacker.GetMilitary();
else score -= attacker.GetMilitary();
}
if (score > 6f)
{
CityData target = null;
int minDis = int.MaxValue;
GridData targetGrid = null;
foreach (var city in TmpCitySetBuffer)
{
if (!Map.GetGridDataByCityId(city.Id, out var cityGrid)) continue;
var distance = Map.GridMap.CalcDistance(cityGrid, unitGrid);
if (distance <= 2 || distance >= minDis) continue;
minDis = distance;
target = city;
targetGrid = cityGrid;
}
if (target != null)
{
FreeUnitStrategy[unit] = Strategy.Retreat;
FreeUnitStrategyTypes[unit.Id] = FreeUnitStrategyType.Retreat;
UnitTargetGrid[unit.Id] = targetGrid;
UnitTargetCity[unit.Id] = target.Id;
continue;
}
}
if (CountryStrategy == Strategy.Attack || CountryStrategy == Strategy.Defend)
{
uint legionId = 1;
foreach (var kv in LegionUnits)
{
if (legionId <= kv.Key) legionId = kv.Key + 1;
}
unit.LegionId = legionId;
Debug.Log($"成立");
return false;
}
// 防守-城市协防
foreach (var cityPair in CityStrategy)
{
if (cityPair.Value != Strategy.EmergencyDefend) continue;
if (!Map.GetGridDataByCityId(cityPair.Key.Id, out var grid)) continue;
var defendDis = Map.GridMap.CalcDistance(grid, unitGrid);
if (defendDis > 4) continue;
if (!PathFinder.FindPathLengthWithin((int)Map.MapConfig.Width, (int)Map.MapConfig.Height,
new(unitGrid.Pos.X, unitGrid.Pos.Y), new(grid.Pos.X, grid.Pos.Y), 4, Map, Player, out _, unit, _reachabilityToken))
continue;
uint legionId = 1;
foreach (var kv in LegionUnits)
{
if (legionId <= kv.Key) legionId = kv.Key + 1;
}
unit.LegionId = legionId;
//Debug.Log($"成立");
return false;
}
FreeUnitStrategy[unit] = Strategy.Development;
FreeUnitStrategyTypes[unit.Id] = FreeUnitStrategyType.FreeDevelopment2;
}
return true;
}
// 计算军团合并策略
public bool CalculateLegionMergeStrategy()
{
_tmpLegionMergeResultBuffer.Clear();
foreach (var origin in LegionUnits)
{
foreach (var target in LegionUnits)
{
if (origin.Key == target.Key) continue;
var combatGap = Mathf.Max(5 - target.Value.Count, 15 - LegionScore[target.Key]);
combatGap = Mathf.Max(0, combatGap);
var x = (LegionGrid[origin.Key].Pos.X * origin.Value.Count +
LegionGrid[target.Key].Pos.X * target.Value.Count) /
(float)(origin.Value.Count + target.Value.Count);
var y = (LegionGrid[origin.Key].Pos.Y * origin.Value.Count +
LegionGrid[target.Key].Pos.Y * target.Value.Count) /
(float)(origin.Value.Count + target.Value.Count);
var center = new Vector2Int((int)Math.Round(x), (int)Math.Round(y));
if (!Map.GridMap.GetGridDataByPos(center.x, center.y, out var centerGrid)) continue;
var disSum = 0;
foreach (var unit in origin.Value)
{
if (!Map.GetGridDataByUnitId(unit.Id, out var unitGrid)) continue;
disSum += Map.GridMap.CalcDistance(unitGrid, centerGrid);
}
foreach (var unit in target.Value)
{
if (!Map.GetGridDataByUnitId(unit.Id, out var unitGrid)) continue;
disSum += Map.GridMap.CalcDistance(unitGrid, centerGrid);
}
var unstableScore = 2 * disSum * (origin.Value.Count + target.Value.Count) / 5f -
LegionUnstableScore[origin.Key] - LegionUnstableScore[target.Key];
var score = -Mathf.Abs(LegionScore[origin.Key] - combatGap) - unstableScore;
if (score <= 0) continue;
_tmpLegionMergeResultBuffer.Add(new LegionMergeResult(origin.Key, target.Key, score));
}
}
_tmpLegionMergeResultBuffer.Sort((a, b) => b.Score.CompareTo(a.Score));
foreach (var result in _tmpLegionMergeResultBuffer)
{
foreach (var unit in LegionUnits[result.OriginId]) unit.LegionId = result.TargetId;
LogSystem.LogInfo($"军团合并 {result.OriginId} 合并入 {result.TargetId}");
return false;
}
return true;
}
// 刷新小兵攻击系数
public void CalculateUnitAttackRatio()
{
TmpCitySetBuffer.Clear();
Map.GetCityDataListByPlayerId(Player.Id, TmpCitySetBuffer);
foreach (var unit in Map.UnitMap.UnitList)
{
var ratio = AiDiffInfo.LegionUnitDevelopment;
if (unit.LegionId != 0)
{
if (LegionStrategy.TryGetValue(unit.LegionId, out var value))
{
if (value == Strategy.Attack) ratio = AiDiffInfo.LegionUnitAttack;
if (value == Strategy.Development) ratio = AiDiffInfo.LegionUnitDevelopment;
if (value == Strategy.Defend) ratio = AiDiffInfo.LegionUnitDefend;
}
}
else
{
if (FreeUnitStrategy.TryGetValue(unit, out var value))
{
if (value == Strategy.Retreat) ratio = AiDiffInfo.FreeUnitRetreat;
else ratio = AiDiffInfo.FreeUnitOther;
}
}
if (Map.GetGridDataByUnitId(unit.Id, out var unitGrid) &&
Map.GetCityDataByGid(unitGrid.Id, out var city) &&
TmpCitySetBuffer.Contains(city)) ratio = AiDiffInfo.UnitInCity;
UnitAttackRatio[unit.Id] = ratio;
}
}
// 计算玩家的军事分
private float CalMilitaryScore(PlayerData player)
{
var score = 0f;
_tmpUnitListBuffer1.Clear();
Map.GetUnitDataListByPlayerId(player.Id, _tmpUnitListBuffer1);
foreach (var unit in _tmpUnitListBuffer1) score += unit.GetMilitary();
return score;
}
// 计算威胁分
private int CalThreatScore(PlayerData target)
{
int score = 0;
if (Player.LastAttackPlayers.Contains(target.Id)) score += 2;
_tmpUIntSetBuffer.Clear();
Map.GetPlayerTerritoryGridIdSet(Player.Id, _tmpUIntSetBuffer);
_tmpGridListBuffer.Clear();
foreach (var gridId in _tmpUIntSetBuffer)
{
if (!Map.GridMap.GetGridDataByGid(gridId, out var grid)) continue;
_tmpGridListBuffer.Add(grid);
}
_tmpUnitListBuffer1.Clear();
Map.GetUnitDataListByPlayerId(target.Id, _tmpUnitListBuffer1);
foreach (var unit in _tmpUnitListBuffer1)
{
if (!Map.GetGridDataByUnitId(unit.Id, out var unitGrid)) continue;
if (_tmpUIntSetBuffer.Contains(unitGrid.Id))
{
score += 2;
continue;
}
foreach (var grid in _tmpGridListBuffer)
{
//TODO 复杂度可从O(n)降低到O(8)
var distance = Map.GridMap.CalcDistance(grid, unitGrid);
if (distance != 1) continue;
score += 1;
break;
}
}
return score;
}
// 计算军事差距
private float CalMilitaryGapScore(PlayerData target)
{
_tmpUnitListBuffer1.Clear();
_tmpUnitListBuffer2.Clear();
Map.GetUnitDataListByPlayerId(Player.Id, _tmpUnitListBuffer1);
Map.GetUnitDataListByPlayerId(target.Id, _tmpUnitListBuffer2);
float score = MilitaryScore[Player.Id] - MilitaryScore[target.Id];
foreach (var selfUnit in _tmpUnitListBuffer1)
{
foreach (var targetUnit in _tmpUnitListBuffer2)
{
score += CalUnitCounterScore(selfUnit, targetUnit);
score -= CalUnitCounterScore(targetUnit, selfUnit);
}
}
return score;
}
// 计算两个小兵的克制分
public float CalUnitCounterScore(UnitData self, UnitData target)
{
if (self.GetHealthRatio() <= 0.5) return CalUnitCounterScore(self.UnitFullType, target.UnitFullType) * 0.5f;
return CalUnitCounterScore(self.UnitFullType, target.UnitFullType);
}
// 计算两个小兵的克制分 克制分表 克制表
public float CalUnitCounterScore(UnitFullType selfType, UnitFullType targetType)
{
var self = selfType.UnitType;
var target = targetType.UnitType;
// 兵种转换
// KaguyaFrenchAnimalWarrior视作Swordsman剑士
if (selfType.UnitType == UnitType.KaguyaFrenchAnimalWarrior) self = UnitType.Swordsman;
if (targetType.UnitType == UnitType.KaguyaFrenchAnimalWarrior) target = UnitType.Swordsman;
// MoriyaRider视作Rider游骑兵
if (selfType.UnitType == UnitType.MoriyaRider) self = UnitType.Rider;
if (targetType.UnitType == UnitType.MoriyaRider) target = UnitType.Rider;
// MoriyaKnight视作Knight骑士
if (selfType.UnitType == UnitType.MoriyaKnight) self = UnitType.Knights;
if (targetType.UnitType == UnitType.MoriyaKnight) target = UnitType.Knights;
// RemiliaEgyptianKoakuma视作Swordsman剑士
if (selfType.UnitType == UnitType.RemiliaEgyptianKoakuma) self = UnitType.Swordsman;
if (targetType.UnitType == UnitType.RemiliaEgyptianKoakuma) target = UnitType.Swordsman;
// RemiliaEgyptianKoakumaLion视作BigGuy巨人
if (selfType.UnitType == UnitType.RemiliaEgyptianKoakumaLion) self = UnitType.BigGuy;
if (targetType.UnitType == UnitType.RemiliaEgyptianKoakumaLion) target = UnitType.BigGuy;
// KaguyaFrenchWolf视作BigGuy巨人
if (selfType.UnitType == UnitType.KaguyaFrenchWolf) self = UnitType.BigGuy;
if (targetType.UnitType == UnitType.KaguyaFrenchWolf) target = UnitType.BigGuy;
// MoriyaHebi 根据等级视作不同兵种
if (selfType.UnitType == UnitType.MoriyaHebi)
{
self = selfType.UnitLevel switch
{
1 => UnitType.Ship,
2 => UnitType.Warrior,
3 => UnitType.Swordsman,
4 => UnitType.BigGuy,
5 => UnitType.BigGuy,
_ => selfType.UnitType,
};
}
if (targetType.UnitType == UnitType.MoriyaHebi)
{
target = targetType.UnitLevel switch
{
1 => UnitType.Ship,
2 => UnitType.Warrior,
3 => UnitType.Swordsman,
4 => UnitType.BigGuy,
5 => UnitType.BigGuy,
_ => targetType.UnitType,
};
}
// KomeijiIndian船视作普通船
if (self == UnitType.KomeijiIndianShip) self = UnitType.Ship;
if (target == UnitType.KomeijiIndianShip) target = UnitType.Ship;
if (self == UnitType.KomeijiIndianBomberShip) self = UnitType.BomberShip;
if (target == UnitType.KomeijiIndianBomberShip) target = UnitType.BomberShip;
if (self == UnitType.Archer && target == UnitType.Warrior) return 1;
if (self == UnitType.Swordsman && target == UnitType.Warrior) return 1f;
if (self == UnitType.Rider && target == UnitType.Archer) return 1;
if (self == UnitType.Knights && target == UnitType.Archer) return 3;
if (self == UnitType.Archer && target == UnitType.Defender) return 1f;
if (self == UnitType.Catapult && target == UnitType.Defender) return 2;
if (self == UnitType.Swordsman && target == UnitType.Defender) return 1;
if (self == UnitType.RammerShip && target == UnitType.Defender) return 1;
if (self == UnitType.Ship && target == UnitType.Defender) return 1f;
if (self == UnitType.BomberShip && target == UnitType.Defender) return 2f;
if (self == UnitType.Archer && target == UnitType.Rider) return 1f;
if (self == UnitType.Rider && target == UnitType.Catapult) return 1;
if (self == UnitType.Knights && target == UnitType.Catapult) return 3;
if (self == UnitType.Catapult && target == UnitType.RammerShip) return 1;
if (self == UnitType.Ship && target == UnitType.RammerShip) return 1f;
if (self == UnitType.Catapult && target == UnitType.Ship) return 1;
if (self == UnitType.RammerShip && target == UnitType.Ship) return 1;
if (self == UnitType.Catapult && target == UnitType.BomberShip) return 1;
if (self == UnitType.Catapult && (target == UnitType.Giant || target == UnitType.BigGuy)) return 2;
if (self == UnitType.Swordsman && (target == UnitType.Giant || target == UnitType.BigGuy)) return 1;
if (self == UnitType.BomberShip && (target == UnitType.Giant || target == UnitType.BigGuy)) return 2;
return 0;
}
// 计算两个小兵的军事差距分
public float CalUnitMilitaryGapScore(MapData map, UnitData self, UnitData target)
{
var score = self.GetMilitary() - target.GetMilitary();
score += CalUnitCounterScore(self, target);
if (map.GetGridDataByUnitId(self.Id, out var selfGrid) &&
map.GetGridDataByUnitId(target.Id, out var targetGrid))
{
var selfMag = self.GetSkill(SkillType.DASH, out _)
? self.GetAttackRange(map) + self.GetActionPoint(ActionPointType.Move)
: self.GetAttackRange(map);
var dis = map.GridMap.CalcDistance(selfGrid, targetGrid);
if (dis <= selfMag)
{
var dmg = Table.Instance.CalcDamage(map, self, target);
var attackScore = (float)dmg / target.GetMaxHealth() * target.GetCost();
score += Mathf.Min(5, attackScore);
}
var targetMag = target.GetSkill(SkillType.DASH, out _)
? target.GetAttackRange(map) + self.GetActionPoint(ActionPointType.Move)
: target.GetAttackRange(map);
if (dis <= targetMag)
{
var dmg = Table.Instance.CalcDamage(map, target, self);
var attackScore = (float)dmg / self.GetMaxHealth() * self.GetCost();
score -= Mathf.Min(self.GetMilitary(), attackScore);
}
}
if (self.GetAttackRange(map) == 1 && map.GetPlayerIdByUnitId(self.Id, out var selfPlayerId))
{
_tmpUnitSetBuffer2.Clear();
Map.GetUnitDataListByPlayerId(selfPlayerId, _tmpUnitSetBuffer2);
var count = 0;
foreach (var unit in _tmpUnitSetBuffer2)
if (unit.GetAttackRange(map) == 1)
count++;
if (count / (float)_tmpUnitSetBuffer2.Count < 0.6f)
score += 6 - count / (float)_tmpUnitSetBuffer2.Count * 10;
}
if (self.UnitType == UnitType.Catapult && map.GetGridDataByUnitId(self.Id, out var grid))
{
AroundGridBuffer.Clear();
Map.GridMap.GetAroundGridData(1, 1, grid, AroundGridBuffer);
bool isSea = true;
foreach (var aroundGrid in AroundGridBuffer)
{
if (aroundGrid.Terrain == TerrainType.Land) isSea = false;
}
if (isSea) score++;
}
return score;
}
//TODO 这个要迭代掉,要改成读数据
// 计算科技对应的兵种
public void GetUnitTypeByTech(TechType techType, out UnitType unitType, out GiantType giantType,
out uint unitLevel)
{
unitType = UnitType.None;
giantType = GiantType.None;
unitLevel = 0;
if (techType == TechType.Archery) unitType = UnitType.Archer;
if (techType == TechType.Riding) unitType = UnitType.Rider;
if (techType == TechType.Strategy) unitType = UnitType.Defender;
if (techType == TechType.HakureiStrategy) unitType = UnitType.HakureiRoundShieldman;
if (techType == TechType.Chivalry) unitType = UnitType.Knights;
if (techType == TechType.Mathematics) unitType = UnitType.Catapult;
if (techType == TechType.Smithery) unitType = UnitType.Swordsman;
if (techType == TechType.HakureiSmithery) unitType = UnitType.Swordsman;
if (techType == TechType.Diplomacy) unitType = UnitType.Cloak;
if (techType == TechType.HakureiDiplomacy) unitType = UnitType.Cloak;
if (techType == TechType.Philosophy) unitType = UnitType.Minder;
if (techType == TechType.Fishing) unitType = UnitType.Boat;
if (techType == TechType.HakureiFishing) unitType = UnitType.HakureiKarvi;
if (techType == TechType.Sailing) unitType = UnitType.Ship;
if (techType == TechType.Ramming) unitType = UnitType.RammerShip;
if (techType == TechType.HakureiRamming) unitType = UnitType.HakureiDragonship;
if (techType == TechType.Navigation) unitType = UnitType.BomberShip;
if (techType == TechType.KomeijiIndianSailing) unitType = UnitType.KomeijiIndianShip;
if (techType == TechType.KomeijiIndianNavigation) unitType = UnitType.KomeijiIndianBomberShip;
}
// 计算最短地缘距离
private int CalPlayerMinDistance(PlayerData target)
{
_tmpUIntSetBuffer.Clear();
_tmpUIntSetBuffer2.Clear();
Map.GetPlayerTerritoryGridIdSet(Player.Id, _tmpUIntSetBuffer);
Map.GetPlayerTerritoryGridIdSet(target.Id, _tmpUIntSetBuffer2);
var minDistance = int.MaxValue;
foreach (var selfGridId in _tmpUIntSetBuffer)
{
if (!Map.GridMap.GetGridDataByGid(selfGridId, out var selfGrid)) continue;
foreach (var targetGridId in _tmpUIntSetBuffer2)
{
if (!Map.GridMap.GetGridDataByGid(targetGridId, out var targetGrid)) continue;
var distance = Map.GridMap.CalcDistance(selfGrid, targetGrid);
if (distance < minDistance) minDistance = distance;
}
}
return minDistance;
}
// 我的领土到对方城市的最近距离
private int CalAttackDistance(PlayerData target)
{
TmpCityListBuffer.Clear();
TmpCitySetBuffer.Clear();
Map.GetCityDataListByPlayerId(Player.Id, TmpCityListBuffer);
Map.GetCityDataListByPlayerId(target.Id, TmpCitySetBuffer);
var minDistance = int.MaxValue;
foreach (var selfCity in TmpCityListBuffer)
{
if (!Map.GetGridDataByCityId(selfCity.Id, out var selfGrid)) continue;
foreach (var targetCity in TmpCitySetBuffer)
{
if (!Map.GetGridDataByCityId(targetCity.Id, out var targetGrid)) continue;
var distance = Map.GridMap.CalcDistance(selfGrid, targetGrid);
if (distance < minDistance)
{
int lengthLimit = minDistance == int.MaxValue ? int.MaxValue : minDistance - 1;
if (!PathFinder.FindPathLengthWithin((int)Map.MapConfig.Width, (int)Map.MapConfig.Height,
new(selfGrid.Pos.X, selfGrid.Pos.Y), new(targetGrid.Pos.X, targetGrid.Pos.Y), lengthLimit, Map, Player,
out var pathLength, null, _reachabilityToken))
continue;
minDistance = pathLength;
}
}
}
return minDistance;
}
// 计算国家战略
private void CalculateCountryStrategy()
{
foreach (var target in Map.PlayerMap.PlayerDataList)
{
if (target == Player) continue;
if (ThreatScore[target.Id] < 10) continue;
if (MilitaryGapScore[target.Id] > -5) continue;
CountryStrategy = Strategy.Defend;
return;
}
var threatScore = 0f;
var militaryScore = 0f;
foreach (var target in Map.PlayerMap.PlayerDataList)
{
if (target == Player) continue;
threatScore += ThreatScore[target.Id];
militaryScore += MilitaryGapScore[target.Id];
}
if (threatScore >= 20 && militaryScore <= -10)
{
CountryStrategy = Strategy.Defend;
return;
}
//判断5格内有没有村庄有的话执行发展战略遍历每个城市然后遍历每个城市中心的5格内情况
TmpCitySetBuffer.Clear();
Map.GetCityDataListByPlayerId(Player.Id, TmpCitySetBuffer);
foreach (var city in TmpCitySetBuffer)
{
if (!Map.GetGridDataByCityId(city.Id, out var cityGrid)) continue;
AroundGridBuffer.Clear();
Map.GridMap.GetAroundGridData(5, 5, cityGrid, AroundGridBuffer);
foreach (var grid in AroundGridBuffer)
{
if (grid.Resource != ResourceType.CityCenter) continue;
if (Map.GetCityDataByGid(grid.Id, out var _)) continue;
if (!PathFinder.FindPathLengthWithin((int)Map.MapConfig.Width, (int)Map.MapConfig.Height,
new(grid.Pos.X, grid.Pos.Y), new(cityGrid.Pos.X, cityGrid.Pos.Y), 5, Map, Player, out _, null, _reachabilityToken))
continue;
CountryStrategy = Strategy.Development;
return;
}
}
int maxScore = 0;
PlayerData maxScorePlayer = null;
foreach (var player in Map.PlayerMap.PlayerDataList)
{
if (player.PlayerScore <= maxScore) continue;
maxScore = player.PlayerScore;
maxScorePlayer = player;
}
//然后判断是否做进攻的国家战略
if (maxScorePlayer != null && maxScorePlayer != Player && AttackDistance[maxScorePlayer.Id] <= 5)
{
CountryStrategy = Strategy.Attack;
AttackPlayerSet.Add(maxScorePlayer);
}
if (maxScorePlayer != null && maxScorePlayer != Map.PlayerMap.SelfPlayerData)
{
foreach (var target in Map.PlayerMap.PlayerDataList)
{
if (target == Player) continue;
if (ThreatScore[target.Id] < 10) continue;
if (MilitaryGapScore[target.Id] < 3) continue;
if (AttackDistance[target.Id] > 7) continue;
CountryStrategy = Strategy.Attack;
AttackPlayerSet.Add(target);
}
foreach (var target in Map.PlayerMap.PlayerDataList)
{
if (target == Player) continue;
if (MilitaryGapScore[target.Id] < 0) continue;
if (AttackDistance[target.Id] > 7) continue;
CountryStrategy = Strategy.Attack;
AttackPlayerSet.Add(target);
}
foreach (var target in Map.PlayerMap.PlayerDataList)
{
if (target == Player) continue;
if (MilitaryGapScore[target.Id] < 3) continue;
bool hasCityCenter = false;
foreach (var city in TmpCitySetBuffer)
{
if (hasCityCenter) break;
if (!Map.GetGridDataByCityId(city.Id, out var cityGrid)) continue;
AroundGridBuffer.Clear();
Map.GridMap.GetAroundGridData(8, 8, cityGrid, AroundGridBuffer);
foreach (var grid in AroundGridBuffer)
{
if (grid.Resource != ResourceType.CityCenter) continue;
if (Map.GetCityDataByGid(grid.Id, out var _)) continue;
if (!PathFinder.FindPathLengthWithin((int)Map.MapConfig.Width, (int)Map.MapConfig.Height,
new(grid.Pos.X, grid.Pos.Y), new(cityGrid.Pos.X, cityGrid.Pos.Y), 8, Map, Player, out _, null, _reachabilityToken))
continue;
hasCityCenter = true;
break;
}
}
if (!hasCityCenter) continue;
CountryStrategy = Strategy.Attack;
AttackPlayerSet.Add(target);
}
}
if (maxScorePlayer != null && maxScorePlayer == Map.PlayerMap.SelfPlayerData)
{
foreach (var target in Map.PlayerMap.PlayerDataList)
{
if (target == Player) continue;
if (MilitaryGapScore[target.Id] < 0) continue;
if (AttackDistance[target.Id] > 5) continue;
CountryStrategy = Strategy.Attack;
AttackPlayerSet.Add(target);
}
}
// 处理外交对于进攻国家的选择逻辑
foreach (var info in Player.DiplomacyData.Info)
{
if (info.PlayerId == Player.Id) continue;
if (!Map.PlayerMap.GetPlayerDataByPlayerID(info.PlayerId, out var playerData)) continue;
if (info.DiplomacyState == DiplomacyState.War || info.FeelingValue <= 30)
{
CountryStrategy = Strategy.Attack;
AttackPlayerSet.Add(playerData);
}
if (info.FeelingValue >= 80 && AttackPlayerSet.Contains(playerData)) AttackPlayerSet.Remove(playerData);
if (info.DiplomacyState == DiplomacyState.League) AttackPlayerSet.Remove(playerData);
}
if (AttackPlayerSet.Count == 0 && CountryStrategy == Strategy.Attack) CountryStrategy = Strategy.None;
if (CountryStrategy == Strategy.None) CountryStrategy = Strategy.Development;
}
// 计算城市周围单位
private void CalCityUnits(CityData city)
{
if (!Map.GetGridDataByCityId(city.Id, out var cityGrid)) return;
_tmpUnitSetBuffer2.Clear();
Map.GetPlayerDataByCityId(city.Id, out var player);
Map.GetUnitDataListByPlayerId(player.Id, _tmpUnitSetBuffer2);
if (!CityDefendUnits.TryGetValue(city.Id, out var defendList))
{
defendList = RentUnitList();
CityDefendUnits[city.Id] = defendList;
}
else
{
defendList.Clear();
}
if (!CityEnemyUnits.TryGetValue(city.Id, out var enemyList))
{
enemyList = RentUnitList();
CityEnemyUnits[city.Id] = enemyList;
}
else
{
enemyList.Clear();
}
foreach (var unit in Map.UnitMap.UnitList)
{
if (!Map.GetGridDataByUnitId(unit.Id, out var unitGrid)) continue;
if (Map.GridMap.CalcDistance(cityGrid, unitGrid) > 3) continue;
if (_tmpUnitSetBuffer2.Contains(unit)) defendList.Add(unit);
else if (Map.IsLeagueUnitByPlayer(Player.Id, unit.Id)) defendList.Add(unit);
else enemyList.Add(unit);
}
}
// 计算城市守备分
private float CalCityDefendScore(CityData city)
{
var score = 0f;
foreach (var unit in CityDefendUnits[city.Id]) score += unit.GetMilitary();
if (city.CityWall) score += 2;
return score;
}
// 计算城市救援分
private float CalCityRescueScore(CityData city)
{
var score = 0f;
_tmpUnitSetBuffer2.Clear();
Map.GetPlayerDataByCityId(city.Id, out var player);
Map.GetUnitDataListByPlayerId(player.Id, _tmpUnitSetBuffer2);
if (!Map.GetGridDataByCityId(city.Id, out var cityGrid)) return 0;
foreach (var unit in Map.UnitMap.UnitList)
{
if (!Map.GetGridDataByUnitId(unit.Id, out var unitGrid)) continue;
if (!_tmpUnitSetBuffer2.Contains(unit) && !Map.IsLeagueUnitByPlayer(Player.Id, unit.Id)) continue;
var distance = Map.GridMap.CalcDistance(cityGrid, unitGrid);
score += unit.GetMilitary() / (distance + 1);
}
return score;
}
// 计算城市敌军分
private float CalCityEnemyScore(CityData city)
{
var score = 0f;
foreach (var unit in CityEnemyUnits[city.Id]) score += unit.GetMilitary();
return score;
}
// 计算城市危险分
private float CalCityDangerScore(CityData city)
{
var score = CityEnemyScore[city.Id] - CityDefendScore[city.Id];
foreach (var attackUnit in CityEnemyUnits[city.Id])
{
foreach (var defendUnit in CityDefendUnits[city.Id])
{
score += CalUnitCounterScore(attackUnit, defendUnit);
score -= CalUnitCounterScore(defendUnit, attackUnit);
}
}
return score;
}
// 计算城市加权危险分
private float CalCityRatioDangerScore(CityData city)
{
if (!Map.GetGridDataByCityId(city.Id, out var cityGrid)) return 0;
var score = 0f;
foreach (var unit in CityEnemyUnits[city.Id])
{
if (!Map.GetGridDataByUnitId(unit.Id, out var unitGrid)) continue;
var ratio = 1;
var dis = Map.GridMap.CalcDistance(unitGrid, cityGrid);
ratio = dis switch
{
0 => 4,
1 => 3,
2 => 1,
3 => 0,
_ => ratio
};
score += unit.GetMilitary() * ratio;
}
foreach (var unit in CityDefendUnits[city.Id])
{
if (!Table.Instance.UnitTypeDataAssets.GetUnitTypeInfo(unit.UnitType, unit.GiantType, unit.UnitLevel,
out var info))
continue;
if (!Map.GetGridDataByUnitId(unit.Id, out var unitGrid)) continue;
var ratio = 1;
var dis = Map.GridMap.CalcDistance(unitGrid, cityGrid);
ratio = dis switch
{
0 => 4,
1 => 3,
2 => 1,
3 => 0,
_ => ratio
};
score -= unit.GetMilitary() * ratio;
}
return score;
}
// 计算两国最近的城市的距离
private int CalPlayerMinCityDistance(PlayerData player1, PlayerData player2)
{
TmpCityListBuffer.Clear();
_tmpCityListBuffer2.Clear();
Map.GetCityDataListByPlayerId(player1.Id, TmpCityListBuffer);
Map.GetCityDataListByPlayerId(player2.Id, _tmpCityListBuffer2);
int minDistance = int.MaxValue;
foreach (var city1 in TmpCityListBuffer)
{
if (!Map.GetGridDataByCityId(city1.Id, out var grid1)) continue;
foreach (var city2 in _tmpCityListBuffer2)
{
if (!Map.GetGridDataByCityId(city2.Id, out var grid2)) continue;
int distance = Map.GridMap.CalcDistance(grid1, grid2);
if (distance < minDistance)
{
minDistance = distance;
}
}
}
return minDistance;
}
//计算一个城市 与另一个国家最近的城市的距离
private int CalPlayerCityMinDistance(PlayerData player1, CityData city2)
{
if (!Map.GetGridDataByCityId(city2.Id, out var grid2)) return int.MaxValue;
TmpCityListBuffer.Clear();
Map.GetCityDataListByPlayerId(player1.Id, TmpCityListBuffer);
int minDistance = int.MaxValue;
foreach (var city1 in TmpCityListBuffer)
{
if (!Map.GetGridDataByCityId(city1.Id, out var grid1)) continue;
int distance = Map.GridMap.CalcDistance(grid1, grid2);
if (distance < minDistance)
{
minDistance = distance;
}
}
return minDistance;
}
// 计算城市最近的别国边境距离
private int CalPlayerBorderDistance(CityData city)
{
if (!Map.GetGridDataByCityId(city.Id, out var selfGrid)) return int.MaxValue;
TmpCitySetBuffer.Clear();
Map.GetCityDataListByPlayerId(Player.Id, TmpCitySetBuffer);
var minDistance = int.MaxValue;
foreach (var targetCity in Map.CityMap.CityList)
{
if (TmpCitySetBuffer.Contains(targetCity)) continue;
if (!Map.GetGridDataByCityId(targetCity.Id, out var targetGrid)) continue;
var distance = Map.GridMap.CalcDistance(selfGrid, targetGrid);
if (distance < minDistance)
{
int lengthLimit = minDistance == int.MaxValue ? int.MaxValue : minDistance - 1;
if (!PathFinder.FindPathLengthWithin((int)Map.MapConfig.Width, (int)Map.MapConfig.Height,
new(selfGrid.Pos.X, selfGrid.Pos.Y), new(targetGrid.Pos.X, targetGrid.Pos.Y), lengthLimit, Map, Player,
out var pathLength, null, _reachabilityToken))
continue;
minDistance = pathLength;
}
}
return minDistance;
}
// 计算城市与其他城市的距离
private void CalCityBorderDistance()
{
TmpCityListBuffer.Clear();
TmpCitySetBuffer.Clear();
Map.GetCityDataListByPlayerId(Player.Id, TmpCityListBuffer);
Map.GetCityDataListByPlayerId(Player.Id, TmpCitySetBuffer);
foreach (var selfCity in TmpCityListBuffer)
{
if (!Map.GetGridDataByCityId(selfCity.Id, out var selfGrid)) continue;
foreach (var targetCity in Map.CityMap.CityList)
{
if (TmpCitySetBuffer.Contains(targetCity)) continue;
if (!Map.GetGridDataByCityId(targetCity.Id, out var targetGrid)) continue;
var distance = Map.GridMap.CalcDistance(selfGrid, targetGrid);
if (distance > 7) continue;
if (!PathFinder.FindPathLengthWithin((int)Map.MapConfig.Width, (int)Map.MapConfig.Height,
new(selfGrid.Pos.X, selfGrid.Pos.Y), new(targetGrid.Pos.X, targetGrid.Pos.Y), 7, Map, Player, out _, null, _reachabilityToken))
continue;
CityBorder.Add(targetCity.Id);
}
}
}
// 计算城市战略
private void CalculateCityStrategy()
{
TmpCityListBuffer.Clear();
Map.GetCityDataListByPlayerId(Player.Id, TmpCityListBuffer);
for (int i = TmpCityListBuffer.Count - 1; i >= 0; i--)
{
var city = TmpCityListBuffer[i];
if (CityRatioDangerScore[city.Id] < 4) continue;
CityStrategy[city] = Strategy.EmergencyDefend;
TmpCityListBuffer.RemoveAt(i);
}
for (int i = TmpCityListBuffer.Count - 1; i >= 0; i--)
{
var city = TmpCityListBuffer[i];
if (CityDangerScore[city.Id] < 5) continue;
if (CityEnemyScore[city.Id] < 15) continue;
CityStrategy[city] = Strategy.EmergencyDefend;
TmpCityListBuffer.RemoveAt(i);
}
// for (int i = selfCity.Count - 1; i >= 0; i--)
// {
// var city = selfCity[i];
// if (CityEnemyScore[city.Id] > 0) continue;
// if (PlayerBorderDistance[city.Id] < 5) continue;
// StrategyCity[Strategy.Development].Add(city);
// selfCity.RemoveAt(i);
// }
for (int i = TmpCityListBuffer.Count - 1; i >= 0; i--)
{
var city = TmpCityListBuffer[i];
if (PlayerBorderDistance[city.Id] > 5) continue;
if (CityDangerScore[city.Id] <= 0) continue;
CityStrategy[city] = Strategy.Military;
TmpCityListBuffer.RemoveAt(i);
}
for (int i = TmpCityListBuffer.Count - 1; i >= 0; i--)
{
var city = TmpCityListBuffer[i];
if (CityDangerScore[city.Id] < 5) continue;
CityStrategy[city] = Strategy.Military;
TmpCityListBuffer.RemoveAt(i);
}
for (int i = TmpCityListBuffer.Count - 1; i >= 0; i--)
{
var city = TmpCityListBuffer[i];
if (CityEnemyScore[city.Id] <= 0) continue;
if (CityDefendScore[city.Id] > 0) continue;
CityStrategy[city] = Strategy.Military;
TmpCityListBuffer.RemoveAt(i);
}
if (CountryStrategy == Strategy.Defend)
{
for (int i = TmpCityListBuffer.Count - 1; i >= 0; i--)
{
var city = TmpCityListBuffer[i];
if (CityDefendScore[city.Id] >= 2) continue;
CityStrategy[city] = Strategy.DefendAttack;
TmpCityListBuffer.RemoveAt(i);
}
}
foreach (var city in TmpCityListBuffer) CityStrategy.TryAdd(city, CountryStrategy);
}
// 计算攻击收益
public float CalculateAttackGain(MapData map, UnitData origin, UnitData target)
{
if (origin == null || target == null) return 0;
if (!UnitAttackRatio.TryGetValue(origin.Id, out var ratio)) return 0;
var selfDmg = Table.Instance.CalcDamage(map, origin, target);
var otherDmg = Table.Instance.CalcCounterDamage(map, origin, target);
var score = selfDmg * ratio - otherDmg;
if (origin.Grid(map)?.CityOnGrid(map) != null)
score = selfDmg - otherDmg * 2;
map.GetPlayerDataByUnitId(origin.Id, out var originPlayer);
map.GetPlayerDataByUnitId(target.Id, out var targetPlayer);
if (originPlayer != null && targetPlayer != null)
{
originPlayer.GetCountryDiplomacyInfo(targetPlayer.Id, out var info);
if (info != null)
{
if (info.FeelingValue >= 90) score -= 10;
else if (info.FeelingValue >= 60) score -= 5;
else if (info.FeelingValue >= 30)
{
}
else score += 5;
}
}
score += (int)(target.GetMilitary() / 3);
if (target.UnitFullType.UnitType == UnitType.Giant)
{
//如果该单位不在城市中央
if (origin.Grid(map)?.CityOnGrid(map) == null)
score += 5;
}
if (origin.UnitFullType.UnitType == UnitType.Giant)
{
score -= otherDmg;
}
return score;
}
// 小兵排序
public void SortUnit(List<UnitData> unitList)
{
_tmpUnitListBuffer1.Clear();
var sortUnitType = s_sortUnitTypes;
var sortGiantType = s_sortGiantTypes;
foreach (var unitType in sortUnitType)
{
for (int i = unitList.Count - 1; i >= 0; i--)
{
if (unitList[i].UnitType == unitType)
{
_tmpUnitListBuffer1.Add(unitList[i]);
unitList.RemoveAt(i);
}
}
}
foreach (var giantType in sortGiantType)
{
for (int i = unitList.Count - 1; i >= 0; i--)
{
if (unitList[i].GiantType == giantType)
{
_tmpUnitListBuffer1.Add(unitList[i]);
unitList.RemoveAt(i);
}
}
}
foreach (var unit in _tmpUnitListBuffer1) unitList.Add(unit);
}
// 计算 Grid 对于 Unit 的格子威胁值
// 对于 Unit A 而言Grid B的 格子威胁值(默认=0)
// 若GridB的周围 R 范围内,存在射程>=R的敌方Unit C
// 格子威胁值 += C的军事分总和。每个敌方仅被计算1次
// R = 1,2,3
public float CalGridThreatScore(MapData map, UnitData unit, GridData grid)
{
float score = 0f;
_tmpCheckedUnitIdBuffer.Clear();
if (!map.GetPlayerIdByUnitId(unit.Id, out var unitPlayerId)) return 0f;
unit.Player(map, out var player);
// 检查半径 1, 2, 3 范围内的威胁
for (int r = 1; r <= 3; r++)
{
AroundGridBuffer.Clear();
map.GridMap.GetAroundGridData(r, r, grid, AroundGridBuffer);
foreach (var aroundGrid in AroundGridBuffer)
{
// 获取该格子上的单位
if (!aroundGrid.VisibleUnit(map,player, out var enemyUnit)) continue;
// 跳过已检查的单位
if (_tmpCheckedUnitIdBuffer.Contains(enemyUnit.Id)) continue;
// 获取敌方单位所属玩家
if (!map.GetPlayerIdByUnitId(enemyUnit.Id, out var enemyPlayerId)) continue;
// 跳过友军和同盟单位
if (enemyPlayerId == unitPlayerId || map.IsLeagueUnitByPlayer(unitPlayerId, enemyUnit.Id)) continue;
// 检查敌方单位的攻击范围是否能覆盖到目标格子
var attackRange = enemyUnit.GetAttackRange(map);
if (attackRange >= r)
{
score += enemyUnit.GetMilitary();
_tmpCheckedUnitIdBuffer.Add(enemyUnit.Id);
}
}
}
return score;
}
// 计算 Grid 对于 Unit 的格子杀人值
// 对于 Unit A 而言Grid B的 格子杀人值(默认=0)
// 若GridB的周围R范围内存在敌方Unit CA攻击C的基础攻防计算结果为kill
// 格子杀人值 = max(格子杀人值,A攻击C的攻击收益)
// R = A的最终射程
public float CalGridKillScore(MapData map, UnitData unit, GridData grid)
{
float maxScore = 0f;
if (!map.GetPlayerIdByUnitId(unit.Id, out var unitPlayerId)) return 0f;
unit.Player(map, out var player);
// 计算单位的最终射程
var attackRange = unit.GetAttackRange(map);
// 检查射程范围内的敌方单位
AroundGridBuffer.Clear();
map.GridMap.GetAroundGridData(attackRange, attackRange, grid, AroundGridBuffer);
foreach (var aroundGrid in AroundGridBuffer)
{
// 获取该格子上的单位
if (!aroundGrid.VisibleUnit(map,player, out var enemyUnit)) continue;
// 获取敌方单位所属玩家
if (!map.GetPlayerIdByUnitId(enemyUnit.Id, out var enemyPlayerId)) continue;
// 跳过友军和同盟单位
if (enemyPlayerId == unitPlayerId || map.IsLeagueUnitByPlayer(unitPlayerId, enemyUnit.Id)) continue;
// 计算伤害
var damage = Table.Instance.CalcDamage(map, unit, enemyUnit);
// 判断是否能击杀
if (damage >= enemyUnit.Health)
{
// 计算攻击收益
var attackGain = CalculateAttackGain(map, unit, enemyUnit);
// 取最大收益
if (attackGain > maxScore) maxScore = attackGain;
}
}
return maxScore;
}
// 计算 Grid 对于 Unit 的格子攻击收益值
// 对于 Unit A 而言Grid B的 格子攻击收益值(默认=0)
// 若GridB的周围R范围内存在敌方Unit C
// 格子攻击收益值 = max(格子攻击收益值,A攻击C的攻击收益)
// R = A的最终射程
public float CalGridAttackGainScore(MapData map, UnitData unit, GridData grid)
{
float maxScore = 0f;
if (!map.GetPlayerIdByUnitId(unit.Id, out var unitPlayerId)) return 0f;
unit.Player(map, out var player);
// 计算单位的最终射程
var attackRange = unit.GetAttackRange(map);
// 检查射程范围内的敌方单位
AroundGridBuffer.Clear();
map.GridMap.GetAroundGridData(attackRange, attackRange, grid, AroundGridBuffer);
foreach (var aroundGrid in AroundGridBuffer)
{
// 获取该格子上的单位
if (!aroundGrid.VisibleUnit(map,player, out var enemyUnit)) continue;
// 获取敌方单位所属玩家
if (!map.GetPlayerIdByUnitId(enemyUnit.Id, out var enemyPlayerId)) continue;
// 跳过友军和同盟单位
if (enemyPlayerId == unitPlayerId || map.IsLeagueUnitByPlayer(unitPlayerId, enemyUnit.Id)) continue;
// 计算攻击收益
var attackGain = CalculateAttackGain(map, unit, enemyUnit);
// 取最大收益
if (attackGain > maxScore) maxScore = attackGain;
}
return maxScore;
}
}
public struct MatchResult
{
public uint LegionId;
public uint CityId;
public float Score;
public MatchResult(uint legionId, uint cityId, float score)
{
LegionId = legionId;
CityId = cityId;
Score = score;
}
}
public struct LegionMergeResult
{
public uint OriginId;
public uint TargetId;
public float Score;
public LegionMergeResult(uint originId, uint targetId, float score)
{
OriginId = originId;
TargetId = targetId;
Score = score;
}
}
public enum AIDifficult
{
EASY,
NORMAL,
HARD,
LUNATIC
}
// AI 行为数据包
public class AIActionBase
{
public CalculateResult Result;
public CommonActionParams Param;
public ActionLogicBase ActionLogic;
public bool IsInSight;
public float Duration;
public AIActionBase(CommonActionParams param, ActionLogicBase action)
{
Param = param;
ActionLogic = action;
IsInSight = false;
Duration = 0;
}
public void CheckIsActionInPlayerSight()
{
IsInSight = true;
using var pooledCheckGridList = THCollectionPool.GetListHandle<GridData>(out var checkGridList);
if (Param.MapData.GridMap.GetGridDataByGid(Param.GridId, out var targetGrid)) checkGridList.Add(targetGrid);
if (Param.MapData.GetGridDataByUnitId(Param.UnitId, out var unitGrid) ) checkGridList.Add(unitGrid);
if (Param.MapData.GetGridDataByUnitId(Param.TargetUnitId, out var targetUnit)) checkGridList.Add(targetUnit);
if (Param.MapData.GetGridDataByCityId(Param.CityId, out var cityGrid)) checkGridList.Add(cityGrid);
if (LobbyManager.Instance.Lobby.IsInLobby())
{
foreach (var kv in Param.MapData.Net.Players)
{
if (!LobbyManager.Instance.Lobby.IsMemberInLobby(kv.Key)) continue;
var player = Param.MapData.PlayerMap.GetPlayerData(kv.Value);
if (player == null) continue;
foreach (var grid in checkGridList) if (player.Sight.CheckIsInSight(grid.Id)) return;
}
}
else
{
foreach (var grid in checkGridList)
if (Param.MapData.PlayerMap.SelfPlayerData.Sight.CheckIsInSight(grid.Id)) return;
}
IsInSight = false;
}
public void CheckIsActionDuration()
{
if (!IsInSight)
{
Duration = Table.Instance.AnimDataAssets.AINoSightActionWaitTime;
return;
}
Duration = Table.Instance.AnimDataAssets.AISightActionWaitTime;
return;
if (ActionLogic.ActionId.ActionType == CommonActionType.UnitMove)
{
Duration = Table.Instance.AnimDataAssets.MoveAnimTime + Table.Instance.AnimDataAssets.AISightActionWaitTime;
return;
}
if (ActionLogic.ActionId.ActionType == CommonActionType.UnitAttack)
{
Duration = ActionLogic.Duration + Table.Instance.AnimDataAssets.AIBeforeAnimWaitTime;
return;
}
Duration = Table.Instance.AnimDataAssets.AISightActionWaitTime;
}
public string DebugInfo()
{
if (ActionLogic.ActionId.ActionType == CommonActionType.Gain || ActionLogic.ActionId.ActionType == CommonActionType.Build || ActionLogic.ActionId.ActionType == CommonActionType.BuildWonder)
return ActionLogic.ActionId.ActionType + " " + ActionLogic.ActionId.ResourceType;
return ActionLogic.ActionId.ActionType.ToString();
}
}
// 行为数据本地包
[MemoryPackable]
public partial class ActionNetData
{
// 行为版本号
public uint Version;
// 地图校验哈希, 指行为处理前的地图哈希值
public string MapHash;
public CommonActionParams Param;
public CommonActionId ActionId;
// 执行时间,房主使用
[MemoryPackIgnore]
public float Time;
[MemoryPackConstructor]
public ActionNetData()
{
}
// 判等
public bool IsEqual(ActionNetData other)
{
if (other == null) return false;
if (Version != other.Version) return false;
if (MapHash != other.MapHash) return false;
if (ActionId != other.ActionId) return false;
return true;
}
}
}