Tune AI Director defense and expansion loop
This commit is contained in:
parent
5947ea048f
commit
dc93fab7da
@ -175,8 +175,8 @@ Development
|
||||
|
||||
Defense 的行为倾向:
|
||||
|
||||
- Emergency 车道优先回防。
|
||||
- 城市优先训练防守单位、建城墙、移动占城格单位。
|
||||
- Emergency 车道优先阻止丢城;危险城市如果空防或只剩唯一守军,先补兵/城墙,再考虑攻击和外派。
|
||||
- 城市优先训练防守单位、建城墙、保留占城格单位。
|
||||
- 科技优先防御、基础兵种、移动和克制。
|
||||
- 英雄优先治疗、保护、控场、守城。
|
||||
|
||||
@ -198,6 +198,8 @@ Expansion 的行为倾向:
|
||||
- 早期扩张只追可在有限回合内转化的目标,避免全地图远距离追城导致移动量很大但二城率不变。
|
||||
- Expansion 只扫描已排序的少量高价值扩张目标;扩大城市数不能让每次决策退化成“所有目标 × 所有单位”的全量搜索。
|
||||
- 单位移动优先满足“本回合能占”或“下回合能站到可占点旁边”,远距离目标只保留作低优先级战线。
|
||||
- 扩张目标必须考虑占后守备风险;如果目标格或占领单位附近威胁很高、当前已有严重城市威胁、或己方单位数不足以覆盖城市数,则降低占领/靠近优先级。普通城市威胁只影响危险目标,不应把安全二城扩张整体压掉。
|
||||
- 危险城市的唯一守军不能外派扩张;非唯一守军可以参与近距离、安全、能快速转化的二城扩张,避免防守过度导致城市数停滞。
|
||||
- Expansion 车道优先处理占领和向目标靠近;UnitOpportunity 负责脚下的占领、遗迹和采集补漏。
|
||||
- Development Front 指向村庄、遗迹、资源和边界。
|
||||
- 城市优先增长和基础建设。
|
||||
@ -542,6 +544,7 @@ AI 可以重新实现,但必须遵守 TH1 的游戏架构:
|
||||
- DevelopmentTarget 只保留 TopN。
|
||||
- Expansion 从 DevelopmentTarget 中只扫描前 N 个扩张目标。
|
||||
- UnitMove 不生成整张地图的所有可走格;先从城市威胁、扩张目标、战线、自城、局部战斗收集移动锚点,每个单位只保留最靠近锚点的少量移动候选。
|
||||
- 同一次决策内,单位到目标格的最佳移动结果可以缓存,避免 Expansion、Emergency、Front、HeroPlaybook 重复扫描同一单位的移动候选。
|
||||
- 行动候选只生成一次。
|
||||
- 同一玩家回合内已经执行过的 stableKey 不再参与下一次候选选择。
|
||||
- 扩张移动、回防移动、前线移动有每回合意图预算;预算耗尽后交给其他车道,避免单一目标吞掉整回合。
|
||||
|
||||
@ -430,6 +430,10 @@ SkillBase
|
||||
|
||||
```text
|
||||
函数 FindBestMoveToward(ctx, unit, targetGrid):
|
||||
cacheKey = (unit.Id, targetGrid.Id)
|
||||
如果 ctx.ActionIndex.BestMoveCache 包含 cacheKey:
|
||||
返回缓存结果
|
||||
|
||||
actions = ctx.Actions.ByUnit[unit.Id].Moves
|
||||
best = None
|
||||
|
||||
@ -440,6 +444,7 @@ SkillBase
|
||||
score += GridOpportunityBonus(ctx, unit, endGrid)
|
||||
best = MaxByScore(best, action, score)
|
||||
|
||||
写入 ctx.ActionIndex.BestMoveCache[cacheKey] = best.Action
|
||||
返回 best.Action
|
||||
```
|
||||
|
||||
@ -1119,6 +1124,10 @@ SkillBase
|
||||
如果 !ShouldUseEmergency(threat):
|
||||
继续
|
||||
|
||||
candidate = TryEmergencyCityProduction(ctx, threat)
|
||||
如果 IsUrgentCityDefenseAction(candidate):
|
||||
返回 candidate
|
||||
|
||||
candidate = TryEmergencyAttack(ctx, threat)
|
||||
如果 candidate.IsValid:
|
||||
返回 candidate
|
||||
@ -1134,6 +1143,17 @@ SkillBase
|
||||
返回 None
|
||||
```
|
||||
|
||||
```text
|
||||
函数 IsUrgentCityDefenseAction(candidate):
|
||||
如果 !candidate.IsValid:
|
||||
返回 false
|
||||
如果 candidate.ActionId.ActionType == TrainUnit:
|
||||
返回 true
|
||||
如果 candidate.ActionId.ActionType == CityAction 且 candidate.ActionId.CityActionType == BuildCityWall:
|
||||
返回 true
|
||||
返回 false
|
||||
```
|
||||
|
||||
```text
|
||||
函数 ShouldUseEmergency(threat):
|
||||
如果 threat == null:
|
||||
@ -1246,9 +1266,17 @@ SkillBase
|
||||
|
||||
如果 id.ActionType == TrainUnit:
|
||||
score = 760 + TrainUnitDefenseValue(ctx, action, threat)
|
||||
如果 threat.DefenderPower <= 0:
|
||||
score += 240
|
||||
如果 threat.IsCapital:
|
||||
score += 120
|
||||
如果 threat.CanBeThreatenedNextTurn:
|
||||
score += 100
|
||||
|
||||
如果 id.ActionType == CityAction 且 id.CityActionType == BuildCityWall:
|
||||
score = 740
|
||||
如果 threat.DefenderPower <= 0:
|
||||
score += 180
|
||||
|
||||
如果 id.ActionType == CityLevelUpAction 且 能选城墙或人口防守收益:
|
||||
score = 700
|
||||
@ -1476,7 +1504,10 @@ Expansion Lane 只处理早期扩张的刚性目标,不处理普通资源开
|
||||
如果 unitGrid != target.Grid:
|
||||
继续
|
||||
|
||||
score = ScoreExpansionTarget(ctx, target) + 420
|
||||
riskPenalty = ExpansionTargetRiskPenalty(ctx, target, unitGrid)
|
||||
score = ScoreExpansionTarget(ctx, target) + 420 - riskPenalty
|
||||
如果 score <= 0:
|
||||
继续
|
||||
candidate = Candidate(action, Expansion, score, "扩张占领")
|
||||
best = MaxCandidate(best, candidate)
|
||||
|
||||
@ -1497,14 +1528,14 @@ Expansion Lane 只处理早期扩张的刚性目标,不处理普通资源开
|
||||
对每个 unit in ctx.Cache.SelfUnits:
|
||||
如果 unit 没有移动行动点:
|
||||
继续
|
||||
如果 unit 是严重城市威胁中的关键守军:
|
||||
继续
|
||||
如果 unit 低血且脚下有威胁:
|
||||
继续
|
||||
|
||||
startGrid = unit.Grid
|
||||
如果 startGrid == target.Grid:
|
||||
继续
|
||||
startDistance = Distance(startGrid, target.Grid)
|
||||
|
||||
如果 ShouldSkipExpansionUnit(ctx, unit, target, startGrid, startDistance):
|
||||
继续
|
||||
|
||||
action = FindBestMove(unit, target.Grid)
|
||||
endGrid = ActionEndGrid(action)
|
||||
@ -1523,6 +1554,10 @@ Expansion Lane 只处理早期扩张的刚性目标,不处理普通资源开
|
||||
score -= 120
|
||||
score -= endGrid 到 target 的距离 * 28
|
||||
score -= GridThreat(ctx, endGrid) * ThreatFactor(target)
|
||||
riskPenalty = ExpansionTargetRiskPenalty(ctx, target, endGrid)
|
||||
score -= riskPenalty
|
||||
如果 score <= 0:
|
||||
继续
|
||||
|
||||
candidate = Candidate(action, Expansion, score, "扩张移动")
|
||||
candidate.IntentKey = intentKey
|
||||
@ -1552,6 +1587,47 @@ Expansion Lane 只处理早期扩张的刚性目标,不处理普通资源开
|
||||
返回 score
|
||||
```
|
||||
|
||||
```text
|
||||
函数 ExpansionTargetRiskPenalty(ctx, target, actingGrid):
|
||||
penalty = GridThreat(ctx, target.Grid) * 0.75
|
||||
如果 actingGrid != null:
|
||||
penalty += GridThreat(ctx, actingGrid) * 0.25
|
||||
|
||||
如果 target.Type == EnemyEmptyCity:
|
||||
penalty += GridThreat(ctx, target.Grid) * 0.35
|
||||
|
||||
如果 有严重城市威胁:
|
||||
penalty += 180
|
||||
|
||||
如果 己方单位数 <= 己方城市数 + 1 且 GridThreat(ctx, target.Grid) > 0:
|
||||
penalty += 120
|
||||
|
||||
返回 penalty
|
||||
```
|
||||
|
||||
```text
|
||||
函数 ShouldSkipExpansionUnit(ctx, unit, target, startGrid, startDistance):
|
||||
如果 startDistance > Config.DevelopmentSearchRange + 2:
|
||||
返回 true
|
||||
|
||||
如果 unit 低血且 startGrid 有威胁:
|
||||
返回 true
|
||||
|
||||
如果 unit 不是危险城市守军:
|
||||
返回 false
|
||||
|
||||
如果 unit 是危险城市唯一守军:
|
||||
返回 true
|
||||
|
||||
如果 己方单位数 <= 己方城市数 + 1:
|
||||
返回 true
|
||||
|
||||
如果 target.Grid 有威胁:
|
||||
返回 true
|
||||
|
||||
返回 false
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. HeroPlaybook Lane
|
||||
@ -2049,7 +2125,10 @@ Byakuren / Miko / Zanmu:
|
||||
如果 unit 低血 且 front.Type == Attack:
|
||||
返回 true
|
||||
|
||||
如果 unit 正站在己方危险城市中心:
|
||||
如果 unit 是危险城市的守军,且城市处于 Emergency:
|
||||
返回 true
|
||||
|
||||
如果 unit 是某个危险城市的唯一守军:
|
||||
返回 true
|
||||
|
||||
返回 false
|
||||
@ -2130,8 +2209,16 @@ Byakuren / Miko / Zanmu:
|
||||
如果 plan.Kind == EmergencyDefense:
|
||||
如果 id.ActionType == TrainUnit:
|
||||
score += TrainUnitDefenseValue(ctx, action, plan.Threat)
|
||||
如果 plan.Threat.DefenderPower <= 0:
|
||||
score += 240
|
||||
如果 plan.Threat.IsCapital:
|
||||
score += 120
|
||||
如果 plan.Threat.CanBeThreatenedNextTurn:
|
||||
score += 100
|
||||
如果 id.ActionType == CityAction 且 id.CityActionType == BuildCityWall:
|
||||
score += 220
|
||||
如果 plan.Threat.DefenderPower <= 0:
|
||||
score += 180
|
||||
如果 id.ActionType in [StartWonder, BuildWonder]:
|
||||
score -= 300
|
||||
|
||||
@ -2545,6 +2632,7 @@ Growth: 只遍历合法动作
|
||||
限制 DevelopmentTarget TopN
|
||||
限制 Front TopN
|
||||
限制 LocalBattle 搜索半径
|
||||
一次决策内缓存 FindBestMoveToward(unit, targetGrid)
|
||||
科技只看当前可学和一层后继预览
|
||||
```
|
||||
|
||||
|
||||
@ -25,6 +25,7 @@ namespace Logic.AI.Director
|
||||
private readonly Dictionary<uint, List<AIActionBase>> _attackGroundByUnit = new();
|
||||
private readonly Dictionary<uint, List<AIActionBase>> _cityActionsByCity = new();
|
||||
private readonly Dictionary<uint, List<AIActionBase>> _gridActionsByGrid = new();
|
||||
private readonly Dictionary<(uint unitId, uint targetGridId), AIActionBase> _bestMoveCache = new();
|
||||
|
||||
public static AIDirectorActionIndex Build(AIDirectorContext ctx)
|
||||
{
|
||||
@ -228,6 +229,8 @@ namespace Logic.AI.Director
|
||||
if (unit == null) return null;
|
||||
if (!_movesByUnit.TryGetValue(unit.Id, out var actions)) return null;
|
||||
if (targetGrid == null) return actions.Count > 0 ? actions[0] : null;
|
||||
var cacheKey = (unit.Id, targetGrid.Id);
|
||||
if (_bestMoveCache.TryGetValue(cacheKey, out var cached)) return cached;
|
||||
|
||||
AIActionBase best = null;
|
||||
var bestDistance = int.MaxValue;
|
||||
@ -241,6 +244,7 @@ namespace Logic.AI.Director
|
||||
best = action;
|
||||
}
|
||||
|
||||
_bestMoveCache[cacheKey] = best;
|
||||
return best;
|
||||
}
|
||||
|
||||
|
||||
@ -29,6 +29,7 @@ namespace Logic.AI.Director
|
||||
|
||||
public static bool Enabled => _enabled;
|
||||
public static string CurrentLogPath => EnsureSession();
|
||||
public static string CurrentLogPathOrEmpty => _currentLogPath ?? string.Empty;
|
||||
|
||||
public static void SetEnabled(bool enabled)
|
||||
{
|
||||
@ -49,7 +50,6 @@ namespace Logic.AI.Director
|
||||
public static void BeginNewSession()
|
||||
{
|
||||
ResetSession();
|
||||
if (_enabled) EnsureSession();
|
||||
}
|
||||
|
||||
private static void ResetSession()
|
||||
@ -1532,6 +1532,7 @@ namespace Logic.AI.Director
|
||||
{
|
||||
public static bool Enabled => false;
|
||||
public static string CurrentLogPath => string.Empty;
|
||||
public static string CurrentLogPathOrEmpty => string.Empty;
|
||||
public static void SetEnabled(bool enabled) { }
|
||||
public static void Enable() { }
|
||||
public static void Disable() { }
|
||||
|
||||
@ -102,6 +102,14 @@ namespace Logic.AI.Director
|
||||
if (threat == null) continue;
|
||||
if (!ShouldUseEmergency(threat, ctx.Config)) continue;
|
||||
|
||||
var urgentCityAction = TryEmergencyCityAction(ctx, decision, threat);
|
||||
if (IsUrgentCityDefenseAction(urgentCityAction))
|
||||
{
|
||||
candidate = urgentCityAction;
|
||||
decision.AddTrace($"Emergency: urgent city action for city={threat.City?.Id}.", ctx.Config.MaxCandidateTraceCount);
|
||||
return true;
|
||||
}
|
||||
|
||||
var attack = TryEmergencyAttack(ctx, decision, threat);
|
||||
if (attack.IsValid)
|
||||
{
|
||||
@ -169,9 +177,11 @@ namespace Logic.AI.Director
|
||||
var grid = unit?.Grid(ctx.Map);
|
||||
if (grid == null || target?.Grid?.Id != grid.Id) continue;
|
||||
|
||||
var score = ScoreExpansionTarget(ctx, target) + 420f;
|
||||
var targetRiskPenalty = ExpansionTargetRiskPenalty(ctx, target, grid);
|
||||
var score = ScoreExpansionTarget(ctx, target) + 420f - targetRiskPenalty;
|
||||
if (score <= 0f) continue;
|
||||
var current = ctx.ActionIndex.Candidate(action, AIDirectorLane.Expansion, $"Expansion.Capture.{target.TargetType}", score);
|
||||
AddTerms(current, ("target", ScoreExpansionTarget(ctx, target)), ("capture", 420f));
|
||||
AddTerms(current, ("target", ScoreExpansionTarget(ctx, target)), ("capture", 420f), ("targetRisk", -targetRiskPenalty));
|
||||
current.Unit = current.Unit ?? unit;
|
||||
current.TargetGrid = current.TargetGrid ?? target.Grid;
|
||||
RecordCandidate(ctx, decision, "ExpansionCapture", current, current.IsValid ? null : "CheckCanFailed");
|
||||
@ -193,16 +203,15 @@ namespace Logic.AI.Director
|
||||
foreach (var unit in ctx.Cache.SelfUnits)
|
||||
{
|
||||
if (unit == null || unit.GetActionPoint(ActionPointType.Move) <= 0) continue;
|
||||
if (UnitIsCriticalCityDefender(ctx, unit)) continue;
|
||||
if (!ctx.Map.GetGridDataByUnitId(unit.Id, out var startGrid)) continue;
|
||||
if (target?.Grid == null || startGrid.Id == target.Grid.Id) continue;
|
||||
if (AIDirectorMath.HealthRatio(unit) <= ctx.Config.CriticalHealthRatio && GridThreat(ctx, startGrid) > 0f) continue;
|
||||
var startDistance = AIDirectorMath.Distance(ctx.Map, startGrid, target.Grid);
|
||||
if (ShouldSkipExpansionUnit(ctx, unit, target, startGrid, startDistance)) continue;
|
||||
|
||||
var action = ctx.ActionIndex.FindBestMove(unit, target.Grid);
|
||||
var endGrid = action?.Param?.TargetGridData ?? action?.Param?.GridData;
|
||||
if (endGrid == null) continue;
|
||||
|
||||
var startDistance = AIDirectorMath.Distance(ctx.Map, startGrid, target.Grid);
|
||||
var endDistance = AIDirectorMath.Distance(ctx.Map, endGrid, target.Grid);
|
||||
if (endDistance >= startDistance && startDistance > 1) continue;
|
||||
|
||||
@ -213,7 +222,9 @@ namespace Logic.AI.Director
|
||||
var mobilityBonus = unit.GetActionPoint(ActionPointType.Move) >= 2 ? 120f : 0f;
|
||||
var longDragPenalty = startDistance >= 5 && startDistance - endDistance <= 1 ? 140f : 0f;
|
||||
var safetyPenalty = GridThreat(ctx, endGrid) * (target.TargetType == AIDirectorDevelopmentTargetType.Village ? 0.25f : 0.5f);
|
||||
var score = targetScore + progressScore + reachBonus + nearConversionBonus + mobilityBonus - longDragPenalty - endDistance * 28f - safetyPenalty;
|
||||
var targetRiskPenalty = ExpansionTargetRiskPenalty(ctx, target, endGrid);
|
||||
var score = targetScore + progressScore + reachBonus + nearConversionBonus + mobilityBonus - longDragPenalty - endDistance * 28f - safetyPenalty - targetRiskPenalty;
|
||||
if (score <= 0f) continue;
|
||||
|
||||
var current = ctx.ActionIndex.Candidate(action, AIDirectorLane.Expansion, $"Expansion.Move.{target.TargetType}", score);
|
||||
AddTerms(
|
||||
@ -225,7 +236,8 @@ namespace Logic.AI.Director
|
||||
("mobility", mobilityBonus),
|
||||
("longDrag", -longDragPenalty),
|
||||
("distance", -endDistance * 28f),
|
||||
("threat", -safetyPenalty));
|
||||
("threat", -safetyPenalty),
|
||||
("targetRisk", -targetRiskPenalty));
|
||||
current.Unit = current.Unit ?? unit;
|
||||
current.TargetGrid = current.TargetGrid ?? target.Grid;
|
||||
current.IntentKey = intentKey;
|
||||
@ -640,12 +652,20 @@ namespace Logic.AI.Director
|
||||
var id = action.ActionLogic.ActionId;
|
||||
var city = action.Param.CityData;
|
||||
var score = 360f + (plan?.Priority ?? 0f) * 0.2f;
|
||||
var threat = plan?.Threat;
|
||||
|
||||
var kind = plan?.Kind ?? AIDirectorCityPlanKind.BacklineGrowth;
|
||||
if (kind == AIDirectorCityPlanKind.EmergencyDefense)
|
||||
{
|
||||
if (id.ActionType == CommonActionType.TrainUnit) score += TrainUnitDefenseValue(ctx, action, plan.Threat);
|
||||
if (id.ActionType == CommonActionType.TrainUnit) score += TrainUnitDefenseValue(ctx, action, threat);
|
||||
if (id.ActionType == CommonActionType.CityAction && id.CityActionType == CityActionType.BuildCityWall) score += 220f;
|
||||
if (threat != null)
|
||||
{
|
||||
if (threat.DefenderPower <= 0f && id.ActionType == CommonActionType.TrainUnit) score += 240f;
|
||||
if (threat.DefenderPower <= 0f && id.ActionType == CommonActionType.CityAction && id.CityActionType == CityActionType.BuildCityWall) score += 180f;
|
||||
if (threat.IsCapital && id.ActionType == CommonActionType.TrainUnit) score += 120f;
|
||||
if (threat.CanBeThreatenedNextTurn && id.ActionType == CommonActionType.TrainUnit) score += 100f;
|
||||
}
|
||||
if (id.ActionType is CommonActionType.StartWonder or CommonActionType.BuildWonder) score -= 300f;
|
||||
}
|
||||
else if (kind is AIDirectorCityPlanKind.Mobilize or AIDirectorCityPlanKind.Frontline)
|
||||
@ -832,7 +852,7 @@ namespace Logic.AI.Director
|
||||
if (unit == null || unit.GetActionPoint(ActionPointType.Move) <= 0) return true;
|
||||
if (unit.TreatedAsHero(ctx.Map, unit) && front.FrontType != AIDirectorFrontType.Defense) return true;
|
||||
if (AIDirectorMath.HealthRatio(unit) <= ctx.Config.LowHealthRatio && front.FrontType == AIDirectorFrontType.Attack) return true;
|
||||
if (IsStandingOnCriticalCityCenter(ctx, unit)) return true;
|
||||
if (UnitIsCriticalCityDefender(ctx, unit)) return true;
|
||||
var target = ResolveFrontTarget(front);
|
||||
var startGrid = unit.Grid(ctx.Map);
|
||||
if (target != null
|
||||
@ -844,13 +864,62 @@ namespace Logic.AI.Director
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool IsStandingOnCriticalCityCenter(AIDirectorContext ctx, UnitData unit)
|
||||
private bool IsUrgentCityDefenseAction(AIDirectorActionCandidate candidate)
|
||||
{
|
||||
if (unit == null || !ctx.Map.GetGridDataByUnitId(unit.Id, out var grid)) return false;
|
||||
if (candidate == null || !candidate.IsValid || candidate.ActionId == null) return false;
|
||||
var id = candidate.ActionId;
|
||||
if (id.ActionType == CommonActionType.TrainUnit) return true;
|
||||
return id.ActionType == CommonActionType.CityAction && id.CityActionType == CityActionType.BuildCityWall;
|
||||
}
|
||||
|
||||
private float ExpansionTargetRiskPenalty(AIDirectorContext ctx, AIDirectorDevelopmentTarget target, GridData actingGrid)
|
||||
{
|
||||
if (ctx?.Cache == null || target?.Grid == null) return 0f;
|
||||
var gridThreat = GridThreat(ctx, target.Grid);
|
||||
var localThreat = actingGrid != null ? GridThreat(ctx, actingGrid) * 0.25f : 0f;
|
||||
var penalty = gridThreat * 0.75f + localThreat;
|
||||
if (target.TargetType == AIDirectorDevelopmentTargetType.EnemyEmptyCity) penalty += gridThreat * 0.35f;
|
||||
if (HasSevereCityThreat(ctx)) penalty += 180f;
|
||||
if (ctx.Cache.SelfUnits.Count <= ctx.Cache.SelfCities.Count + 1 && gridThreat > 0f) penalty += 120f;
|
||||
return penalty;
|
||||
}
|
||||
|
||||
private bool ShouldSkipExpansionUnit(
|
||||
AIDirectorContext ctx,
|
||||
UnitData unit,
|
||||
AIDirectorDevelopmentTarget target,
|
||||
GridData startGrid,
|
||||
int startDistance)
|
||||
{
|
||||
if (ctx == null || unit == null || target?.Grid == null || startGrid == null) return true;
|
||||
if (startDistance > ctx.Config.DevelopmentSearchRange + 2) return true;
|
||||
if (AIDirectorMath.HealthRatio(unit) <= ctx.Config.CriticalHealthRatio && GridThreat(ctx, startGrid) > 0f) return true;
|
||||
if (!UnitIsCriticalCityDefender(ctx, unit)) return false;
|
||||
if (UnitIsOnlyCriticalCityDefender(ctx, unit)) return true;
|
||||
if (ctx.Cache.SelfUnits.Count <= ctx.Cache.SelfCities.Count + 1) return true;
|
||||
return GridThreat(ctx, target.Grid) > 0f;
|
||||
}
|
||||
|
||||
private bool IsOnlyDefenderForThreat(AIDirectorCityThreat threat, UnitData unit)
|
||||
{
|
||||
if (threat == null || unit == null) return false;
|
||||
var count = 0;
|
||||
foreach (var defender in threat.Defenders)
|
||||
{
|
||||
if (defender == null) continue;
|
||||
count++;
|
||||
}
|
||||
|
||||
return count <= 1 && threat.Defenders.Contains(unit);
|
||||
}
|
||||
|
||||
private bool UnitIsOnlyCriticalCityDefender(AIDirectorContext ctx, UnitData unit)
|
||||
{
|
||||
if (unit == null || ctx?.Cache == null) return false;
|
||||
foreach (var threat in ctx.Cache.CityThreats)
|
||||
{
|
||||
if (!threat.IsCritical) continue;
|
||||
if (threat.CityGrid?.Id == grid.Id) return true;
|
||||
if (!ShouldUseEmergency(threat, ctx.Config)) continue;
|
||||
if (IsOnlyDefenderForThreat(threat, unit)) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
@ -923,6 +992,7 @@ namespace Logic.AI.Director
|
||||
foreach (var threat in ctx.Cache.CityThreats)
|
||||
{
|
||||
if (!ShouldUseEmergency(threat, ctx.Config)) continue;
|
||||
if (IsOnlyDefenderForThreat(threat, unit)) return true;
|
||||
foreach (var defender in threat.Defenders)
|
||||
{
|
||||
if (defender?.Id == unit.Id) return true;
|
||||
|
||||
@ -170,7 +170,6 @@ namespace TH1_Logic.Editor
|
||||
AIDirectorBatchRuntime.RandomSeedOverride = options.Seed == 0 ? 0 : options.Seed + gameIndex;
|
||||
#if TH1_AI_DIRECTOR_DIAGNOSTICS || UNITY_EDITOR
|
||||
AIDirectorDiagnostics.BeginNewSession();
|
||||
result.diagnosticsLogPath = AIDirectorDiagnostics.CurrentLogPath;
|
||||
#endif
|
||||
|
||||
var main = Main.Instance;
|
||||
@ -546,6 +545,9 @@ namespace TH1_Logic.Editor
|
||||
result.curPlayerTurn = map.CurPlayer?.Turn ?? 0;
|
||||
result.survivingPlayers = CountSurvivingPlayers(map);
|
||||
result.players = BuildPlayerResults(map);
|
||||
#if TH1_AI_DIRECTOR_DIAGNOSTICS || UNITY_EDITOR
|
||||
result.diagnosticsLogPath = AIDirectorDiagnostics.CurrentLogPathOrEmpty;
|
||||
#endif
|
||||
result.diagnostics = BuildDiagnosticsSummary(result.diagnosticsLogPath);
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user