Allow AI hero slot culture cards

This commit is contained in:
wuwenbo 2026-07-02 12:00:34 +08:00
parent dc93fab7da
commit 15593c487b
6 changed files with 119 additions and 23 deletions

View File

@ -169,7 +169,7 @@ Classify the repeated action before patching:
Known temporary filters from this iteration:
- `CommonActionType.BuyCultureCard` is disabled for AI generation.
- `CommonActionType.BuyCultureCard` is only generated for `SecondHero` and `ThirdHero` slot unlock cards; other culture cards remain disabled for AI generation.
- `UnitActionType.ToggleShenlan` is filtered because it is a debug/visual toggle with no action-point cost.
- `PlayerActionType.FinishHeroTask` is filtered because it can execute repeatedly without observable AI turn progress.

View File

@ -491,8 +491,8 @@ Director 不直接推演行为结果,而是从合法 Action 池中选择。
| 单位行为 | Capture、Examine、Gather、Recover、Upgrade、HeroUpgrade、CultureUnitUpgrade、ShipUpgrade、AbsorbMarker、英雄主动 |
| 城市行为 | TrainUnit、CityLevelUpAction、CityAction、StartWonder、BuildWonder |
| 地块行为 | Gain、Build、GridMisc |
| 玩家行为 | LearnTech、PlayerActionBuyCultureCard 暂不进入 AI 候选,等文化卡策略单独回归 |
| 英雄管理 | SelectHero、TrainUnit:Giant 出场/复活、FinishHeroTask |
| 玩家行为 | LearnTech、PlayerActionBuyCultureCard 只开放英雄槽位卡,不开放普通文化卡 |
| 英雄管理 | SelectHero、TrainUnit:Giant 出场/复活、SecondHero/ThirdHero 槽位卡、FinishHeroTask |
UnitAttackAlly 只由 HeroPlaybook、支援战术或明确的友军互动规则使用不进入 Fallback。友军目标动作如果落到兜底通常说明它缺少英雄或支援规则。

View File

@ -386,6 +386,9 @@ SkillBase
pool.PlayerActions.Add(action)
如果 id.ActionType == BuyCultureCard:
如果 IsAIHeroSlotCultureCard(player, id.CultureCardType):
pool.HeroManagementActions.Add(action)
否则:
继续
如果 id.ActionType == PlayerAction:
@ -395,6 +398,23 @@ SkillBase
pool.PlayerActions.Add(action)
```
```text
函数 IsAIHeroSlotCultureCard(player, cardType):
如果 player 没有 HeroData 或 CultureInfo:
返回 false
如果 player 已拥有 cardType:
返回 false
如果 cardType == SecondHero:
返回 MaxHeroCount == 1 且 HeroCount >= 1
如果 cardType == ThirdHero:
返回 MaxHeroCount == 2 且 HeroCount >= 2 且 已拥有 SecondHero
返回 false
```
### 4.3 危险动作过滤
```text
@ -1303,6 +1323,10 @@ SkillBase
如果 candidate.IsValid:
返回 candidate
candidate = TryBuyHeroSlotCultureCard(ctx)
如果 candidate.IsValid:
返回 candidate
candidate = TryForceFinishHeroTask(ctx)
如果 candidate.IsValid:
返回 candidate
@ -1369,7 +1393,19 @@ SkillBase
英雄出场仍然走 TrainUnitAction 的 CheckCan 和 Execute。AI 只负责更早选择这个合法动作,不直接改变英雄状态。
### 7.4 FinishHeroTask
### 7.4 BuyHeroSlotCultureCard
```text
函数 TryBuyHeroSlotCultureCard(ctx):
action = FindHeroSlotCultureCard(ctx.Player)
如果 action == None:
返回 None
score = ScoreCultureCard(ctx, action.CultureCardType)
返回 Candidate(action, HeroManagement, score, "购买英雄槽位卡")
```
### 7.5 FinishHeroTask
```text
函数 TryForceFinishHeroTask(ctx):
@ -2336,20 +2372,20 @@ Byakuren / Miko / Zanmu:
```text
函数 ScoreCultureCard(ctx, action):
当前 AI 不生成 BuyCultureCard 动作。
此函数只作为未来恢复文化卡策略时的评分入口。
如果 !IsAIHeroSlotCultureCard(ctx.Player, action.CultureCardType):
返回 0
score = 300
score = 860
如果 前线吃紧 且文化卡提供即时战力:
score += 120
如果 英雄体系成型 且文化卡强化英雄:
score += 100
如果 安全发展 且文化卡提供长期经济:
如果 action.CultureCardType == SecondHero 且 MaxHeroCount == 1:
score += 80
如果 action.CultureCardType == ThirdHero 且 MaxHeroCount == 2:
score += 70
如果 当前没有在场英雄:
score -= 120
返回 score
```

View File

@ -292,6 +292,7 @@ namespace Logic.AI
GeneratorActionIds(data, CommonActionType.LearnTech);
GeneratorActionIds(data, CommonActionType.StartWonder);
GeneratorActionIds(data, CommonActionType.PlayerAction);
GeneratorActionIds(data, CommonActionType.BuyCultureCard);
foreach (var city in selfCities)
{
data.TargetParam.CityData = city;
@ -376,6 +377,7 @@ namespace Logic.AI
GeneratorActionIds(data, CommonActionType.LearnTech);
GeneratorActionIds(data, CommonActionType.StartWonder);
GeneratorActionIds(data, CommonActionType.PlayerAction);
GeneratorActionIds(data, CommonActionType.BuyCultureCard);
foreach (var city in selfCities)
{
data.TargetParam.CityData = city;
@ -453,9 +455,6 @@ namespace Logic.AI
public static void GeneratorActionIds(AICalculatorData data, CommonActionType type)
{
// AI暂不购买文化卡避免隐藏/里程碑卡被当成普通候选行为反复执行。
if (type == CommonActionType.BuyCultureCard) return;
var actions = ActionLogicFactory.GetActionLogicByType(type);
if (actions == null || actions.Count == 0) return;
@ -832,6 +831,7 @@ namespace Logic.AI
data.TargetParam.MainObjectType = ActionLogicFactory.GetMainObjectType(type);
foreach (var action in actions)
{
if (!IsAIHeroSlotCultureCard(data.TargetParam.PlayerData, action.ActionId.CultureCardType)) continue;
if (!action.CheckCan(data.TargetParam)) continue;
var param = data.TargetParam.GetCopyParam();
param.UnitData = null;
@ -846,6 +846,24 @@ namespace Logic.AI
}
}
public static bool IsAIHeroSlotCultureCard(PlayerData player, CultureCardType cardType)
{
var heroData = player?.PlayerHeroData;
var cultureInfo = player?.PlayerCultureInfo;
if (heroData == null || cultureInfo == null) return false;
if (cultureInfo.CultureCardList != null && cultureInfo.CultureCardList.Contains(cardType)) return false;
return cardType switch
{
CultureCardType.SecondHero => heroData.MaxHeroCount == 1 && heroData.HeroCount >= 1,
CultureCardType.ThirdHero => heroData.MaxHeroCount == 2
&& heroData.HeroCount >= 2
&& cultureInfo.CultureCardList != null
&& cultureInfo.CultureCardList.Contains(CultureCardType.SecondHero),
_ => false
};
}
private static void GenerateMoveActionsTowardAnchors(
AICalculatorData data,
List<ActionLogicBase> actions,

View File

@ -191,6 +191,23 @@ namespace Logic.AI.Director
return null;
}
public AIActionBase FindHeroSlotCultureCard(PlayerData player)
{
AIActionBase best = null;
var bestType = CultureCardType.Max;
foreach (var action in HeroManagementActions)
{
var id = action.ActionLogic.ActionId;
if (id.ActionType != CommonActionType.BuyCultureCard) continue;
if (!AIActionGenerator.IsAIHeroSlotCultureCard(player, id.CultureCardType)) continue;
if (id.CultureCardType >= bestType) continue;
bestType = id.CultureCardType;
best = action;
}
return best;
}
public IEnumerable<AIActionBase> GetCityActions(CityData city)
{
if (city == null) yield break;
@ -321,9 +338,14 @@ namespace Logic.AI.Director
PlayerActions.Add(action);
break;
case CommonActionType.LearnTech:
case CommonActionType.BuyCultureCard:
PlayerActions.Add(action);
break;
case CommonActionType.BuyCultureCard:
if (AIActionGenerator.IsAIHeroSlotCultureCard(action.Param.PlayerData, id.CultureCardType))
{
HeroManagementActions.Add(action);
}
break;
}
}

View File

@ -347,6 +347,14 @@ namespace Logic.AI.Director
return true;
}
var heroSlotCard = TryHeroSlotCultureCard(ctx, decision);
if (heroSlotCard.IsValid)
{
candidate = heroSlotCard;
decision.AddTrace($"HeroManagement: buy hero slot card {candidate.ActionId?.CultureCardType}.", ctx.Config.MaxCandidateTraceCount);
return true;
}
var finishTask = ctx.ActionIndex.FindBestHeroTaskFinish(ctx.Player);
candidate = ctx.ActionIndex.Candidate(finishTask, AIDirectorLane.HeroManagement, "HeroManagement.FinishLowestTask", 870f);
AddTerms(candidate, ("base", 870f));
@ -360,6 +368,16 @@ namespace Logic.AI.Director
return false;
}
private AIDirectorActionCandidate TryHeroSlotCultureCard(AIDirectorContext ctx, AIDirectorDecision decision)
{
var action = ctx.ActionIndex.FindHeroSlotCultureCard(ctx.Player);
var score = ScoreCultureCard(ctx, action?.ActionLogic?.ActionId?.CultureCardType ?? CultureCardType.None);
var candidate = ctx.ActionIndex.Candidate(action, AIDirectorLane.HeroManagement, "HeroManagement.BuyHeroSlotCard", score);
AddTerms(candidate, ("heroSlotCard", score));
RecordCandidate(ctx, decision, "BuyHeroSlotCard", candidate, action == null ? "NoAction" : (candidate.IsValid ? null : "CheckCanFailed"));
return candidate;
}
private AIDirectorActionCandidate FindBestHeroSpawn(AIDirectorContext ctx, AIDirectorDecision decision)
{
AIDirectorActionCandidate best = AIDirectorActionCandidate.None;
@ -746,10 +764,12 @@ namespace Logic.AI.Director
private float ScoreCultureCard(AIDirectorContext ctx, CultureCardType card)
{
var score = 300f;
if (ctx.Cache.StrategicPosture == AIDirectorStrategicPosture.Defense && card == CultureCardType.AdvancedMilitaryEnhance) score += 120f;
if (ctx.Cache.SelfHeroes.Count >= 1 && card is CultureCardType.SecondHero or CultureCardType.ThirdHero or CultureCardType.AdvancedHeroEnhance) score += 100f;
if (ctx.Cache.StrategicPosture == AIDirectorStrategicPosture.Development && card == CultureCardType.AdvancedEconomyEnhance) score += 80f;
if (!AIActionGenerator.IsAIHeroSlotCultureCard(ctx.Player, card)) return 0f;
var heroData = ctx.Player?.PlayerHeroData;
var score = 860f;
if (card == CultureCardType.SecondHero && heroData?.MaxHeroCount == 1) score += 80f;
if (card == CultureCardType.ThirdHero && heroData?.MaxHeroCount == 2) score += 70f;
if (ctx.Cache.SelfHeroes.Count <= 0) score -= 120f;
return score;
}