- Attempting to fix a long-standing bug with the delayed triggers getting the wrong activator set at their resolution time (the AI was aggressively overwriting the activator via its SpellAbility simulation routines).

- Ensured that all callers of getOriginalAndAltCostAbilities both call setActivatingPlayer and then reset it to its original value if there was one after the simulation completes. Thus, an aggressive setActivatingPlayer inside getOriginalAndAltCostAbilities should not be necessary.
This commit is contained in:
Agetian
2017-08-06 11:52:03 +00:00
parent cb9d0ce3ed
commit d6dfc2ffb6
5 changed files with 47 additions and 12 deletions

View File

@@ -545,10 +545,11 @@ public class AiController {
for (final SpellAbility sa : ComputerUtilAbility.getOriginalAndAltCostAbilities(possibleCounters, player)) {
SpellAbility currentSA = sa;
Player originalActivator = sa.getActivatingPlayer(); // needed for delayed triggers
sa.setActivatingPlayer(player);
// check everything necessary
AiPlayDecision opinion = canPlayAndPayFor(currentSA);
//PhaseHandler ph = game.getPhaseHandler();
// System.out.printf("Ai thinks '%s' of %s @ %s %s >>> \n", opinion, sa, Lang.getPossesive(ph.getPlayerTurn().getName()), ph.getPhase());
@@ -566,6 +567,11 @@ public class AiController {
}
}
}
// This was only a simulation, so set the original activator back if there was one
if (originalActivator != null) {
sa.setActivatingPlayer(originalActivator);
}
}
// TODO - "Look" at Targeted SA and "calculate" the threshold
@@ -592,11 +598,17 @@ public class AiController {
if (sa.getApi() == ApiType.Counter || sa.getApi() == exceptSA) {
continue;
}
Player originalActivator = sa.getActivatingPlayer(); // needed for delayed triggers
sa.setActivatingPlayer(player);
// TODO: this currently only works as a limited prediction of permanent spells.
// Ideally this should cast canPlaySa to determine that the AI is truly able/willing to cast a spell,
// but that is currently difficult to implement due to various side effects leading to stack overflow.
if (!ComputerUtil.castPermanentInMain1(player, sa) && sa.getHostCard() != null && !sa.getHostCard().isLand() && ComputerUtilCost.canPayCost(sa, player)) {
if (originalActivator != null) {
sa.setActivatingPlayer(originalActivator); // we are only simulating, so set the original activator back
}
if (sa instanceof SpellPermanent) {
return sa;
}
@@ -1118,6 +1130,10 @@ public class AiController {
}
}
// We need the original activating player in case the SA is on a delayed trigger waiting to be
// activated by another player (e.g. Rainbow Vale EOT trigger).
Player origActivatingPlayer = sa.getActivatingPlayer();
sa.setActivatingPlayer(player);
sa.setLastStateBattlefield(game.getLastStateBattlefield());
sa.setLastStateGraveyard(game.getLastStateGraveyard());
@@ -1126,8 +1142,13 @@ public class AiController {
// PhaseHandler ph = game.getPhaseHandler();
// System.out.printf("Ai thinks '%s' of %s -> %s @ %s %s >>> \n", opinion, sa.getHostCard(), sa, Lang.getPossesive(ph.getPlayerTurn().getName()), ph.getPhase());
if (opinion != AiPlayDecision.WillPlay)
if (origActivatingPlayer != null) {
sa.setActivatingPlayer(origActivatingPlayer); // since this was only a simulation, restore the original activator
}
if (opinion != AiPlayDecision.WillPlay) {
continue;
}
return sa;
}

View File

@@ -95,7 +95,6 @@ public class ComputerUtilAbility {
public static List<SpellAbility> getOriginalAndAltCostAbilities(final List<SpellAbility> originList, final Player player) {
final List<SpellAbility> newAbilities = new ArrayList<SpellAbility>();
for (SpellAbility sa : originList) {
sa.setActivatingPlayer(player);
//add alternative costs as additional spell abilities
newAbilities.add(sa);
newAbilities.addAll(GameActionUtil.getAlternativeCosts(sa, player));
@@ -103,7 +102,6 @@ public class ComputerUtilAbility {
final List<SpellAbility> result = new ArrayList<SpellAbility>();
for (SpellAbility sa : newAbilities) {
sa.setActivatingPlayer(player);
result.addAll(GameActionUtil.getOptionalCosts(sa));
}
return result;

View File

@@ -471,11 +471,19 @@ public class SpecialCardAi {
continue;
}
Player originalActivator = testSa.getActivatingPlayer(); // needed for delayed triggers
testSa.setActivatingPlayer(ai);
boolean willPlay = false;
if (((PlayerControllerAi)ai.getController()).getAi().canPlaySa(testSa) == AiPlayDecision.WillPlay) {
// the AI is willing to play the spell
return true;
willPlay = true;
}
if (originalActivator != null) {
testSa.setActivatingPlayer(originalActivator); // we are only simulating, so set the original activator back
}
if (willPlay) { return true; } // The AI seems willing to play the spell
}
return false; // haven't found anything to play with the excess generated mana

View File

@@ -156,7 +156,7 @@ public class ManaEffectAi extends SpellAbilityAi {
}
SpellAbility testSaNoCost = testSa.copyWithNoManaCost();
testSaNoCost.setActivatingPlayer(ai);
testSaNoCost.setActivatingPlayer(ai); // should be safe since we're operating on a copy
if (((PlayerControllerAi)ai.getController()).getAi().canPlaySa(testSaNoCost) == AiPlayDecision.WillPlay) {
if (testSa.getHostCard().isPermanent() && !testSa.getHostCard().hasKeyword("Haste")
&& !ai.getGame().getPhaseHandler().is(PhaseType.MAIN2)) {

View File

@@ -81,6 +81,8 @@ public class SpellAbilityPicker {
if (sa.isManaAbility()) {
continue;
}
Player originalActivator = sa.getActivatingPlayer(); // needed for delayed triggers
sa.setActivatingPlayer(player);
AiPlayDecision opinion = canPlayAndPayForSim(sa);
@@ -88,8 +90,14 @@ public class SpellAbilityPicker {
// PhaseHandler ph = game.getPhaseHandler();
// System.out.printf("Ai thinks '%s' of %s -> %s @ %s %s >>> \n", opinion, sa.getHostCard(), sa, Lang.getPossesive(ph.getPlayerTurn().getName()), ph.getPhase());
if (opinion != AiPlayDecision.WillPlay)
if (originalActivator != null) {
sa.setActivatingPlayer(originalActivator); // we are only simulating, so set the original activator back
}
if (opinion != AiPlayDecision.WillPlay) {
continue;
}
candidateSAs.set(writeIndex, sa);
writeIndex++;
}