1077 lines
41 KiB
C#
1077 lines
41 KiB
C#
/*
|
|
* @Author: 白哉
|
|
* @Description: AI 行为类
|
|
* @Date: 2025年04月01日 星期二 14:04:35
|
|
* @Modify:
|
|
*/
|
|
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using Logic.Action;
|
|
using RuntimeData;
|
|
using UnityEngine;
|
|
using Vector2 = System.Numerics.Vector2;
|
|
|
|
|
|
namespace Logic.AI
|
|
{
|
|
public enum Strategy
|
|
{
|
|
Attack,
|
|
Defend,
|
|
Development,
|
|
Military,
|
|
EmergencyDefend,
|
|
Retreat,
|
|
Common,
|
|
}
|
|
|
|
|
|
public enum CalculateType
|
|
{
|
|
PlayerTechDefend,
|
|
PlayerTechAttack,
|
|
PlayerTechScore,
|
|
|
|
CityLevelUpDefend,
|
|
CityTrainDefend,
|
|
CityTrainAttack,
|
|
CityDevelopment,
|
|
CityOK,
|
|
|
|
UnitCollect,
|
|
UnitUpgrade,
|
|
UnitRecovery,
|
|
UnitAttackCityCenter,
|
|
UnitExplore,
|
|
UnitRetreat,
|
|
UnitMoveToTargetGrid,
|
|
UnitAuto,
|
|
UnitAttack,
|
|
|
|
LegionDefendKill,
|
|
LegionDefendMove,
|
|
LegionDefendMoveForTrain,
|
|
LegionDefendAttack,
|
|
|
|
LegionAttackMoveInCity,
|
|
LegionAttackMoveToCity,
|
|
LegionAttackCityUnit,
|
|
LegionAttackCityTerritoryUnit,
|
|
LegionAttackUnit,
|
|
|
|
LegionDevelopmentMoveToCityTerritory,
|
|
LegionDevelopmentKill,
|
|
LegionDevelopmentMoveToCity,
|
|
LegionDevelopmentAttackUnit,
|
|
LegionDevelopmentMoveToOtherCity,
|
|
}
|
|
|
|
|
|
public class AICalculatorData
|
|
{
|
|
// 核心数据
|
|
public MapData Map;
|
|
public PlayerData Player;
|
|
public Strategy CountryStrategy;
|
|
public PlayerData AttackPlayer;
|
|
public Dictionary<Strategy, List<CityData>> StrategyCity;
|
|
public Dictionary<Strategy, List<UnitData>> StrategyUnit;
|
|
public Dictionary<Strategy, List<uint>> StrategyLegion;
|
|
|
|
// 玩家的军事分
|
|
public Dictionary<uint, int> MilitaryScore;
|
|
// 玩家的发展分
|
|
public Dictionary<uint, int> DevelopmentScore;
|
|
// 玩家对我的威胁分
|
|
public Dictionary<uint, int> ThreatScore;
|
|
// 玩家对我的军事差距
|
|
public Dictionary<uint, int> MilitaryGapScore;
|
|
// 玩家对我的地缘距离
|
|
public Dictionary<uint, int> GeographicalDistance;
|
|
|
|
// 城市3格内我方单位
|
|
public Dictionary<uint, List<UnitData>> CityDefendUnits;
|
|
// 城市3格内敌方单位
|
|
public Dictionary<uint, List<UnitData>> CityEnemyUnits;
|
|
// 城市守备分
|
|
public Dictionary<uint, int> CityDefendScore;
|
|
// 城市救援分
|
|
public Dictionary<uint, int> CityRescueScore;
|
|
// 城市敌军分
|
|
public Dictionary<uint, int> CityEnemyScore;
|
|
// 城市危险分
|
|
public Dictionary<uint, int> CityDangerScore;
|
|
// 别国对我城市的最近边境距离
|
|
public Dictionary<uint, int> CityBorderDistance;
|
|
|
|
// 军团单位
|
|
public Dictionary<uint, List<UnitData>> LegionUnits;
|
|
// 自由人单位
|
|
public List<UnitData> FreeUnits;
|
|
// 军团分
|
|
public Dictionary<uint, int> LegionScore;
|
|
// 军团坐标
|
|
public Dictionary<uint, GridData> LegionGrid;
|
|
// 军团战力缺口
|
|
public Dictionary<uint, int> LegionGapScore;
|
|
// 军团不稳定值
|
|
public Dictionary<uint, float> LegionUnstableScore;
|
|
// 军团目标城市
|
|
public Dictionary<uint, uint> LegionTargetCity;
|
|
// 小兵目标格子
|
|
public Dictionary<uint, GridData> UnitTargetGrid;
|
|
|
|
|
|
public Strategy Strategy;
|
|
public bool IsPlayer;
|
|
public bool IsCity;
|
|
public bool IsUnit;
|
|
public bool IsLegion;
|
|
|
|
public List<AIActionBase> AIActions;
|
|
public AIActionBase MaxAiAction;
|
|
public CommonActionParams TargetParam;
|
|
public HashSet<string> Marks;
|
|
|
|
public bool IsExcute;
|
|
public bool IsFinish;
|
|
public bool IsInSight;
|
|
|
|
|
|
public AICalculatorData()
|
|
{
|
|
StrategyCity = new Dictionary<Strategy, List<CityData>>();
|
|
StrategyUnit = new Dictionary<Strategy, List<UnitData>>();
|
|
StrategyLegion = new Dictionary<Strategy, List<uint>>();
|
|
AIActions = new List<AIActionBase>();
|
|
TargetParam = new CommonActionParams();
|
|
Marks = new HashSet<string>();
|
|
|
|
MilitaryScore = new Dictionary<uint, int>();
|
|
DevelopmentScore = new Dictionary<uint, int>();
|
|
ThreatScore = new Dictionary<uint, int>();
|
|
MilitaryGapScore = new Dictionary<uint, int>();
|
|
GeographicalDistance = new Dictionary<uint, int>();
|
|
|
|
CityDefendUnits = new Dictionary<uint, List<UnitData>>();
|
|
CityEnemyUnits = new Dictionary<uint, List<UnitData>>();
|
|
CityDefendScore = new Dictionary<uint, int>();
|
|
CityRescueScore = new Dictionary<uint, int>();
|
|
CityEnemyScore = new Dictionary<uint, int>();
|
|
CityDangerScore = new Dictionary<uint, int>();
|
|
CityBorderDistance = new Dictionary<uint, int>();
|
|
|
|
LegionUnits = new Dictionary<uint, List<UnitData>>();
|
|
FreeUnits = new List<UnitData>();
|
|
LegionScore = new Dictionary<uint, int>();
|
|
LegionGrid = new Dictionary<uint, GridData>();
|
|
LegionGapScore = new Dictionary<uint, int>();
|
|
LegionUnstableScore = new Dictionary<uint, float>();
|
|
LegionTargetCity = new Dictionary<uint, uint>();
|
|
UnitTargetGrid = new Dictionary<uint, GridData>();
|
|
|
|
for (int i = (int)Strategy.Attack; i <= (int)Strategy.Common; i++)
|
|
{
|
|
StrategyCity[(Strategy)i] = new List<CityData>();
|
|
StrategyUnit[(Strategy)i] = new List<UnitData>();
|
|
StrategyLegion[(Strategy)i] = new List<uint>();
|
|
}
|
|
IsFinish = false;
|
|
IsInSight = false;
|
|
}
|
|
|
|
public void Refresh(MapData map, PlayerData player)
|
|
{
|
|
Map = map;
|
|
Player = player;
|
|
IsFinish = false;
|
|
IsInSight = false;
|
|
IsExcute = false;
|
|
|
|
Strategy = Strategy.EmergencyDefend;
|
|
IsPlayer = false;
|
|
IsCity = false;
|
|
IsPlayer = false;
|
|
IsLegion = false;
|
|
|
|
foreach (var kv in StrategyCity) kv.Value.Clear();
|
|
foreach (var kv in StrategyUnit) kv.Value.Clear();
|
|
foreach (var kv in StrategyLegion) kv.Value.Clear();
|
|
AIActions.Clear();
|
|
TargetParam = new CommonActionParams();
|
|
Marks.Clear();
|
|
|
|
MilitaryScore.Clear();
|
|
DevelopmentScore.Clear();
|
|
ThreatScore.Clear();
|
|
MilitaryGapScore.Clear();
|
|
GeographicalDistance.Clear();
|
|
|
|
CityDefendUnits.Clear();
|
|
CityEnemyUnits.Clear();
|
|
CityDefendScore.Clear();
|
|
CityRescueScore.Clear();
|
|
CityEnemyScore.Clear();
|
|
CityDangerScore.Clear();
|
|
CityBorderDistance.Clear();
|
|
|
|
LegionUnits.Clear();
|
|
FreeUnits.Clear();
|
|
LegionScore.Clear();
|
|
LegionGrid.Clear();
|
|
LegionGapScore.Clear();
|
|
LegionUnstableScore.Clear();
|
|
LegionTargetCity.Clear();
|
|
UnitTargetGrid.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);
|
|
}
|
|
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);
|
|
CityBorderDistance[city.Id] = CalCityBorderDistance(city);
|
|
}
|
|
CalculateCityStrategy();
|
|
|
|
while (true)
|
|
{
|
|
CalculateLegionStrategy();
|
|
if (CalculateFreeUnitStrategy()) break;
|
|
}
|
|
TargetParam.OnParamChanged();
|
|
}
|
|
|
|
public void ClearCache()
|
|
{
|
|
IsFinish = false;
|
|
IsInSight = false;
|
|
IsExcute = false;
|
|
|
|
Strategy = Strategy.EmergencyDefend;
|
|
IsPlayer = false;
|
|
IsCity = false;
|
|
IsPlayer = false;
|
|
IsLegion = false;
|
|
|
|
AIActions.Clear();
|
|
TargetParam = new CommonActionParams();
|
|
}
|
|
|
|
// 计算军团战略
|
|
public void CalculateLegionStrategy()
|
|
{
|
|
LegionUnits.Clear();
|
|
FreeUnits.Clear();
|
|
LegionScore.Clear();
|
|
LegionGrid.Clear();
|
|
LegionGapScore.Clear();
|
|
LegionUnstableScore.Clear();
|
|
LegionTargetCity.Clear();
|
|
UnitTargetGrid.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);
|
|
StrategyLegion[Strategy.Common].Add(unit.Id);
|
|
}
|
|
}
|
|
|
|
foreach (var kv in LegionUnits)
|
|
{
|
|
var score = 0;
|
|
foreach (var unit in kv.Value)
|
|
{
|
|
if (!Table.Instance.UnitTypeDataAssets.GetUnitTypeInfo(unit.UnitType, unit.GiantType, out var info))
|
|
continue;
|
|
score += info.Cost;
|
|
}
|
|
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 (!Table.Instance.UnitTypeDataAssets.GetUnitTypeInfo(unit.UnitType, unit.GiantType, out var info))
|
|
continue;
|
|
if (!Map.GetGridDataByUnitId(unit.Id, out var grid)) continue;
|
|
var ratio = info.Cost / (float)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)
|
|
{
|
|
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(3, 3, centerGrid);
|
|
foreach (var grid in aroundGrids)
|
|
{
|
|
if (!Map.GetCityDataByGid(grid.Id, out var cityData)) continue;
|
|
citySet.Add(cityData);
|
|
}
|
|
|
|
bool isFinish = false;
|
|
foreach (var defendCity in StrategyCity[Strategy.EmergencyDefend])
|
|
{
|
|
if (!citySet.Contains(defendCity)) continue;
|
|
LegionTargetCity[kv.Key] = defendCity.Id;
|
|
StrategyLegion[Strategy.Defend].Add(kv.Key);
|
|
isFinish = true;
|
|
break;
|
|
}
|
|
if (isFinish) continue;
|
|
|
|
foreach (var targetCity in citySet)
|
|
{
|
|
if (selfCity.Contains(targetCity)) 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;
|
|
StrategyLegion[Strategy.Attack].Add(kv.Key);
|
|
isFinish = true;
|
|
break;
|
|
}
|
|
}
|
|
if (isFinish) continue;
|
|
}
|
|
|
|
if (LegionTargetCity.Count == LegionUnits.Count) return;
|
|
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 (!Map.GetGridDataByCityId(city.Id, out var cityGrid)) continue;
|
|
var score = (LegionScore[legionId] - CityDangerScore[city.Id]) /
|
|
Map.GridMap.CalcDistance(cityGrid, LegionGrid[legionId]);
|
|
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;
|
|
if (LegionTargetCity.ContainsValue(result.CityId)) continue;
|
|
LegionTargetCity[result.LegionId] = result.CityId;
|
|
StrategyLegion[Strategy.Defend].Add(result.LegionId);
|
|
}
|
|
|
|
if (LegionTargetCity.Count == LegionUnits.Count) return;
|
|
uint targetId = 0;
|
|
foreach (var city in selfCity)
|
|
{
|
|
if (targetId == 0 || CityDangerScore[city.Id] > CityDangerScore[targetId]) targetId = city.Id;
|
|
}
|
|
foreach (var legionId in waitLegions)
|
|
{
|
|
if (!LegionTargetCity.TryAdd(legionId, targetId)) continue;
|
|
StrategyLegion[Strategy.Defend].Add(legionId);
|
|
}
|
|
}
|
|
if (CountryStrategy == Strategy.Attack)
|
|
{
|
|
foreach (var legionId in waitLegions)
|
|
{
|
|
foreach (var city in Map.CityMap.CityList)
|
|
{
|
|
if (selfCity.Contains(city)) continue;
|
|
if (!Map.GetGridDataByCityId(city.Id, out var cityGrid)) continue;
|
|
var score = (2 * LegionScore[legionId] - CityDefendScore[city.Id] - CityRescueScore[city.Id]) /
|
|
Map.GridMap.CalcDistance(cityGrid, LegionGrid[legionId]);
|
|
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;
|
|
if (LegionTargetCity.ContainsValue(result.CityId)) continue;
|
|
LegionTargetCity[result.LegionId] = result.CityId;
|
|
StrategyLegion[Strategy.Attack].Add(result.LegionId);
|
|
}
|
|
|
|
if (LegionTargetCity.Count == LegionUnits.Count) return;
|
|
uint targetId = 0;
|
|
foreach (var city in Map.CityMap.CityList)
|
|
{
|
|
if (selfCity.Contains(city)) continue;
|
|
if (targetId == 0 || CityDefendScore[city.Id] + CityRescueScore[city.Id] <
|
|
CityDefendScore[targetId] + CityRescueScore[targetId]) targetId = city.Id;
|
|
}
|
|
foreach (var legionId in waitLegions)
|
|
{
|
|
if (!LegionTargetCity.TryAdd(legionId, targetId)) continue;
|
|
StrategyLegion[Strategy.Defend].Add(legionId);
|
|
}
|
|
}
|
|
|
|
// 计算战力缺口
|
|
foreach (var kv in LegionUnits)
|
|
{
|
|
var combatGap = 0;
|
|
if (LegionTargetCity.ContainsKey(kv.Key))
|
|
{
|
|
if (StrategyLegion[Strategy.Attack].Contains(kv.Key))
|
|
{
|
|
combatGap = LegionScore[kv.Key] - CityDefendScore[LegionTargetCity[kv.Key]];
|
|
}
|
|
if (StrategyLegion[Strategy.Defend].Contains(kv.Key))
|
|
{
|
|
combatGap = LegionScore[kv.Key] - CityEnemyScore[LegionTargetCity[kv.Key]];
|
|
}
|
|
}
|
|
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;
|
|
FreeUnits.Add(LegionUnits[legionId][0]);
|
|
LegionUnits.Remove(legionId);
|
|
}
|
|
|
|
StrategyLegion[Strategy.Development].Add(legionId);
|
|
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;
|
|
FreeUnits.Add(maxUnit);
|
|
LegionUnits[legionId].Remove(maxUnit);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 计算自由人策略
|
|
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 actionId = new CommonActionId();
|
|
actionId.ActionType = CommonActionType.UnitAction;
|
|
actionId.UnitActionType = UnitActionType.Upgrade;
|
|
var action = ActionLogicFactory.GetActionLogic(actionId);
|
|
var param = new CommonActionParams();
|
|
param.MapData = Map;
|
|
param.PlayerData = Player;
|
|
param.MainObjectType = MainObjectType.Unit;
|
|
foreach (var unit in FreeUnits)
|
|
{
|
|
bool isFinish = false;
|
|
if (action != null)
|
|
{
|
|
param.UnitData = unit;
|
|
if (action.CheckCan(param))
|
|
{
|
|
StrategyUnit[Strategy.Defend].Add(unit);
|
|
isFinish = true;
|
|
}
|
|
}
|
|
if (isFinish) continue;
|
|
|
|
if (!Table.Instance.UnitTypeDataAssets.GetUnitTypeInfo(unit.UnitType, unit.GiantType, out var info))
|
|
continue;
|
|
if (!Map.GetGridDataByUnitId(unit.Id, out var unitGrid)) continue;
|
|
var aroundGrids = Map.GridMap.GetAroundGridData(info.AttackRange, info.AttackRange, unitGrid);
|
|
foreach (var aroundGrid in aroundGrids)
|
|
{
|
|
if (!Map.GetCityDataByGid(aroundGrid.Id, out var city)) continue;
|
|
if (!selfCity.Contains(city)) continue;
|
|
if (!Map.GetUnitDataByGid(aroundGrid.Id, out var cityUnit)) continue;
|
|
if (selfUnit.Contains(cityUnit)) continue;
|
|
StrategyUnit[Strategy.Defend].Add(unit);
|
|
isFinish = true;
|
|
break;
|
|
}
|
|
if (isFinish) continue;
|
|
|
|
if (unitGrid.Resource == ResourceType.Treasure)
|
|
{
|
|
isFinish = true;
|
|
StrategyUnit[Strategy.Defend].Add(unit);
|
|
}
|
|
if (unitGrid.Resource == ResourceType.CityCenter)
|
|
{
|
|
if (!Map.GetCityDataByGid(unitGrid.Id, out var city))
|
|
{
|
|
isFinish = true;
|
|
StrategyUnit[Strategy.Development].Add(unit);
|
|
}
|
|
else
|
|
{
|
|
if (!selfCity.Contains(city))
|
|
{
|
|
isFinish = true;
|
|
StrategyUnit[Strategy.Development].Add(unit);
|
|
}
|
|
}
|
|
}
|
|
if (unitGrid.Resource == ResourceType.Starfish)
|
|
{
|
|
isFinish = true;
|
|
StrategyUnit[Strategy.Development].Add(unit);
|
|
}
|
|
|
|
if (isFinish) continue;
|
|
|
|
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;
|
|
}
|
|
|
|
aroundGrids = Map.GridMap.GetAroundGridData(2, 2, unitGrid);
|
|
var score = 0;
|
|
foreach (var aroundGrid in aroundGrids)
|
|
{
|
|
if (!Map.GetUnitDataByGid(aroundGrid.Id, out var attacker)) continue;
|
|
if (!Table.Instance.UnitTypeDataAssets.GetUnitTypeInfo(attacker.UnitType, attacker.GiantType, out var attackInfo))
|
|
continue;
|
|
if (selfUnit.Contains(attacker)) score += attackInfo.Cost;
|
|
else score -= attackInfo.Cost;
|
|
}
|
|
if (score > 6)
|
|
{
|
|
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 >= minDis) continue;
|
|
minDis = distance;
|
|
target = city;
|
|
targetGrid = cityGrid;
|
|
}
|
|
|
|
if (target != null)
|
|
{
|
|
isFinish = true;
|
|
StrategyUnit[Strategy.Retreat].Add(unit);
|
|
UnitTargetGrid[unit.Id] = targetGrid;
|
|
}
|
|
}
|
|
if (isFinish) continue;
|
|
|
|
if (CountryStrategy == Strategy.Attack || CountryStrategy == Strategy.Defend)
|
|
{
|
|
uint legionId = 0;
|
|
foreach (var kv in LegionUnits)
|
|
{
|
|
if (legionId <= kv.Key) legionId = kv.Key + 1;
|
|
}
|
|
unit.LegionId = legionId;
|
|
return false;
|
|
}
|
|
StrategyUnit[Strategy.Development].Add(unit);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// 计算玩家的军事分
|
|
private int CalMilitaryScore(PlayerData player)
|
|
{
|
|
int score = 0;
|
|
var unitList = new List<UnitData>();
|
|
Map.GetUnitDataListByPlayerId(player.Id, unitList);
|
|
foreach (var unit in unitList)
|
|
{
|
|
if (!Table.Instance.UnitTypeDataAssets.GetUnitTypeInfo(unit.UnitType, unit.GiantType, out var info))
|
|
continue;
|
|
score += info.Cost;
|
|
}
|
|
|
|
return score;
|
|
}
|
|
|
|
// 计算威胁分
|
|
private int CalThreatScore(PlayerData target)
|
|
{
|
|
int score = 0;
|
|
if (Player.LastAttackPlayers.Contains(target.Id)) score = 10;
|
|
|
|
var gridSet = Map.GetPlayerTerritoryGridIdSet(Player.Id);
|
|
var unitList = new List<UnitData>();
|
|
Map.GetUnitDataListByPlayerId(target.Id, unitList);
|
|
foreach (var gridId in gridSet)
|
|
{
|
|
if (!Map.GridMap.GetGridDataByGid(gridId, out var grid)) continue;
|
|
foreach (var unit in unitList)
|
|
{
|
|
if (!Map.GetGridDataByUnitId(unit.Id, out var unitGrid)) continue;
|
|
if (gridSet.Contains(unitGrid.Id))
|
|
{
|
|
score += 2;
|
|
continue;
|
|
}
|
|
|
|
var distance = Map.GridMap.CalcDistance(grid, unitGrid);
|
|
if (distance == 1) score += 1;
|
|
}
|
|
}
|
|
return score;
|
|
}
|
|
|
|
// 计算军事差距
|
|
private int CalMilitaryGapScore(PlayerData target)
|
|
{
|
|
var selfUnitList = new List<UnitData>();
|
|
var targetUnitList = new List<UnitData>();
|
|
Map.GetUnitDataListByPlayerId(Player.Id, selfUnitList);
|
|
Map.GetUnitDataListByPlayerId(target.Id, targetUnitList);
|
|
|
|
int 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 int CalUnitCounterScore(UnitData self, UnitData target)
|
|
{
|
|
if (self.UnitType == UnitType.Archer && target.UnitType == UnitType.Warrior) return 1;
|
|
if (self.UnitType == UnitType.Catapult && target.UnitType == UnitType.Warrior) return 3;
|
|
if (self.UnitType == UnitType.BomberShip && target.UnitType == UnitType.Warrior) return 1;
|
|
|
|
if (self.UnitType == UnitType.Defender && target.UnitType == UnitType.Archer) return 1;
|
|
if (self.UnitType == UnitType.Rider && target.UnitType == UnitType.Archer) return 1;
|
|
if (self.UnitType == UnitType.Knights && target.UnitType == UnitType.Archer) return 3;
|
|
|
|
if (self.UnitType == UnitType.Catapult && target.UnitType == UnitType.Defender) return 1;
|
|
if (self.UnitType == UnitType.BomberShip && target.UnitType == UnitType.Defender) return 1;
|
|
if (self.UnitType == UnitType.Minder && target.UnitType == UnitType.Defender) return 3;
|
|
|
|
if (self.UnitType == UnitType.Defender && target.UnitType == UnitType.Rider) return 1;
|
|
if (self.UnitType == UnitType.Swordsman && target.UnitType == UnitType.Rider) return 2;
|
|
|
|
if (self.UnitType == UnitType.Defender && target.UnitType == UnitType.Knights) return 1;
|
|
if (self.UnitType == UnitType.Swordsman && target.UnitType == UnitType.Knights) return 2;
|
|
|
|
if (self.UnitType == UnitType.Rider && target.UnitType == UnitType.Catapult) return 2;
|
|
if (self.UnitType == UnitType.Knights && target.UnitType == UnitType.Catapult) return 3;
|
|
|
|
if (self.UnitType == UnitType.Archer && target.UnitType == UnitType.Swordsman) return 1;
|
|
if (self.UnitType == UnitType.Catapult && target.UnitType == UnitType.Swordsman) return 2;
|
|
if (self.UnitType == UnitType.BomberShip && target.UnitType == UnitType.Swordsman) return 1;
|
|
|
|
if (self.UnitType == UnitType.Minder && target.UnitType == UnitType.BomberShip) return 2;
|
|
|
|
if (self.UnitType == UnitType.Rider && target.UnitType == UnitType.Minder) return 2;
|
|
if (self.UnitType == UnitType.Knights && target.UnitType == UnitType.Minder) return 3;
|
|
return 0;
|
|
}
|
|
|
|
// 计算最短地缘距离
|
|
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 void CalculateCountryStrategy()
|
|
{
|
|
foreach (var target in Map.PlayerMap.PlayerDataList)
|
|
{
|
|
if (target == Player) continue;
|
|
if (ThreatScore[target.Id] < 10) continue;
|
|
if (MilitaryGapScore[target.Id] < 2) continue;
|
|
CountryStrategy = Strategy.Defend;
|
|
return;
|
|
}
|
|
|
|
var threatScore = 0;
|
|
var militaryScore = 0;
|
|
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;
|
|
}
|
|
|
|
foreach (var target in Map.PlayerMap.PlayerDataList)
|
|
{
|
|
if (target == Player) continue;
|
|
if (ThreatScore[target.Id] < 10) continue;
|
|
if (MilitaryGapScore[target.Id] > -2) continue;
|
|
CountryStrategy = Strategy.Attack;
|
|
AttackPlayer = target;
|
|
return;
|
|
}
|
|
|
|
foreach (var target in Map.PlayerMap.PlayerDataList)
|
|
{
|
|
if (target == Player) continue;
|
|
if (MilitaryGapScore[target.Id] > -8) continue;
|
|
CountryStrategy = Strategy.Attack;
|
|
AttackPlayer = target;
|
|
return;
|
|
}
|
|
|
|
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 int CalCityDefendScore(CityData city)
|
|
{
|
|
var score = 0;
|
|
foreach (var unit in CityDefendUnits[city.Id])
|
|
{
|
|
if (!Table.Instance.UnitTypeDataAssets.GetUnitTypeInfo(unit.UnitType, unit.GiantType, out var info))
|
|
continue;
|
|
score += info.Cost;
|
|
}
|
|
|
|
if (city.CityWall) score += 2;
|
|
return score;
|
|
}
|
|
|
|
// 计算城市救援分
|
|
private int CalCityRescueScore(CityData city)
|
|
{
|
|
var score = 0;
|
|
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;
|
|
if (!Table.Instance.UnitTypeDataAssets.GetUnitTypeInfo(unit.UnitType, unit.GiantType, out var info))
|
|
continue;
|
|
var distance = Map.GridMap.CalcDistance(cityGrid, unitGrid);
|
|
score += info.Cost / (distance + 1);
|
|
}
|
|
|
|
return score;
|
|
}
|
|
|
|
// 计算城市敌军分
|
|
private int CalCityEnemyScore(CityData city)
|
|
{
|
|
var score = 0;
|
|
foreach (var unit in CityEnemyUnits[city.Id])
|
|
{
|
|
if (!Table.Instance.UnitTypeDataAssets.GetUnitTypeInfo(unit.UnitType, unit.GiantType, out var info))
|
|
continue;
|
|
score += info.Cost;
|
|
}
|
|
return score;
|
|
}
|
|
|
|
// 计算城市危险分
|
|
private int 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 int CalCityBorderDistance(CityData city)
|
|
{
|
|
if (!Map.GetGridDataByCityId(city.Id, out var selfGrid)) return 0;
|
|
|
|
var selfCity = new HashSet<CityData>();
|
|
Map.GetPlayerDataByCityId(city.Id, out var player);
|
|
Map.GetCityDataListByPlayerId(player.Id, selfCity);
|
|
|
|
var targetGridList = new List<GridData>();
|
|
foreach (var target in Map.CityMap.CityList)
|
|
{
|
|
if (selfCity.Contains(target)) continue;
|
|
foreach (var gridId in target.Territory.TerritoryArea)
|
|
{
|
|
if (!Map.GridMap.GetGridDataByGid(gridId, out var targetGrid)) continue;
|
|
targetGridList.Add(targetGrid);
|
|
}
|
|
}
|
|
|
|
var minDistance = int.MaxValue;
|
|
foreach (var target in targetGridList)
|
|
{
|
|
var distance = Map.GridMap.CalcDistance(selfGrid, target);
|
|
if (distance < minDistance) minDistance = distance;
|
|
}
|
|
return minDistance;
|
|
}
|
|
|
|
// 计算城市战略
|
|
private void CalculateCityStrategy()
|
|
{
|
|
var selfCity = new List<CityData>();
|
|
Map.GetCityDataListByPlayerId(Player.Id, selfCity);
|
|
for (int i = selfCity.Count - 1; i >= 0; i--)
|
|
{
|
|
StrategyCity[Strategy.Development].Add(selfCity[i]);
|
|
}
|
|
|
|
for (int i = selfCity.Count - 1; i >= 0; i--)
|
|
{
|
|
var city = selfCity[i];
|
|
if (CityDangerScore[city.Id] < 2) continue;
|
|
if (CityEnemyScore[city.Id] < 10) continue;
|
|
StrategyCity[Strategy.EmergencyDefend].Add(city);
|
|
selfCity.RemoveAt(i);
|
|
}
|
|
|
|
// for (int i = selfCity.Count - 1; i >= 0; i--)
|
|
// {
|
|
// var city = selfCity[i];
|
|
// if (CityEnemyScore[city.Id] > 0) continue;
|
|
// if (CityBorderDistance[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 (CityBorderDistance[city.Id] < 5) continue;
|
|
if (CityDangerScore[city.Id] <= 0) continue;
|
|
StrategyCity[Strategy.Military].Add(city);
|
|
selfCity.RemoveAt(i);
|
|
}
|
|
|
|
for (int i = selfCity.Count - 1; i >= 0; i--)
|
|
{
|
|
var city = selfCity[i];
|
|
if (CityDangerScore[city.Id] < 5) continue;
|
|
StrategyCity[Strategy.Military].Add(city);
|
|
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;
|
|
StrategyCity[Strategy.Military].Add(city);
|
|
selfCity.RemoveAt(i);
|
|
}
|
|
|
|
for (int i = selfCity.Count - 1; i >= 0; i--)
|
|
{
|
|
var city = selfCity[i];
|
|
StrategyCity[CountryStrategy].Add(city);
|
|
}
|
|
}
|
|
}
|
|
|
|
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 enum AIDifficult
|
|
{
|
|
Simple,
|
|
Normal,
|
|
Hard,
|
|
}
|
|
|
|
|
|
public class AIActionBase
|
|
{
|
|
public CalculateResult Result;
|
|
public CommonActionParams Param;
|
|
public ActionLogicBase ActionLogic;
|
|
public bool IsInSight;
|
|
|
|
|
|
public AIActionBase(CommonActionParams param, ActionLogicBase action)
|
|
{
|
|
Param = param;
|
|
ActionLogic = action;
|
|
IsInSight = false;
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
} |