2555 lines
103 KiB
C#
2555 lines
103 KiB
C#
/*
|
||
* @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 C,A攻击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;
|
||
}
|
||
}
|
||
}
|