Skip to content

Fix AI for Forge of Heroes and similar counter-boon pump wrappers#10620

Open
Madwand99 wants to merge 1 commit intoCard-Forge:masterfrom
Madwand99:FixAIUseOfForgeOfHeroes
Open

Fix AI for Forge of Heroes and similar counter-boon pump wrappers#10620
Madwand99 wants to merge 1 commit intoCard-Forge:masterfrom
Madwand99:FixAIUseOfForgeOfHeroes

Conversation

@Madwand99
Copy link
Copy Markdown
Contributor

This fixes an AI targeting issue where Forge of Heroes could target an opponent’s commander instead of the AI’s own commander.

Forge of Heroes is scripted as a Pump ability with PutCounter sub-abilities that apply to the parent target. Since the top-level ability has no normal pump stats or keyword effect, PumpAi could fall through to generic target selection and choose the “best” legal commander overall, including an opponent’s commander.

This PR teaches PumpAi to recognize beneficial counter sub-abilities attached to the pump target, then restrict those target choices to valid permanents controlled by the AI’s team. It currently treats +1/+1, loyalty, and keyword counters as beneficial, while avoiding hostile or arbitrary counters such as stun, corruption, oil, and -1/-1.

It also updates sub-ability drawback handling so inactive conditional sub-abilities and parent-targeted sub-abilities are not evaluated as independent drawback decisions.

Cards that benefit from this general handling include:

  • Forge of Heroes
  • Big Play
  • Heightened Reflexes
  • Wing It
  • Fully Grown
  • Sudden Spinnerets
  • Spontaneous Flight
  • Will of the All-Hunter
  • Sorin, Imperious Bloodlord
  • A-Sorin, Imperious Bloodlord
  • Throw from the Saddle

Added a regression test confirming that Forge of Heroes targets the AI’s own commander even when the opponent’s commander is also legal and stronger.

Tested with:

mvn -pl forge-gui-desktop -am -Dtest=forge.ai.ability.PumpAiTest -Dsurefire.failIfNoSpecifiedTests=false test

Fixes #9641


private static boolean isDefinedByParentTarget(AbilitySub ab) {
final String defined = ab.getParam("Defined");
return "ParentTarget".equals(defined) || "Targeted".equals(defined) || "ThisTargetedCard".equals(defined);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no duplicated methods please

@squee1968 squee1968 requested a review from Agetian May 7, 2026 09:24
}

if (counterBoon) {
CardCollection counterBoonTargets = getCounterBoonTargets(ai, sa);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not a fan of attempting all this lookahead logic

a better solution could be just refactoring the script without Pump effect so the top API logic might fit better 🤔

*/
public AiAbilityDecision chkDrawbackWithSubs(Player aiPlayer, AbilitySub ab) {
final AbilitySub subAb = ab.getSubAbility();
if (!ab.metConditions() || isDefinedByParentTarget(ab)) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can cause negative side effects with way too generic assumption:
lots of conditions might only be true after putting it on the stack

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

AI uses "Forge of Heroes" on the opponent's commander

2 participants