From 0df3e72d00dee2e9cb1262964c96b8cc84d8872f Mon Sep 17 00:00:00 2001 From: tool4EvEr Date: Sat, 18 Dec 2021 12:56:45 +0100 Subject: [PATCH] untilHostLeavesPlayCommand: Fix NPE when returning Aura --- .../src/main/java/forge/game/GameAction.java | 376 +++++++++--------- .../game/ability/SpellAbilityEffect.java | 5 +- 2 files changed, 186 insertions(+), 195 deletions(-) diff --git a/forge-game/src/main/java/forge/game/GameAction.java b/forge-game/src/main/java/forge/game/GameAction.java index ce0186dca77..44cea24e6f9 100644 --- a/forge-game/src/main/java/forge/game/GameAction.java +++ b/forge-game/src/main/java/forge/game/GameAction.java @@ -162,10 +162,10 @@ public class GameAction { if (Iterables.any(game.getPlayers(), PlayerPredicates.canBeAttached(c))) { found = true; } - if (Iterables.any((CardCollectionView) params.get(AbilityKey.LastStateBattlefield), CardPredicates.canBeAttached(c))) { + else if (Iterables.any((CardCollectionView) params.get(AbilityKey.LastStateBattlefield), CardPredicates.canBeAttached(c))) { found = true; } - if (Iterables.any((CardCollectionView) params.get(AbilityKey.LastStateGraveyard), CardPredicates.canBeAttached(c))) { + else if (Iterables.any((CardCollectionView) params.get(AbilityKey.LastStateGraveyard), CardPredicates.canBeAttached(c))) { found = true; } if (!found) { @@ -699,16 +699,36 @@ public class GameAction { public final Card moveTo(final Zone zoneTo, Card c, SpellAbility cause) { return moveTo(zoneTo, c, cause, null); } - - public final Card moveTo(final Zone zoneTo, Card c, Integer position, SpellAbility cause) { - return moveTo(zoneTo, c, position, cause, null); - } - public final Card moveTo(final Zone zoneTo, Card c, SpellAbility cause, Map params) { // FThreads.assertExecutedByEdt(false); // This code must never be executed from EDT, // use FThreads.invokeInNewThread to run code in a pooled thread return moveTo(zoneTo, c, null, cause, params); } + public final Card moveTo(final Zone zoneTo, Card c, Integer position, SpellAbility cause) { + return moveTo(zoneTo, c, position, cause, null); + } + + public final Card moveTo(final ZoneType name, final Card c, SpellAbility cause) { + return moveTo(name, c, 0, cause); + } + public final Card moveTo(final ZoneType name, final Card c, final int libPosition, SpellAbility cause) { + return moveTo(name, c, libPosition, cause, null); + } + public final Card moveTo(final ZoneType name, final Card c, final int libPosition, SpellAbility cause, Map params) { + // Call specific functions to set PlayerZone, then move onto moveTo + switch(name) { + case Hand: return moveToHand(c, cause, params); + case Library: return moveToLibrary(c, libPosition, cause, params); + case Battlefield: return moveToPlay(c, c.getController(), cause, params); + case Graveyard: return moveToGraveyard(c, cause, params); + case Exile: return exile(c, cause, params); + case Stack: return moveToStack(c, cause, params); + case PlanarDeck: return moveToVariantDeck(c, ZoneType.PlanarDeck, libPosition, cause, params); + case SchemeDeck: return moveToVariantDeck(c, ZoneType.SchemeDeck, libPosition, cause, params); + default: // sideboard will also get there + return moveTo(c.getOwner().getZone(name), c, cause); + } + } private Card moveTo(final Zone zoneTo, Card c, Integer position, SpellAbility cause, Map params) { // Ideally move to should never be called without a prevZone @@ -787,6 +807,161 @@ public class GameAction { return c; } + public final Card moveToStack(final Card c, SpellAbility cause) { + return moveToStack(c, cause, null); + } + public final Card moveToStack(final Card c, SpellAbility cause, Map params) { + Card result = moveTo(game.getStackZone(), c, cause, params); + if (cause != null && cause.isSpell() && result.equals(cause.getHostCard())) { + result.setSplitStateToPlayAbility(cause); + + // CR 112.2 A spell’s controller is, by default, the player who put it on the stack. + result.setController(cause.getActivatingPlayer(), 0); + // for triggers like from Wild-Magic Sorcerer + game.getAction().checkStaticAbilities(false); + game.getTriggerHandler().resetActiveTriggers(); + } + return result; + } + + public final Card moveToGraveyard(final Card c, SpellAbility cause) { + return moveToGraveyard(c, cause, null); + } + public final Card moveToGraveyard(final Card c, SpellAbility cause, Map params) { + final PlayerZone grave = c.getOwner().getZone(ZoneType.Graveyard); + // must put card in OWNER's graveyard not controller's + return moveTo(grave, c, cause, params); + } + + public final Card moveToHand(final Card c, SpellAbility cause) { + return moveToHand(c, cause, null); + } + public final Card moveToHand(final Card c, SpellAbility cause, Map params) { + final PlayerZone hand = c.getOwner().getZone(ZoneType.Hand); + return moveTo(hand, c, cause, params); + } + + public final Card moveToPlay(final Card c, SpellAbility cause, Map params) { + return moveToPlay(c, c.getController(), cause, params); + } + public final Card moveToPlay(final Card c, final Player p, SpellAbility cause, Map params) { + // move to a specific player's Battlefield + final PlayerZone play = p.getZone(ZoneType.Battlefield); + return moveTo(play, c, cause, params); + } + + public final Card moveToBottomOfLibrary(final Card c, SpellAbility cause) { + return moveToBottomOfLibrary(c, cause, null); + } + public final Card moveToBottomOfLibrary(final Card c, SpellAbility cause, Map params) { + return moveToLibrary(c, -1, cause, params); + } + + public final Card moveToLibrary(final Card c, SpellAbility cause) { + return moveToLibrary(c, cause, null); + } + public final Card moveToLibrary(final Card c, SpellAbility cause, Map params) { + return moveToLibrary(c, 0, cause, params); + } + public final Card moveToLibrary(Card c, int libPosition, SpellAbility cause) { + return moveToLibrary(c, libPosition, cause, null); + } + public final Card moveToLibrary(Card c, int libPosition, SpellAbility cause, Map params) { + final PlayerZone library = c.getOwner().getZone(ZoneType.Library); + if (libPosition == -1 || libPosition > library.size()) { + libPosition = library.size(); + } + return changeZone(game.getZoneOf(c), library, c, libPosition, cause, params); + } + + public final Card moveToVariantDeck(Card c, ZoneType zone, int deckPosition, SpellAbility cause) { + return moveToVariantDeck(c, zone, deckPosition, cause, null); + } + public final Card moveToVariantDeck(Card c, ZoneType zone, int deckPosition, SpellAbility cause, Map params) { + final PlayerZone deck = c.getOwner().getZone(zone); + if (deckPosition == -1 || deckPosition > deck.size()) { + deckPosition = deck.size(); + } + return changeZone(game.getZoneOf(c), deck, c, deckPosition, cause, params); + } + + public final Card exile(final Card c, SpellAbility cause) { + if (c == null) { + return null; + } + return exile(new CardCollection(c), cause).get(0); + } + public final CardCollection exile(final CardCollection cards, SpellAbility cause) { + CardZoneTable table = new CardZoneTable(); + CardCollection result = new CardCollection(); + for (Card card : cards) { + if (cause != null) { + table.put(card.getZone().getZoneType(), ZoneType.Exile, card); + } + result.add(exile(card, cause, null)); + } + if (cause != null) { + table.triggerChangesZoneAll(game, cause); + } + return result; + } + public final Card exile(final Card c, SpellAbility cause, Map params) { + if (game.isCardExiled(c)) { + return c; + } + + final Zone origin = c.getZone(); + final PlayerZone removed = c.getOwner().getZone(ZoneType.Exile); + final Card copied = moveTo(removed, c, cause, params); + + // Run triggers + final Map runParams = AbilityKey.mapFromCard(c); + runParams.put(AbilityKey.Cause, cause); + if (origin != null) { // is generally null when adding via dev mode + runParams.put(AbilityKey.Origin, origin.getZoneType().name()); + } + if (params != null) { + runParams.putAll(params); + } + + game.getTriggerHandler().runTrigger(TriggerType.Exiled, runParams, false); + + return copied; + } + + public void ceaseToExist(Card c, boolean skipTrig) { + if (c.isInZone(ZoneType.Stack)) { + c.getGame().getStack().remove(c); + } + c.getZone().remove(c); + + // CR 603.6c other players LTB triggers should work + if (!skipTrig) { + game.addChangeZoneLKIInfo(c); + CardCollectionView lastBattlefield = game.getLastStateBattlefield(); + int idx = lastBattlefield.indexOf(c); + Card lki = null; + if (idx != -1) { + lki = lastBattlefield.get(idx); + } + if (lki == null) { + lki = CardUtil.getLKICopy(c); + } + if (game.getCombat() != null) { + game.getCombat().removeFromCombat(c); + game.getCombat().saveLKI(lki); + } + // again, make sure no triggers run from cards leaving controlled by loser + if (!lki.getController().equals(lki.getOwner())) { + game.getTriggerHandler().registerActiveLTBTrigger(lki); + } + final Map runParams = AbilityKey.mapFromCard(c); + runParams.put(AbilityKey.CardLKI, lki); + runParams.put(AbilityKey.Origin, c.getZone().getZoneType().name()); + game.getTriggerHandler().runTrigger(TriggerType.ChangesZone, runParams, false); + } + } + public final void controllerChangeZoneCorrection(final Card c) { System.out.println("Correcting zone for " + c.toString()); final Zone oldBattlefield = game.getZoneOf(c); @@ -843,193 +1018,6 @@ public class GameAction { c.runChangeControllerCommands(); } - public final Card moveToStack(final Card c, SpellAbility cause) { - return moveToStack(c, cause, null); - } - - public final Card moveToStack(final Card c, SpellAbility cause, Map params) { - Card result = moveTo(game.getStackZone(), c, cause, params); - if (cause != null && cause.isSpell() && result.equals(cause.getHostCard())) { - result.setSplitStateToPlayAbility(cause); - - // CR 112.2 A spell’s controller is, by default, the player who put it on the stack. - result.setController(cause.getActivatingPlayer(), 0); - // for triggers like from Wild-Magic Sorcerer - game.getAction().checkStaticAbilities(false); - game.getTriggerHandler().resetActiveTriggers(); - } - return result; - } - - public final Card moveToGraveyard(final Card c, SpellAbility cause) { - return moveToGraveyard(c, cause, null); - } - public final Card moveToGraveyard(final Card c, SpellAbility cause, Map params) { - final PlayerZone grave = c.getOwner().getZone(ZoneType.Graveyard); - // must put card in OWNER's graveyard not controller's - return moveTo(grave, c, cause, params); - } - - public final Card moveToHand(final Card c, SpellAbility cause) { - return moveToHand(c, cause, null); - } - - public final Card moveToHand(final Card c, SpellAbility cause, Map params) { - final PlayerZone hand = c.getOwner().getZone(ZoneType.Hand); - return moveTo(hand, c, cause, params); - } - - public final Card moveToPlay(final Card c, SpellAbility cause, Map params) { - return moveToPlay(c, c.getController(), cause, params); - } - - public final Card moveToPlay(final Card c, final Player p, SpellAbility cause, Map params) { - // move to a specific player's Battlefield - final PlayerZone play = p.getZone(ZoneType.Battlefield); - return moveTo(play, c, cause, params); - } - - public final Card moveToBottomOfLibrary(final Card c, SpellAbility cause) { - return moveToBottomOfLibrary(c, cause, null); - } - - public final Card moveToBottomOfLibrary(final Card c, SpellAbility cause, Map params) { - return moveToLibrary(c, -1, cause, params); - } - - public final Card moveToLibrary(final Card c, SpellAbility cause) { - return moveToLibrary(c, cause, null); - } - - public final Card moveToLibrary(final Card c, SpellAbility cause, Map params) { - return moveToLibrary(c, 0, cause, params); - } - - public final Card moveToLibrary(Card c, int libPosition, SpellAbility cause) { - return moveToLibrary(c, libPosition, cause, null); - } - - public final Card moveToLibrary(Card c, int libPosition, SpellAbility cause, Map params) { - final PlayerZone library = c.getOwner().getZone(ZoneType.Library); - if (libPosition == -1 || libPosition > library.size()) { - libPosition = library.size(); - } - return changeZone(game.getZoneOf(c), library, c, libPosition, cause, params); - } - - public final Card moveToVariantDeck(Card c, ZoneType zone, int deckPosition, SpellAbility cause) { - return moveToVariantDeck(c, zone, deckPosition, cause, null); - } - - public final Card moveToVariantDeck(Card c, ZoneType zone, int deckPosition, SpellAbility cause, Map params) { - final PlayerZone deck = c.getOwner().getZone(zone); - if (deckPosition == -1 || deckPosition > deck.size()) { - deckPosition = deck.size(); - } - return changeZone(game.getZoneOf(c), deck, c, deckPosition, cause, params); - } - - public final Card exile(final Card c, SpellAbility cause) { - if (c == null) { - return null; - } - return exile(new CardCollection(c), cause).get(0); - } - public final CardCollection exile(final CardCollection cards, SpellAbility cause) { - CardZoneTable table = new CardZoneTable(); - CardCollection result = new CardCollection(); - for (Card card : cards) { - if (cause != null) { - table.put(card.getZone().getZoneType(), ZoneType.Exile, card); - } - result.add(exile(card, cause, null)); - } - if (cause != null) { - table.triggerChangesZoneAll(game, cause); - } - return result; - } - public final Card exile(final Card c, SpellAbility cause, Map params) { - if (game.isCardExiled(c)) { - return c; - } - - final Zone origin = c.getZone(); - final PlayerZone removed = c.getOwner().getZone(ZoneType.Exile); - final Card copied = moveTo(removed, c, cause, params); - - // Run triggers - final Map runParams = AbilityKey.mapFromCard(c); - runParams.put(AbilityKey.Cause, cause); - if (origin != null) { // is generally null when adding via dev mode - runParams.put(AbilityKey.Origin, origin.getZoneType().name()); - } - if (params != null) { - runParams.putAll(params); - } - - game.getTriggerHandler().runTrigger(TriggerType.Exiled, runParams, false); - - return copied; - } - - public final Card moveTo(final ZoneType name, final Card c, SpellAbility cause) { - return moveTo(name, c, 0, cause); - } - - public final Card moveTo(final ZoneType name, final Card c, final int libPosition, SpellAbility cause) { - return moveTo(name, c, libPosition, cause, null); - } - - public final Card moveTo(final ZoneType name, final Card c, final int libPosition, SpellAbility cause, Map params) { - // Call specific functions to set PlayerZone, then move onto moveTo - switch(name) { - case Hand: return moveToHand(c, cause, params); - case Library: return moveToLibrary(c, libPosition, cause, params); - case Battlefield: return moveToPlay(c, c.getController(), cause, params); - case Graveyard: return moveToGraveyard(c, cause, params); - case Exile: return exile(c, cause, params); - case Stack: return moveToStack(c, cause, params); - case PlanarDeck: return moveToVariantDeck(c, ZoneType.PlanarDeck, libPosition, cause, params); - case SchemeDeck: return moveToVariantDeck(c, ZoneType.SchemeDeck, libPosition, cause, params); - default: // sideboard will also get there - return moveTo(c.getOwner().getZone(name), c, cause); - } - } - - public void ceaseToExist(Card c, boolean skipTrig) { - if (c.isInZone(ZoneType.Stack)) { - c.getGame().getStack().remove(c); - } - c.getZone().remove(c); - - // CR 603.6c other players LTB triggers should work - if (!skipTrig) { - game.addChangeZoneLKIInfo(c); - CardCollectionView lastBattlefield = game.getLastStateBattlefield(); - int idx = lastBattlefield.indexOf(c); - Card lki = null; - if (idx != -1) { - lki = lastBattlefield.get(idx); - } - if (lki == null) { - lki = CardUtil.getLKICopy(c); - } - if (game.getCombat() != null) { - game.getCombat().removeFromCombat(c); - game.getCombat().saveLKI(lki); - } - // again, make sure no triggers run from cards leaving controlled by loser - if (!lki.getController().equals(lki.getOwner())) { - game.getTriggerHandler().registerActiveLTBTrigger(lki); - } - final Map runParams = AbilityKey.mapFromCard(c); - runParams.put(AbilityKey.CardLKI, lki); - runParams.put(AbilityKey.Origin, c.getZone().getZoneType().name()); - game.getTriggerHandler().runTrigger(TriggerType.ChangesZone, runParams, false); - } - } - // Temporarily disable (if mode = true) actively checking static abilities. private void setHoldCheckingStaticAbilities(boolean mode) { holdCheckingStaticAbilities = mode; diff --git a/forge-game/src/main/java/forge/game/ability/SpellAbilityEffect.java b/forge-game/src/main/java/forge/game/ability/SpellAbilityEffect.java index ff0e821b3d7..c8232876235 100644 --- a/forge-game/src/main/java/forge/game/ability/SpellAbilityEffect.java +++ b/forge-game/src/main/java/forge/game/ability/SpellAbilityEffect.java @@ -660,6 +660,9 @@ public abstract class SpellAbilityEffect { if (untilCards.isEmpty()) { return; } + Map moveParams = AbilityKey.newMap(); + moveParams.put(AbilityKey.LastStateBattlefield, game.copyLastStateBattlefield()); + moveParams.put(AbilityKey.LastStateBattlefield, game.copyLastStateGraveyard()); for (Table.Cell cell : triggerList.cellSet()) { for (Card c : cell.getValue()) { // check if card is still in the until host leaves play list @@ -672,7 +675,7 @@ public abstract class SpellAbilityEffect { continue; } // no cause there? - Card movedCard = game.getAction().moveTo(cell.getRowKey(), newCard, null); + Card movedCard = game.getAction().moveTo(cell.getRowKey(), newCard, 0, null, moveParams); untilTable.put(cell.getColumnKey(), cell.getRowKey(), movedCard); } }