TH1/Unity/Assets/Scripts/Logic/AI/AIActionBase.cs
2025-07-30 18:03:37 +08:00

1820 lines
74 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 System.Linq;
using Logic.Action;
using Logic.CrashSight;
using OPS.Obfuscator.Attribute;
using RuntimeData;
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,
}
[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 HashSet<string> Marks;
// 迭代器
public List<UnitData> ForeachUnit;
public List<uint> ForeachLegion;
public List<CityData> ForeachCity;
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>();
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;
}
public void Refresh(MapData map, PlayerData player)
{
Map = map;
Player = player;
CountryStrategy = Strategy.None;
AttackPlayerSet.Clear();
IsFinish = false;
AIActions.Clear();
TargetParam = new CommonActionParams();
Marks.Clear();
ForeachUnit.Clear();
ForeachLegion.Clear();
ForeachCity.Clear();
CityStrategy.Clear();
FreeUnitStrategy.Clear();
LegionStrategy.Clear();
LegionStrategyTypes.Clear();
FreeUnitStrategyTypes.Clear();
MilitaryScore.Clear();
DevelopmentScore.Clear();
ThreatScore.Clear();
MilitaryGapScore.Clear();
GeographicalDistance.Clear();
AttackDistance.Clear();
CityDefendUnits.Clear();
CityEnemyUnits.Clear();
CityDefendScore.Clear();
CityRescueScore.Clear();
CityEnemyScore.Clear();
CityDangerScore.Clear();
CityRatioDangerScore.Clear();
PlayerBorderDistance.Clear();
CityBorder.Clear();
LegionUnits.Clear();
FreeUnits.Clear();
LegionScore.Clear();
LegionGrid.Clear();
LegionGapScore.Clear();
LegionUnstableScore.Clear();
LegionTargetCity.Clear();
UnitTargetGrid.Clear();
UnitTargetCity.Clear();
UnitAttackRatio.Clear();
LegionCanMoveCities.Clear();
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();
// 城市策略
var selfCity = new List<CityData>();
map.GetCityDataListByPlayerId(Player.Id, selfCity);
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();
}
// 小兵目标城市统计
public void CalculateUnitTargetCity()
{
var selfUnitList = new List<UnitData>();
Map.GetUnitDataListByPlayerId(Player.Id, selfUnitList);
foreach (var unit in selfUnitList)
{
if (unit.LegionId == 0) continue;
if (!LegionTargetCity.TryGetValue(unit.LegionId, out var value)) continue;
UnitTargetCity[unit.Id] = value;
}
}
// 科技资源分计算
public void CalculateTechResourceScore()
{
bool isSea = false;
var set = new HashSet<GridData>();
foreach (var city in Map.CityMap.CityList)
{
foreach (var gridId in city.Territory.TerritoryArea)
{
if (!Map.GridMap.GetGridDataByGid(gridId, out var grid)) continue;
set.Add(grid);
}
}
foreach (var grid in set)
{
if (grid.Terrain != TerrainType.ShallowSea) continue;
isSea = true;
break;
}
for (int i = (int)TechType.Climbing; i <= (int)TechType.EgyptSakuya; i++)
{
var techType = (TechType)i;
var techSet = Table.Instance.TechDataAssets.GetNextTechs(techType);
var score = 0f;
score += CalCountryTechResourceScore(techType) * 2;
foreach (var tech in techSet)
{
score += CalCountryTechResourceScore(tech);
}
TechResourceScore[techType] = score;
}
if (isSea) return;
var allTech = Table.Instance.TechDataAssets.GetNextTechs(TechType.Fishing);
TechResourceScore[TechType.Fishing] = 0;
foreach (var tech in allTech) TechResourceScore[tech] = 0;
}
// 计算科技对国家的资源分
public float CalCountryTechResourceScore(TechType techType)
{
float score = 0f;
if (techType == TechType.Climbing || techType == TechType.Fishing || techType == TechType.Sailing)
{
var set = new HashSet<GridData>();
foreach (var city in Map.CityMap.CityList)
{
foreach (var gridId in city.Territory.TerritoryArea)
{
if (!Map.GridMap.GetGridDataByGid(gridId, out var grid)) continue;
set.Add(grid);
}
}
foreach (var grid in set)
{
if (grid.Feature == TerrainFeature.Mountain && techType == TechType.Climbing) score += 0.5f;
if (grid.Terrain == TerrainType.ShallowSea && techType == TechType.Fishing) score += 0.5f;
if (grid.Terrain == TerrainType.DeepSea && techType == TechType.Sailing) score += 0.5f;
}
}
else if (techType == TechType.Roads)
{
var selfCity = new HashSet<CityData>();
Map.GetCityDataListByPlayerId(Player.Id, selfCity);
score = selfCity.Count * 2;
}
else if (techType == TechType.Trade)
{
var set = Map.GetPlayerTerritoryGridIdSet(Player.Id);
foreach (var id in set)
{
if (!Map.GridMap.GetGridDataByGid(id, out var grid)) continue;
if (grid.Resource != ResourceType.Windmill && grid.Resource != ResourceType.Sawmill &&
grid.Resource != ResourceType.Forge) continue;
score += grid.buildingLevel;
}
}
else
{
var gridSet = Map.GetPlayerTerritoryGridIdSet(Player.Id);
foreach(var gid in gridSet)
{
if (!Map.GridMap.GetGridDataByGid(gid, out var grid)) continue;
//计算每一种techActions在grid下的资源分
score += CalcGridTechResourceScore(techType, grid);
}
}
return score;
}
// 计算科技对一个格子的资源分。该函数主要用于计算科技对于一个国家的资源分
public int CalcGridTechResourceScore(TechType techType, GridData gridData)
{
int score = 0;
var techInfo = Table.Instance.TechDataAssets.GetTechInfo(techType);
foreach (var t in techInfo.techActions)
{
if (t.ActionType == CommonActionType.Gain)
{
if (!Table.Instance.GridAndResourceDataAssets.GetResourceInfo(t.ResourceType, out var resourceInfo))
continue;
score += resourceInfo.Exp;
}
if (t.ActionType == CommonActionType.Build)
{
if (!Table.Instance.GridAndResourceDataAssets.GetResourceInfo(t.ResourceType, out var resourceInfo))
continue;
score += t.ResourceType switch
{
ResourceType.LumberHut => gridData.Vegetation == Vegetation.Trees ? 1 : 0,
ResourceType.Farm => gridData.Resource == ResourceType.Crop ? 2 : 0,
ResourceType.Mine => gridData.Resource == ResourceType.Metal ? 2 : 0,
ResourceType.Sawmill => gridData.Vegetation == Vegetation.Trees ? 1 : 0,
ResourceType.Windmill => gridData.Resource == ResourceType.Crop || gridData.Resource == ResourceType.Farm ? 1 : 0,
ResourceType.Forge => gridData.Resource == ResourceType.Metal || gridData.Resource == ResourceType.Mine ? 2 : 0,
ResourceType.Port => gridData.Terrain == TerrainType.ShallowSea ? 1 : 0,
_ => 0
};
}
}
return score;
}
// 科技克制分计算
public void CalculateTechGapScore()
{
bool isSea = false;
var set = new HashSet<GridData>();
foreach (var city in Map.CityMap.CityList)
{
foreach (var gridId in city.Territory.TerritoryArea)
{
if (!Map.GridMap.GetGridDataByGid(gridId, out var grid)) continue;
set.Add(grid);
}
}
foreach (var grid in set)
{
if (grid.Terrain != TerrainType.ShallowSea) continue;
isSea = true;
break;
}
for (int i = (int)TechType.Climbing; i <= (int)TechType.EgyptSakuya; i++)
{
var techType = (TechType)i;
var techSet = Table.Instance.TechDataAssets.GetNextTechs(techType);
var score = 0f;
foreach (var player in Map.PlayerMap.PlayerDataList)
{
if (player == Player) continue;
score += CalCountryTechGapScore(techType, player.Id) * 2;
}
foreach (var tech in techSet)
{
foreach (var player in Map.PlayerMap.PlayerDataList)
{
if (player == Player) continue;
score += CalCountryTechGapScore(tech, player.Id);
}
}
TechGapScore[techType] = score;
}
if (isSea) return;
var allTech = Table.Instance.TechDataAssets.GetNextTechs(TechType.Fishing);
TechResourceScore[TechType.Fishing] = 0;
foreach (var tech in allTech) TechResourceScore[tech] = 0;
}
// 计算科技对某个国家的克制分
public float CalCountryTechGapScore(TechType techType, uint playerId)
{
GetUnitTypeByTech(techType, out var unitType, out var giantType);
if (!Table.Instance.UnitTypeDataAssets.GetUnitTypeInfo(unitType, giantType, out var info))
return 0;
var selfCity = new HashSet<CityData>();
Map.GetCityDataListByPlayerId(Player.Id, selfCity);
if (selfCity.Count == 0) return 0;
var unitList = new List<UnitData>();
Map.GetUnitDataListByPlayerId(playerId, unitList);
var score = 0f;
foreach (var targetUnit in unitList)
{
if (!Table.Instance.UnitTypeDataAssets.GetUnitTypeInfo(targetUnit.UnitType, targetUnit.GiantType, out var targetInfo))
continue;
if (!Map.GetGridDataByUnitId(targetUnit.Id, out var unitGrid)) continue;
var minDis = int.MaxValue;
foreach (var city in selfCity)
{
if (!Map.GetGridDataByCityId(city.Id, out var cityGrid)) continue;
var dis = Map.GridMap.CalcDistance(unitGrid, cityGrid);
if (dis < minDis) minDis = dis;
}
score += CalUnitCounterScore(info.UnitType, targetInfo.UnitType);
score -= CalUnitCounterScore(targetInfo.UnitType, info.UnitType);
score /= minDis;
}
return score;
}
// 计算军团战略
public bool CalculateLegionStrategy()
{
LegionStrategy.Clear();
LegionStrategyTypes.Clear();
FreeUnitStrategyTypes.Clear();
LegionUnits.Clear();
FreeUnits.Clear();
LegionScore.Clear();
LegionGrid.Clear();
LegionGapScore.Clear();
LegionUnstableScore.Clear();
LegionTargetCity.Clear();
UnitTargetGrid.Clear();
UnitTargetCity.Clear();
UnitAttackRatio.Clear();
LegionCanMoveCities.Clear();
var selfUnitList = new List<UnitData>();
Map.GetUnitDataListByPlayerId(Player.Id, selfUnitList);
foreach (var unit in selfUnitList)
{
if (unit.LegionId == 0) FreeUnits.Add(unit);
else
{
if (!LegionUnits.ContainsKey(unit.LegionId)) LegionUnits[unit.LegionId] = new List<UnitData>();
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;
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)) continue;
LegionGrid[kv.Key] = centerGrid;
}
foreach (var kv in LegionUnits)
{
LegionCanMoveCities[kv.Key] = new HashSet<uint>();
foreach (var city in Map.CityMap.CityList)
{
if (!Map.GetGridDataByCityId(city.Id, out var cityGrid)) continue;
var path = PathFinder.FindPath((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);
if (!path.found) 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;
}
// 自主战略
var selfCity = new HashSet<CityData>();
Map.GetCityDataListByPlayerId(Player.Id, selfCity);
foreach (var kv in LegionUnits)
{
var citySet = new HashSet<CityData>();
var centerGrid = LegionGrid[kv.Key];
var aroundGrids = Map.GridMap.GetAroundGridData(4, 4, centerGrid);
foreach (var grid in aroundGrids)
{
if (!Map.GetCityDataByGid(grid.Id, out var cityData)) continue;
citySet.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 citySet)
{
if (selfCity.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;
var waitLegions = new List<uint>();
foreach (var kv in LegionUnits)
{
if (LegionTargetCity.ContainsKey(kv.Key)) continue;
waitLegions.Add(kv.Key);
}
// 国家防守
var results = new List<MatchResult>();
if (CountryStrategy == Strategy.Defend)
{
foreach (var legionId in waitLegions)
{
foreach (var city in selfCity)
{
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;
results.Add(new MatchResult(legionId, city.Id, score));
}
}
results = results.OrderByDescending(r => r.Score).ToList();
foreach (var result in results)
{
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)
{
foreach (var legionId in waitLegions)
{
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];
results.Add(new MatchResult(legionId, cityId, score));
}
}
results = results.OrderByDescending(r => r.Score).ToList();
foreach (var result in results)
{
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 waitLegions)
{
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;
var selfCity = new HashSet<CityData>();
var selfUnit = new List<UnitData>();
Map.GetUnitDataListByPlayerId(Player.Id, selfUnit);
Map.GetCityDataListByPlayerId(Player.Id, selfCity);
var param = new CommonActionParams();
param.MapData = Map;
param.PlayerData = Player;
param.MainObjectType = MainObjectType.Unit;
foreach (var unit in FreeUnits)
{
// 如果当前在遗迹/村庄/敌方城市中心/可采集的海星上,则确定自由发展战略
if (!Map.GetGridDataByUnitId(unit.Id, out var unitGrid)) continue;
if (unitGrid.Resource == ResourceType.Treasure || unitGrid.Resource == ResourceType.Starfish)
{
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 && !selfCity.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;
var path = PathFinder.FindPath((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), Map, Player);
if (!path.found) continue;
if (path.length > 3) continue;
if (path.length < dis)
{
legion = kv.Key;
dis = path.length;
}
}
if (legion != 0)
{
unit.LegionId = legion;
return false;
}
// 如果2直线距离内交战国的敌军总分-友军总分>=6且自由人距离最近的我方城市中心距离>2则执行撤退战略
var aroundGrids = Map.GridMap.GetAroundGridData(2, 2, unitGrid);
var score = 0f;
foreach (var aroundGrid in aroundGrids)
{
if (!Map.GetUnitDataByGid(aroundGrid.Id, out var attacker)) continue;
if (!selfUnit.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 selfCity)
{
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;
var path = PathFinder.FindPath((int)Map.MapConfig.Width, (int)Map.MapConfig.Height,
new (unitGrid.Pos.X, unitGrid.Pos.Y), new (grid.Pos.X, grid.Pos.Y), Map, Player);
if (!path.found) continue;
if (path.length > 4) 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()
{
var results = new List<LegionMergeResult>();
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;
results.Add(new LegionMergeResult(origin.Key, target.Key, score));
}
}
results = results.OrderByDescending(r => r.Score).ToList();
foreach (var result in results)
{
foreach (var unit in LegionUnits[result.OriginId]) unit.LegionId = result.TargetId;
LogSystem.LogInfo($"军团合并 {result.OriginId} 合并入 {result.TargetId}");
return false;
}
return true;
}
// 刷新小兵攻击系数
public void CalculateUnitAttackRatio()
{
var selfCity = new List<CityData>();
Map.GetCityDataListByPlayerId(Player.Id, selfCity);
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) &&
selfCity.Contains(city)) ratio = AiDiffInfo.UnitInCity;
UnitAttackRatio[unit.Id] = ratio;
}
}
// 计算玩家的军事分
private float CalMilitaryScore(PlayerData player)
{
var score = 0f;
var unitList = new List<UnitData>();
Map.GetUnitDataListByPlayerId(player.Id, unitList);
foreach (var unit in unitList) score += unit.GetMilitary();
return score;
}
// 计算威胁分
private int CalThreatScore(PlayerData target)
{
int score = 0;
if (Player.LastAttackPlayers.Contains(target.Id)) score += 2;
var gridSet = Map.GetPlayerTerritoryGridIdSet(Player.Id);
var unitList = new List<UnitData>();
Map.GetUnitDataListByPlayerId(target.Id, unitList);
foreach (var unit in unitList)
{
if (!Map.GetGridDataByUnitId(unit.Id, out var unitGrid)) continue;
foreach (var gridId in gridSet)
{
if (!Map.GridMap.GetGridDataByGid(gridId, out var grid)) continue;
if (gridSet.Contains(unitGrid.Id))
{
score += 2;
break;
}
//TODO 复杂度可从O(n)降低到O(8)
var distance = Map.GridMap.CalcDistance(grid, unitGrid);
if (distance == 1)
{
score += 1;
break;
}
}
}
return score;
}
// 计算军事差距
private float CalMilitaryGapScore(PlayerData target)
{
var selfUnitList = new List<UnitData>();
var targetUnitList = new List<UnitData>();
Map.GetUnitDataListByPlayerId(Player.Id, selfUnitList);
Map.GetUnitDataListByPlayerId(target.Id, targetUnitList);
float score = MilitaryScore[Player.Id] - MilitaryScore[target.Id];
foreach (var selfUnit in selfUnitList)
{
foreach (var targetUnit in targetUnitList)
{
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.UnitType, target.UnitType) * 0.5f;
return CalUnitCounterScore(self.UnitType, target.UnitType);
}
// 计算两个小兵的克制分 克制分表 克制表
public float CalUnitCounterScore(UnitType self, UnitType target)
{
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() + self.MP
: self.GetAttackRange();
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() + self.MP
: target.GetAttackRange();
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() == 1 && map.GetPlayerIdByUnitId(self.Id, out var selfPlayerId))
{
var selfUnits = new HashSet<UnitData>();
Map.GetUnitDataListByPlayerId(selfPlayerId, selfUnits);
var count = 0;
foreach (var unit in selfUnits) if (unit.GetAttackRange() == 1) count++;
if (count / (float)selfUnits.Count < 0.6f)
score += 6 - count / (float)selfUnits.Count * 10;
}
if (self.UnitType == UnitType.Catapult && map.GetGridDataByUnitId(self.Id, out var grid))
{
var gridList = Map.GridMap.GetAroundGridData(1, 1, grid);
bool isSea = true;
foreach (var aroundGrid in gridList)
{
if (aroundGrid.Terrain == TerrainType.Land) isSea = false;
}
if (isSea) score++;
}
return score;
}
// 计算科技对应的兵种
public void GetUnitTypeByTech(TechType techType, out UnitType unitType, out GiantType giantType)
{
unitType = UnitType.None;
giantType = GiantType.None;
if (techType == TechType.Archery) unitType = UnitType.Archer;
if (techType == TechType.Riding) unitType = UnitType.Rider;
if (techType == TechType.Strategy) unitType = UnitType.Defender;
if (techType == TechType.Chivalry) unitType = UnitType.Knights;
if (techType == TechType.Mathematics) unitType = UnitType.Catapult;
if (techType == TechType.Smithery) unitType = UnitType.Swordsman;
if (techType == TechType.Diplomacy) unitType = UnitType.Cloak;
if (techType == TechType.Philosophy) unitType = UnitType.Minder;
if (techType == TechType.Fishing) unitType = UnitType.Boat;
if (techType == TechType.Sailing) unitType = UnitType.Ship;
if (techType == TechType.Ramming) unitType = UnitType.RammerShip;
if (techType == TechType.Navigation) unitType = UnitType.BomberShip;
}
// 计算最短地缘距离
private int CalPlayerMinDistance(PlayerData target)
{
var selfGridSet = Map.GetPlayerTerritoryGridIdSet(Player.Id);
var targetGridSet = Map.GetPlayerTerritoryGridIdSet(target.Id);
var minDistance = int.MaxValue;
foreach (var selfGridId in selfGridSet)
{
if (!Map.GridMap.GetGridDataByGid(selfGridId, out var selfGrid)) continue;
foreach (var targetGridId in targetGridSet)
{
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)
{
var selfCityList = new List<CityData>();
Map.GetCityDataListByPlayerId(Player.Id, selfCityList);
var targetCityList = new HashSet<CityData>();
Map.GetCityDataListByPlayerId(target.Id, targetCityList);
var minDistance = int.MaxValue;
foreach (var selfCity in selfCityList)
{
if (!Map.GetGridDataByCityId(selfCity.Id, out var selfGrid)) continue;
foreach (var targetCity in targetCityList)
{
if (!Map.GetGridDataByCityId(targetCity.Id, out var targetGrid)) continue;
var distance = Map.GridMap.CalcDistance(selfGrid, targetGrid);
if (distance < minDistance)
{
var path = PathFinder.FindPath((int)Map.MapConfig.Width, (int)Map.MapConfig.Height,
new (selfGrid.Pos.X, selfGrid.Pos.Y), new (targetGrid.Pos.X, targetGrid.Pos.Y), Map, Player);
if (!path.found) continue;
if (path.length > minDistance) continue;
minDistance = path.length;
}
}
}
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格内情况
var selfCity = new HashSet<CityData>();
Map.GetCityDataListByPlayerId(Player.Id, selfCity);
foreach (var city in selfCity)
{
if(!Map.GetGridDataByCityId(city.Id,out var cityGrid)) continue;
var gridList = Map.GridMap.GetAroundGridData(5, 5, cityGrid);
foreach (var grid in gridList)
{
if (grid.Resource != ResourceType.CityCenter) continue;
if (Map.GetCityDataByGid(grid.Id, out var _)) continue;
var path = PathFinder.FindPath((int)Map.MapConfig.Width, (int)Map.MapConfig.Height,
new (grid.Pos.X, grid.Pos.Y), new (cityGrid.Pos.X, cityGrid.Pos.Y), Map, Player);
if (!path.found) continue;
if (path.length > 5) 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 selfCity)
{
if (hasCityCenter) break;
if (!Map.GetGridDataByCityId(city.Id, out var cityGrid)) continue;
var gridList = Map.GridMap.GetAroundGridData(8, 8, cityGrid);
foreach (var grid in gridList)
{
if (grid.Resource != ResourceType.CityCenter) continue;
if (Map.GetCityDataByGid(grid.Id, out var _)) continue;
var path = PathFinder.FindPath((int)Map.MapConfig.Width, (int)Map.MapConfig.Height,
new(grid.Pos.X, grid.Pos.Y), new(cityGrid.Pos.X, cityGrid.Pos.Y), Map, Player);
if (!path.found) continue;
if (path.length > 8) 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);
}
}
if (CountryStrategy == Strategy.None) CountryStrategy = Strategy.Development;
}
// 计算城市周围单位
private void CalCityUnits(CityData city)
{
if (!Map.GetGridDataByCityId(city.Id, out var cityGrid)) return;
var selfUnits = new HashSet<UnitData>();
Map.GetPlayerDataByCityId(city.Id, out var player);
Map.GetUnitDataListByPlayerId(player.Id, selfUnits);
CityDefendUnits[city.Id] = new List<UnitData>();
CityEnemyUnits[city.Id] = new List<UnitData>();
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 (selfUnits.Contains(unit)) CityDefendUnits[city.Id].Add(unit);
else CityEnemyUnits[city.Id].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;
var selfUnits = new HashSet<UnitData>();
Map.GetPlayerDataByCityId(city.Id, out var player);
Map.GetUnitDataListByPlayerId(player.Id, selfUnits);
if (!Map.GetGridDataByCityId(city.Id, out var cityGrid)) return 0;
foreach (var unit in selfUnits)
{
if (!Map.GetGridDataByUnitId(unit.Id, out var unitGrid)) 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, 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)
{
var player1Cities = new List<CityData>();
var player2Cities = new List<CityData>();
Map.GetCityDataListByPlayerId(player1.Id, player1Cities);
Map.GetCityDataListByPlayerId(player2.Id, player2Cities);
int minDistance = int.MaxValue;
foreach (var city1 in player1Cities)
{
if (!Map.GetGridDataByCityId(city1.Id, out var grid1)) continue;
foreach (var city2 in player2Cities)
{
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;
var player1Cities = new List<CityData>();
Map.GetCityDataListByPlayerId(player1.Id, player1Cities);
int minDistance = int.MaxValue;
foreach (var city1 in player1Cities)
{
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;
var selfCityList = new List<CityData>();
Map.GetCityDataListByPlayerId(Player.Id, selfCityList);
var minDistance = int.MaxValue;
foreach (var targetCity in Map.CityMap.CityList)
{
if (selfCityList.Contains(targetCity)) continue;
if (!Map.GetGridDataByCityId(targetCity.Id, out var targetGrid)) continue;
var distance = Map.GridMap.CalcDistance(selfGrid, targetGrid);
if (distance < minDistance)
{
var path = PathFinder.FindPath((int)Map.MapConfig.Width, (int)Map.MapConfig.Height,
new (selfGrid.Pos.X, selfGrid.Pos.Y), new (targetGrid.Pos.X, targetGrid.Pos.Y), Map, Player);
if (!path.found) continue;
if (path.length > minDistance) continue;
minDistance = path.length;
}
}
return minDistance;
}
// 计算城市与其他城市的距离
private void CalCityBorderDistance()
{
var selfCityList = new List<CityData>();
Map.GetCityDataListByPlayerId(Player.Id, selfCityList);
foreach (var selfCity in selfCityList)
{
if (!Map.GetGridDataByCityId(selfCity.Id, out var selfGrid)) continue;
foreach (var targetCity in Map.CityMap.CityList)
{
if (selfCityList.Contains(targetCity)) continue;
if (!Map.GetGridDataByCityId(targetCity.Id, out var targetGrid)) continue;
var distance = Map.GridMap.CalcDistance(selfGrid, targetGrid);
if (distance > 7) continue;
var path = PathFinder.FindPath((int)Map.MapConfig.Width, (int)Map.MapConfig.Height,
new (selfGrid.Pos.X, selfGrid.Pos.Y), new (targetGrid.Pos.X, targetGrid.Pos.Y), Map, Player);
if (!path.found) continue;
if (path.length > 7) continue;
CityBorder.Add(targetCity.Id);
}
}
}
// 计算城市战略
private void CalculateCityStrategy()
{
var selfCity = new List<CityData>();
Map.GetCityDataListByPlayerId(Player.Id, selfCity);
for (int i = selfCity.Count - 1; i >= 0; i--)
{
var city = selfCity[i];
if (CityRatioDangerScore[city.Id] < 4) continue;
CityStrategy[city] = Strategy.EmergencyDefend;
selfCity.RemoveAt(i);
}
for (int i = selfCity.Count - 1; i >= 0; i--)
{
var city = selfCity[i];
if (CityDangerScore[city.Id] < 5) continue;
if (CityEnemyScore[city.Id] < 15) continue;
CityStrategy[city] = Strategy.EmergencyDefend;
selfCity.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 = selfCity.Count - 1; i >= 0; i--)
{
var city = selfCity[i];
if (PlayerBorderDistance[city.Id] > 5) continue;
if (CityDangerScore[city.Id] <= 0) continue;
CityStrategy[city] = Strategy.Military;
selfCity.RemoveAt(i);
}
for (int i = selfCity.Count - 1; i >= 0; i--)
{
var city = selfCity[i];
if (CityDangerScore[city.Id] < 5) continue;
CityStrategy[city] = Strategy.Military;
selfCity.RemoveAt(i);
}
for (int i = selfCity.Count - 1; i >= 0; i--)
{
var city = selfCity[i];
if (CityEnemyScore[city.Id] <= 0) continue;
if (CityDefendScore[city.Id] > 0) continue;
CityStrategy[city] = Strategy.Military;
selfCity.RemoveAt(i);
}
if (CountryStrategy == Strategy.Defend)
{
for (int i = selfCity.Count - 1; i >= 0; i--)
{
var city = selfCity[i];
if (CityDefendScore[city.Id] >= 2) continue;
CityStrategy[city] = Strategy.DefendAttack;
selfCity.RemoveAt(i);
}
}
foreach (var city in selfCity) CityStrategy.TryAdd(city, CountryStrategy);
}
}
public class 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 class 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
}
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;
var player = Param.MapData.PlayerMap.SelfPlayerData;
if (Param.MapData.GridMap.GetGridDataByGid(Param.GridId, out var grid)
&& player.Sight.CheckIsInSight(grid.Id)) return;
if (Param.MapData.GetGridDataByUnitId(Param.UnitId, out var unitGrid)
&& player.Sight.CheckIsInSight(unitGrid.Id)) return;
if (Param.MapData.GetGridDataByUnitId(Param.TargetUnitId, out var targetUnit)
&& player.Sight.CheckIsInSight(targetUnit.Id)) return;
if (Param.MapData.GetGridDataByCityId(Param.CityId, out var cityGrid)
&& player.Sight.CheckIsInSight(cityGrid.Id)) return;
IsInSight = false;
}
public void CheckIsActionDuration()
{
if (!IsInSight)
{
Duration = Table.Instance.AnimDataAssets.AINoSightActionWaitTime;
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();
}
}
}