Merge branch '2053-ltb-trigger-might-be-stopped-by-own-die-replacementeffect' into 'master'

Resolve "LTB Trigger might be stopped by own Die ReplacementEffect"

Closes #2053

See merge request core-developers/forge!6298
This commit is contained in:
Michael Kamensky
2022-02-25 17:30:13 +00:00
35 changed files with 305 additions and 195 deletions

View File

@@ -1250,7 +1250,7 @@ public abstract class GameState {
} }
if (cardsWithoutETBTrigs.contains(c)) { if (cardsWithoutETBTrigs.contains(c)) {
p.getGame().getAction().moveTo(ZoneType.Battlefield, c, null); p.getGame().getAction().moveTo(ZoneType.Battlefield, c, null, null);
} else { } else {
p.getZone(ZoneType.Hand).add(c); p.getZone(ZoneType.Hand).add(c);
p.getGame().getAction().moveToPlay(c, null, null); p.getGame().getAction().moveToPlay(c, null, null);

View File

@@ -26,6 +26,7 @@ import java.util.Map;
import java.util.Set; import java.util.Set;
import forge.util.*; import forge.util.*;
import org.apache.commons.lang3.tuple.ImmutablePair; import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.Pair; import org.apache.commons.lang3.tuple.Pair;
@@ -202,6 +203,16 @@ public class GameAction {
lastKnownInfo = (Card) cause.getReplacingObject(AbilityKey.CardLKI); lastKnownInfo = (Card) cause.getReplacingObject(AbilityKey.CardLKI);
} }
} }
CardCollectionView lastBattlefield = null;
if (params != null) {
lastBattlefield = (CardCollectionView) params.get(AbilityKey.LastStateBattlefield);
}
if (lastBattlefield == null && cause != null) {
lastBattlefield = cause.getLastStateBattlefield();
}
if (lastBattlefield == null) {
lastBattlefield = game.getLastStateBattlefield();
}
if (c.isSplitCard()) { if (c.isSplitCard()) {
boolean resetToOriginal = false; boolean resetToOriginal = false;
@@ -254,7 +265,6 @@ public class GameAction {
// if from Battlefield to Graveyard and Card does exist in LastStateBattlefield // if from Battlefield to Graveyard and Card does exist in LastStateBattlefield
// use that instead // use that instead
if (fromBattlefield) { if (fromBattlefield) {
CardCollectionView lastBattlefield = game.getLastStateBattlefield();
int idx = lastBattlefield.indexOf(c); int idx = lastBattlefield.indexOf(c);
if (idx != -1) { if (idx != -1) {
lastKnownInfo = lastBattlefield.get(idx); lastKnownInfo = lastBattlefield.get(idx);
@@ -577,7 +587,10 @@ public class GameAction {
} }
game.getTriggerHandler().clearActiveTriggers(copied, null); game.getTriggerHandler().clearActiveTriggers(copied, null);
game.getTriggerHandler().registerActiveLTBTrigger(lastKnownInfo); // register all LTB trigger from last state battlefield
for (Card lki : lastBattlefield) {
game.getTriggerHandler().registerActiveLTBTrigger(lki);
}
game.getTriggerHandler().registerActiveTrigger(copied, false); game.getTriggerHandler().registerActiveTrigger(copied, false);
table.replaceCounterEffect(game, null, true); table.replaceCounterEffect(game, null, true);
@@ -705,7 +718,7 @@ public class GameAction {
} }
public final Card moveTo(final Zone zoneTo, Card c, SpellAbility cause) { public final Card moveTo(final Zone zoneTo, Card c, SpellAbility cause) {
return moveTo(zoneTo, c, cause, null); return moveTo(zoneTo, c, cause, AbilityKey.newMap());
} }
public final Card moveTo(final Zone zoneTo, Card c, SpellAbility cause, Map<AbilityKey, Object> params) { public final Card moveTo(final Zone zoneTo, Card c, SpellAbility cause, Map<AbilityKey, Object> params) {
// FThreads.assertExecutedByEdt(false); // This code must never be executed from EDT, // FThreads.assertExecutedByEdt(false); // This code must never be executed from EDT,
@@ -713,14 +726,14 @@ public class GameAction {
return moveTo(zoneTo, c, null, cause, params); return moveTo(zoneTo, c, null, cause, params);
} }
public final Card moveTo(final Zone zoneTo, Card c, Integer position, SpellAbility cause) { public final Card moveTo(final Zone zoneTo, Card c, Integer position, SpellAbility cause) {
return moveTo(zoneTo, c, position, cause, null); return moveTo(zoneTo, c, position, cause, AbilityKey.newMap());
} }
public final Card moveTo(final ZoneType name, final Card c, SpellAbility cause) { public final Card moveTo(final ZoneType name, final Card c, SpellAbility cause, Map<AbilityKey, Object> params) {
return moveTo(name, c, 0, cause); return moveTo(name, c, 0, cause, params);
} }
public final Card moveTo(final ZoneType name, final Card c, final int libPosition, SpellAbility cause) { public final Card moveTo(final ZoneType name, final Card c, final int libPosition, SpellAbility cause) {
return moveTo(name, c, libPosition, cause, null); return moveTo(name, c, libPosition, cause, AbilityKey.newMap());
} }
public final Card moveTo(final ZoneType name, final Card c, final int libPosition, SpellAbility cause, Map<AbilityKey, Object> params) { public final Card moveTo(final ZoneType name, final Card c, final int libPosition, SpellAbility cause, Map<AbilityKey, Object> params) {
// Call specific functions to set PlayerZone, then move onto moveTo // Call specific functions to set PlayerZone, then move onto moveTo
@@ -786,7 +799,7 @@ public class GameAction {
if (maingameCard.getZone().is(ZoneType.Stack)) { if (maingameCard.getZone().is(ZoneType.Stack)) {
game.getMaingame().getStack().remove(maingameCard); game.getMaingame().getStack().remove(maingameCard);
} }
game.getMaingame().getAction().moveTo(ZoneType.Subgame, maingameCard, null); game.getMaingame().getAction().moveTo(ZoneType.Subgame, maingameCard, null, params);
} }
} }
@@ -816,7 +829,10 @@ public class GameAction {
} }
public final Card moveToStack(final Card c, SpellAbility cause) { public final Card moveToStack(final Card c, SpellAbility cause) {
return moveToStack(c, cause, null); Map<AbilityKey, Object> params = AbilityKey.newMap();
params.put(AbilityKey.LastStateBattlefield, game.getLastStateBattlefield());
params.put(AbilityKey.LastStateGraveyard, game.getLastStateGraveyard());
return moveToStack(c, cause, params);
} }
public final Card moveToStack(final Card c, SpellAbility cause, Map<AbilityKey, Object> params) { public final Card moveToStack(final Card c, SpellAbility cause, Map<AbilityKey, Object> params) {
Card result = moveTo(game.getStackZone(), c, cause, params); Card result = moveTo(game.getStackZone(), c, cause, params);
@@ -833,7 +849,7 @@ public class GameAction {
} }
public final Card moveToGraveyard(final Card c, SpellAbility cause) { public final Card moveToGraveyard(final Card c, SpellAbility cause) {
return moveToGraveyard(c, cause, null); return moveToGraveyard(c, cause, AbilityKey.newMap());
} }
public final Card moveToGraveyard(final Card c, SpellAbility cause, Map<AbilityKey, Object> params) { public final Card moveToGraveyard(final Card c, SpellAbility cause, Map<AbilityKey, Object> params) {
final PlayerZone grave = c.getOwner().getZone(ZoneType.Graveyard); final PlayerZone grave = c.getOwner().getZone(ZoneType.Graveyard);
@@ -842,7 +858,7 @@ public class GameAction {
} }
public final Card moveToHand(final Card c, SpellAbility cause) { public final Card moveToHand(final Card c, SpellAbility cause) {
return moveToHand(c, cause, null); return moveToHand(c, cause, AbilityKey.newMap());
} }
public final Card moveToHand(final Card c, SpellAbility cause, Map<AbilityKey, Object> params) { public final Card moveToHand(final Card c, SpellAbility cause, Map<AbilityKey, Object> params) {
final PlayerZone hand = c.getOwner().getZone(ZoneType.Hand); final PlayerZone hand = c.getOwner().getZone(ZoneType.Hand);
@@ -859,14 +875,14 @@ public class GameAction {
} }
public final Card moveToBottomOfLibrary(final Card c, SpellAbility cause) { public final Card moveToBottomOfLibrary(final Card c, SpellAbility cause) {
return moveToBottomOfLibrary(c, cause, null); return moveToBottomOfLibrary(c, cause, AbilityKey.newMap());
} }
public final Card moveToBottomOfLibrary(final Card c, SpellAbility cause, Map<AbilityKey, Object> params) { public final Card moveToBottomOfLibrary(final Card c, SpellAbility cause, Map<AbilityKey, Object> params) {
return moveToLibrary(c, -1, cause, params); return moveToLibrary(c, -1, cause, params);
} }
public final Card moveToLibrary(final Card c, SpellAbility cause) { public final Card moveToLibrary(final Card c, SpellAbility cause) {
return moveToLibrary(c, cause, null); return moveToLibrary(c, cause, AbilityKey.newMap());
} }
public final Card moveToLibrary(final Card c, SpellAbility cause, Map<AbilityKey, Object> params) { public final Card moveToLibrary(final Card c, SpellAbility cause, Map<AbilityKey, Object> params) {
return moveToLibrary(c, 0, cause, params); return moveToLibrary(c, 0, cause, params);
@@ -1229,7 +1245,12 @@ public class GameAction {
// do this multiple times, sometimes creatures/permanents will survive when they shouldn't // do this multiple times, sometimes creatures/permanents will survive when they shouldn't
boolean orderedDesCreats = false; boolean orderedDesCreats = false;
boolean orderedNoRegCreats = false; boolean orderedNoRegCreats = false;
boolean orderedSacrificeList = false;
CardCollection cardsToUpdateLKI = new CardCollection(); CardCollection cardsToUpdateLKI = new CardCollection();
Map<AbilityKey, Object> mapParams = AbilityKey.newMap();
mapParams.put(AbilityKey.LastStateBattlefield, game.getLastStateBattlefield());
mapParams.put(AbilityKey.LastStateGraveyard, game.getLastStateGraveyard());
for (int q = 0; q < 9; q++) { for (int q = 0; q < 9; q++) {
checkStaticAbilities(false, affectedCards, CardCollection.EMPTY); checkStaticAbilities(false, affectedCards, CardCollection.EMPTY);
boolean checkAgain = false; boolean checkAgain = false;
@@ -1253,16 +1274,14 @@ public class GameAction {
} }
} }
} }
CardCollection noRegCreats = null; CardCollection noRegCreats = new CardCollection();
CardCollection desCreats = null; CardCollection desCreats = null;
CardCollection unAttachList = new CardCollection(); CardCollection unAttachList = new CardCollection();
CardCollection sacrificeList = new CardCollection();
for (final Card c : game.getCardsIn(ZoneType.Battlefield)) { for (final Card c : game.getCardsIn(ZoneType.Battlefield)) {
if (c.isCreature()) { if (c.isCreature()) {
// Rule 704.5f - Put into grave (no regeneration) for toughness <= 0 // Rule 704.5f - Put into grave (no regeneration) for toughness <= 0
if (c.getNetToughness() <= 0) { if (c.getNetToughness() <= 0) {
if (noRegCreats == null) {
noRegCreats = new CardCollection();
}
noRegCreats.add(c); noRegCreats.add(c);
checkAgain = true; checkAgain = true;
} else if (c.hasKeyword("CARDNAME can't be destroyed by lethal damage unless lethal damage dealt by a single source is marked on it.")) { } else if (c.hasKeyword("CARDNAME can't be destroyed by lethal damage unless lethal damage dealt by a single source is marked on it.")) {
@@ -1306,7 +1325,7 @@ public class GameAction {
} }
} }
checkAgain |= stateBasedAction_Saga(c, table); checkAgain |= stateBasedAction_Saga(c, sacrificeList);
checkAgain |= stateBasedAction704_attach(c, unAttachList); // Attachment checkAgain |= stateBasedAction704_attach(c, unAttachList); // Attachment
checkAgain |= stateBasedAction704_5r(c); // annihilate +1/+1 counters with -1/-1 ones checkAgain |= stateBasedAction704_5r(c); // annihilate +1/+1 counters with -1/-1 ones
@@ -1336,9 +1355,6 @@ public class GameAction {
// cleanup aura // cleanup aura
if (c.isAura() && c.isInPlay() && !c.isEnchanting()) { if (c.isAura() && c.isInPlay() && !c.isEnchanting()) {
if (noRegCreats == null) {
noRegCreats = new CardCollection();
}
noRegCreats.add(c); noRegCreats.add(c);
checkAgain = true; checkAgain = true;
} }
@@ -1351,28 +1367,46 @@ public class GameAction {
// cleanup aura // cleanup aura
if (u.isAura() && u.isInPlay() && !u.isEnchanting()) { if (u.isAura() && u.isInPlay() && !u.isEnchanting()) {
if (noRegCreats == null) {
noRegCreats = new CardCollection();
}
noRegCreats.add(u); noRegCreats.add(u);
checkAgain = true; checkAgain = true;
} }
} }
for (Player p : game.getPlayers()) {
if (handleLegendRule(p, noRegCreats)) {
checkAgain = true;
}
if ((game.getRules().hasAppliedVariant(GameType.Commander)
|| game.getRules().hasAppliedVariant(GameType.Brawl)
|| game.getRules().hasAppliedVariant(GameType.Planeswalker)) && !checkAgain) {
for (final Card c : p.getCardsIn(ZoneType.Graveyard).threadSafeIterable()) {
checkAgain |= stateBasedAction903_9a(c);
}
for (final Card c : p.getCardsIn(ZoneType.Exile).threadSafeIterable()) {
checkAgain |= stateBasedAction903_9a(c);
}
}
if (handlePlaneswalkerRule(p, noRegCreats)) {
checkAgain = true;
}
}
// 704.5m World rule
checkAgain |= handleWorldRule(noRegCreats);
// only check static abilities once after destroying all the creatures // only check static abilities once after destroying all the creatures
// (e.g. helpful for Erebos's Titan and another creature dealing lethal damage to each other simultaneously) // (e.g. helpful for Erebos's Titan and another creature dealing lethal damage to each other simultaneously)
setHoldCheckingStaticAbilities(true); setHoldCheckingStaticAbilities(true);
if (noRegCreats != null) {
if (noRegCreats.size() > 1 && !orderedNoRegCreats) { if (noRegCreats.size() > 1 && !orderedNoRegCreats) {
noRegCreats = (CardCollection) GameActionUtil.orderCardsByTheirOwners(game, noRegCreats, ZoneType.Graveyard, null); noRegCreats = (CardCollection) GameActionUtil.orderCardsByTheirOwners(game, noRegCreats, ZoneType.Graveyard, null);
orderedNoRegCreats = true; orderedNoRegCreats = true;
} }
for (Card c : noRegCreats) { for (Card c : noRegCreats) {
c.updateWasDestroyed(true); c.updateWasDestroyed(true);
sacrificeDestroy(c, null, table, null); sacrificeDestroy(c, null, table, mapParams);
}
} }
if (desCreats != null) { if (desCreats != null) {
if (desCreats.size() > 1 && !orderedDesCreats) { if (desCreats.size() > 1 && !orderedDesCreats) {
desCreats = CardLists.filter(desCreats, CardPredicates.Presets.CAN_BE_DESTROYED); desCreats = CardLists.filter(desCreats, CardPredicates.Presets.CAN_BE_DESTROYED);
@@ -1382,39 +1416,24 @@ public class GameAction {
orderedDesCreats = true; orderedDesCreats = true;
} }
for (Card c : desCreats) { for (Card c : desCreats) {
destroy(c, null, true, table, null); destroy(c, null, true, table, mapParams);
} }
} }
if (sacrificeList.size() > 1 && !orderedSacrificeList) {
sacrificeList = (CardCollection) GameActionUtil.orderCardsByTheirOwners(game, sacrificeList, ZoneType.Graveyard, null);
orderedSacrificeList = true;
}
for (Card c : sacrificeList) {
c.updateWasDestroyed(true);
sacrifice(c, null, true, table, mapParams);
}
setHoldCheckingStaticAbilities(false); setHoldCheckingStaticAbilities(false);
if (game.getTriggerHandler().runWaitingTriggers()) { if (game.getTriggerHandler().runWaitingTriggers()) {
checkAgain = true; checkAgain = true;
} }
for (Player p : game.getPlayers()) {
if (handleLegendRule(p, table)) {
checkAgain = true;
}
if ((game.getRules().hasAppliedVariant(GameType.Commander)
|| game.getRules().hasAppliedVariant(GameType.Brawl)
|| game.getRules().hasAppliedVariant(GameType.Planeswalker)) && !checkAgain) {
Iterable<Card> cards = p.getCardsIn(ZoneType.Graveyard).threadSafeIterable();
for (final Card c : cards) {
checkAgain |= stateBasedAction903_9a(c);
}
cards = p.getCardsIn(ZoneType.Exile).threadSafeIterable();
for (final Card c : cards) {
checkAgain |= stateBasedAction903_9a(c);
}
}
if (handlePlaneswalkerRule(p, table)) {
checkAgain = true;
}
}
// 704.5m World rule
checkAgain |= handleWorldRule(table);
if (game.getCombat() != null) { if (game.getCombat() != null) {
game.getCombat().removeAbsentCombatants(); game.getCombat().removeAbsentCombatants();
@@ -1459,7 +1478,7 @@ public class GameAction {
game.runSBACheckedCommands(); game.runSBACheckedCommands();
} }
private boolean stateBasedAction_Saga(Card c, CardZoneTable table) { private boolean stateBasedAction_Saga(Card c, CardCollection sacrificeList) {
boolean checkAgain = false; boolean checkAgain = false;
if (!c.getType().hasSubtype("Saga")) { if (!c.getType().hasSubtype("Saga")) {
return false; return false;
@@ -1472,7 +1491,7 @@ public class GameAction {
} }
if (!game.getStack().hasSourceOnStack(c, SpellAbilityPredicates.isChapter())) { if (!game.getStack().hasSourceOnStack(c, SpellAbilityPredicates.isChapter())) {
// needs to be effect, because otherwise it might be a cost? // needs to be effect, because otherwise it might be a cost?
sacrifice(c, null, true, table, null); sacrificeList.add(c);
checkAgain = true; checkAgain = true;
} }
return checkAgain; return checkAgain;
@@ -1653,57 +1672,28 @@ public class GameAction {
game.getStack().clearSimultaneousStack(); game.getStack().clearSimultaneousStack();
} }
private boolean handlePlaneswalkerRule(Player p, CardZoneTable table) { private boolean handlePlaneswalkerRule(Player p, CardCollection noRegCreats) {
// get all Planeswalkers // get all Planeswalkers
final List<Card> list = p.getPlaneswalkersInPlay(); final List<Card> list = p.getPlaneswalkersInPlay();
boolean recheck = false; boolean recheck = false;
//final Multimap<String, Card> uniqueWalkers = ArrayListMultimap.create(); // Not used as of Ixalan
for (Card c : list) { for (Card c : list) {
if (c.getCounters(CounterEnumType.LOYALTY) <= 0) { if (c.getCounters(CounterEnumType.LOYALTY) <= 0) {
//for animation noRegCreats.add(c);
c.updateWasDestroyed(true);
sacrificeDestroy(c, null, table, null);
// Play the Destroy sound
game.fireEvent(new GameEventCardDestroyed());
recheck = true; recheck = true;
} }
/* -- Not used as of Ixalan --
for (final String type : c.getType()) {
if (CardType.isAPlaneswalkerType(type)) {
uniqueWalkers.put(type, c);
}
}*/
} }
/* -- Not used as of Ixalan --
for (String key : uniqueWalkers.keySet()) {
Collection<Card> duplicates = uniqueWalkers.get(key);
if (duplicates.size() < 2) {
continue;
}
recheck = true;
Card toKeep = p.getController().chooseSingleEntityForEffect(new CardCollection(duplicates), new AbilitySub(ApiType.InternalLegendaryRule, null, null, null), "You have multiple planeswalkers of type \""+key+"\"in play.\n\nChoose one to stay on battlefield (the rest will be moved to graveyard)");
for (Card c: duplicates) {
if (c != toKeep) {
moveToGraveyard(c, null);
}
}
}
*/
return recheck; return recheck;
} }
private boolean handleLegendRule(Player p, CardZoneTable table) { private boolean handleLegendRule(Player p, CardCollection noRegCreats) {
final List<Card> a = CardLists.getType(p.getCardsIn(ZoneType.Battlefield), "Legendary"); final List<Card> a = CardLists.getType(p.getCardsIn(ZoneType.Battlefield), "Legendary");
if (a.isEmpty() || game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noLegendRule)) { if (a.isEmpty() || game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noLegendRule)) {
return false; return false;
} }
boolean recheck = false; boolean recheck = false;
// TODO legend rule exception into static ability
List<Card> yamazaki = CardLists.getKeyword(a, "Legend rule doesn't apply to CARDNAME."); List<Card> yamazaki = CardLists.getKeyword(a, "Legend rule doesn't apply to CARDNAME.");
a.removeAll(yamazaki); a.removeAll(yamazaki);
@@ -1715,6 +1705,8 @@ public class GameAction {
} }
} }
// TODO handle Spy Kit
for (String name : uniqueLegends.keySet()) { for (String name : uniqueLegends.keySet()) {
Collection<Card> cc = uniqueLegends.get(name); Collection<Card> cc = uniqueLegends.get(name);
if (cc.size() < 2) { if (cc.size() < 2) {
@@ -1725,20 +1717,14 @@ public class GameAction {
Card toKeep = p.getController().chooseSingleEntityForEffect(new CardCollection(cc), new SpellAbility.EmptySa(ApiType.InternalLegendaryRule, new Card(-1, game), p), Card toKeep = p.getController().chooseSingleEntityForEffect(new CardCollection(cc), new SpellAbility.EmptySa(ApiType.InternalLegendaryRule, new Card(-1, game), p),
"You have multiple legendary permanents named \""+name+"\" in play.\n\nChoose the one to stay on battlefield (the rest will be moved to graveyard)", null); "You have multiple legendary permanents named \""+name+"\" in play.\n\nChoose the one to stay on battlefield (the rest will be moved to graveyard)", null);
for (Card c: cc) { cc.remove(toKeep);
if (c != toKeep) { noRegCreats.addAll(cc);
//for animation
c.updateWasDestroyed(true);
sacrificeDestroy(c, null, table, null);
}
}
game.fireEvent(new GameEventCardDestroyed());
} }
return recheck; return recheck;
} }
private boolean handleWorldRule(CardZoneTable table) { private boolean handleWorldRule(CardCollection noRegCreats) {
final List<Card> worlds = CardLists.getType(game.getCardsIn(ZoneType.Battlefield), "World"); final List<Card> worlds = CardLists.getType(game.getCardsIn(ZoneType.Battlefield), "World");
if (worlds.size() <= 1) { if (worlds.size() <= 1) {
return false; return false;
@@ -1762,12 +1748,7 @@ public class GameAction {
worlds.removeAll(toKeep); worlds.removeAll(toKeep);
} }
for (Card c : worlds) { noRegCreats.addAll(worlds);
//for animation
c.updateWasDestroyed(true);
sacrificeDestroy(c, null, table, null);
game.fireEvent(new GameEventCardDestroyed());
}
return true; return true;
} }

View File

@@ -691,7 +691,7 @@ public final class GameActionUtil {
// TODO: Add targeting to the effect so it knows who it's dealing with // TODO: Add targeting to the effect so it knows who it's dealing with
game.getTriggerHandler().suppressMode(TriggerType.ChangesZone); game.getTriggerHandler().suppressMode(TriggerType.ChangesZone);
game.getAction().moveTo(ZoneType.Command, eff, null); game.getAction().moveTo(ZoneType.Command, eff, null, null);
game.getTriggerHandler().clearSuppression(TriggerType.ChangesZone); game.getTriggerHandler().clearSuppression(TriggerType.ChangesZone);
return eff; return eff;

View File

@@ -7,6 +7,7 @@ import forge.deck.CardPool;
import forge.deck.Deck; import forge.deck.Deck;
import forge.deck.DeckFormat; import forge.deck.DeckFormat;
import forge.deck.DeckSection; import forge.deck.DeckSection;
import forge.game.ability.AbilityKey;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.card.CardCollectionView; import forge.game.card.CardCollectionView;
import forge.game.event.Event; import forge.game.event.Event;
@@ -80,7 +81,7 @@ public class Match {
Multimap<Player, Card> list = game.chooseCardsForAnte(rules.getMatchAnteRarity()); Multimap<Player, Card> list = game.chooseCardsForAnte(rules.getMatchAnteRarity());
for (Entry<Player, Card> kv : list.entries()) { for (Entry<Player, Card> kv : list.entries()) {
Player p = kv.getKey(); Player p = kv.getKey();
game.getAction().moveTo(ZoneType.Ante, kv.getValue(), null); game.getAction().moveTo(ZoneType.Ante, kv.getValue(), null, AbilityKey.newMap());
game.getGameLog().add(GameLogEntryType.ANTE, p + " anted " + kv.getValue()); game.getGameLog().add(GameLogEntryType.ANTE, p + " anted " + kv.getValue());
} }
game.fireEvent(new GameEventAnteCardsSelected(list)); game.fireEvent(new GameEventAnteCardsSelected(list));
@@ -303,7 +304,7 @@ public class Match {
// Create an effect that lets you cast your companion from your sideboard // Create an effect that lets you cast your companion from your sideboard
if (companion != null) { if (companion != null) {
PlayerZone commandZone = player.getZone(ZoneType.Command); PlayerZone commandZone = player.getZone(ZoneType.Command);
companion = game.getAction().moveTo(ZoneType.Command, companion, null); companion = game.getAction().moveTo(ZoneType.Command, companion, null, AbilityKey.newMap());
commandZone.add(Player.createCompanionEffect(game, companion)); commandZone.add(Player.createCompanionEffect(game, companion));
player.updateZoneForView(commandZone); player.updateZoneForView(commandZone);

View File

@@ -435,7 +435,7 @@ public abstract class SpellAbilityEffect {
// TODO: Add targeting to the effect so it knows who it's dealing with // TODO: Add targeting to the effect so it knows who it's dealing with
game.getTriggerHandler().suppressMode(TriggerType.ChangesZone); game.getTriggerHandler().suppressMode(TriggerType.ChangesZone);
game.getAction().moveTo(ZoneType.Command, eff, sa); game.getAction().moveTo(ZoneType.Command, eff, sa, null);
eff.updateStateForView(); eff.updateStateForView();
game.getTriggerHandler().clearSuppression(TriggerType.ChangesZone); game.getTriggerHandler().clearSuppression(TriggerType.ChangesZone);
} }
@@ -553,7 +553,7 @@ public abstract class SpellAbilityEffect {
// TODO: Add targeting to the effect so it knows who it's dealing with // TODO: Add targeting to the effect so it knows who it's dealing with
game.getTriggerHandler().suppressMode(TriggerType.ChangesZone); game.getTriggerHandler().suppressMode(TriggerType.ChangesZone);
game.getAction().moveTo(ZoneType.Command, eff, sa); game.getAction().moveTo(ZoneType.Command, eff, sa, null);
eff.updateStateForView(); eff.updateStateForView();
game.getTriggerHandler().clearSuppression(TriggerType.ChangesZone); game.getTriggerHandler().clearSuppression(TriggerType.ChangesZone);
} }
@@ -685,13 +685,13 @@ public abstract class SpellAbilityEffect {
}; };
} }
protected static void discard(SpellAbility sa, CardZoneTable table, final boolean effect, Map<Player, CardCollectionView> discardedMap) { protected static void discard(SpellAbility sa, CardZoneTable table, final boolean effect, Map<Player, CardCollectionView> discardedMap, Map<AbilityKey, Object> params) {
Set<Player> discarders = discardedMap.keySet(); Set<Player> discarders = discardedMap.keySet();
for (Player p : discarders) { for (Player p : discarders) {
final CardCollection discardedByPlayer = new CardCollection(); final CardCollection discardedByPlayer = new CardCollection();
for (Card card : Lists.newArrayList(discardedMap.get(p))) { // without copying will get concurrent modification exception for (Card card : Lists.newArrayList(discardedMap.get(p))) { // without copying will get concurrent modification exception
if (card == null) { continue; } if (card == null) { continue; }
if (p.discard(card, sa, effect, table) != null) { if (p.discard(card, sa, effect, table, params) != null) {
discardedByPlayer.add(card); discardedByPlayer.add(card);
if (sa.hasParam("RememberDiscarded")) { if (sa.hasParam("RememberDiscarded")) {

View File

@@ -4,6 +4,7 @@ import java.util.List;
import forge.game.Game; import forge.game.Game;
import forge.game.ability.AbilityFactory; import forge.game.ability.AbilityFactory;
import forge.game.ability.AbilityKey;
import forge.game.ability.AbilityUtils; import forge.game.ability.AbilityUtils;
import forge.game.ability.SpellAbilityEffect; import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card; import forge.game.card.Card;
@@ -84,7 +85,7 @@ public class AddTurnEffect extends SpellAbilityEffect {
eff.addStaticAbility(stEffect); eff.addStaticAbility(stEffect);
game.getTriggerHandler().suppressMode(TriggerType.ChangesZone); game.getTriggerHandler().suppressMode(TriggerType.ChangesZone);
game.getAction().moveTo(ZoneType.Command, eff, sa); game.getAction().moveTo(ZoneType.Command, eff, sa, AbilityKey.newMap());
eff.updateStateForView(); eff.updateStateForView();
game.getTriggerHandler().clearSuppression(TriggerType.ChangesZone); game.getTriggerHandler().clearSuppression(TriggerType.ChangesZone);
} }

View File

@@ -49,6 +49,8 @@ public class BalanceEffect extends SpellAbilityEffect {
} }
Map<AbilityKey, Object> params = AbilityKey.newMap(); Map<AbilityKey, Object> params = AbilityKey.newMap();
params.put(AbilityKey.LastStateBattlefield, sa.getLastStateBattlefield());
params.put(AbilityKey.LastStateGraveyard, sa.getLastStateGraveyard());
CardZoneTable table = new CardZoneTable(); CardZoneTable table = new CardZoneTable();
for (int i = 0; i < players.size(); i++) { for (int i = 0; i < players.size(); i++) {
Player p = players.get(i); Player p = players.get(i);
@@ -67,7 +69,7 @@ public class BalanceEffect extends SpellAbilityEffect {
} }
if (zone.equals(ZoneType.Hand)) { if (zone.equals(ZoneType.Hand)) {
discard(sa, table, true, discardedMap); discard(sa, table, true, discardedMap, params);
} }
table.triggerChangesZoneAll(game, sa); table.triggerChangesZoneAll(game, sa);

View File

@@ -620,7 +620,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
} }
} }
Map<AbilityKey, Object> moveParams = Maps.newEnumMap(AbilityKey.class); Map<AbilityKey, Object> moveParams = AbilityKey.newMap();
moveParams.put(AbilityKey.LastStateBattlefield, lastStateBattlefield); moveParams.put(AbilityKey.LastStateBattlefield, lastStateBattlefield);
moveParams.put(AbilityKey.LastStateGraveyard, lastStateGraveyard); moveParams.put(AbilityKey.LastStateGraveyard, lastStateGraveyard);
if (sa.isReplacementAbility()) { if (sa.isReplacementAbility()) {
@@ -699,7 +699,10 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
gameCard.setExiledWith(host); gameCard.setExiledWith(host);
gameCard.setExiledBy(host.getController()); gameCard.setExiledBy(host.getController());
} }
movedCard = game.getAction().moveTo(destination, gameCard, sa); Map<AbilityKey, Object> moveParams = AbilityKey.newMap();
moveParams.put(AbilityKey.LastStateBattlefield, lastStateBattlefield);
moveParams.put(AbilityKey.LastStateGraveyard, lastStateGraveyard);
movedCard = game.getAction().moveTo(destination, gameCard, sa, moveParams);
if (ZoneType.Hand.equals(destination) && ZoneType.Command.equals(originZone.getZoneType())) { if (ZoneType.Hand.equals(destination) && ZoneType.Command.equals(originZone.getZoneType())) {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
sb.append(movedCard.getName()).append(" has moved from Command Zone to ").append(player).append("'s hand."); sb.append(movedCard.getName()).append(" has moved from Command Zone to ").append(player).append("'s hand.");
@@ -1210,7 +1213,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
for (final Card c : chosenCards) { for (final Card c : chosenCards) {
Card movedCard = null; Card movedCard = null;
final Zone originZone = game.getZoneOf(c); final Zone originZone = game.getZoneOf(c);
Map<AbilityKey, Object> moveParams = Maps.newEnumMap(AbilityKey.class); Map<AbilityKey, Object> moveParams = AbilityKey.newMap();
moveParams.put(AbilityKey.FoundSearchingLibrary, searchedLibrary); moveParams.put(AbilityKey.FoundSearchingLibrary, searchedLibrary);
moveParams.put(AbilityKey.LastStateBattlefield, lastStateBattlefield); moveParams.put(AbilityKey.LastStateBattlefield, lastStateBattlefield);
moveParams.put(AbilityKey.LastStateGraveyard, lastStateGraveyard); moveParams.put(AbilityKey.LastStateGraveyard, lastStateGraveyard);

View File

@@ -6,6 +6,7 @@ import forge.GameCommand;
import forge.game.Game; import forge.game.Game;
import forge.game.GameObject; import forge.game.GameObject;
import forge.game.ability.AbilityFactory; import forge.game.ability.AbilityFactory;
import forge.game.ability.AbilityKey;
import forge.game.ability.AbilityUtils; import forge.game.ability.AbilityUtils;
import forge.game.ability.SpellAbilityEffect; import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card; import forge.game.card.Card;
@@ -65,7 +66,7 @@ public abstract class DamagePreventEffectBase extends SpellAbilityEffect {
} }
game.getTriggerHandler().suppressMode(TriggerType.ChangesZone); game.getTriggerHandler().suppressMode(TriggerType.ChangesZone);
game.getAction().moveTo(ZoneType.Command, eff, sa); game.getAction().moveTo(ZoneType.Command, eff, sa, AbilityKey.newMap());
eff.updateStateForView(); eff.updateStateForView();
game.getTriggerHandler().clearSuppression(TriggerType.ChangesZone); game.getTriggerHandler().clearSuppression(TriggerType.ChangesZone);

View File

@@ -9,6 +9,7 @@ import com.google.common.collect.Maps;
import forge.game.Game; import forge.game.Game;
import forge.game.GameActionUtil; import forge.game.GameActionUtil;
import forge.game.ability.AbilityKey;
import forge.game.ability.AbilityUtils; import forge.game.ability.AbilityUtils;
import forge.game.ability.SpellAbilityEffect; import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card; import forge.game.card.Card;
@@ -287,7 +288,11 @@ public class DiscardEffect extends SpellAbilityEffect {
discardedMap.put(p, toBeDiscarded); discardedMap.put(p, toBeDiscarded);
} }
discard(sa, table, true, discardedMap); Map<AbilityKey, Object> params = AbilityKey.newMap();
params.put(AbilityKey.LastStateBattlefield, sa.getLastStateBattlefield());
params.put(AbilityKey.LastStateGraveyard, sa.getLastStateGraveyard());
discard(sa, table, true, discardedMap, params);
// run trigger if something got milled // run trigger if something got milled
table.triggerChangesZoneAll(game, sa); table.triggerChangesZoneAll(game, sa);

View File

@@ -1,7 +1,9 @@
package forge.game.ability.effects; package forge.game.ability.effects;
import java.util.List; import java.util.List;
import java.util.Map;
import forge.game.ability.AbilityKey;
import forge.game.ability.AbilityUtils; import forge.game.ability.AbilityUtils;
import forge.game.ability.SpellAbilityEffect; import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card; import forge.game.card.Card;
@@ -52,6 +54,9 @@ public class DrawEffect extends SpellAbilityEffect {
final boolean upto = sa.hasParam("Upto"); final boolean upto = sa.hasParam("Upto");
final boolean optional = sa.hasParam("OptionalDecider") || upto; final boolean optional = sa.hasParam("OptionalDecider") || upto;
Map<AbilityKey, Object> moveParams = AbilityKey.newMap();
moveParams.put(AbilityKey.LastStateBattlefield, sa.getLastStateBattlefield());
moveParams.put(AbilityKey.LastStateGraveyard, sa.getLastStateGraveyard());
for (final Player p : getDefinedPlayersOrTargeted(sa)) { for (final Player p : getDefinedPlayersOrTargeted(sa)) {
// TODO can this be removed? // TODO can this be removed?
@@ -80,7 +85,7 @@ public class DrawEffect extends SpellAbilityEffect {
actualNum = p.getController().chooseNumber(sa, Localizer.getInstance().getMessage("lblHowManyCardDoYouWantDraw"), 0, actualNum); actualNum = p.getController().chooseNumber(sa, Localizer.getInstance().getMessage("lblHowManyCardDoYouWantDraw"), 0, actualNum);
} }
final CardCollectionView drawn = p.drawCards(actualNum, sa); final CardCollectionView drawn = p.drawCards(actualNum, sa, moveParams);
if (sa.hasParam("Reveal")) { if (sa.hasParam("Reveal")) {
if (sa.getParam("Reveal").equals("All")) { if (sa.getParam("Reveal").equals("All")) {
p.getGame().getAction().reveal(drawn, p, false); p.getGame().getAction().reveal(drawn, p, false);

View File

@@ -2,6 +2,7 @@ package forge.game.ability.effects;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.List; import java.util.List;
import java.util.Map;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
@@ -12,6 +13,7 @@ import forge.card.CardRarity;
import forge.game.Game; import forge.game.Game;
import forge.game.GameObject; import forge.game.GameObject;
import forge.game.ability.AbilityFactory; import forge.game.ability.AbilityFactory;
import forge.game.ability.AbilityKey;
import forge.game.ability.AbilityUtils; import forge.game.ability.AbilityUtils;
import forge.game.ability.SpellAbilityEffect; import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card; import forge.game.card.Card;
@@ -136,6 +138,10 @@ public class EffectEffect extends SpellAbilityEffect {
image = hostCard.getImageKey(); image = hostCard.getImageKey();
} }
Map<AbilityKey, Object> params = AbilityKey.newMap();
params.put(AbilityKey.LastStateBattlefield, sa.getLastStateBattlefield());
params.put(AbilityKey.LastStateGraveyard, sa.getLastStateGraveyard());
for (Player controller : effectOwner) { for (Player controller : effectOwner) {
final Card eff = createEffect(sa, controller, name, image); final Card eff = createEffect(sa, controller, name, image);
eff.setSetCode(sa.getHostCard().getSetCode()); eff.setSetCode(sa.getHostCard().getSetCode());
@@ -322,7 +328,7 @@ public class EffectEffect extends SpellAbilityEffect {
// TODO: Add targeting to the effect so it knows who it's dealing with // TODO: Add targeting to the effect so it knows who it's dealing with
game.getTriggerHandler().suppressMode(TriggerType.ChangesZone); game.getTriggerHandler().suppressMode(TriggerType.ChangesZone);
game.getAction().moveTo(ZoneType.Command, eff, sa); game.getAction().moveTo(ZoneType.Command, eff, sa, params);
eff.updateStateForView(); eff.updateStateForView();
game.getTriggerHandler().clearSuppression(TriggerType.ChangesZone); game.getTriggerHandler().clearSuppression(TriggerType.ChangesZone);
//if (effectTriggers != null) { //if (effectTriggers != null) {

View File

@@ -1,7 +1,10 @@
package forge.game.ability.effects; package forge.game.ability.effects;
import java.util.Map;
import forge.game.Game; import forge.game.Game;
import forge.game.GameLogEntryType; import forge.game.GameLogEntryType;
import forge.game.ability.AbilityKey;
import forge.game.ability.SpellAbilityEffect; import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.card.CardCollectionView; import forge.game.card.CardCollectionView;
@@ -49,8 +52,12 @@ public class EncodeEffect extends SpellAbilityEffect {
return; return;
} }
Map<AbilityKey, Object> moveParams = AbilityKey.newMap();
moveParams.put(AbilityKey.LastStateBattlefield, sa.getLastStateBattlefield());
moveParams.put(AbilityKey.LastStateGraveyard, sa.getLastStateGraveyard());
// move host card to exile // move host card to exile
Card movedCard = game.getAction().moveTo(ZoneType.Exile, host, sa); Card movedCard = game.getAction().moveTo(ZoneType.Exile, host, sa, moveParams);
// choose a creature // choose a creature
Card choice = player.getController().chooseSingleEntityForEffect(choices, sa, Localizer.getInstance().getMessage("lblChooseACreatureYouControlToEncode") + " ", true, null); Card choice = player.getController().chooseSingleEntityForEffect(choices, sa, Localizer.getInstance().getMessage("lblChooseACreatureYouControlToEncode") + " ", true, null);

View File

@@ -1,6 +1,7 @@
package forge.game.ability.effects; package forge.game.ability.effects;
import java.util.List; import java.util.List;
import java.util.Map;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
@@ -48,6 +49,9 @@ public class ExploreEffect extends SpellAbilityEffect {
GameEntityCounterTable table = new GameEntityCounterTable(); GameEntityCounterTable table = new GameEntityCounterTable();
final CardZoneTable triggerList = new CardZoneTable(); final CardZoneTable triggerList = new CardZoneTable();
Map<AbilityKey, Object> moveParams = AbilityKey.newMap();
moveParams.put(AbilityKey.LastStateBattlefield, sa.getLastStateBattlefield());
moveParams.put(AbilityKey.LastStateGraveyard, sa.getLastStateGraveyard());
for (final Card c : getTargetCards(sa)) { for (final Card c : getTargetCards(sa)) {
// revealed land card // revealed land card
boolean revealedLand = false; boolean revealedLand = false;
@@ -59,7 +63,7 @@ public class ExploreEffect extends SpellAbilityEffect {
final Card r = top.getFirst(); final Card r = top.getFirst();
final Zone originZone = game.getZoneOf(r); final Zone originZone = game.getZoneOf(r);
if (r.isLand()) { if (r.isLand()) {
movedCard = game.getAction().moveTo(ZoneType.Hand, r, sa); movedCard = game.getAction().moveTo(ZoneType.Hand, r, sa, moveParams);
revealedLand = true; revealedLand = true;
} else { } else {
// TODO find better way to choose optional send away // TODO find better way to choose optional send away
@@ -67,7 +71,7 @@ public class ExploreEffect extends SpellAbilityEffect {
ZoneType.Graveyard, Lists.newArrayList(ZoneType.Library), sa, top, null, ZoneType.Graveyard, Lists.newArrayList(ZoneType.Library), sa, top, null,
Localizer.getInstance().getMessage("lblPutThisCardToYourGraveyard"), true, pl); Localizer.getInstance().getMessage("lblPutThisCardToYourGraveyard"), true, pl);
if (choosen != null) { if (choosen != null) {
movedCard = game.getAction().moveTo(ZoneType.Graveyard, choosen, sa); movedCard = game.getAction().moveTo(ZoneType.Graveyard, choosen, sa, moveParams);
} }
} }

View File

@@ -2,6 +2,7 @@ package forge.game.ability.effects;
import forge.GameCommand; import forge.GameCommand;
import forge.game.Game; import forge.game.Game;
import forge.game.ability.AbilityKey;
import forge.game.ability.SpellAbilityEffect; import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.replacement.ReplacementEffect; import forge.game.replacement.ReplacementEffect;
@@ -32,7 +33,7 @@ public class FogEffect extends SpellAbilityEffect {
eff.addReplacementEffect(re); eff.addReplacementEffect(re);
game.getTriggerHandler().suppressMode(TriggerType.ChangesZone); game.getTriggerHandler().suppressMode(TriggerType.ChangesZone);
game.getAction().moveTo(ZoneType.Command, eff, sa); game.getAction().moveTo(ZoneType.Command, eff, sa, AbilityKey.newMap());
eff.updateStateForView(); eff.updateStateForView();
game.getTriggerHandler().clearSuppression(TriggerType.ChangesZone); game.getTriggerHandler().clearSuppression(TriggerType.ChangesZone);

View File

@@ -1,6 +1,9 @@
package forge.game.ability.effects; package forge.game.ability.effects;
import java.util.Map;
import forge.game.Game; import forge.game.Game;
import forge.game.ability.AbilityKey;
import forge.game.ability.SpellAbilityEffect; import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.card.CardZoneTable; import forge.game.card.CardZoneTable;
@@ -18,8 +21,11 @@ public class LearnEffect extends SpellAbilityEffect {
final Card source = sa.getHostCard(); final Card source = sa.getHostCard();
final Game game = source.getGame(); final Game game = source.getGame();
CardZoneTable table = new CardZoneTable(); CardZoneTable table = new CardZoneTable();
Map<AbilityKey, Object> moveParams = AbilityKey.newMap();
moveParams.put(AbilityKey.LastStateBattlefield, sa.getLastStateBattlefield());
moveParams.put(AbilityKey.LastStateGraveyard, sa.getLastStateGraveyard());
for (Player p : getTargetPlayers(sa)) { for (Player p : getTargetPlayers(sa)) {
p.learnLesson(sa, table); p.learnLesson(sa, table, moveParams);
} }
table.triggerChangesZoneAll(game, sa); table.triggerChangesZoneAll(game, sa);
} }

View File

@@ -1,7 +1,10 @@
package forge.game.ability.effects; package forge.game.ability.effects;
import java.util.Map;
import forge.StaticData; import forge.StaticData;
import forge.game.Game; import forge.game.Game;
import forge.game.ability.AbilityKey;
import forge.game.ability.AbilityUtils; import forge.game.ability.AbilityUtils;
import forge.game.ability.SpellAbilityEffect; import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card; import forge.game.card.Card;
@@ -13,6 +16,9 @@ import forge.game.zone.ZoneType;
public class MakeCardEffect extends SpellAbilityEffect { public class MakeCardEffect extends SpellAbilityEffect {
@Override @Override
public void resolve(SpellAbility sa) { public void resolve(SpellAbility sa) {
Map<AbilityKey, Object> moveParams = AbilityKey.newMap();
moveParams.put(AbilityKey.LastStateBattlefield, sa.getLastStateBattlefield());
moveParams.put(AbilityKey.LastStateGraveyard, sa.getLastStateGraveyard());
for (final Player player : getTargetPlayers(sa)) { for (final Player player : getTargetPlayers(sa)) {
final Card source = sa.getHostCard(); final Card source = sa.getHostCard();
final Game game = player.getGame(); final Game game = player.getGame();
@@ -43,13 +49,13 @@ public class MakeCardEffect extends SpellAbilityEffect {
if (!sa.hasParam("NotToken")) { if (!sa.hasParam("NotToken")) {
card.setTokenCard(true); card.setTokenCard(true);
} }
game.getAction().moveTo(ZoneType.None, card, sa); game.getAction().moveTo(ZoneType.None, card, sa, moveParams);
cards.add(card); cards.add(card);
amount--; amount--;
} }
for (final Card c : cards) { for (final Card c : cards) {
game.getAction().moveTo(zone, c, sa); game.getAction().moveTo(zone, c, sa, moveParams);
if (sa.hasParam("RememberMade")) { if (sa.hasParam("RememberMade")) {
sa.getHostCard().addRemembered(c); sa.getHostCard().addRemembered(c);
} }

View File

@@ -1,7 +1,10 @@
package forge.game.ability.effects; package forge.game.ability.effects;
import java.util.Map;
import forge.game.Game; import forge.game.Game;
import forge.game.GameLogEntryType; import forge.game.GameLogEntryType;
import forge.game.ability.AbilityKey;
import forge.game.ability.AbilityUtils; import forge.game.ability.AbilityUtils;
import forge.game.ability.SpellAbilityEffect; import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card; import forge.game.card.Card;
@@ -35,6 +38,9 @@ public class MillEffect extends SpellAbilityEffect {
} }
final CardZoneTable table = new CardZoneTable(); final CardZoneTable table = new CardZoneTable();
Map<AbilityKey, Object> moveParams = AbilityKey.newMap();
moveParams.put(AbilityKey.LastStateBattlefield, sa.getLastStateBattlefield());
moveParams.put(AbilityKey.LastStateGraveyard, sa.getLastStateGraveyard());
for (final Player p : getTargetPlayers(sa)) { for (final Player p : getTargetPlayers(sa)) {
if (!sa.usesTargeting() || p.canBeTargetedBy(sa)) { if (!sa.usesTargeting() || p.canBeTargetedBy(sa)) {
@@ -45,7 +51,7 @@ public class MillEffect extends SpellAbilityEffect {
continue; continue;
} }
} }
final CardCollectionView milled = p.mill(numCards, destination, bottom, sa, table); final CardCollectionView milled = p.mill(numCards, destination, bottom, sa, table, moveParams);
// Reveal the milled cards, so players don't have to manually inspect the // Reveal the milled cards, so players don't have to manually inspect the
// graveyard to figure out which ones were milled. // graveyard to figure out which ones were milled.
if (!facedown && reveal) { // do not reveal when exiling face down if (!facedown && reveal) { // do not reveal when exiling face down

View File

@@ -18,9 +18,9 @@ public class PlaneswalkEffect extends SpellAbilityEffect {
} }
if (sa.hasParam("Defined")) { if (sa.hasParam("Defined")) {
CardCollectionView destinations = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("Defined"), sa); CardCollectionView destinations = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("Defined"), sa);
sa.getActivatingPlayer().planeswalkTo(destinations); sa.getActivatingPlayer().planeswalkTo(sa, destinations);
} else { } else {
sa.getActivatingPlayer().planeswalk(); sa.getActivatingPlayer().planeswalk(sa);
} }
} }
} }

View File

@@ -19,6 +19,7 @@ import forge.StaticData;
import forge.card.CardRulesPredicates; import forge.card.CardRulesPredicates;
import forge.game.Game; import forge.game.Game;
import forge.game.ability.AbilityFactory; import forge.game.ability.AbilityFactory;
import forge.game.ability.AbilityKey;
import forge.game.ability.AbilityUtils; import forge.game.ability.AbilityUtils;
import forge.game.ability.SpellAbilityEffect; import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card; import forge.game.card.Card;
@@ -216,6 +217,11 @@ public class PlayEffect extends SpellAbilityEffect {
boolean singleOption = tgtCards.size() == 1 && amount == 1 && optional; boolean singleOption = tgtCards.size() == 1 && amount == 1 && optional;
Map<String, Object> params = hasTotalCMCLimit ? new HashMap<>() : null; Map<String, Object> params = hasTotalCMCLimit ? new HashMap<>() : null;
Map<AbilityKey, Object> moveParams = AbilityKey.newMap();
moveParams.put(AbilityKey.LastStateBattlefield, sa.getLastStateBattlefield());
moveParams.put(AbilityKey.LastStateGraveyard, sa.getLastStateGraveyard());
while (!tgtCards.isEmpty() && amount > 0 && totalCMCLimit >= 0) { while (!tgtCards.isEmpty() && amount > 0 && totalCMCLimit >= 0) {
if (hasTotalCMCLimit) { if (hasTotalCMCLimit) {
// filter out cards with mana value greater than limit // filter out cards with mana value greater than limit
@@ -376,12 +382,12 @@ public class PlayEffect extends SpellAbilityEffect {
// can't be done later // can't be done later
if (sa.hasParam("ReplaceGraveyard")) { if (sa.hasParam("ReplaceGraveyard")) {
addReplaceGraveyardEffect(tgtCard, sa, sa.getParam("ReplaceGraveyard")); addReplaceGraveyardEffect(tgtCard, sa, sa.getParam("ReplaceGraveyard"), moveParams);
} }
// For Illusionary Mask effect // For Illusionary Mask effect
if (sa.hasParam("ReplaceIlluMask")) { if (sa.hasParam("ReplaceIlluMask")) {
addIllusionaryMaskReplace(tgtCard, sa); addIllusionaryMaskReplace(tgtCard, sa, moveParams);
} }
tgtSA.setSVar("IsCastFromPlayEffect", "True"); tgtSA.setSVar("IsCastFromPlayEffect", "True");
@@ -420,7 +426,7 @@ public class PlayEffect extends SpellAbilityEffect {
} // end resolve } // end resolve
protected void addReplaceGraveyardEffect(Card c, SpellAbility sa, String zone) { protected void addReplaceGraveyardEffect(Card c, SpellAbility sa, String zone, Map<AbilityKey, Object> moveParams) {
final Card hostCard = sa.getHostCard(); final Card hostCard = sa.getHostCard();
final Game game = hostCard.getGame(); final Game game = hostCard.getGame();
final Player controller = sa.getActivatingPlayer(); final Player controller = sa.getActivatingPlayer();
@@ -461,12 +467,12 @@ public class PlayEffect extends SpellAbilityEffect {
// TODO: Add targeting to the effect so it knows who it's dealing with // TODO: Add targeting to the effect so it knows who it's dealing with
game.getTriggerHandler().suppressMode(TriggerType.ChangesZone); game.getTriggerHandler().suppressMode(TriggerType.ChangesZone);
game.getAction().moveTo(ZoneType.Command, eff, sa); game.getAction().moveTo(ZoneType.Command, eff, sa, moveParams);
eff.updateStateForView(); eff.updateStateForView();
game.getTriggerHandler().clearSuppression(TriggerType.ChangesZone); game.getTriggerHandler().clearSuppression(TriggerType.ChangesZone);
} }
protected void addIllusionaryMaskReplace(Card c, SpellAbility sa) { protected void addIllusionaryMaskReplace(Card c, SpellAbility sa, Map<AbilityKey, Object> moveParams) {
final Card hostCard = sa.getHostCard(); final Card hostCard = sa.getHostCard();
final Game game = hostCard.getGame(); final Game game = hostCard.getGame();
final Player controller = sa.getActivatingPlayer(); final Player controller = sa.getActivatingPlayer();
@@ -497,7 +503,7 @@ public class PlayEffect extends SpellAbilityEffect {
addExileOnCounteredTrigger(eff); addExileOnCounteredTrigger(eff);
game.getTriggerHandler().suppressMode(TriggerType.ChangesZone); game.getTriggerHandler().suppressMode(TriggerType.ChangesZone);
game.getAction().moveTo(ZoneType.Command, eff, sa); game.getAction().moveTo(ZoneType.Command, eff, sa, moveParams);
eff.updateStateForView(); eff.updateStateForView();
game.getTriggerHandler().clearSuppression(TriggerType.ChangesZone); game.getTriggerHandler().clearSuppression(TriggerType.ChangesZone);
} }

View File

@@ -3,6 +3,7 @@ package forge.game.ability.effects;
import forge.GameCommand; import forge.GameCommand;
import forge.game.Game; import forge.game.Game;
import forge.game.ability.AbilityFactory; import forge.game.ability.AbilityFactory;
import forge.game.ability.AbilityKey;
import forge.game.ability.AbilityUtils; import forge.game.ability.AbilityUtils;
import forge.game.ability.SpellAbilityEffect; import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card; import forge.game.card.Card;
@@ -71,7 +72,7 @@ public abstract class RegenerateBaseEffect extends SpellAbilityEffect {
c.addShield(eff); c.addShield(eff);
} }
game.getTriggerHandler().suppressMode(TriggerType.ChangesZone); game.getTriggerHandler().suppressMode(TriggerType.ChangesZone);
game.getAction().moveTo(ZoneType.Command, eff, sa); game.getAction().moveTo(ZoneType.Command, eff, sa, AbilityKey.newMap());
eff.updateStateForView(); eff.updateStateForView();
game.getTriggerHandler().clearSuppression(TriggerType.ChangesZone); game.getTriggerHandler().clearSuppression(TriggerType.ChangesZone);

View File

@@ -41,7 +41,7 @@ public class SetInMotionEffect extends SpellAbilityEffect {
} }
game.getTriggerHandler().suppressMode(TriggerType.ChangesZone); game.getTriggerHandler().suppressMode(TriggerType.ChangesZone);
game.getAction().moveTo(ZoneType.Command, controller.getActiveScheme(), null); game.getAction().moveTo(ZoneType.Command, controller.getActiveScheme(), null, AbilityKey.newMap());
game.getTriggerHandler().clearSuppression(TriggerType.ChangesZone); game.getTriggerHandler().clearSuppression(TriggerType.ChangesZone);
// Run triggers // Run triggers

View File

@@ -5,6 +5,7 @@ import java.util.List;
import forge.GameCommand; import forge.GameCommand;
import forge.game.Game; import forge.game.Game;
import forge.game.ability.AbilityFactory; import forge.game.ability.AbilityFactory;
import forge.game.ability.AbilityKey;
import forge.game.ability.SpellAbilityEffect; import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.player.Player; import forge.game.player.Player;
@@ -115,7 +116,7 @@ public class SkipPhaseEffect extends SpellAbilityEffect {
eff.addReplacementEffect(re); eff.addReplacementEffect(re);
game.getTriggerHandler().suppressMode(TriggerType.ChangesZone); game.getTriggerHandler().suppressMode(TriggerType.ChangesZone);
game.getAction().moveTo(ZoneType.Command, eff, sa); game.getAction().moveTo(ZoneType.Command, eff, sa, AbilityKey.newMap());
eff.updateStateForView(); eff.updateStateForView();
game.getTriggerHandler().clearSuppression(TriggerType.ChangesZone); game.getTriggerHandler().clearSuppression(TriggerType.ChangesZone);
} }

View File

@@ -4,6 +4,7 @@ import java.util.List;
import forge.game.Game; import forge.game.Game;
import forge.game.ability.AbilityFactory; import forge.game.ability.AbilityFactory;
import forge.game.ability.AbilityKey;
import forge.game.ability.AbilityUtils; import forge.game.ability.AbilityUtils;
import forge.game.ability.SpellAbilityEffect; import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card; import forge.game.card.Card;
@@ -61,7 +62,7 @@ public class SkipTurnEffect extends SpellAbilityEffect {
eff.addReplacementEffect(re); eff.addReplacementEffect(re);
game.getTriggerHandler().suppressMode(TriggerType.ChangesZone); game.getTriggerHandler().suppressMode(TriggerType.ChangesZone);
game.getAction().moveTo(ZoneType.Command, eff, sa); game.getAction().moveTo(ZoneType.Command, eff, sa, AbilityKey.newMap());
eff.updateStateForView(); eff.updateStateForView();
game.getTriggerHandler().clearSuppression(TriggerType.ChangesZone); game.getTriggerHandler().clearSuppression(TriggerType.ChangesZone);
} }

View File

@@ -9,6 +9,7 @@ import com.google.common.collect.Lists;
import forge.card.MagicColor; import forge.card.MagicColor;
import forge.game.Game; import forge.game.Game;
import forge.game.GameOutcome; import forge.game.GameOutcome;
import forge.game.ability.AbilityKey;
import forge.game.ability.ApiType; import forge.game.ability.ApiType;
import forge.game.ability.SpellAbilityEffect; import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card; import forge.game.card.Card;
@@ -127,7 +128,7 @@ public class SubgameEffect extends SpellAbilityEffect {
// Create an effect that lets you cast your companion from your sideboard // Create an effect that lets you cast your companion from your sideboard
if (companion != null) { if (companion != null) {
PlayerZone commandZone = player.getZone(ZoneType.Command); PlayerZone commandZone = player.getZone(ZoneType.Command);
companion = subgame.getAction().moveTo(ZoneType.Command, companion, null); companion = subgame.getAction().moveTo(ZoneType.Command, companion, null, AbilityKey.newMap());
commandZone.add(Player.createCompanionEffect(subgame, companion)); commandZone.add(Player.createCompanionEffect(subgame, companion));
player.updateZoneForView(commandZone); player.updateZoneForView(commandZone);
@@ -242,7 +243,7 @@ public class SubgameEffect extends SpellAbilityEffect {
} }
} }
for (final Card card : movedCommanders) { for (final Card card : movedCommanders) {
maingame.getAction().moveTo(ZoneType.Library, card, null); maingame.getAction().moveTo(ZoneType.Library, card, null, AbilityKey.newMap());
} }
player.shuffle(sa); player.shuffle(sa);

View File

@@ -1,5 +1,8 @@
package forge.game.ability.effects; package forge.game.ability.effects;
import java.util.Map;
import forge.game.ability.AbilityKey;
import forge.game.ability.AbilityUtils; import forge.game.ability.AbilityUtils;
import forge.game.ability.SpellAbilityEffect; import forge.game.ability.SpellAbilityEffect;
import forge.game.card.CardZoneTable; import forge.game.card.CardZoneTable;
@@ -34,6 +37,9 @@ public class SurveilEffect extends SpellAbilityEffect {
boolean isOptional = sa.hasParam("Optional"); boolean isOptional = sa.hasParam("Optional");
CardZoneTable table = new CardZoneTable(); CardZoneTable table = new CardZoneTable();
Map<AbilityKey, Object> moveParams = AbilityKey.newMap();
moveParams.put(AbilityKey.LastStateBattlefield, sa.getLastStateBattlefield());
moveParams.put(AbilityKey.LastStateGraveyard, sa.getLastStateGraveyard());
for (final Player p : getTargetPlayers(sa)) { for (final Player p : getTargetPlayers(sa)) {
if (!sa.usesTargeting() || p.canBeTargetedBy(sa)) { if (!sa.usesTargeting() || p.canBeTargetedBy(sa)) {
@@ -41,7 +47,7 @@ public class SurveilEffect extends SpellAbilityEffect {
continue; continue;
} }
p.surveil(num, sa, table); p.surveil(num, sa, table, moveParams);
} }
} }
table.triggerChangesZoneAll(sa.getHostCard().getGame(), sa); table.triggerChangesZoneAll(sa.getHostCard().getGame(), sa);

View File

@@ -27,7 +27,7 @@ import forge.util.Localizer;
public class VentureEffect extends SpellAbilityEffect { public class VentureEffect extends SpellAbilityEffect {
private Card getDungeonCard(SpellAbility sa, Player player) { private Card getDungeonCard(SpellAbility sa, Player player, Map<AbilityKey, Object> moveParams) {
final Game game = player.getGame(); final Game game = player.getGame();
CardCollectionView commandCards = player.getCardsIn(ZoneType.Command); CardCollectionView commandCards = player.getCardsIn(ZoneType.Command);
@@ -50,7 +50,7 @@ public class VentureEffect extends SpellAbilityEffect {
Card dungeon = player.getController().chooseDungeon(player, dungeonCards, message); Card dungeon = player.getController().chooseDungeon(player, dungeonCards, message);
game.getTriggerHandler().suppressMode(TriggerType.ChangesZone); game.getTriggerHandler().suppressMode(TriggerType.ChangesZone);
game.getAction().moveTo(ZoneType.Command, dungeon, sa); game.getAction().moveTo(ZoneType.Command, dungeon, sa, moveParams);
game.getTriggerHandler().clearSuppression(TriggerType.ChangesZone); game.getTriggerHandler().clearSuppression(TriggerType.ChangesZone);
return dungeon; return dungeon;
@@ -85,13 +85,13 @@ public class VentureEffect extends SpellAbilityEffect {
} }
} }
private void ventureIntoDungeon(SpellAbility sa, Player player) { private void ventureIntoDungeon(SpellAbility sa, Player player, Map<AbilityKey, Object> moveParams) {
if (StaticAbilityCantVenture.cantVenture(player)) { if (StaticAbilityCantVenture.cantVenture(player)) {
return; return;
} }
final Game game = player.getGame(); final Game game = player.getGame();
Card dungeon = getDungeonCard(sa, player); Card dungeon = getDungeonCard(sa, player, moveParams);
String room = dungeon.getCurrentRoom(); String room = dungeon.getCurrentRoom();
String nextRoom = null; String nextRoom = null;
@@ -117,9 +117,13 @@ public class VentureEffect extends SpellAbilityEffect {
@Override @Override
public void resolve(SpellAbility sa) { public void resolve(SpellAbility sa) {
Map<AbilityKey, Object> moveParams = AbilityKey.newMap();
moveParams.put(AbilityKey.LastStateBattlefield, sa.getLastStateBattlefield());
moveParams.put(AbilityKey.LastStateGraveyard, sa.getLastStateGraveyard());
for (final Player p : getTargetPlayers(sa)) { for (final Player p : getTargetPlayers(sa)) {
if (!sa.usesTargeting() || p.canBeTargetedBy(sa)) { if (!sa.usesTargeting() || p.canBeTargetedBy(sa)) {
ventureIntoDungeon(sa, p); ventureIntoDungeon(sa, p, moveParams);
} }
} }
} }

View File

@@ -1,6 +1,9 @@
package forge.game.ability.effects; package forge.game.ability.effects;
import java.util.Map;
import forge.game.Game; import forge.game.Game;
import forge.game.ability.AbilityKey;
import forge.game.ability.AbilityUtils; import forge.game.ability.AbilityUtils;
import forge.game.ability.SpellAbilityEffect; import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card; import forge.game.card.Card;
@@ -87,8 +90,11 @@ public class ZoneExchangeEffect extends SpellAbilityEffect {
object1.unattachFromEntity(c); object1.unattachFromEntity(c);
object2.attachToEntity(c); object2.attachToEntity(c);
} }
Map<AbilityKey, Object> moveParams = AbilityKey.newMap();
moveParams.put(AbilityKey.LastStateBattlefield, sa.getLastStateBattlefield());
moveParams.put(AbilityKey.LastStateGraveyard, sa.getLastStateGraveyard());
// Exchange Zone // Exchange Zone
game.getAction().moveTo(zone2, object1, sa); game.getAction().moveTo(zone2, object1, sa, moveParams);
game.getAction().moveTo(zone1, object2, sa); game.getAction().moveTo(zone1, object2, sa, moveParams);
} }
} }

View File

@@ -17,6 +17,9 @@
*/ */
package forge.game.cost; package forge.game.cost;
import java.util.Map;
import forge.game.ability.AbilityKey;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.player.PlayerCollection; import forge.game.player.PlayerCollection;
@@ -94,8 +97,11 @@ public class CostDraw extends CostPart {
*/ */
@Override @Override
public final boolean payAsDecided(final Player ai, final PaymentDecision decision, SpellAbility ability, final boolean effect) { public final boolean payAsDecided(final Player ai, final PaymentDecision decision, SpellAbility ability, final boolean effect) {
Map<AbilityKey, Object> moveParams = AbilityKey.newMap();
moveParams.put(AbilityKey.LastStateBattlefield, ability.getLastStateBattlefield());
moveParams.put(AbilityKey.LastStateGraveyard, ability.getLastStateGraveyard());
for (final Player p : decision.players) { for (final Player p : decision.players) {
p.drawCards(decision.c, ability); p.drawCards(decision.c, ability, moveParams);
} }
return true; return true;
} }

View File

@@ -17,6 +17,9 @@
*/ */
package forge.game.cost; package forge.game.cost;
import java.util.Map;
import forge.game.ability.AbilityKey;
import forge.game.card.CardCollection; import forge.game.card.CardCollection;
import forge.game.card.CardZoneTable; import forge.game.card.CardZoneTable;
import forge.game.player.Player; import forge.game.player.Player;
@@ -88,7 +91,10 @@ public class CostMill extends CostPart {
@Override @Override
public final boolean payAsDecided(final Player ai, final PaymentDecision decision, SpellAbility ability, final boolean effect) { public final boolean payAsDecided(final Player ai, final PaymentDecision decision, SpellAbility ability, final boolean effect) {
CardZoneTable table = new CardZoneTable(); CardZoneTable table = new CardZoneTable();
ability.getPaidHash().put("Milled", (CardCollection) ai.mill(decision.c, ZoneType.Graveyard, false, ability, table)); Map<AbilityKey, Object> moveParams = AbilityKey.newMap();
moveParams.put(AbilityKey.LastStateBattlefield, ai.getGame().getLastStateBattlefield());
moveParams.put(AbilityKey.LastStateGraveyard, ai.getGame().getLastStateGraveyard());
ability.getPaidHash().put("Milled", (CardCollection) ai.mill(decision.c, ZoneType.Graveyard, false, ability, table, moveParams));
table.triggerChangesZoneAll(ai.getGame(), ability); table.triggerChangesZoneAll(ai.getGame(), ability);
return true; return true;
} }

View File

@@ -19,8 +19,11 @@ package forge.game.cost;
import forge.card.CardType; import forge.card.CardType;
import java.util.Map;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import forge.game.ability.AbilityKey;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.card.CardCollectionView; import forge.game.card.CardCollectionView;
import forge.game.card.CardLists; import forge.game.card.CardLists;
@@ -133,7 +136,10 @@ public class CostSacrifice extends CostPartWithList {
@Override @Override
protected Card doPayment(SpellAbility ability, Card targetCard, final boolean effect) { protected Card doPayment(SpellAbility ability, Card targetCard, final boolean effect) {
// no table there, it is already handled by CostPartWithList // no table there, it is already handled by CostPartWithList
return targetCard.getGame().getAction().sacrifice(targetCard, ability, effect, null, null); Map<AbilityKey, Object> moveParams = AbilityKey.newMap();
moveParams.put(AbilityKey.LastStateBattlefield, ability.getLastStateBattlefield());
moveParams.put(AbilityKey.LastStateGraveyard, ability.getLastStateGraveyard());
return targetCard.getGame().getAction().sacrifice(targetCard, ability, effect, null, moveParams);
} }
/* (non-Javadoc) /* (non-Javadoc)

View File

@@ -393,11 +393,15 @@ public class PhaseHandler implements java.io.Serializable {
int numDiscard = playerTurn.isUnlimitedHandSize() || handSize <= max || handSize == 0 ? 0 : handSize - max; int numDiscard = playerTurn.isUnlimitedHandSize() || handSize <= max || handSize == 0 ? 0 : handSize - max;
if (numDiscard > 0) { if (numDiscard > 0) {
Map<AbilityKey, Object> moveParams = AbilityKey.newMap();
moveParams.put(AbilityKey.LastStateBattlefield, game.getLastStateBattlefield());
moveParams.put(AbilityKey.LastStateGraveyard, game.getLastStateGraveyard());
final CardZoneTable table = new CardZoneTable(); final CardZoneTable table = new CardZoneTable();
final CardCollection discarded = new CardCollection(); final CardCollection discarded = new CardCollection();
boolean firstDiscarded = playerTurn.getNumDiscardedThisTurn() == 0; boolean firstDiscarded = playerTurn.getNumDiscardedThisTurn() == 0;
for (Card c : playerTurn.getController().chooseCardsToDiscardToMaximumHandSize(numDiscard)) { for (Card c : playerTurn.getController().chooseCardsToDiscardToMaximumHandSize(numDiscard)) {
if (playerTurn.discard(c, null, false, table) != null) { if (playerTurn.discard(c, null, false, table, moveParams) != null) {
discarded.add(c); discarded.add(c);
} }
} }

View File

@@ -326,10 +326,14 @@ public class Player extends GameEntity implements Comparable<Player> {
return; return;
} }
Map<AbilityKey, Object> moveParams = AbilityKey.newMap();
moveParams.put(AbilityKey.LastStateBattlefield, game.getLastStateBattlefield());
moveParams.put(AbilityKey.LastStateGraveyard, game.getLastStateGraveyard());
game.getTriggerHandler().suppressMode(TriggerType.ChangesZone); game.getTriggerHandler().suppressMode(TriggerType.ChangesZone);
activeScheme = getZone(ZoneType.SchemeDeck).get(0); activeScheme = getZone(ZoneType.SchemeDeck).get(0);
// gameAction moveTo ? // gameAction moveTo ?
game.getAction().moveTo(ZoneType.Command, activeScheme, null); game.getAction().moveTo(ZoneType.Command, activeScheme, null, moveParams);
game.getTriggerHandler().clearSuppression(TriggerType.ChangesZone); game.getTriggerHandler().clearSuppression(TriggerType.ChangesZone);
// Run triggers // Run triggers
@@ -1101,10 +1105,13 @@ public class Player extends GameEntity implements Comparable<Player> {
return true; return true;
} }
public void surveil(int num, SpellAbility cause, CardZoneTable table) { public void surveil(int num, SpellAbility cause, CardZoneTable table, Map<AbilityKey, Object> params) {
final Map<AbilityKey, Object> repParams = AbilityKey.mapFromAffected(this); final Map<AbilityKey, Object> repParams = AbilityKey.mapFromAffected(this);
repParams.put(AbilityKey.Source, cause); repParams.put(AbilityKey.Source, cause);
repParams.put(AbilityKey.SurveilNum, num); repParams.put(AbilityKey.SurveilNum, num);
if (params != null) {
repParams.putAll(params);
}
switch (getGame().getReplacementHandler().run(ReplacementType.Surveil, repParams)) { switch (getGame().getReplacementHandler().run(ReplacementType.Surveil, repParams)) {
case NotReplaced: case NotReplaced:
@@ -1133,7 +1140,7 @@ public class Player extends GameEntity implements Comparable<Player> {
if (toGrave != null) { if (toGrave != null) {
for (Card c : toGrave) { for (Card c : toGrave) {
ZoneType oZone = c.getZone().getZoneType(); ZoneType oZone = c.getZone().getZoneType();
Card moved = getGame().getAction().moveToGraveyard(c, cause); Card moved = getGame().getAction().moveToGraveyard(c, cause, params);
table.put(oZone, moved.getZone().getZoneType(), moved); table.put(oZone, moved.getZone().getZoneType(), moved);
numToGrave++; numToGrave++;
} }
@@ -1142,7 +1149,7 @@ public class Player extends GameEntity implements Comparable<Player> {
if (toTop != null) { if (toTop != null) {
Collections.reverse(toTop); // the last card in list will become topmost in library, have to revert thus. Collections.reverse(toTop); // the last card in list will become topmost in library, have to revert thus.
for (Card c : toTop) { for (Card c : toTop) {
getGame().getAction().moveToLibrary(c, cause); getGame().getAction().moveToLibrary(c, cause, params);
numToTop++; numToTop++;
} }
} }
@@ -1152,6 +1159,9 @@ public class Player extends GameEntity implements Comparable<Player> {
surveilThisTurn++; surveilThisTurn++;
final Map<AbilityKey, Object> runParams = AbilityKey.mapFromPlayer(this); final Map<AbilityKey, Object> runParams = AbilityKey.mapFromPlayer(this);
runParams.put(AbilityKey.NumThisTurn, surveilThisTurn); runParams.put(AbilityKey.NumThisTurn, surveilThisTurn);
if (params != null) {
runParams.putAll(params);
}
getGame().getTriggerHandler().runTrigger(TriggerType.Surveil, runParams, false); getGame().getTriggerHandler().runTrigger(TriggerType.Surveil, runParams, false);
} }
@@ -1176,13 +1186,13 @@ public class Player extends GameEntity implements Comparable<Player> {
} }
public final CardCollectionView drawCard() { public final CardCollectionView drawCard() {
return drawCards(1, null); return drawCards(1, null, AbilityKey.newMap());
} }
public final CardCollectionView drawCards(final int n) { public final CardCollectionView drawCards(final int n) {
return drawCards(n, null); return drawCards(n, null, AbilityKey.newMap());
} }
public final CardCollectionView drawCards(final int n, SpellAbility cause) { public final CardCollectionView drawCards(final int n, SpellAbility cause, Map<AbilityKey, Object> params) {
final CardCollection drawn = new CardCollection(); final CardCollection drawn = new CardCollection();
if (n <= 0) { if (n <= 0) {
return drawn; return drawn;
@@ -1191,6 +1201,9 @@ public class Player extends GameEntity implements Comparable<Player> {
// Replacement effects // Replacement effects
final Map<AbilityKey, Object> repRunParams = AbilityKey.mapFromAffected(this); final Map<AbilityKey, Object> repRunParams = AbilityKey.mapFromAffected(this);
repRunParams.put(AbilityKey.Number, n); repRunParams.put(AbilityKey.Number, n);
if (params != null) {
repRunParams.putAll(params);
}
if (game.getReplacementHandler().run(ReplacementType.DrawCards, repRunParams) != ReplacementResult.NotReplaced) { if (game.getReplacementHandler().run(ReplacementType.DrawCards, repRunParams) != ReplacementResult.NotReplaced) {
return drawn; return drawn;
@@ -1204,7 +1217,7 @@ public class Player extends GameEntity implements Comparable<Player> {
if (gameStarted && !canDraw()) { if (gameStarted && !canDraw()) {
return drawn; return drawn;
} }
drawn.addAll(doDraw(toReveal, cause)); drawn.addAll(doDraw(toReveal, cause, params));
} }
// reveal multiple drawn cards when playing with the top of the library revealed // reveal multiple drawn cards when playing with the top of the library revealed
@@ -1219,13 +1232,16 @@ public class Player extends GameEntity implements Comparable<Player> {
/** /**
* @return a CardCollectionView of cards actually drawn * @return a CardCollectionView of cards actually drawn
*/ */
private CardCollectionView doDraw(Map<Player, CardCollection> revealed, SpellAbility cause) { private CardCollectionView doDraw(Map<Player, CardCollection> revealed, SpellAbility cause, Map<AbilityKey, Object> params) {
final CardCollection drawn = new CardCollection(); final CardCollection drawn = new CardCollection();
final PlayerZone library = getZone(ZoneType.Library); final PlayerZone library = getZone(ZoneType.Library);
// Replacement effects // Replacement effects
Map<AbilityKey, Object> repParams = AbilityKey.mapFromAffected(this); Map<AbilityKey, Object> repParams = AbilityKey.mapFromAffected(this);
repParams.put(AbilityKey.Cause, cause); repParams.put(AbilityKey.Cause, cause);
if (params != null) {
repParams.putAll(params);
}
if (game.getReplacementHandler().run(ReplacementType.Draw, repParams) != ReplacementResult.NotReplaced) { if (game.getReplacementHandler().run(ReplacementType.Draw, repParams) != ReplacementResult.NotReplaced) {
return drawn; return drawn;
} }
@@ -1246,7 +1262,7 @@ public class Player extends GameEntity implements Comparable<Player> {
} }
} }
c = game.getAction().moveToHand(c, cause); c = game.getAction().moveToHand(c, cause, params);
drawn.add(c); drawn.add(c);
for (Player p : pList) { for (Player p : pList) {
@@ -1267,6 +1283,9 @@ public class Player extends GameEntity implements Comparable<Player> {
view.updateNumDrawnThisTurn(this); view.updateNumDrawnThisTurn(this);
final Map<AbilityKey, Object> runParams = AbilityKey.mapFromPlayer(this); final Map<AbilityKey, Object> runParams = AbilityKey.mapFromPlayer(this);
if (params != null) {
runParams.putAll(params);
}
// CR 121.8 card was drawn as part of another sa (e.g. paying with Chromantic Sphere), hide it temporarily // CR 121.8 card was drawn as part of another sa (e.g. paying with Chromantic Sphere), hide it temporarily
if (game.getTopLibForPlayer(this) != null && getPaidForSA() != null && cause != null && getPaidForSA() != cause.getRootAbility()) { if (game.getTopLibForPlayer(this) != null && getPaidForSA() != null && cause != null && getPaidForSA() != cause.getRootAbility()) {
@@ -1414,9 +1433,6 @@ public class Player extends GameEntity implements Comparable<Player> {
numRollsThisTurn++; numRollsThisTurn++;
} }
public final Card discard(final Card c, final SpellAbility sa, final boolean effect, CardZoneTable table) {
return discard(c, sa, effect, table, null);
}
public final Card discard(final Card c, final SpellAbility sa, final boolean effect, CardZoneTable table, Map<AbilityKey, Object> params) { public final Card discard(final Card c, final SpellAbility sa, final boolean effect, CardZoneTable table, Map<AbilityKey, Object> params) {
if (!c.canBeDiscardedBy(sa, effect)) { if (!c.canBeDiscardedBy(sa, effect)) {
return null; return null;
@@ -1575,13 +1591,16 @@ public class Player extends GameEntity implements Comparable<Player> {
} }
public final CardCollectionView mill(int n, final ZoneType destination, public final CardCollectionView mill(int n, final ZoneType destination,
final boolean bottom, SpellAbility sa, CardZoneTable table) { final boolean bottom, SpellAbility sa, CardZoneTable table, Map<AbilityKey, Object> params) {
final CardCollectionView lib = getCardsIn(ZoneType.Library); final CardCollectionView lib = getCardsIn(ZoneType.Library);
final CardCollection milled = new CardCollection(); final CardCollection milled = new CardCollection();
// Replacement effects // Replacement effects
final Map<AbilityKey, Object> repRunParams = AbilityKey.mapFromAffected(this); final Map<AbilityKey, Object> repRunParams = AbilityKey.mapFromAffected(this);
repRunParams.put(AbilityKey.Number, n); repRunParams.put(AbilityKey.Number, n);
if (params != null) {
repRunParams.putAll(params);
}
if (destination == ZoneType.Graveyard && !bottom) { if (destination == ZoneType.Graveyard && !bottom) {
switch (getGame().getReplacementHandler().run(ReplacementType.Mill, repRunParams)) { switch (getGame().getReplacementHandler().run(ReplacementType.Mill, repRunParams)) {
@@ -1618,7 +1637,7 @@ public class Player extends GameEntity implements Comparable<Player> {
for (Card m : milledView) { for (Card m : milledView) {
final ZoneType origin = m.getZone().getZoneType(); final ZoneType origin = m.getZone().getZoneType();
final Card d = game.getAction().moveTo(destination, m, sa); final Card d = game.getAction().moveTo(destination, m, sa, params);
if (d.getZone().is(destination)) { if (d.getZone().is(destination)) {
table.put(origin, d.getZone().getZoneType(), d); table.put(origin, d.getZone().getZoneType(), d);
} }
@@ -2580,21 +2599,25 @@ public class Player extends GameEntity implements Comparable<Player> {
* Takes the top plane of the planar deck and put it face up in the command zone. * Takes the top plane of the planar deck and put it face up in the command zone.
* Then runs triggers. * Then runs triggers.
*/ */
public void planeswalk() { public void planeswalk(SpellAbility sa) {
planeswalkTo(new CardCollection(getZone(ZoneType.PlanarDeck).get(0))); planeswalkTo(sa, new CardCollection(getZone(ZoneType.PlanarDeck).get(0)));
} }
/** /**
* Puts the planes in the argument and puts them face up in the command zone. * Puts the planes in the argument and puts them face up in the command zone.
* Then runs triggers. * Then runs triggers.
*/ */
public void planeswalkTo(final CardCollectionView destinations) { public void planeswalkTo(SpellAbility sa, final CardCollectionView destinations) {
System.out.println(getName() + ": planeswalk to " + destinations.toString()); System.out.println(getName() + ": planeswalk to " + destinations.toString());
currentPlanes.addAll(destinations); currentPlanes.addAll(destinations);
game.getView().updatePlanarPlayer(getView()); game.getView().updatePlanarPlayer(getView());
Map<AbilityKey, Object> moveParams = AbilityKey.newMap();
moveParams.put(AbilityKey.LastStateBattlefield, sa.getLastStateBattlefield());
moveParams.put(AbilityKey.LastStateGraveyard, sa.getLastStateGraveyard());
for (Card c : currentPlanes) { for (Card c : currentPlanes) {
game.getAction().moveTo(ZoneType.Command,c, null); game.getAction().moveTo(ZoneType.Command, c, sa, moveParams);
//getZone(ZoneType.PlanarDeck).remove(c); //getZone(ZoneType.PlanarDeck).remove(c);
//getZone(ZoneType.Command).add(c); //getZone(ZoneType.Command).add(c);
} }
@@ -3414,10 +3437,13 @@ public class Player extends GameEntity implements Comparable<Player> {
} }
} }
public void learnLesson(SpellAbility sa, CardZoneTable table) { public void learnLesson(SpellAbility sa, CardZoneTable table, Map<AbilityKey, Object> params) {
// Replacement effects // Replacement effects
Map<AbilityKey, Object> repParams = AbilityKey.mapFromAffected(this); Map<AbilityKey, Object> repParams = AbilityKey.mapFromAffected(this);
repParams.put(AbilityKey.Cause, sa); repParams.put(AbilityKey.Cause, sa);
if (params != null) {
repParams.putAll(params);
}
if (game.getReplacementHandler().run(ReplacementType.Learn, repParams) != ReplacementResult.NotReplaced) { if (game.getReplacementHandler().run(ReplacementType.Learn, repParams) != ReplacementResult.NotReplaced) {
return; return;
} }
@@ -3438,11 +3464,11 @@ public class Player extends GameEntity implements Comparable<Player> {
} }
if (c.isInZone(ZoneType.Sideboard)) { // Sideboard Lesson to Hand if (c.isInZone(ZoneType.Sideboard)) { // Sideboard Lesson to Hand
game.getAction().reveal(new CardCollection(c), c.getOwner(), true); game.getAction().reveal(new CardCollection(c), c.getOwner(), true);
Card moved = game.getAction().moveTo(ZoneType.Hand, c, sa); Card moved = game.getAction().moveTo(ZoneType.Hand, c, sa, params);
table.put(ZoneType.Sideboard, ZoneType.Hand, moved); table.put(ZoneType.Sideboard, ZoneType.Hand, moved);
} else if (c.isInZone(ZoneType.Hand)) { // Discard and Draw } else if (c.isInZone(ZoneType.Hand)) { // Discard and Draw
boolean firstDiscard = getNumDiscardedThisTurn() == 0; boolean firstDiscard = getNumDiscardedThisTurn() == 0;
if (discard(c, sa, true, table) != null) { if (discard(c, sa, true, table, params) != null) {
// Change this if something would make multiple player learn at the same time // Change this if something would make multiple player learn at the same time
// Discard Trigger outside Effect // Discard Trigger outside Effect
@@ -3450,9 +3476,12 @@ public class Player extends GameEntity implements Comparable<Player> {
runParams.put(AbilityKey.Cards, new CardCollection(c)); runParams.put(AbilityKey.Cards, new CardCollection(c));
runParams.put(AbilityKey.Cause, sa); runParams.put(AbilityKey.Cause, sa);
runParams.put(AbilityKey.FirstTime, firstDiscard); runParams.put(AbilityKey.FirstTime, firstDiscard);
if (params != null) {
runParams.putAll(params);
}
getGame().getTriggerHandler().runTrigger(TriggerType.DiscardedAll, runParams, false); getGame().getTriggerHandler().runTrigger(TriggerType.DiscardedAll, runParams, false);
for (Card d : drawCards(1, sa)) { for (Card d : drawCards(1, sa, params)) {
table.put(ZoneType.Library, ZoneType.Hand, d); // does a ChangesZoneAll care about moving from Library to Hand table.put(ZoneType.Library, ZoneType.Hand, d); // does a ChangesZoneAll care about moving from Library to Hand
} }
} }

View File

@@ -44,7 +44,8 @@ import io.sentry.Sentry;
import io.sentry.event.BreadcrumbBuilder; import io.sentry.event.BreadcrumbBuilder;
public class TriggerHandler { public class TriggerHandler {
private final List<TriggerType> suppressedModes = Collections.synchronizedList(new ArrayList<>()); private final Set<TriggerType> suppressedModes = Collections.synchronizedSet(EnumSet.noneOf(TriggerType.class));
private boolean allSuppressed = false;
private final List<Trigger> activeTriggers = Collections.synchronizedList(new ArrayList<>()); private final List<Trigger> activeTriggers = Collections.synchronizedList(new ArrayList<>());
private final List<Trigger> delayedTriggers = Collections.synchronizedList(new ArrayList<>()); private final List<Trigger> delayedTriggers = Collections.synchronizedList(new ArrayList<>());
@@ -106,18 +107,15 @@ public class TriggerHandler {
} }
public final void setSuppressAllTriggers(final boolean suppress) { public final void setSuppressAllTriggers(final boolean suppress) {
for (TriggerType t : TriggerType.values()) { allSuppressed = suppress;
if (suppress) {
suppressMode(t);
} else {
clearSuppression(t);
}
}
} }
public final void clearSuppression(final TriggerType mode) { public final void clearSuppression(final TriggerType mode) {
suppressedModes.remove(mode); suppressedModes.remove(mode);
} }
public boolean isTriggerSuppressed(final TriggerType mode) {
return allSuppressed || suppressedModes.contains(mode);
}
public static Trigger parseTrigger(final String trigParse, final Card host, final boolean intrinsic) { public static Trigger parseTrigger(final String trigParse, final Card host, final boolean intrinsic) {
return parseTrigger(trigParse, host, intrinsic, host.getCurrentState()); return parseTrigger(trigParse, host, intrinsic, host.getCurrentState());
@@ -252,7 +250,7 @@ public class TriggerHandler {
} }
public final void runTrigger(final TriggerType mode, final Map<AbilityKey, Object> runParams, boolean holdTrigger) { public final void runTrigger(final TriggerType mode, final Map<AbilityKey, Object> runParams, boolean holdTrigger) {
if (suppressedModes.contains(mode)) { if (isTriggerSuppressed(mode)) {
return; return;
} }

View File

@@ -2803,7 +2803,7 @@ public class PlayerControllerHuman extends PlayerController implements IGameCont
} }
} }
} }
getGame().getAction().moveTo(targetZone, forgeCard, null); getGame().getAction().moveTo(targetZone, forgeCard, null, AbilityKey.newMap());
if (forgeCard.isCreature()) { if (forgeCard.isCreature()) {
forgeCard.setSickness(lastSummoningSickness); forgeCard.setSickness(lastSummoningSickness);
} }
@@ -2858,7 +2858,7 @@ public class PlayerControllerHuman extends PlayerController implements IGameCont
getGame().getAction().moveToBottomOfLibrary(forgeCard, null); getGame().getAction().moveToBottomOfLibrary(forgeCard, null);
} }
} else { } else {
getGame().getAction().moveTo(targetZone, forgeCard, null); getGame().getAction().moveTo(targetZone, forgeCard, null, AbilityKey.newMap());
} }
lastAdded = f; lastAdded = f;
@@ -2898,7 +2898,7 @@ public class PlayerControllerHuman extends PlayerController implements IGameCont
if (c == null) { if (c == null) {
continue; continue;
} }
if (getGame().getAction().moveTo(ZoneType.Exile, c, null) != null) { if (getGame().getAction().moveTo(ZoneType.Exile, c, null, AbilityKey.newMap()) != null) {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
sb.append(p).append(" exiles ").append(c).append(" due to Dev Cheats."); sb.append(p).append(" exiles ").append(c).append(" due to Dev Cheats.");
getGame().getGameLog().add(GameLogEntryType.DISCARD, sb.toString()); getGame().getGameLog().add(GameLogEntryType.DISCARD, sb.toString());
@@ -2937,7 +2937,7 @@ public class PlayerControllerHuman extends PlayerController implements IGameCont
if (c == null) { if (c == null) {
continue; continue;
} }
if (getGame().getAction().moveTo(ZoneType.Exile, c, null) != null) { if (getGame().getAction().moveTo(ZoneType.Exile, c, null, AbilityKey.newMap()) != null) {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
sb.append(p).append(" exiles ").append(c).append(" due to Dev Cheats."); sb.append(p).append(" exiles ").append(c).append(" due to Dev Cheats.");
getGame().getGameLog().add(GameLogEntryType.ZONE_CHANGE, sb.toString()); getGame().getGameLog().add(GameLogEntryType.ZONE_CHANGE, sb.toString());