/* * @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 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, } [DoNotObfuscateClass] public class AICalculatorData { // 核心数据 public MapData Map; public PlayerData Player; public Strategy CountryStrategy; public HashSet AttackPlayerSet; // 策略集合 public Dictionary CityStrategy; public Dictionary FreeUnitStrategy; public Dictionary LegionStrategy; public Dictionary LegionStrategyTypes; public Dictionary FreeUnitStrategyTypes; // 玩家的军事分 public Dictionary MilitaryScore; // 玩家的发展分 public Dictionary DevelopmentScore; // 玩家对我的威胁分 public Dictionary ThreatScore; // 我减去其他玩家的军事差距 public Dictionary MilitaryGapScore; // 玩家对我的地缘距离 public Dictionary GeographicalDistance; // 我的城市到对方城市的最近距离 public Dictionary AttackDistance; // 城市3格内我方单位 public Dictionary> CityDefendUnits; // 城市3格内敌方单位 public Dictionary> CityEnemyUnits; // 城市守备分 public Dictionary CityDefendScore; // 城市救援分 public Dictionary CityRescueScore; // 城市敌军分 public Dictionary CityEnemyScore; // 城市危险分 public Dictionary CityDangerScore; // 城市加权危险分 public Dictionary CityRatioDangerScore; // 别国任意城市对我城市的最近距离 public Dictionary PlayerBorderDistance; // 别国城市对我任意城市的最近距离小于8的城市 public List CityBorder; // 军团单位 public Dictionary> LegionUnits; // 自由人单位 public List FreeUnits; // 军团分 public Dictionary LegionScore; // 军团坐标 public Dictionary LegionGrid; // 军团战力缺口 public Dictionary LegionGapScore; // 军团不稳定值 public Dictionary LegionUnstableScore; // 军团目标城市 public Dictionary LegionTargetCity; // 小兵目标格子 public Dictionary UnitTargetGrid; // 小兵目标城市 public Dictionary UnitTargetCity; // 小兵攻击系数 public Dictionary UnitAttackRatio; // 军团可达城市 public Dictionary> LegionCanMoveCities; // 科技克制分缓存 public Dictionary TechGapScore; // 科技资源分缓存 public Dictionary TechResourceScore; public List AIActions; public AIActionBase MaxAiAction; public CommonActionParams TargetParam; public Strategy TargetStrategy; public HashSet Marks; // 迭代器 public List ForeachUnit; public List ForeachLegion; public List ForeachCity; public bool IsFinish; public AIDiffInfo AiDiffInfo; public AICalculatorData() { AttackPlayerSet = new HashSet(); CityStrategy = new Dictionary(); FreeUnitStrategy = new Dictionary(); LegionStrategy = new Dictionary(); LegionStrategyTypes = new Dictionary(); FreeUnitStrategyTypes = new Dictionary(); AIActions = new List(); TargetParam = new CommonActionParams(); Marks = new HashSet(); ForeachUnit = new List(); ForeachLegion = new List(); ForeachCity = new List(); MilitaryScore = new Dictionary(); DevelopmentScore = new Dictionary(); ThreatScore = new Dictionary(); MilitaryGapScore = new Dictionary(); GeographicalDistance = new Dictionary(); AttackDistance = new Dictionary(); CityDefendUnits = new Dictionary>(); CityEnemyUnits = new Dictionary>(); CityDefendScore = new Dictionary(); CityRescueScore = new Dictionary(); CityEnemyScore = new Dictionary(); CityDangerScore = new Dictionary(); CityRatioDangerScore = new Dictionary(); PlayerBorderDistance = new Dictionary(); CityBorder = new List(); LegionUnits = new Dictionary>(); FreeUnits = new List(); LegionScore = new Dictionary(); LegionGrid = new Dictionary(); LegionGapScore = new Dictionary(); LegionUnstableScore = new Dictionary(); LegionTargetCity = new Dictionary(); UnitTargetGrid = new Dictionary(); UnitTargetCity = new Dictionary(); UnitAttackRatio = new Dictionary(); LegionCanMoveCities = new Dictionary>(); TechGapScore = new Dictionary(); TechResourceScore = new Dictionary(); IsFinish = false; TargetStrategy = Strategy.None; } public void Refresh(MapData map, PlayerData player) { Map = map; Player = player; TargetStrategy = Strategy.None; 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(); 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(); 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(); 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.RemiliaFreeSpirit; 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); } score += CalForceTechResourceScore(techType); 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 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; } return 0; } // 计算科技对国家的资源分 public float CalCountryTechResourceScore(TechType techType) { float score = 0f; if (techType == TechType.Climbing || techType == TechType.Fishing || techType == TechType.Sailing) { var set = new HashSet(); 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(); 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); if (techInfo == null) { //Debug.Log($"Wrong With Get Tech Info of {techType}"); return 0; } 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(); 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.RemiliaFreeSpirit; 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, out var unitLevel); if (!Table.Instance.UnitTypeDataAssets.GetUnitTypeInfo(unitType, giantType, unitLevel, out var info)) return 0; var selfCity = new HashSet(); Map.GetCityDataListByPlayerId(Player.Id, selfCity); if (selfCity.Count == 0) return 0; var unitList = new List(); Map.GetUnitDataListByPlayerId(playerId, unitList); var score = 0f; foreach (var targetUnit in unitList) { if (!Table.Instance.UnitTypeDataAssets.GetUnitTypeInfo(targetUnit.UnitType, targetUnit.GiantType, targetUnit.UnitLevel, 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(); 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(); 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] = new HashSet(); foreach (var city in Map.CityMap.CityList) { if (!Map.GetGridDataByCityId(city.Id, out var cityGrid)) continue; if (!LegionGrid.ContainsKey(kv.Key)) 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(); Map.GetCityDataListByPlayerId(Player.Id, selfCity); foreach (var kv in LegionUnits) { var citySet = new HashSet(); 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(); foreach (var kv in LegionUnits) { if (LegionTargetCity.ContainsKey(kv.Key)) continue; waitLegions.Add(kv.Key); } // 国家防守 var results = new List(); 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(); var selfUnit = new List(); 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 && 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 && !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(); 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(); 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(); 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(); 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(); var targetUnitList = new List(); 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(); 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; } //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.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(); Map.GetCityDataListByPlayerId(Player.Id, selfCityList); var targetCityList = new HashSet(); 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(); 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); } } // 处理外交对于进攻国家的选择逻辑 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; var selfUnits = new HashSet(); Map.GetPlayerDataByCityId(city.Id, out var player); Map.GetUnitDataListByPlayerId(player.Id, selfUnits); CityDefendUnits[city.Id] = new List(); CityEnemyUnits[city.Id] = new List(); 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 if (Map.IsLeagueUnitByPlayer(Player.Id, unit.Id)) 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(); 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 Map.UnitMap.UnitList) { if (!Map.GetGridDataByUnitId(unit.Id, out var unitGrid)) continue; if (!selfUnits.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) { var player1Cities = new List(); var player2Cities = new List(); 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(); 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(); 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(); 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(); 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 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 += 10; } } score += (int)(target.GetMilitary() / 3); if (target.UnitFullType.UnitType == UnitType.Giant) { //如果该单位不在城市中央 if(origin.Grid(map)?.CityOnGrid(map) == null) score += 5; } return score; } } 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 } // 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; var checkGridList = new List(); 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; [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; } } }