From db720acb01cc7109b9afd69b776481f5fd232b84 Mon Sep 17 00:00:00 2001 From: Maxmtg Date: Tue, 5 Feb 2013 06:51:09 +0000 Subject: [PATCH] predictThreatenedObjects moved from AbilityFactory to ComputerUtil (for now, will find a more specific place later) GameActionUtil.payCostDuringAbilityResolve method now demands player parameter (though it is always human player) --- src/main/java/forge/GameAction.java | 2 +- src/main/java/forge/GameActionUtil.java | 3 +- .../card/abilityfactory/AbilityFactory.java | 192 +----------------- .../card/abilityfactory/ai/ChangeZoneAi.java | 4 +- .../abilityfactory/ai/DamagePreventAi.java | 3 +- .../card/abilityfactory/ai/PumpAiBase.java | 3 +- .../card/abilityfactory/ai/RegenerateAi.java | 5 +- .../abilityfactory/ai/RegenerateAllAi.java | 4 +- src/main/java/forge/game/ai/ComputerUtil.java | 190 +++++++++++++++++ .../java/forge/game/ai/ComputerUtilCost.java | 2 +- .../java/forge/game/phase/CombatUtil.java | 2 +- src/main/java/forge/game/phase/Upkeep.java | 4 +- 12 files changed, 208 insertions(+), 206 deletions(-) diff --git a/src/main/java/forge/GameAction.java b/src/main/java/forge/GameAction.java index b8cff818cbe..3a476fcd5af 100644 --- a/src/main/java/forge/GameAction.java +++ b/src/main/java/forge/GameAction.java @@ -555,7 +555,7 @@ public class GameAction { public void resolve() { Player p = recoverable.getController(); if (p.isHuman()) { - GameActionUtil.payCostDuringAbilityResolve(abRecover, abRecover.getPayCosts(), + GameActionUtil.payCostDuringAbilityResolve(p, abRecover, abRecover.getPayCosts(), paidCommand, unpaidCommand, null, game); } else { // computer if (ComputerUtilCost.canPayCost(abRecover, p)) { diff --git a/src/main/java/forge/GameActionUtil.java b/src/main/java/forge/GameActionUtil.java index e53c35cccaa..810ddc13561 100644 --- a/src/main/java/forge/GameActionUtil.java +++ b/src/main/java/forge/GameActionUtil.java @@ -423,11 +423,10 @@ public final class GameActionUtil { * a {@link forge.Command} object. * @param sourceAbility TODO */ - public static void payCostDuringAbilityResolve(final SpellAbility ability, final Cost cost, final Command paid, + public static void payCostDuringAbilityResolve(final Player p, final SpellAbility ability, final Cost cost, final Command paid, final Command unpaid, SpellAbility sourceAbility, final GameState game) { final Card source = ability.getSourceCard(); final List parts = cost.getCostParts(); - Player p = Singletons.getControl().getPlayer(); ArrayList remainingParts = new ArrayList(cost.getCostParts()); CostPart costPart = null; if (!parts.isEmpty()) { diff --git a/src/main/java/forge/card/abilityfactory/AbilityFactory.java b/src/main/java/forge/card/abilityfactory/AbilityFactory.java index 03a031d17b7..ebd1ce8fba3 100644 --- a/src/main/java/forge/card/abilityfactory/AbilityFactory.java +++ b/src/main/java/forge/card/abilityfactory/AbilityFactory.java @@ -1237,196 +1237,6 @@ public class AbilityFactory { return objects; } - /** - *

- * predictThreatenedObjects. - *

- * - * @param saviourAf - * a AbilityFactory object - * @return a {@link java.util.ArrayList} object. - * @since 1.0.15 - */ - public static ArrayList predictThreatenedObjects(final Player aiPlayer, final SpellAbility sa) { - final ArrayList objects = new ArrayList(); - if (Singletons.getModel().getGame().getStack().isEmpty()) { - return objects; - } - - // check stack for something that will kill this - final SpellAbility topStack = Singletons.getModel().getGame().getStack().peekAbility(); - objects.addAll(AbilityFactory.predictThreatenedObjects(aiPlayer, sa, topStack)); - - return objects; - } - - /** - *

- * predictThreatenedObjects. - *

- * - * @param saviourAf - * a AbilityFactory object - * @param topStack - * a {@link forge.card.spellability.SpellAbility} object. - * @return a {@link java.util.ArrayList} object. - * @since 1.0.15 - */ - public static ArrayList predictThreatenedObjects(final Player aiPlayer, final SpellAbility saviour, - final SpellAbility topStack) { - ArrayList objects = new ArrayList(); - final ArrayList threatened = new ArrayList(); - ApiType saviourApi = saviour.getApi(); - - if (topStack == null) { - return objects; - } - - final Card source = topStack.getSourceCard(); - final ApiType threatApi = topStack.getApi(); - - // Can only Predict things from AFs - if (threatApi != null) { - final Target tgt = topStack.getTarget(); - - if (tgt == null) { - if (topStack.hasParam("Defined")) { - objects = AbilityFactory.getDefinedObjects(source, topStack.getParam("Defined"), topStack); - } else if (topStack.hasParam("ValidCards")) { - List battleField = aiPlayer.getCardsIn(ZoneType.Battlefield); - List cards = CardLists.getValidCards(battleField, topStack.getParam("ValidCards").split(","), source.getController(), source); - for (Card card : cards) { - objects.add(card); - } - } - } else { - objects = tgt.getTargets(); - } - - // Determine if Defined Objects are "threatened" will be destroyed - // due to this SA - - // Lethal Damage => prevent damage/regeneration/bounce/shroud - if (threatApi == ApiType.DealDamage || threatApi == ApiType.DamageAll) { - // If PredictDamage is >= Lethal Damage - final int dmg = AbilityFactory.calculateAmount(topStack.getSourceCard(), - topStack.getParam("NumDmg"), topStack); - for (final Object o : objects) { - if (o instanceof Card) { - final Card c = (Card) o; - - // indestructible - if (c.hasKeyword("Indestructible")) { - continue; - } - - // already regenerated - if (c.getShield() > 0) { - continue; - } - - // don't use it on creatures that can't be regenerated - if ((saviourApi == ApiType.Regenerate || saviourApi == ApiType.RegenerateAll) && !c.canBeShielded()) { - continue; - } - - // give Shroud to targeted creatures - if (saviourApi == ApiType.Pump && tgt == null && saviour.hasParam("KW") - && (saviour.getParam("KW").endsWith("Shroud") - || saviour.getParam("KW").endsWith("Hexproof"))) { - continue; - } - - // don't bounce or blink a permanent that the human - // player owns or is a token - if (saviourApi == ApiType.ChangeZone && (c.getOwner().isHuman() || c.isToken())) { - continue; - } - - if (c.predictDamage(dmg, source, false) >= c.getKillDamage()) { - threatened.add(c); - } - } else if (o instanceof Player) { - final Player p = (Player) o; - - if (source.hasKeyword("Infect")) { - if (p.predictDamage(dmg, source, false) >= p.getPoisonCounters()) { - threatened.add(p); - } - } else if (p.predictDamage(dmg, source, false) >= p.getLife()) { - threatened.add(p); - } - } - } - } - // Destroy => regeneration/bounce/shroud - else if ((threatApi == ApiType.Destroy || threatApi == ApiType.DestroyAll) - && (((saviourApi == ApiType.Regenerate || saviourApi == ApiType.RegenerateAll) - && !topStack.hasParam("NoRegen")) || saviourApi == ApiType.ChangeZone || saviourApi == ApiType.Pump)) { - for (final Object o : objects) { - if (o instanceof Card) { - final Card c = (Card) o; - // indestructible - if (c.hasKeyword("Indestructible")) { - continue; - } - - // already regenerated - if (c.getShield() > 0) { - continue; - } - - // give Shroud to targeted creatures - if (saviourApi == ApiType.Pump && tgt == null && saviour.hasParam("KW") - && (saviour.getParam("KW").endsWith("Shroud") - || saviour.getParam("KW").endsWith("Hexproof"))) { - continue; - } - - // don't bounce or blink a permanent that the human - // player owns or is a token - if (saviourApi == ApiType.ChangeZone && (c.getOwner().isHuman() || c.isToken())) { - continue; - } - - // don't use it on creatures that can't be regenerated - if (saviourApi == ApiType.Regenerate && !c.canBeShielded()) { - continue; - } - threatened.add(c); - } - } - } - // Exiling => bounce/shroud - else if ((threatApi == ApiType.ChangeZone || threatApi == ApiType.ChangeZoneAll) - && (saviourApi == ApiType.ChangeZone || saviourApi == ApiType.Pump) - && topStack.hasParam("Destination") - && topStack.getParam("Destination").equals("Exile")) { - for (final Object o : objects) { - if (o instanceof Card) { - final Card c = (Card) o; - // give Shroud to targeted creatures - if (saviourApi == ApiType.Pump && tgt == null && saviour.hasParam("KW") - && (saviour.getParam("KW").endsWith("Shroud") || saviour.getParam("KW").endsWith("Hexproof"))) { - continue; - } - - // don't bounce or blink a permanent that the human - // player owns or is a token - if (saviourApi == ApiType.ChangeZone && (c.getOwner().isHuman() || c.isToken())) { - continue; - } - - threatened.add(c); - } - } - } - } - - threatened.addAll(AbilityFactory.predictThreatenedObjects(aiPlayer, saviour, topStack.getSubAbility())); - return threatened; - } - /** *

* handleRemembering. @@ -1667,7 +1477,7 @@ public class AbilityFactory { if (paid) { unpaidCommand = paidCommand; } - GameActionUtil.payCostDuringAbilityResolve(ability, cost, paidCommand, unpaidCommand, sa, game); + GameActionUtil.payCostDuringAbilityResolve(payer, ability, cost, paidCommand, unpaidCommand, sa, game); waitForInput = true; // wait for the human input break; // multiple human players are not supported } diff --git a/src/main/java/forge/card/abilityfactory/ai/ChangeZoneAi.java b/src/main/java/forge/card/abilityfactory/ai/ChangeZoneAi.java index 4aa0766c717..40e1eea0eb3 100644 --- a/src/main/java/forge/card/abilityfactory/ai/ChangeZoneAi.java +++ b/src/main/java/forge/card/abilityfactory/ai/ChangeZoneAi.java @@ -600,7 +600,7 @@ public class ChangeZoneAi extends SpellAiLogic { return false; } - final ArrayList objects = AbilityFactory.predictThreatenedObjects(ai, sa); + final ArrayList objects = ComputerUtil.predictThreatenedObjects(ai, sa); boolean contains = false; for (final Card c : retrieval) { if (objects.contains(c)) { @@ -720,7 +720,7 @@ public class ChangeZoneAi extends SpellAiLogic { // check stack for something on the stack that will kill // anything i control if (Singletons.getModel().getGame().getStack().size() > 0) { - final ArrayList objects = AbilityFactory.predictThreatenedObjects(ai, sa); + final ArrayList objects = ComputerUtil.predictThreatenedObjects(ai, sa); final List threatenedTargets = new ArrayList(); diff --git a/src/main/java/forge/card/abilityfactory/ai/DamagePreventAi.java b/src/main/java/forge/card/abilityfactory/ai/DamagePreventAi.java index 29b2e8fe34f..aad2069ac3c 100644 --- a/src/main/java/forge/card/abilityfactory/ai/DamagePreventAi.java +++ b/src/main/java/forge/card/abilityfactory/ai/DamagePreventAi.java @@ -13,6 +13,7 @@ import forge.card.cardfactory.CardFactoryUtil; import forge.card.cost.Cost; import forge.card.spellability.SpellAbility; import forge.card.spellability.Target; +import forge.game.ai.ComputerUtil; import forge.game.ai.ComputerUtilCombat; import forge.game.ai.ComputerUtilCost; import forge.game.phase.PhaseHandler; @@ -56,7 +57,7 @@ public class DamagePreventAi extends SpellAiLogic { // react to threats on the stack if (Singletons.getModel().getGame().getStack().size() > 0) { - final ArrayList threatenedObjects = AbilityFactory.predictThreatenedObjects(sa.getActivatingPlayer(), sa); + final ArrayList threatenedObjects = ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), sa); for (final Object o : objects) { if (threatenedObjects.contains(o)) { chance = true; diff --git a/src/main/java/forge/card/abilityfactory/ai/PumpAiBase.java b/src/main/java/forge/card/abilityfactory/ai/PumpAiBase.java index 4d81a3e09e9..041b14ac7cc 100644 --- a/src/main/java/forge/card/abilityfactory/ai/PumpAiBase.java +++ b/src/main/java/forge/card/abilityfactory/ai/PumpAiBase.java @@ -17,6 +17,7 @@ import forge.card.abilityfactory.SpellAiLogic; import forge.card.cardfactory.CardFactoryUtil; import forge.card.spellability.SpellAbility; import forge.game.GameState; +import forge.game.ai.ComputerUtil; import forge.game.ai.ComputerUtilCombat; import forge.game.phase.Combat; import forge.game.phase.CombatUtil; @@ -336,7 +337,7 @@ public abstract class PumpAiBase extends SpellAiLogic { return false; } } else if (keyword.equals("Shroud") || keyword.equals("Hexproof")) { - if (!AbilityFactory.predictThreatenedObjects(sa.getActivatingPlayer(), sa).contains(card)) { + if (!ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), sa).contains(card)) { return false; } } else if (keyword.equals("Islandwalk")) { diff --git a/src/main/java/forge/card/abilityfactory/ai/RegenerateAi.java b/src/main/java/forge/card/abilityfactory/ai/RegenerateAi.java index 36b72a55447..9ba6ab208b5 100644 --- a/src/main/java/forge/card/abilityfactory/ai/RegenerateAi.java +++ b/src/main/java/forge/card/abilityfactory/ai/RegenerateAi.java @@ -31,6 +31,7 @@ import forge.card.cardfactory.CardFactoryUtil; import forge.card.cost.Cost; import forge.card.spellability.SpellAbility; import forge.card.spellability.Target; +import forge.game.ai.ComputerUtil; import forge.game.ai.ComputerUtilCombat; import forge.game.ai.ComputerUtilCost; import forge.game.phase.PhaseType; @@ -83,7 +84,7 @@ public class RegenerateAi extends SpellAiLogic { final List list = AbilityFactory.getDefinedCards(hostCard, sa.getParam("Defined"), sa); if (Singletons.getModel().getGame().getStack().size() > 0) { - final List objects = AbilityFactory.predictThreatenedObjects(sa.getActivatingPlayer(), sa); + final List objects = ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), sa); for (final Card c : list) { if (objects.contains(c)) { @@ -119,7 +120,7 @@ public class RegenerateAi extends SpellAiLogic { if (Singletons.getModel().getGame().getStack().size() > 0) { // check stack for something on the stack will kill anything i // control - final ArrayList objects = AbilityFactory.predictThreatenedObjects(sa.getActivatingPlayer(), sa); + final ArrayList objects = ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), sa); final List threatenedTargets = new ArrayList(); diff --git a/src/main/java/forge/card/abilityfactory/ai/RegenerateAllAi.java b/src/main/java/forge/card/abilityfactory/ai/RegenerateAllAi.java index 01729c013f7..3405bc07db3 100644 --- a/src/main/java/forge/card/abilityfactory/ai/RegenerateAllAi.java +++ b/src/main/java/forge/card/abilityfactory/ai/RegenerateAllAi.java @@ -7,10 +7,10 @@ import forge.Card; import forge.CardLists; import forge.CardPredicates; import forge.Singletons; -import forge.card.abilityfactory.AbilityFactory; import forge.card.abilityfactory.SpellAiLogic; import forge.card.cost.Cost; import forge.card.spellability.SpellAbility; +import forge.game.ai.ComputerUtil; import forge.game.ai.ComputerUtilCombat; import forge.game.ai.ComputerUtilCost; import forge.game.phase.PhaseType; @@ -56,7 +56,7 @@ public class RegenerateAllAi extends SpellAiLogic { int numSaved = 0; if (Singletons.getModel().getGame().getStack().size() > 0) { - final ArrayList objects = AbilityFactory.predictThreatenedObjects(sa.getActivatingPlayer(), sa); + final ArrayList objects = ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), sa); for (final Card c : list) { if (objects.contains(c) && c.getShield() == 0) { diff --git a/src/main/java/forge/game/ai/ComputerUtil.java b/src/main/java/forge/game/ai/ComputerUtil.java index df1d13cab8e..d0f86b553b7 100644 --- a/src/main/java/forge/game/ai/ComputerUtil.java +++ b/src/main/java/forge/game/ai/ComputerUtil.java @@ -1047,4 +1047,194 @@ public class ComputerUtil { return false; } // hasACardGivingHaste + + /** + *

+ * predictThreatenedObjects. + *

+ * + * @param saviourAf + * a AbilityFactory object + * @return a {@link java.util.ArrayList} object. + * @since 1.0.15 + */ + public static ArrayList predictThreatenedObjects(final Player aiPlayer, final SpellAbility sa) { + final ArrayList objects = new ArrayList(); + if (Singletons.getModel().getGame().getStack().isEmpty()) { + return objects; + } + + // check stack for something that will kill this + final SpellAbility topStack = Singletons.getModel().getGame().getStack().peekAbility(); + objects.addAll(ComputerUtil.predictThreatenedObjects(aiPlayer, sa, topStack)); + + return objects; + } + + /** + *

+ * predictThreatenedObjects. + *

+ * + * @param saviourAf + * a AbilityFactory object + * @param topStack + * a {@link forge.card.spellability.SpellAbility} object. + * @return a {@link java.util.ArrayList} object. + * @since 1.0.15 + */ + public static ArrayList predictThreatenedObjects(final Player aiPlayer, final SpellAbility saviour, + final SpellAbility topStack) { + ArrayList objects = new ArrayList(); + final ArrayList threatened = new ArrayList(); + ApiType saviourApi = saviour.getApi(); + + if (topStack == null) { + return objects; + } + + final Card source = topStack.getSourceCard(); + final ApiType threatApi = topStack.getApi(); + + // Can only Predict things from AFs + if (threatApi != null) { + final Target tgt = topStack.getTarget(); + + if (tgt == null) { + if (topStack.hasParam("Defined")) { + objects = AbilityFactory.getDefinedObjects(source, topStack.getParam("Defined"), topStack); + } else if (topStack.hasParam("ValidCards")) { + List battleField = aiPlayer.getCardsIn(ZoneType.Battlefield); + List cards = CardLists.getValidCards(battleField, topStack.getParam("ValidCards").split(","), source.getController(), source); + for (Card card : cards) { + objects.add(card); + } + } + } else { + objects = tgt.getTargets(); + } + + // Determine if Defined Objects are "threatened" will be destroyed + // due to this SA + + // Lethal Damage => prevent damage/regeneration/bounce/shroud + if (threatApi == ApiType.DealDamage || threatApi == ApiType.DamageAll) { + // If PredictDamage is >= Lethal Damage + final int dmg = AbilityFactory.calculateAmount(topStack.getSourceCard(), + topStack.getParam("NumDmg"), topStack); + for (final Object o : objects) { + if (o instanceof Card) { + final Card c = (Card) o; + + // indestructible + if (c.hasKeyword("Indestructible")) { + continue; + } + + // already regenerated + if (c.getShield() > 0) { + continue; + } + + // don't use it on creatures that can't be regenerated + if ((saviourApi == ApiType.Regenerate || saviourApi == ApiType.RegenerateAll) && !c.canBeShielded()) { + continue; + } + + // give Shroud to targeted creatures + if (saviourApi == ApiType.Pump && tgt == null && saviour.hasParam("KW") + && (saviour.getParam("KW").endsWith("Shroud") + || saviour.getParam("KW").endsWith("Hexproof"))) { + continue; + } + + // don't bounce or blink a permanent that the human + // player owns or is a token + if (saviourApi == ApiType.ChangeZone && (c.getOwner().isHuman() || c.isToken())) { + continue; + } + + if (c.predictDamage(dmg, source, false) >= c.getKillDamage()) { + threatened.add(c); + } + } else if (o instanceof Player) { + final Player p = (Player) o; + + if (source.hasKeyword("Infect")) { + if (p.predictDamage(dmg, source, false) >= p.getPoisonCounters()) { + threatened.add(p); + } + } else if (p.predictDamage(dmg, source, false) >= p.getLife()) { + threatened.add(p); + } + } + } + } + // Destroy => regeneration/bounce/shroud + else if ((threatApi == ApiType.Destroy || threatApi == ApiType.DestroyAll) + && (((saviourApi == ApiType.Regenerate || saviourApi == ApiType.RegenerateAll) + && !topStack.hasParam("NoRegen")) || saviourApi == ApiType.ChangeZone || saviourApi == ApiType.Pump)) { + for (final Object o : objects) { + if (o instanceof Card) { + final Card c = (Card) o; + // indestructible + if (c.hasKeyword("Indestructible")) { + continue; + } + + // already regenerated + if (c.getShield() > 0) { + continue; + } + + // give Shroud to targeted creatures + if (saviourApi == ApiType.Pump && tgt == null && saviour.hasParam("KW") + && (saviour.getParam("KW").endsWith("Shroud") + || saviour.getParam("KW").endsWith("Hexproof"))) { + continue; + } + + // don't bounce or blink a permanent that the human + // player owns or is a token + if (saviourApi == ApiType.ChangeZone && (c.getOwner().isHuman() || c.isToken())) { + continue; + } + + // don't use it on creatures that can't be regenerated + if (saviourApi == ApiType.Regenerate && !c.canBeShielded()) { + continue; + } + threatened.add(c); + } + } + } + // Exiling => bounce/shroud + else if ((threatApi == ApiType.ChangeZone || threatApi == ApiType.ChangeZoneAll) + && (saviourApi == ApiType.ChangeZone || saviourApi == ApiType.Pump) + && topStack.hasParam("Destination") + && topStack.getParam("Destination").equals("Exile")) { + for (final Object o : objects) { + if (o instanceof Card) { + final Card c = (Card) o; + // give Shroud to targeted creatures + if (saviourApi == ApiType.Pump && tgt == null && saviour.hasParam("KW") + && (saviour.getParam("KW").endsWith("Shroud") || saviour.getParam("KW").endsWith("Hexproof"))) { + continue; + } + + // don't bounce or blink a permanent that the human + // player owns or is a token + if (saviourApi == ApiType.ChangeZone && (c.getOwner().isHuman() || c.isToken())) { + continue; + } + + threatened.add(c); + } + } + } + } + + threatened.addAll(ComputerUtil.predictThreatenedObjects(aiPlayer, saviour, topStack.getSubAbility())); + return threatened; + } } diff --git a/src/main/java/forge/game/ai/ComputerUtilCost.java b/src/main/java/forge/game/ai/ComputerUtilCost.java index 5342fc6e846..625ae61d16d 100644 --- a/src/main/java/forge/game/ai/ComputerUtilCost.java +++ b/src/main/java/forge/game/ai/ComputerUtilCost.java @@ -342,7 +342,7 @@ public class ComputerUtilCost { final GameState game = Singletons.getModel().getGame(); // Check for stuff like Nether Void int extraManaNeeded = 0; - if (sa instanceof Spell && player.isComputer()) { + if (sa instanceof Spell) { for (Player opp : player.getOpponents()) { for (Card c : opp.getCardsIn(ZoneType.Battlefield)) { final String snem = c.getSVar("SpellsNeedExtraMana"); diff --git a/src/main/java/forge/game/phase/CombatUtil.java b/src/main/java/forge/game/phase/CombatUtil.java index 90f02548fbd..9c4bb190c82 100644 --- a/src/main/java/forge/game/phase/CombatUtil.java +++ b/src/main/java/forge/game/phase/CombatUtil.java @@ -1201,7 +1201,7 @@ public class CombatUtil { ability.setActivatingPlayer(c.getController()); if (c.getController().isHuman()) { - GameActionUtil.payCostDuringAbilityResolve(ability, attackCost, paidCommand, unpaidCommand, null, game); + GameActionUtil.payCostDuringAbilityResolve(c.getController(), ability, attackCost, paidCommand, unpaidCommand, null, game); } else { // computer if (ComputerUtilCost.canPayCost(ability, c.getController())) { ComputerUtil.playNoStack(c.getController(), ability, game); diff --git a/src/main/java/forge/game/phase/Upkeep.java b/src/main/java/forge/game/phase/Upkeep.java index dc4cd0ca1b7..cfee534d83f 100644 --- a/src/main/java/forge/game/phase/Upkeep.java +++ b/src/main/java/forge/game/phase/Upkeep.java @@ -197,7 +197,7 @@ public class Upkeep extends Phase { Player controller = c.getController(); if (controller.isHuman()) { Cost cost = new Cost(c, c.getEchoCost().trim(), true); - GameActionUtil.payCostDuringAbilityResolve(blankAbility, cost, paidCommand, unpaidCommand, null, game); + GameActionUtil.payCostDuringAbilityResolve(controller, blankAbility, cost, paidCommand, unpaidCommand, null, game); } else { // computer if (ComputerUtilCost.canPayCost(blankAbility, controller)) { ComputerUtil.playNoStack(controller, blankAbility, game); @@ -357,7 +357,7 @@ public class Upkeep extends Phase { @Override public void resolve() { if (controller.isHuman()) { - GameActionUtil.payCostDuringAbilityResolve(blankAbility, blankAbility.getPayCosts(), + GameActionUtil.payCostDuringAbilityResolve(controller, blankAbility, blankAbility.getPayCosts(), paidCommand, unpaidCommand, null, game); } else { // computer if (ComputerUtilCost.shouldPayCost(controller, c, upkeepCost) && ComputerUtilCost.canPayCost(blankAbility, controller)) {