Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 19 additions & 9 deletions forge-ai/src/main/java/forge/ai/AiController.java
Original file line number Diff line number Diff line change
Expand Up @@ -1534,13 +1534,6 @@ private boolean isSafeToHoldLandDropForMain2(Card landToPlay) {
}

private SpellAbility getSpellAbilityToPlay() {
if (skipped != null) {
//FIXME: this is for failed SA to skip temporarily, don't know why AI computation for mana fails, maybe due to auto mana compute?
for (SpellAbility sa : skipped) {
//System.out.println("Unskip: " + sa.toString() + " (" + sa.getHostCard().getName() + ").");
sa.setSkip(false);
}
}
CardCollection cards = ComputerUtilAbility.getAvailableCards(game, player);
cards = ComputerUtilCard.dedupeCards(cards);
List<SpellAbility> saList = Lists.newArrayList();
Expand All @@ -1566,6 +1559,7 @@ private SpellAbility getSpellAbilityToPlay() {
saList = ComputerUtilAbility.getSpellAbilities(cards, player); // get the SA list early to check for copy SAs
if (ComputerUtilAbility.getFirstCopySASpell(saList) == null) {
// Nothing to copy the spell with, so do nothing.
clearSkippedSpellAbilities();
return null;
}
}
Expand All @@ -1589,22 +1583,38 @@ private SpellAbility getSpellAbilityToPlay() {
// TODO allow when experimental profile?
return spellAbility.isLandAbility() || (spellAbility.getHostCard() != null && ComputerUtilCard.isCardRemAIDeck(spellAbility.getHostCard()));
});
//removed skipped SA
// Keep failed SAs skipped for this priority sequence. Unskip only when passing so a later
// priority (new mana / board state) can retry — unskipping every choose caused equip loops.
skipped = saList.stream().filter(SpellAbility::isSkip).collect(Collectors.toList());
if (!skipped.isEmpty())
if (!skipped.isEmpty()) {
saList.removeAll(skipped);
}
//update LivingEndPlayer
useLivingEnd = IterableUtil.any(player.getZone(ZoneType.Library), CardPredicates.nameEquals("Living End"));

SpellAbility chosenSa = chooseSpellAbilityToPlayFromList(saList, true);

if (topOwnedByAI && !mustRespond && chosenSa != ComputerUtilAbility.getFirstCopySASpell(saList)) {
clearSkippedSpellAbilities();
return null; // not planning to copy the spell and not marked as something the AI would respond to
}

if (chosenSa == null) {
clearSkippedSpellAbilities();
}
return chosenSa;
}

private void clearSkippedSpellAbilities() {
if (skipped == null || skipped.isEmpty()) {
return;
}
for (SpellAbility sa : skipped) {
sa.setSkip(false);
}
skipped.clear();
}

private SpellAbility chooseSpellAbilityToPlayFromList(final List<SpellAbility> all, boolean skipCounter) {
if (all == null || all.isEmpty())
return null;
Expand Down
8 changes: 5 additions & 3 deletions forge-ai/src/main/java/forge/ai/PlayerControllerAi.java
Original file line number Diff line number Diff line change
Expand Up @@ -837,11 +837,13 @@ public boolean playChosenSpellAbility(SpellAbility sa) {
if (sa.isLandAbility()) {
if (sa.canPlay()) {
sa.resolve();
return true;
}
} else {
ComputerUtil.handlePlayingSpellAbility(player, sa, getDeferredTargetingPlayerAction(sa));
return false;
}
return true;
// Must return the real result: always-true made PhaseHandler treat failed equips/spells as
// successful plays, so the AI re-chose the same SA until "AI looped too much".
return ComputerUtil.handlePlayingSpellAbility(player, sa, getDeferredTargetingPlayerAction(sa));
}

/**
Expand Down
Loading