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)) {
p.getGame().getAction().moveTo(ZoneType.Battlefield, c, null);
p.getGame().getAction().moveTo(ZoneType.Battlefield, c, null, null);
} else {
p.getZone(ZoneType.Hand).add(c);
p.getGame().getAction().moveToPlay(c, null, null);

View File

@@ -26,6 +26,7 @@ import java.util.Map;
import java.util.Set;
import forge.util.*;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.Pair;
@@ -202,6 +203,16 @@ public class GameAction {
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()) {
boolean resetToOriginal = false;
@@ -254,7 +265,6 @@ public class GameAction {
// if from Battlefield to Graveyard and Card does exist in LastStateBattlefield
// use that instead
if (fromBattlefield) {
CardCollectionView lastBattlefield = game.getLastStateBattlefield();
int idx = lastBattlefield.indexOf(c);
if (idx != -1) {
lastKnownInfo = lastBattlefield.get(idx);
@@ -577,7 +587,10 @@ public class GameAction {
}
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);
table.replaceCounterEffect(game, null, true);
@@ -705,7 +718,7 @@ public class GameAction {
}
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) {
// 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);
}
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) {
return moveTo(name, c, 0, cause);
public final Card moveTo(final ZoneType name, final Card c, SpellAbility cause, Map<AbilityKey, Object> params) {
return moveTo(name, c, 0, cause, params);
}
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) {
// Call specific functions to set PlayerZone, then move onto moveTo
@@ -786,7 +799,7 @@ public class GameAction {
if (maingameCard.getZone().is(ZoneType.Stack)) {
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) {
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) {
Card result = moveTo(game.getStackZone(), c, cause, params);
@@ -833,7 +849,7 @@ public class GameAction {
}
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) {
final PlayerZone grave = c.getOwner().getZone(ZoneType.Graveyard);
@@ -842,7 +858,7 @@ public class GameAction {
}
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) {
final PlayerZone hand = c.getOwner().getZone(ZoneType.Hand);
@@ -859,14 +875,14 @@ public class GameAction {
}
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) {
return moveToLibrary(c, -1, cause, params);
}
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) {
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
boolean orderedDesCreats = false;
boolean orderedNoRegCreats = false;
boolean orderedSacrificeList = false;
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++) {
checkStaticAbilities(false, affectedCards, CardCollection.EMPTY);
boolean checkAgain = false;
@@ -1253,16 +1274,14 @@ public class GameAction {
}
}
}
CardCollection noRegCreats = null;
CardCollection noRegCreats = new CardCollection();
CardCollection desCreats = null;
CardCollection unAttachList = new CardCollection();
CardCollection sacrificeList = new CardCollection();
for (final Card c : game.getCardsIn(ZoneType.Battlefield)) {
if (c.isCreature()) {
// Rule 704.5f - Put into grave (no regeneration) for toughness <= 0
if (c.getNetToughness() <= 0) {
if (noRegCreats == null) {
noRegCreats = new CardCollection();
}
noRegCreats.add(c);
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.")) {
@@ -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_5r(c); // annihilate +1/+1 counters with -1/-1 ones
@@ -1336,9 +1355,6 @@ public class GameAction {
// cleanup aura
if (c.isAura() && c.isInPlay() && !c.isEnchanting()) {
if (noRegCreats == null) {
noRegCreats = new CardCollection();
}
noRegCreats.add(c);
checkAgain = true;
}
@@ -1351,28 +1367,46 @@ public class GameAction {
// cleanup aura
if (u.isAura() && u.isInPlay() && !u.isEnchanting()) {
if (noRegCreats == null) {
noRegCreats = new CardCollection();
}
noRegCreats.add(u);
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
// (e.g. helpful for Erebos's Titan and another creature dealing lethal damage to each other simultaneously)
setHoldCheckingStaticAbilities(true);
if (noRegCreats != null) {
if (noRegCreats.size() > 1 && !orderedNoRegCreats) {
noRegCreats = (CardCollection) GameActionUtil.orderCardsByTheirOwners(game, noRegCreats, ZoneType.Graveyard, null);
orderedNoRegCreats = true;
}
for (Card c : noRegCreats) {
c.updateWasDestroyed(true);
sacrificeDestroy(c, null, table, null);
}
if (noRegCreats.size() > 1 && !orderedNoRegCreats) {
noRegCreats = (CardCollection) GameActionUtil.orderCardsByTheirOwners(game, noRegCreats, ZoneType.Graveyard, null);
orderedNoRegCreats = true;
}
for (Card c : noRegCreats) {
c.updateWasDestroyed(true);
sacrificeDestroy(c, null, table, mapParams);
}
if (desCreats != null) {
if (desCreats.size() > 1 && !orderedDesCreats) {
desCreats = CardLists.filter(desCreats, CardPredicates.Presets.CAN_BE_DESTROYED);
@@ -1382,39 +1416,24 @@ public class GameAction {
orderedDesCreats = true;
}
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);
if (game.getTriggerHandler().runWaitingTriggers()) {
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) {
game.getCombat().removeAbsentCombatants();
@@ -1459,7 +1478,7 @@ public class GameAction {
game.runSBACheckedCommands();
}
private boolean stateBasedAction_Saga(Card c, CardZoneTable table) {
private boolean stateBasedAction_Saga(Card c, CardCollection sacrificeList) {
boolean checkAgain = false;
if (!c.getType().hasSubtype("Saga")) {
return false;
@@ -1472,7 +1491,7 @@ public class GameAction {
}
if (!game.getStack().hasSourceOnStack(c, SpellAbilityPredicates.isChapter())) {
// needs to be effect, because otherwise it might be a cost?
sacrifice(c, null, true, table, null);
sacrificeList.add(c);
checkAgain = true;
}
return checkAgain;
@@ -1653,57 +1672,28 @@ public class GameAction {
game.getStack().clearSimultaneousStack();
}
private boolean handlePlaneswalkerRule(Player p, CardZoneTable table) {
private boolean handlePlaneswalkerRule(Player p, CardCollection noRegCreats) {
// get all Planeswalkers
final List<Card> list = p.getPlaneswalkersInPlay();
boolean recheck = false;
//final Multimap<String, Card> uniqueWalkers = ArrayListMultimap.create(); // Not used as of Ixalan
for (Card c : list) {
if (c.getCounters(CounterEnumType.LOYALTY) <= 0) {
//for animation
c.updateWasDestroyed(true);
sacrificeDestroy(c, null, table, null);
// Play the Destroy sound
game.fireEvent(new GameEventCardDestroyed());
noRegCreats.add(c);
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;
}
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");
if (a.isEmpty() || game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noLegendRule)) {
return false;
}
boolean recheck = false;
// TODO legend rule exception into static ability
List<Card> yamazaki = CardLists.getKeyword(a, "Legend rule doesn't apply to CARDNAME.");
a.removeAll(yamazaki);
@@ -1715,6 +1705,8 @@ public class GameAction {
}
}
// TODO handle Spy Kit
for (String name : uniqueLegends.keySet()) {
Collection<Card> cc = uniqueLegends.get(name);
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),
"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) {
if (c != toKeep) {
//for animation
c.updateWasDestroyed(true);
sacrificeDestroy(c, null, table, null);
}
}
game.fireEvent(new GameEventCardDestroyed());
cc.remove(toKeep);
noRegCreats.addAll(cc);
}
return recheck;
}
private boolean handleWorldRule(CardZoneTable table) {
private boolean handleWorldRule(CardCollection noRegCreats) {
final List<Card> worlds = CardLists.getType(game.getCardsIn(ZoneType.Battlefield), "World");
if (worlds.size() <= 1) {
return false;
@@ -1762,12 +1748,7 @@ public class GameAction {
worlds.removeAll(toKeep);
}
for (Card c : worlds) {
//for animation
c.updateWasDestroyed(true);
sacrificeDestroy(c, null, table, null);
game.fireEvent(new GameEventCardDestroyed());
}
noRegCreats.addAll(worlds);
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
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);
return eff;

View File

@@ -7,6 +7,7 @@ import forge.deck.CardPool;
import forge.deck.Deck;
import forge.deck.DeckFormat;
import forge.deck.DeckSection;
import forge.game.ability.AbilityKey;
import forge.game.card.Card;
import forge.game.card.CardCollectionView;
import forge.game.event.Event;
@@ -80,7 +81,7 @@ public class Match {
Multimap<Player, Card> list = game.chooseCardsForAnte(rules.getMatchAnteRarity());
for (Entry<Player, Card> kv : list.entries()) {
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.fireEvent(new GameEventAnteCardsSelected(list));
@@ -303,7 +304,7 @@ public class Match {
// Create an effect that lets you cast your companion from your sideboard
if (companion != null) {
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));
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
game.getTriggerHandler().suppressMode(TriggerType.ChangesZone);
game.getAction().moveTo(ZoneType.Command, eff, sa);
game.getAction().moveTo(ZoneType.Command, eff, sa, null);
eff.updateStateForView();
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
game.getTriggerHandler().suppressMode(TriggerType.ChangesZone);
game.getAction().moveTo(ZoneType.Command, eff, sa);
game.getAction().moveTo(ZoneType.Command, eff, sa, null);
eff.updateStateForView();
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();
for (Player p : discarders) {
final CardCollection discardedByPlayer = new CardCollection();
for (Card card : Lists.newArrayList(discardedMap.get(p))) { // without copying will get concurrent modification exception
if (card == null) { continue; }
if (p.discard(card, sa, effect, table) != null) {
if (p.discard(card, sa, effect, table, params) != null) {
discardedByPlayer.add(card);
if (sa.hasParam("RememberDiscarded")) {

View File

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

View File

@@ -49,6 +49,8 @@ public class BalanceEffect extends SpellAbilityEffect {
}
Map<AbilityKey, Object> params = AbilityKey.newMap();
params.put(AbilityKey.LastStateBattlefield, sa.getLastStateBattlefield());
params.put(AbilityKey.LastStateGraveyard, sa.getLastStateGraveyard());
CardZoneTable table = new CardZoneTable();
for (int i = 0; i < players.size(); i++) {
Player p = players.get(i);
@@ -67,7 +69,7 @@ public class BalanceEffect extends SpellAbilityEffect {
}
if (zone.equals(ZoneType.Hand)) {
discard(sa, table, true, discardedMap);
discard(sa, table, true, discardedMap, params);
}
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.LastStateGraveyard, lastStateGraveyard);
if (sa.isReplacementAbility()) {
@@ -699,7 +699,10 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
gameCard.setExiledWith(host);
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())) {
StringBuilder sb = new StringBuilder();
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) {
Card movedCard = null;
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.LastStateBattlefield, lastStateBattlefield);
moveParams.put(AbilityKey.LastStateGraveyard, lastStateGraveyard);

View File

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

View File

@@ -9,6 +9,7 @@ import com.google.common.collect.Maps;
import forge.game.Game;
import forge.game.GameActionUtil;
import forge.game.ability.AbilityKey;
import forge.game.ability.AbilityUtils;
import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card;
@@ -287,7 +288,11 @@ public class DiscardEffect extends SpellAbilityEffect {
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
table.triggerChangesZoneAll(game, sa);

View File

@@ -1,7 +1,9 @@
package forge.game.ability.effects;
import java.util.List;
import java.util.Map;
import forge.game.ability.AbilityKey;
import forge.game.ability.AbilityUtils;
import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card;
@@ -52,6 +54,9 @@ public class DrawEffect extends SpellAbilityEffect {
final boolean upto = sa.hasParam("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)) {
// 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);
}
final CardCollectionView drawn = p.drawCards(actualNum, sa);
final CardCollectionView drawn = p.drawCards(actualNum, sa, moveParams);
if (sa.hasParam("Reveal")) {
if (sa.getParam("Reveal").equals("All")) {
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.List;
import java.util.Map;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
@@ -12,6 +13,7 @@ import forge.card.CardRarity;
import forge.game.Game;
import forge.game.GameObject;
import forge.game.ability.AbilityFactory;
import forge.game.ability.AbilityKey;
import forge.game.ability.AbilityUtils;
import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card;
@@ -136,6 +138,10 @@ public class EffectEffect extends SpellAbilityEffect {
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) {
final Card eff = createEffect(sa, controller, name, image);
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
game.getTriggerHandler().suppressMode(TriggerType.ChangesZone);
game.getAction().moveTo(ZoneType.Command, eff, sa);
game.getAction().moveTo(ZoneType.Command, eff, sa, params);
eff.updateStateForView();
game.getTriggerHandler().clearSuppression(TriggerType.ChangesZone);
//if (effectTriggers != null) {

View File

@@ -1,7 +1,10 @@
package forge.game.ability.effects;
import java.util.Map;
import forge.game.Game;
import forge.game.GameLogEntryType;
import forge.game.ability.AbilityKey;
import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card;
import forge.game.card.CardCollectionView;
@@ -49,8 +52,12 @@ public class EncodeEffect extends SpellAbilityEffect {
return;
}
Map<AbilityKey, Object> moveParams = AbilityKey.newMap();
moveParams.put(AbilityKey.LastStateBattlefield, sa.getLastStateBattlefield());
moveParams.put(AbilityKey.LastStateGraveyard, sa.getLastStateGraveyard());
// 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
Card choice = player.getController().chooseSingleEntityForEffect(choices, sa, Localizer.getInstance().getMessage("lblChooseACreatureYouControlToEncode") + " ", true, null);

View File

@@ -1,6 +1,7 @@
package forge.game.ability.effects;
import java.util.List;
import java.util.Map;
import com.google.common.collect.Lists;
@@ -48,6 +49,9 @@ public class ExploreEffect extends SpellAbilityEffect {
GameEntityCounterTable table = new GameEntityCounterTable();
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)) {
// revealed land card
boolean revealedLand = false;
@@ -59,7 +63,7 @@ public class ExploreEffect extends SpellAbilityEffect {
final Card r = top.getFirst();
final Zone originZone = game.getZoneOf(r);
if (r.isLand()) {
movedCard = game.getAction().moveTo(ZoneType.Hand, r, sa);
movedCard = game.getAction().moveTo(ZoneType.Hand, r, sa, moveParams);
revealedLand = true;
} else {
// 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,
Localizer.getInstance().getMessage("lblPutThisCardToYourGraveyard"), true, pl);
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.game.Game;
import forge.game.ability.AbilityKey;
import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card;
import forge.game.replacement.ReplacementEffect;
@@ -32,7 +33,7 @@ public class FogEffect extends SpellAbilityEffect {
eff.addReplacementEffect(re);
game.getTriggerHandler().suppressMode(TriggerType.ChangesZone);
game.getAction().moveTo(ZoneType.Command, eff, sa);
game.getAction().moveTo(ZoneType.Command, eff, sa, AbilityKey.newMap());
eff.updateStateForView();
game.getTriggerHandler().clearSuppression(TriggerType.ChangesZone);

View File

@@ -1,6 +1,9 @@
package forge.game.ability.effects;
import java.util.Map;
import forge.game.Game;
import forge.game.ability.AbilityKey;
import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card;
import forge.game.card.CardZoneTable;
@@ -18,8 +21,11 @@ public class LearnEffect extends SpellAbilityEffect {
final Card source = sa.getHostCard();
final Game game = source.getGame();
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)) {
p.learnLesson(sa, table);
p.learnLesson(sa, table, moveParams);
}
table.triggerChangesZoneAll(game, sa);
}

View File

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

View File

@@ -1,7 +1,10 @@
package forge.game.ability.effects;
import java.util.Map;
import forge.game.Game;
import forge.game.GameLogEntryType;
import forge.game.ability.AbilityKey;
import forge.game.ability.AbilityUtils;
import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card;
@@ -35,6 +38,9 @@ public class MillEffect extends SpellAbilityEffect {
}
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)) {
if (!sa.usesTargeting() || p.canBeTargetedBy(sa)) {
@@ -45,7 +51,7 @@ public class MillEffect extends SpellAbilityEffect {
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
// graveyard to figure out which ones were milled.
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")) {
CardCollectionView destinations = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("Defined"), sa);
sa.getActivatingPlayer().planeswalkTo(destinations);
sa.getActivatingPlayer().planeswalkTo(sa, destinations);
} else {
sa.getActivatingPlayer().planeswalk();
sa.getActivatingPlayer().planeswalk(sa);
}
}
}

View File

@@ -19,6 +19,7 @@ import forge.StaticData;
import forge.card.CardRulesPredicates;
import forge.game.Game;
import forge.game.ability.AbilityFactory;
import forge.game.ability.AbilityKey;
import forge.game.ability.AbilityUtils;
import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card;
@@ -216,6 +217,11 @@ public class PlayEffect extends SpellAbilityEffect {
boolean singleOption = tgtCards.size() == 1 && amount == 1 && optional;
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) {
if (hasTotalCMCLimit) {
// filter out cards with mana value greater than limit
@@ -376,12 +382,12 @@ public class PlayEffect extends SpellAbilityEffect {
// can't be done later
if (sa.hasParam("ReplaceGraveyard")) {
addReplaceGraveyardEffect(tgtCard, sa, sa.getParam("ReplaceGraveyard"));
addReplaceGraveyardEffect(tgtCard, sa, sa.getParam("ReplaceGraveyard"), moveParams);
}
// For Illusionary Mask effect
if (sa.hasParam("ReplaceIlluMask")) {
addIllusionaryMaskReplace(tgtCard, sa);
addIllusionaryMaskReplace(tgtCard, sa, moveParams);
}
tgtSA.setSVar("IsCastFromPlayEffect", "True");
@@ -420,7 +426,7 @@ public class PlayEffect extends SpellAbilityEffect {
} // 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 Game game = hostCard.getGame();
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
game.getTriggerHandler().suppressMode(TriggerType.ChangesZone);
game.getAction().moveTo(ZoneType.Command, eff, sa);
game.getAction().moveTo(ZoneType.Command, eff, sa, moveParams);
eff.updateStateForView();
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 Game game = hostCard.getGame();
final Player controller = sa.getActivatingPlayer();
@@ -497,7 +503,7 @@ public class PlayEffect extends SpellAbilityEffect {
addExileOnCounteredTrigger(eff);
game.getTriggerHandler().suppressMode(TriggerType.ChangesZone);
game.getAction().moveTo(ZoneType.Command, eff, sa);
game.getAction().moveTo(ZoneType.Command, eff, sa, moveParams);
eff.updateStateForView();
game.getTriggerHandler().clearSuppression(TriggerType.ChangesZone);
}

View File

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

View File

@@ -41,7 +41,7 @@ public class SetInMotionEffect extends SpellAbilityEffect {
}
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);
// Run triggers

View File

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

View File

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

View File

@@ -9,6 +9,7 @@ import com.google.common.collect.Lists;
import forge.card.MagicColor;
import forge.game.Game;
import forge.game.GameOutcome;
import forge.game.ability.AbilityKey;
import forge.game.ability.ApiType;
import forge.game.ability.SpellAbilityEffect;
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
if (companion != null) {
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));
player.updateZoneForView(commandZone);
@@ -242,7 +243,7 @@ public class SubgameEffect extends SpellAbilityEffect {
}
}
for (final Card card : movedCommanders) {
maingame.getAction().moveTo(ZoneType.Library, card, null);
maingame.getAction().moveTo(ZoneType.Library, card, null, AbilityKey.newMap());
}
player.shuffle(sa);

View File

@@ -1,5 +1,8 @@
package forge.game.ability.effects;
import java.util.Map;
import forge.game.ability.AbilityKey;
import forge.game.ability.AbilityUtils;
import forge.game.ability.SpellAbilityEffect;
import forge.game.card.CardZoneTable;
@@ -34,6 +37,9 @@ public class SurveilEffect extends SpellAbilityEffect {
boolean isOptional = sa.hasParam("Optional");
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)) {
if (!sa.usesTargeting() || p.canBeTargetedBy(sa)) {
@@ -41,7 +47,7 @@ public class SurveilEffect extends SpellAbilityEffect {
continue;
}
p.surveil(num, sa, table);
p.surveil(num, sa, table, moveParams);
}
}
table.triggerChangesZoneAll(sa.getHostCard().getGame(), sa);

View File

@@ -27,7 +27,7 @@ import forge.util.Localizer;
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();
CardCollectionView commandCards = player.getCardsIn(ZoneType.Command);
@@ -50,7 +50,7 @@ public class VentureEffect extends SpellAbilityEffect {
Card dungeon = player.getController().chooseDungeon(player, dungeonCards, message);
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);
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)) {
return;
}
final Game game = player.getGame();
Card dungeon = getDungeonCard(sa, player);
Card dungeon = getDungeonCard(sa, player, moveParams);
String room = dungeon.getCurrentRoom();
String nextRoom = null;
@@ -117,9 +117,13 @@ public class VentureEffect extends SpellAbilityEffect {
@Override
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)) {
if (!sa.usesTargeting() || p.canBeTargetedBy(sa)) {
ventureIntoDungeon(sa, p);
ventureIntoDungeon(sa, p, moveParams);
}
}
}

View File

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

View File

@@ -17,6 +17,9 @@
*/
package forge.game.cost;
import java.util.Map;
import forge.game.ability.AbilityKey;
import forge.game.card.Card;
import forge.game.player.Player;
import forge.game.player.PlayerCollection;
@@ -94,8 +97,11 @@ public class CostDraw extends CostPart {
*/
@Override
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) {
p.drawCards(decision.c, ability);
p.drawCards(decision.c, ability, moveParams);
}
return true;
}

View File

@@ -17,6 +17,9 @@
*/
package forge.game.cost;
import java.util.Map;
import forge.game.ability.AbilityKey;
import forge.game.card.CardCollection;
import forge.game.card.CardZoneTable;
import forge.game.player.Player;
@@ -88,7 +91,10 @@ public class CostMill extends CostPart {
@Override
public final boolean payAsDecided(final Player ai, final PaymentDecision decision, SpellAbility ability, final boolean effect) {
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);
return true;
}

View File

@@ -19,8 +19,11 @@ package forge.game.cost;
import forge.card.CardType;
import java.util.Map;
import com.google.common.collect.Iterables;
import forge.game.ability.AbilityKey;
import forge.game.card.Card;
import forge.game.card.CardCollectionView;
import forge.game.card.CardLists;
@@ -133,7 +136,10 @@ public class CostSacrifice extends CostPartWithList {
@Override
protected Card doPayment(SpellAbility ability, Card targetCard, final boolean effect) {
// 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)

View File

@@ -393,11 +393,15 @@ public class PhaseHandler implements java.io.Serializable {
int numDiscard = playerTurn.isUnlimitedHandSize() || handSize <= max || handSize == 0 ? 0 : handSize - max;
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 CardCollection discarded = new CardCollection();
boolean firstDiscarded = playerTurn.getNumDiscardedThisTurn() == 0;
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);
}
}

View File

@@ -326,10 +326,14 @@ public class Player extends GameEntity implements Comparable<Player> {
return;
}
Map<AbilityKey, Object> moveParams = AbilityKey.newMap();
moveParams.put(AbilityKey.LastStateBattlefield, game.getLastStateBattlefield());
moveParams.put(AbilityKey.LastStateGraveyard, game.getLastStateGraveyard());
game.getTriggerHandler().suppressMode(TriggerType.ChangesZone);
activeScheme = getZone(ZoneType.SchemeDeck).get(0);
// gameAction moveTo ?
game.getAction().moveTo(ZoneType.Command, activeScheme, null);
game.getAction().moveTo(ZoneType.Command, activeScheme, null, moveParams);
game.getTriggerHandler().clearSuppression(TriggerType.ChangesZone);
// Run triggers
@@ -1101,10 +1105,13 @@ public class Player extends GameEntity implements Comparable<Player> {
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);
repParams.put(AbilityKey.Source, cause);
repParams.put(AbilityKey.SurveilNum, num);
if (params != null) {
repParams.putAll(params);
}
switch (getGame().getReplacementHandler().run(ReplacementType.Surveil, repParams)) {
case NotReplaced:
@@ -1133,7 +1140,7 @@ public class Player extends GameEntity implements Comparable<Player> {
if (toGrave != null) {
for (Card c : toGrave) {
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);
numToGrave++;
}
@@ -1142,7 +1149,7 @@ public class Player extends GameEntity implements Comparable<Player> {
if (toTop != null) {
Collections.reverse(toTop); // the last card in list will become topmost in library, have to revert thus.
for (Card c : toTop) {
getGame().getAction().moveToLibrary(c, cause);
getGame().getAction().moveToLibrary(c, cause, params);
numToTop++;
}
}
@@ -1152,6 +1159,9 @@ public class Player extends GameEntity implements Comparable<Player> {
surveilThisTurn++;
final Map<AbilityKey, Object> runParams = AbilityKey.mapFromPlayer(this);
runParams.put(AbilityKey.NumThisTurn, surveilThisTurn);
if (params != null) {
runParams.putAll(params);
}
getGame().getTriggerHandler().runTrigger(TriggerType.Surveil, runParams, false);
}
@@ -1176,13 +1186,13 @@ public class Player extends GameEntity implements Comparable<Player> {
}
public final CardCollectionView drawCard() {
return drawCards(1, null);
return drawCards(1, null, AbilityKey.newMap());
}
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();
if (n <= 0) {
return drawn;
@@ -1191,6 +1201,9 @@ public class Player extends GameEntity implements Comparable<Player> {
// Replacement effects
final Map<AbilityKey, Object> repRunParams = AbilityKey.mapFromAffected(this);
repRunParams.put(AbilityKey.Number, n);
if (params != null) {
repRunParams.putAll(params);
}
if (game.getReplacementHandler().run(ReplacementType.DrawCards, repRunParams) != ReplacementResult.NotReplaced) {
return drawn;
@@ -1204,7 +1217,7 @@ public class Player extends GameEntity implements Comparable<Player> {
if (gameStarted && !canDraw()) {
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
@@ -1219,13 +1232,16 @@ public class Player extends GameEntity implements Comparable<Player> {
/**
* @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 PlayerZone library = getZone(ZoneType.Library);
// Replacement effects
Map<AbilityKey, Object> repParams = AbilityKey.mapFromAffected(this);
repParams.put(AbilityKey.Cause, cause);
if (params != null) {
repParams.putAll(params);
}
if (game.getReplacementHandler().run(ReplacementType.Draw, repParams) != ReplacementResult.NotReplaced) {
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);
for (Player p : pList) {
@@ -1267,6 +1283,9 @@ public class Player extends GameEntity implements Comparable<Player> {
view.updateNumDrawnThisTurn(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
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++;
}
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) {
if (!c.canBeDiscardedBy(sa, effect)) {
return null;
@@ -1575,13 +1591,16 @@ public class Player extends GameEntity implements Comparable<Player> {
}
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 CardCollection milled = new CardCollection();
// Replacement effects
final Map<AbilityKey, Object> repRunParams = AbilityKey.mapFromAffected(this);
repRunParams.put(AbilityKey.Number, n);
if (params != null) {
repRunParams.putAll(params);
}
if (destination == ZoneType.Graveyard && !bottom) {
switch (getGame().getReplacementHandler().run(ReplacementType.Mill, repRunParams)) {
@@ -1618,7 +1637,7 @@ public class Player extends GameEntity implements Comparable<Player> {
for (Card m : milledView) {
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)) {
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.
* Then runs triggers.
*/
public void planeswalk() {
planeswalkTo(new CardCollection(getZone(ZoneType.PlanarDeck).get(0)));
public void planeswalk(SpellAbility sa) {
planeswalkTo(sa, new CardCollection(getZone(ZoneType.PlanarDeck).get(0)));
}
/**
* Puts the planes in the argument and puts them face up in the command zone.
* 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());
currentPlanes.addAll(destinations);
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) {
game.getAction().moveTo(ZoneType.Command,c, null);
game.getAction().moveTo(ZoneType.Command, c, sa, moveParams);
//getZone(ZoneType.PlanarDeck).remove(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
Map<AbilityKey, Object> repParams = AbilityKey.mapFromAffected(this);
repParams.put(AbilityKey.Cause, sa);
if (params != null) {
repParams.putAll(params);
}
if (game.getReplacementHandler().run(ReplacementType.Learn, repParams) != ReplacementResult.NotReplaced) {
return;
}
@@ -3438,11 +3464,11 @@ public class Player extends GameEntity implements Comparable<Player> {
}
if (c.isInZone(ZoneType.Sideboard)) { // Sideboard Lesson to Hand
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);
} else if (c.isInZone(ZoneType.Hand)) { // Discard and Draw
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
// 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.Cause, sa);
runParams.put(AbilityKey.FirstTime, firstDiscard);
if (params != null) {
runParams.putAll(params);
}
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
}
}

View File

@@ -44,7 +44,8 @@ import io.sentry.Sentry;
import io.sentry.event.BreadcrumbBuilder;
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> delayedTriggers = Collections.synchronizedList(new ArrayList<>());
@@ -106,18 +107,15 @@ public class TriggerHandler {
}
public final void setSuppressAllTriggers(final boolean suppress) {
for (TriggerType t : TriggerType.values()) {
if (suppress) {
suppressMode(t);
} else {
clearSuppression(t);
}
}
allSuppressed = suppress;
}
public final void clearSuppression(final TriggerType 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) {
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) {
if (suppressedModes.contains(mode)) {
if (isTriggerSuppressed(mode)) {
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()) {
forgeCard.setSickness(lastSummoningSickness);
}
@@ -2858,7 +2858,7 @@ public class PlayerControllerHuman extends PlayerController implements IGameCont
getGame().getAction().moveToBottomOfLibrary(forgeCard, null);
}
} else {
getGame().getAction().moveTo(targetZone, forgeCard, null);
getGame().getAction().moveTo(targetZone, forgeCard, null, AbilityKey.newMap());
}
lastAdded = f;
@@ -2898,7 +2898,7 @@ public class PlayerControllerHuman extends PlayerController implements IGameCont
if (c == null) {
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();
sb.append(p).append(" exiles ").append(c).append(" due to Dev Cheats.");
getGame().getGameLog().add(GameLogEntryType.DISCARD, sb.toString());
@@ -2937,7 +2937,7 @@ public class PlayerControllerHuman extends PlayerController implements IGameCont
if (c == null) {
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();
sb.append(p).append(" exiles ").append(c).append(" due to Dev Cheats.");
getGame().getGameLog().add(GameLogEntryType.ZONE_CHANGE, sb.toString());