Consolidate CardZoneTable logic (#4751)

This commit is contained in:
tool4ever
2024-02-28 14:46:46 +01:00
committed by GitHub
parent 511ccd697c
commit ac24802fef
24 changed files with 171 additions and 223 deletions

View File

@@ -127,36 +127,27 @@ public class GameAction {
return c; return c;
} }
CardCollectionView lastBattlefield = getLastState(AbilityKey.LastStateBattlefield, cause, params, false);
CardCollectionView lastGraveyard = getLastState(AbilityKey.LastStateGraveyard, cause, params, false);
// Aura entering indirectly // Aura entering indirectly
// need to check before it enters // need to check before it enters
if (c.isAura() && !c.isAttachedToEntity() && toBattlefield && (zoneFrom == null || !zoneFrom.is(ZoneType.Stack))) { if (c.isAura() && !c.isAttachedToEntity() && toBattlefield && (zoneFrom == null || !zoneFrom.is(ZoneType.Stack))) {
boolean found = false; boolean found = false;
try {
if (Iterables.any(game.getPlayers(), PlayerPredicates.canBeAttached(c, null))) { if (Iterables.any(game.getPlayers(), PlayerPredicates.canBeAttached(c, null))) {
found = true; found = true;
} }
} catch (Exception e1) {
found = false;
}
if (!found) { if (!found) {
try { if (Iterables.any(lastBattlefield, CardPredicates.canBeAttached(c, null))) {
if (Iterables.any((CardCollectionView) params.get(AbilityKey.LastStateBattlefield), CardPredicates.canBeAttached(c, null))) {
found = true; found = true;
} }
} catch (Exception e2) {
found = false;
}
} }
if (!found) { if (!found) {
try { if (Iterables.any(lastGraveyard, CardPredicates.canBeAttached(c, null))) {
if (Iterables.any((CardCollectionView) params.get(AbilityKey.LastStateGraveyard), CardPredicates.canBeAttached(c, null))) {
found = true; found = true;
} }
} catch (Exception e3) {
found = false;
}
} }
if (!found) { if (!found) {
c.clearControllers(); c.clearControllers();
@@ -184,8 +175,6 @@ public class GameAction {
lastKnownInfo = (Card) cause.getReplacingObject(AbilityKey.CardLKI); lastKnownInfo = (Card) cause.getReplacingObject(AbilityKey.CardLKI);
} }
} }
CardCollectionView lastBattlefield = getLastState(AbilityKey.LastStateBattlefield, cause, params);
CardCollectionView lastGraveyard = getLastState(AbilityKey.LastStateGraveyard, cause, params);
if (c.isSplitCard()) { if (c.isSplitCard()) {
boolean resetToOriginal = false; boolean resetToOriginal = false;
@@ -403,10 +392,10 @@ public class GameAction {
if (Iterables.any(game.getPlayers(), PlayerPredicates.canBeAttached(copied, null))) { if (Iterables.any(game.getPlayers(), PlayerPredicates.canBeAttached(copied, null))) {
found = true; found = true;
} }
if (Iterables.any((CardCollectionView) params.get(AbilityKey.LastStateBattlefield), CardPredicates.canBeAttached(copied, null))) { if (Iterables.any(lastBattlefield, CardPredicates.canBeAttached(copied, null))) {
found = true; found = true;
} }
if (Iterables.any((CardCollectionView) params.get(AbilityKey.LastStateGraveyard), CardPredicates.canBeAttached(copied, null))) { if (Iterables.any(lastGraveyard, CardPredicates.canBeAttached(copied, null))) {
found = true; found = true;
} }
if (!found) { if (!found) {
@@ -2608,12 +2597,13 @@ public class GameAction {
} }
} }
private CardCollectionView getLastState(final AbilityKey key, final SpellAbility cause, final Map<AbilityKey, Object> params) { public CardCollectionView getLastState(final AbilityKey key, final SpellAbility cause, final Map<AbilityKey, Object> params, final boolean refreshIfEmpty) {
CardCollectionView lastState = null; CardCollectionView lastState = null;
if (params != null) { if (params != null) {
lastState = (CardCollectionView) params.get(key); lastState = (CardCollectionView) params.get(key);
} }
if (lastState == null && cause != null) { if (lastState == null && cause != null) {
// inside RE
if (key == AbilityKey.LastStateBattlefield) { if (key == AbilityKey.LastStateBattlefield) {
lastState = cause.getLastStateBattlefield(); lastState = cause.getLastStateBattlefield();
} }
@@ -2622,13 +2612,22 @@ public class GameAction {
} }
} }
if (lastState == null) { if (lastState == null) {
// this fallback should be rare unless called when creating a new CardZoneTable
if (key == AbilityKey.LastStateBattlefield) { if (key == AbilityKey.LastStateBattlefield) {
if (refreshIfEmpty) {
lastState = game.copyLastStateBattlefield();
} else {
lastState = game.getLastStateBattlefield(); lastState = game.getLastStateBattlefield();
} }
}
if (key == AbilityKey.LastStateGraveyard) { if (key == AbilityKey.LastStateGraveyard) {
if (refreshIfEmpty) {
lastState = game.copyLastStateGraveyard();
} else {
lastState = game.getLastStateGraveyard(); lastState = game.getLastStateGraveyard();
} }
} }
}
return lastState; return lastState;
} }
} }

View File

@@ -1,6 +1,5 @@
package forge.game.ability; package forge.game.ability;
import forge.game.Game;
import forge.game.GameEntity; import forge.game.GameEntity;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.card.CardZoneTable; import forge.game.card.CardZoneTable;
@@ -207,19 +206,14 @@ public enum AbilityKey {
return runParams; return runParams;
} }
public static void addCardZoneTableParams(Map<AbilityKey, Object> map, CardZoneTable table) { public static CardZoneTable addCardZoneTableParams(Map<AbilityKey, Object> params, SpellAbility sa) {
map.put(AbilityKey.LastStateBattlefield, table.getLastStateBattlefield()); CardZoneTable table = CardZoneTable.getSimultaneousInstance(sa);
map.put(AbilityKey.LastStateGraveyard, table.getLastStateGraveyard()); addCardZoneTableParams(params, table);
map.put(AbilityKey.InternalTriggerTable, table);
}
public static CardZoneTable addCardZoneTableParams(Map<AbilityKey, Object> map, SpellAbility sa) {
CardZoneTable table = new CardZoneTable(sa.getLastStateBattlefield(), sa.getLastStateGraveyard());
addCardZoneTableParams(map, table);
return table; return table;
} }
public static CardZoneTable addCardZoneTableParams(Map<AbilityKey, Object> map, Game game) { public static void addCardZoneTableParams(Map<AbilityKey, Object> params, CardZoneTable table) {
CardZoneTable table = new CardZoneTable(game.getLastStateBattlefield(), game.getLastStateGraveyard()); params.put(AbilityKey.LastStateBattlefield, table.getLastStateBattlefield());
addCardZoneTableParams(map, table); params.put(AbilityKey.LastStateGraveyard, table.getLastStateGraveyard());
return table; params.put(AbilityKey.InternalTriggerTable, table);
} }
} }

View File

@@ -17,7 +17,6 @@ import forge.game.player.PlayerCollection;
import forge.game.replacement.ReplacementEffect; import forge.game.replacement.ReplacementEffect;
import forge.game.replacement.ReplacementHandler; import forge.game.replacement.ReplacementHandler;
import forge.game.replacement.ReplacementLayer; import forge.game.replacement.ReplacementLayer;
import forge.game.replacement.ReplacementType;
import forge.game.spellability.AbilitySub; import forge.game.spellability.AbilitySub;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.game.trigger.Trigger; import forge.game.trigger.Trigger;
@@ -971,15 +970,6 @@ public abstract class SpellAbilityEffect {
movedCard.setExiledBy(cause.getActivatingPlayer()); movedCard.setExiledBy(cause.getActivatingPlayer());
} }
public CardZoneTable getChangeZoneTable(SpellAbility sa, CardCollectionView lastStateBattlefield, CardCollectionView lastStateGraveyard) {
if (sa.isReplacementAbility() && sa.getReplacementEffect().getMode() == ReplacementType.Moved
&& sa.getReplacingObject(AbilityKey.InternalTriggerTable) != null) {
// if a RE changes the destination zone try to make it simultaneous
return (CardZoneTable) sa.getReplacingObject(AbilityKey.InternalTriggerTable);
}
return new CardZoneTable(lastStateBattlefield, lastStateGraveyard);
}
public static GameCommand exileEffectCommand(final Game game, final Card effect) { public static GameCommand exileEffectCommand(final Game game, final Card effect) {
return new GameCommand() { return new GameCommand() {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;

View File

@@ -12,7 +12,6 @@ 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;
import forge.game.card.CardCollection; import forge.game.card.CardCollection;
import forge.game.card.CardCollectionView;
import forge.game.card.CardFactoryUtil; import forge.game.card.CardFactoryUtil;
import forge.game.card.CardLists; import forge.game.card.CardLists;
import forge.game.card.CardPredicates; import forge.game.card.CardPredicates;
@@ -55,8 +54,6 @@ public class ChangeZoneAllEffect extends SpellAbilityEffect {
CardCollection cards; CardCollection cards;
List<Player> tgtPlayers = getTargetPlayers(sa); List<Player> tgtPlayers = getTargetPlayers(sa);
final Game game = sa.getActivatingPlayer().getGame(); final Game game = sa.getActivatingPlayer().getGame();
CardCollectionView lastStateBattlefield = game.copyLastStateBattlefield();
CardCollectionView lastStateGraveyard = game.copyLastStateGraveyard();
if ((!sa.usesTargeting() && !sa.hasParam("Defined")) || sa.hasParam("UseAllOriginZones")) { if ((!sa.usesTargeting() && !sa.hasParam("Defined")) || sa.hasParam("UseAllOriginZones")) {
cards = new CardCollection(game.getCardsIn(origin)); cards = new CardCollection(game.getCardsIn(origin));
@@ -154,7 +151,8 @@ public class ChangeZoneAllEffect extends SpellAbilityEffect {
CardLists.shuffle(cards); CardLists.shuffle(cards);
} }
final CardZoneTable triggerList = getChangeZoneTable(sa, lastStateBattlefield, lastStateGraveyard); final CardZoneTable triggerList = CardZoneTable.getSimultaneousInstance(sa);
for (final Card c : cards) { for (final Card c : cards) {
final Zone originZone = game.getZoneOf(c); final Zone originZone = game.getZoneOf(c);

View File

@@ -470,11 +470,10 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
libraryPosition = pair.getValue(); libraryPosition = pair.getValue();
} }
CardCollectionView lastStateBattlefield = game.copyLastStateBattlefield(); final GameEntityCounterTable counterTable = new GameEntityCounterTable();
CardCollectionView lastStateGraveyard = game.copyLastStateGraveyard(); final CardZoneTable triggerList = CardZoneTable.getSimultaneousInstance(sa);
final CardCollectionView lastStateBattlefield = triggerList.getLastStateBattlefield();
final CardZoneTable triggerList = getChangeZoneTable(sa, lastStateBattlefield, lastStateGraveyard);
GameEntityCounterTable counterTable = new GameEntityCounterTable();
// changing zones for spells on the stack // changing zones for spells on the stack
for (final SpellAbility tgtSA : getTargetSpells(sa)) { for (final SpellAbility tgtSA : getTargetSpells(sa)) {
if (!tgtSA.isSpell()) { // Catch any abilities or triggers that slip through somehow if (!tgtSA.isSpell()) { // Catch any abilities or triggers that slip through somehow
@@ -1260,10 +1259,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
final boolean imprint = sa.hasParam("Imprint"); final boolean imprint = sa.hasParam("Imprint");
boolean combatChanged = false; boolean combatChanged = false;
final CardZoneTable triggerList = CardZoneTable.getSimultaneousInstance(sa);
CardCollectionView lastStateBattlefield = game.copyLastStateBattlefield();
CardCollectionView lastStateGraveyard = game.copyLastStateGraveyard();
final CardZoneTable triggerList = getChangeZoneTable(sa, lastStateBattlefield, lastStateGraveyard);
for (Player player : HiddenOriginChoicesMap.keySet()) { for (Player player : HiddenOriginChoicesMap.keySet()) {
boolean searchedLibrary = HiddenOriginChoicesMap.get(player).searchedLibrary; boolean searchedLibrary = HiddenOriginChoicesMap.get(player).searchedLibrary;
@@ -1281,6 +1277,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
Map<AbilityKey, Object> moveParams = AbilityKey.newMap(); Map<AbilityKey, Object> moveParams = AbilityKey.newMap();
moveParams.put(AbilityKey.FoundSearchingLibrary, searchedLibrary); moveParams.put(AbilityKey.FoundSearchingLibrary, searchedLibrary);
AbilityKey.addCardZoneTableParams(moveParams, triggerList); AbilityKey.addCardZoneTableParams(moveParams, triggerList);
if (destination.equals(ZoneType.Library)) { if (destination.equals(ZoneType.Library)) {
movedCard = game.getAction().moveToLibrary(c, libraryPos, sa, moveParams); movedCard = game.getAction().moveToLibrary(c, libraryPos, sa, moveParams);
} }
@@ -1330,7 +1327,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
if (sa.hasParam("AttachedTo") && c.isAttachment()) { if (sa.hasParam("AttachedTo") && c.isAttachment()) {
CardCollection list = AbilityUtils.getDefinedCards(source, sa.getParam("AttachedTo"), sa); CardCollection list = AbilityUtils.getDefinedCards(source, sa.getParam("AttachedTo"), sa);
if (list.isEmpty()) { if (list.isEmpty()) {
list = CardLists.getValidCards(lastStateBattlefield, sa.getParam("AttachedTo"), source.getController(), source, sa); list = CardLists.getValidCards(triggerList.getLastStateBattlefield(), sa.getParam("AttachedTo"), source.getController(), source, sa);
} }
// only valid choices are when they could be attached // only valid choices are when they could be attached
// TODO for multiple Auras entering attached this way, need to use LKI info // TODO for multiple Auras entering attached this way, need to use LKI info

View File

@@ -65,13 +65,12 @@ public class ConniveEffect extends SpellAbilityEffect {
} }
for (final Player p : controllers) { for (final Player p : controllers) {
CardCollection connivers = CardLists.filterControlledBy(toConnive, p); final CardCollection connivers = CardLists.filterControlledBy(toConnive, p);
while (!connivers.isEmpty()) { while (!connivers.isEmpty()) {
GameEntityCounterTable table = new GameEntityCounterTable(); final Map<Player, CardCollectionView> discardedMap = Maps.newHashMap();
final CardZoneTable triggerList = new CardZoneTable(game.copyLastStateBattlefield(), game.copyLastStateGraveyard()); final Map<AbilityKey, Object> moveParams = AbilityKey.newMap();
Map<Player, CardCollectionView> discardedMap = Maps.newHashMap(); final CardZoneTable zoneMovements = AbilityKey.addCardZoneTableParams(moveParams, sa);
Map<AbilityKey, Object> moveParams = AbilityKey.newMap(); final GameEntityCounterTable counterPlacements = new GameEntityCounterTable();
AbilityKey.addCardZoneTableParams(moveParams, triggerList);
Card conniver = connivers.size() > 1 ? p.getController().chooseSingleEntityForEffect(connivers, sa, Card conniver = connivers.size() > 1 ? p.getController().chooseSingleEntityForEffect(connivers, sa,
Localizer.getInstance().getMessage("lblChooseConniver"), null) : connivers.get(0); Localizer.getInstance().getMessage("lblChooseConniver"), null) : connivers.get(0);
@@ -96,12 +95,12 @@ public class ConniveEffect extends SpellAbilityEffect {
Card gamec = game.getCardState(conniver); Card gamec = game.getCardState(conniver);
// if the card is not in the game anymore, this might still return true, but it's no problem // if the card is not in the game anymore, this might still return true, but it's no problem
if (game.getZoneOf(gamec).is(ZoneType.Battlefield) && gamec.equalsWithTimestamp(conniver)) { if (game.getZoneOf(gamec).is(ZoneType.Battlefield) && gamec.equalsWithTimestamp(conniver)) {
conniver.addCounter(CounterEnumType.P1P1, numCntrs, p, table); conniver.addCounter(CounterEnumType.P1P1, numCntrs, p, counterPlacements);
} }
discardedMap.put(p, CardCollection.getView(toBeDiscarded)); discardedMap.put(p, CardCollection.getView(toBeDiscarded));
discard(sa, true, discardedMap, moveParams); discard(sa, true, discardedMap, moveParams);
table.replaceCounterEffect(game, sa, true); counterPlacements.replaceCounterEffect(game, sa, true);
triggerList.triggerChangesZoneAll(game, sa); zoneMovements.triggerChangesZoneAll(game, sa);
} }
} }
} }

View File

@@ -53,7 +53,7 @@ public class CounterEffect extends SpellAbilityEffect {
public void resolve(SpellAbility sa) { public void resolve(SpellAbility sa) {
final Game game = sa.getActivatingPlayer().getGame(); final Game game = sa.getActivatingPlayer().getGame();
Map<AbilityKey, Object> params = AbilityKey.newMap(); Map<AbilityKey, Object> params = AbilityKey.newMap();
final CardZoneTable table = AbilityKey.addCardZoneTableParams(params, game); final CardZoneTable zoneMovements = AbilityKey.addCardZoneTableParams(params, sa);
for (final SpellAbility tgtSA : getTargetSpells(sa)) { for (final SpellAbility tgtSA : getTargetSpells(sa)) {
final Card tgtSACard = tgtSA.getHostCard(); final Card tgtSACard = tgtSA.getHostCard();
@@ -103,8 +103,8 @@ public class CounterEffect extends SpellAbilityEffect {
sa.getHostCard().addRemembered(tgtSACard); sa.getHostCard().addRemembered(tgtSACard);
} }
} }
table.triggerChangesZoneAll(game, sa); zoneMovements.triggerChangesZoneAll(game, sa);
} // end counterResolve }
public static boolean checkForConditionWouldDestroy(SpellAbility sa, SpellAbility tgtSA) { public static boolean checkForConditionWouldDestroy(SpellAbility sa, SpellAbility tgtSA) {
List<SpellAbility> testChain = Lists.newArrayList(); List<SpellAbility> testChain = Lists.newArrayList();

View File

@@ -2,8 +2,6 @@ package forge.game.ability.effects;
import java.util.Map; import java.util.Map;
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.AbilityKey;
@@ -13,7 +11,6 @@ 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;
import forge.game.card.CardPredicates; import forge.game.card.CardPredicates;
import forge.game.card.CardUtil;
import forge.game.card.CardZoneTable; import forge.game.card.CardZoneTable;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
@@ -90,16 +87,15 @@ public class DestroyAllEffect extends SpellAbilityEffect {
list = GameActionUtil.orderCardsByTheirOwners(game, list, ZoneType.Graveyard, sa); list = GameActionUtil.orderCardsByTheirOwners(game, list, ZoneType.Graveyard, sa);
Map<AbilityKey, Object> params = AbilityKey.newMap(); Map<AbilityKey, Object> params = AbilityKey.newMap();
CardZoneTable table = new CardZoneTable(game.copyLastStateBattlefield(), game.getLastStateGraveyard()); CardZoneTable zoneMovements = AbilityKey.addCardZoneTableParams(params, sa);
AbilityKey.addCardZoneTableParams(params, table);
Map<Integer, Card> cachedMap = Maps.newHashMap();
for (Card c : list) { for (Card c : list) {
if (game.getAction().destroy(c, sa, !noRegen, params) && remDestroyed) { if (game.getAction().destroy(c, sa, !noRegen, params) && remDestroyed) {
card.addRemembered(CardUtil.getLKICopy(c, cachedMap)); card.addRemembered(zoneMovements.getLastStateBattlefield().get(c));
} }
} }
table.triggerChangesZoneAll(game, sa);
zoneMovements.triggerChangesZoneAll(game, sa);
} }
} }

View File

@@ -3,14 +3,11 @@ package forge.game.ability.effects;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
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.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.CardCollection;
import forge.game.card.CardCollectionView; import forge.game.card.CardCollectionView;
import forge.game.card.CardUtil; import forge.game.card.CardUtil;
import forge.game.card.CardZoneTable; import forge.game.card.CardZoneTable;
@@ -47,23 +44,22 @@ public class DestroyEffect extends SpellAbilityEffect {
@Override @Override
public void resolve(SpellAbility sa) { public void resolve(SpellAbility sa) {
final Card card = sa.getHostCard(); final Card host = sa.getHostCard();
final Game game = card.getGame(); final Game game = host.getGame();
if (sa.hasParam("RememberDestroyed")) { if (sa.hasParam("RememberDestroyed")) {
card.clearRemembered(); host.clearRemembered();
} }
CardCollectionView tgtCards = getTargetCards(sa);
CardCollectionView untargetedCards = CardUtil.getRadiance(sa); CardCollectionView untargetedCards = CardUtil.getRadiance(sa);
CardCollectionView tgtCards = getTargetCards(sa);
tgtCards = GameActionUtil.orderCardsByTheirOwners(game, tgtCards, ZoneType.Graveyard, sa); tgtCards = GameActionUtil.orderCardsByTheirOwners(game, tgtCards, ZoneType.Graveyard, sa);
untargetedCards = GameActionUtil.orderCardsByTheirOwners(game, untargetedCards, ZoneType.Graveyard, sa);
Map<AbilityKey, Object> params = AbilityKey.newMap(); Map<AbilityKey, Object> params = AbilityKey.newMap();
CardZoneTable table = getChangeZoneTable(sa, game.copyLastStateBattlefield(), CardCollection.EMPTY); CardZoneTable zoneMovements = AbilityKey.addCardZoneTableParams(params, sa);
AbilityKey.addCardZoneTableParams(params, table);
Map<Integer, Card> cachedMap = Maps.newHashMap();
for (final Card tgtC : tgtCards) { for (final Card tgtC : tgtCards) {
if (!tgtC.isInPlay()) { if (!tgtC.isInPlay()) {
continue; continue;
@@ -75,29 +71,26 @@ public class DestroyEffect extends SpellAbilityEffect {
if (gameCard == null || !tgtC.equalsWithTimestamp(gameCard)) { if (gameCard == null || !tgtC.equalsWithTimestamp(gameCard)) {
continue; continue;
} }
internalDestroy(gameCard, sa, cachedMap, params); internalDestroy(gameCard, sa, params, zoneMovements);
} }
untargetedCards = GameActionUtil.orderCardsByTheirOwners(game, untargetedCards, ZoneType.Graveyard, sa);
for (final Card unTgtC : untargetedCards) { for (final Card unTgtC : untargetedCards) {
if (unTgtC.isInPlay()) { if (unTgtC.isInPlay()) {
internalDestroy(unTgtC, sa, cachedMap, params); internalDestroy(unTgtC, sa, params, zoneMovements);
} }
} }
table.triggerChangesZoneAll(game, sa); zoneMovements.triggerChangesZoneAll(game, sa);
} }
protected void internalDestroy(Card gameCard, SpellAbility sa, Map<Integer, Card> cachedMap, Map<AbilityKey, Object> params) { protected void internalDestroy(Card gameCard, SpellAbility sa, Map<AbilityKey, Object> params, CardZoneTable zoneMovements) {
final Card card = sa.getHostCard(); final Card host = sa.getHostCard();
final Game game = card.getGame(); final Game game = host.getGame();
final boolean remDestroyed = sa.hasParam("RememberDestroyed"); final boolean remDestroyed = sa.hasParam("RememberDestroyed");
final boolean noRegen = sa.hasParam("NoRegen"); final boolean noRegen = sa.hasParam("NoRegen");
final boolean sac = sa.hasParam("Sacrifice"); final boolean sac = sa.hasParam("Sacrifice");
final boolean alwaysRem = sa.hasParam("AlwaysRemember"); final boolean alwaysRem = sa.hasParam("AlwaysRemember");
boolean destroyed = false; boolean destroyed = false;
final Card lki = sa.hasParam("RememberLKI") ? CardUtil.getLKICopy(gameCard, cachedMap) : null;
SpellAbility cause = sa; SpellAbility cause = sa;
if (sa.isReplacementAbility()) { if (sa.isReplacementAbility()) {
@@ -110,10 +103,10 @@ public class DestroyEffect extends SpellAbilityEffect {
destroyed = game.getAction().destroy(gameCard, cause, !noRegen, params); destroyed = game.getAction().destroy(gameCard, cause, !noRegen, params);
} }
if (destroyed && remDestroyed) { if (destroyed && remDestroyed) {
card.addRemembered(gameCard); host.addRemembered(gameCard);
} }
if ((destroyed || alwaysRem) && sa.hasParam("RememberLKI")) { if ((destroyed || alwaysRem) && sa.hasParam("RememberLKI")) {
card.addRemembered(lki); host.addRemembered(zoneMovements.getLastStateBattlefield().get(gameCard));
} }
} }

View File

@@ -117,12 +117,13 @@ public class DigEffect extends SpellAbilityEffect {
final ZoneType destZone1 = sa.hasParam("DestinationZone") ? ZoneType.smartValueOf(sa.getParam("DestinationZone")) : ZoneType.Hand; final ZoneType destZone1 = sa.hasParam("DestinationZone") ? ZoneType.smartValueOf(sa.getParam("DestinationZone")) : ZoneType.Hand;
final ZoneType destZone2 = sa.hasParam("DestinationZone2") ? ZoneType.smartValueOf(sa.getParam("DestinationZone2")) : ZoneType.Library; final ZoneType destZone2 = sa.hasParam("DestinationZone2") ? ZoneType.smartValueOf(sa.getParam("DestinationZone2")) : ZoneType.Library;
int libraryPosition = sa.hasParam("LibraryPosition") ? Integer.parseInt(sa.getParam("LibraryPosition")) : -1; final int libraryPosition = sa.hasParam("LibraryPosition") ? Integer.parseInt(sa.getParam("LibraryPosition")) : -1;
final int libraryPosition2 = sa.hasParam("LibraryPosition2") ? Integer.parseInt(sa.getParam("LibraryPosition2")) : -1;
int destZone1ChangeNum = 1; int destZone1ChangeNum = 1;
String changeValid = sa.getParamOrDefault("ChangeValid", ""); String changeValid = sa.getParamOrDefault("ChangeValid", "");
final boolean anyNumber = sa.hasParam("AnyNumber"); final boolean anyNumber = sa.hasParam("AnyNumber");
final int libraryPosition2 = sa.hasParam("LibraryPosition2") ? Integer.parseInt(sa.getParam("LibraryPosition2")) : -1;
final boolean optional = sa.hasParam("Optional"); final boolean optional = sa.hasParam("Optional");
final boolean noMove = sa.hasParam("NoMove"); final boolean noMove = sa.hasParam("NoMove");
final boolean skipReorder = sa.hasParam("SkipReorder"); final boolean skipReorder = sa.hasParam("SkipReorder");
@@ -161,7 +162,7 @@ public class DigEffect extends SpellAbilityEffect {
} }
} }
CardZoneTable table = new CardZoneTable(game.copyLastStateBattlefield(), game.copyLastStateGraveyard()); CardZoneTable zoneMovements = new CardZoneTable(game.copyLastStateBattlefield(), game.copyLastStateGraveyard());
GameEntityCounterTable counterTable = new GameEntityCounterTable(); GameEntityCounterTable counterTable = new GameEntityCounterTable();
boolean combatChanged = false; boolean combatChanged = false;
@@ -375,7 +376,7 @@ public class DigEffect extends SpellAbilityEffect {
} }
Map<AbilityKey, Object> moveParams = AbilityKey.newMap(); Map<AbilityKey, Object> moveParams = AbilityKey.newMap();
AbilityKey.addCardZoneTableParams(moveParams, table); AbilityKey.addCardZoneTableParams(moveParams, zoneMovements);
for (Card c : movedCards) { for (Card c : movedCards) {
if (destZone1.equals(ZoneType.Library) || destZone1.equals(ZoneType.PlanarDeck) || destZone1.equals(ZoneType.SchemeDeck)) { if (destZone1.equals(ZoneType.Library) || destZone1.equals(ZoneType.PlanarDeck) || destZone1.equals(ZoneType.SchemeDeck)) {
@@ -499,7 +500,7 @@ public class DigEffect extends SpellAbilityEffect {
game.fireEvent(new GameEventCombatChanged()); game.fireEvent(new GameEventCombatChanged());
} }
table.triggerChangesZoneAll(game, sa); zoneMovements.triggerChangesZoneAll(game, sa);
counterTable.replaceCounterEffect(game, sa, true); counterTable.replaceCounterEffect(game, sa, true);
} }

View File

@@ -32,36 +32,33 @@ public class EncodeEffect extends SpellAbilityEffect {
@Override @Override
public void resolve(SpellAbility sa) { public void resolve(SpellAbility sa) {
final Card host = sa.getHostCard(); final Card host = sa.getHostCard();
final Player player = sa.getActivatingPlayer(); final Player activator = sa.getActivatingPlayer();
final Game game = player.getGame(); final Game game = activator.getGame();
if (host.isToken()) { if (host.isToken()) {
return; return;
} }
// make list of creatures that controller has on Battlefield
CardCollectionView choices = host.getController().getCreaturesInPlay(); CardCollectionView choices = host.getController().getCreaturesInPlay();
// if no creatures on battlefield, cannot encoded // if no creatures on battlefield, cannot encoded
if (choices.isEmpty()) { if (choices.isEmpty()) {
return; return;
} }
// Handle choice of whether or not to encoded
if (!player.getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblDoYouWantExileCardAndEncodeOntoYouCreature", CardTranslation.getTranslatedName(host.getName())), null)) { // Handle choice of whether or not to encoded
if (!activator.getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblDoYouWantExileCardAndEncodeOntoYouCreature", CardTranslation.getTranslatedName(host.getName())), null)) {
return; return;
} }
Map<AbilityKey, Object> moveParams = AbilityKey.newMap(); Map<AbilityKey, Object> moveParams = AbilityKey.newMap();
CardZoneTable table = new CardZoneTable(sa.getLastStateBattlefield(), sa.getLastStateGraveyard()); CardZoneTable zoneMovements = AbilityKey.addCardZoneTableParams(moveParams, sa);
AbilityKey.addCardZoneTableParams(moveParams, table);
// move host card to exile Card moved = game.getAction().exile(host, sa, moveParams);
Card movedCard = game.getAction().exile(host, sa, moveParams);
table.triggerChangesZoneAll(game, sa);
// choose a creature zoneMovements.triggerChangesZoneAll(game, sa);
Card choice = player.getController().chooseSingleEntityForEffect(choices, sa, Localizer.getInstance().getMessage("lblChooseACreatureYouControlToEncode") + " ", false, null);
Card choice = activator.getController().chooseSingleEntityForEffect(choices, sa, Localizer.getInstance().getMessage("lblChooseACreatureYouControlToEncode") + " ", false, null);
if (choice == null) { if (choice == null) {
return; return;
@@ -72,8 +69,8 @@ public class EncodeEffect extends SpellAbilityEffect {
game.getGameLog().add(GameLogEntryType.STACK_RESOLVE, codeLog.toString()); game.getGameLog().add(GameLogEntryType.STACK_RESOLVE, codeLog.toString());
// store hostcard in encoded array // store hostcard in encoded array
choice.addEncodedCard(movedCard); choice.addEncodedCard(moved);
movedCard.setEncodingCard(choice); moved.setEncodingCard(choice);
} }
} }

View File

@@ -30,11 +30,11 @@ public class EndCombatPhaseEffect extends SpellAbilityEffect {
// 1) All spells and abilities on the stack are exiled. // 1) All spells and abilities on the stack are exiled.
Map<AbilityKey, Object> moveParams = AbilityKey.newMap(); Map<AbilityKey, Object> moveParams = AbilityKey.newMap();
CardZoneTable table = new CardZoneTable(sa.getLastStateBattlefield(), sa.getLastStateGraveyard()); CardZoneTable zoneMovements = AbilityKey.addCardZoneTableParams(moveParams, sa);
AbilityKey.addCardZoneTableParams(moveParams, table);
game.getAction().exile(new CardCollection(game.getStackZone().getCards()), sa, moveParams); game.getAction().exile(new CardCollection(game.getStackZone().getCards()), sa, moveParams);
table.triggerChangesZoneAll(game, sa);
zoneMovements.triggerChangesZoneAll(game, sa);
game.getStack().clear(); game.getStack().clear();
game.getStack().clearSimultaneousStack(); game.getStack().clearSimultaneousStack();

View File

@@ -38,11 +38,10 @@ public class EndTurnEffect extends SpellAbilityEffect {
// Time Stop, though it will continue to resolve. It also includes // Time Stop, though it will continue to resolve. It also includes
// spells and abilities that can't be countered. // spells and abilities that can't be countered.
Map<AbilityKey, Object> moveParams = AbilityKey.newMap(); Map<AbilityKey, Object> moveParams = AbilityKey.newMap();
CardZoneTable table = new CardZoneTable(sa.getLastStateBattlefield(), sa.getLastStateGraveyard()); CardZoneTable zoneMovements = AbilityKey.addCardZoneTableParams(moveParams, sa);
AbilityKey.addCardZoneTableParams(moveParams, table);
game.getAction().exile(new CardCollection(game.getStackZone().getCards()), sa, moveParams); game.getAction().exile(new CardCollection(game.getStackZone().getCards()), sa, moveParams);
table.triggerChangesZoneAll(game, sa); zoneMovements.triggerChangesZoneAll(game, sa);
game.getStack().clear(); game.getStack().clear();
game.getStack().clearSimultaneousStack(); game.getStack().clearSimultaneousStack();

View File

@@ -25,11 +25,10 @@ public class HauntEffect extends SpellAbilityEffect {
} else if (sa.usesTargeting() && !card.isToken() && host.equalsWithTimestamp(card)) { } else if (sa.usesTargeting() && !card.isToken() && host.equalsWithTimestamp(card)) {
// haunt target but only if card is no token and still in grave // haunt target but only if card is no token and still in grave
Map<AbilityKey, Object> moveParams = AbilityKey.newMap(); Map<AbilityKey, Object> moveParams = AbilityKey.newMap();
CardZoneTable table = new CardZoneTable(sa.getLastStateBattlefield(), sa.getLastStateGraveyard()); CardZoneTable zoneMovements = AbilityKey.addCardZoneTableParams(moveParams, sa);
AbilityKey.addCardZoneTableParams(moveParams, table); final Card moved = game.getAction().exile(card, sa, moveParams);
final Card copy = game.getAction().exile(card, sa, moveParams); sa.getTargetCard().addHauntedBy(moved);
sa.getTargetCard().addHauntedBy(copy); zoneMovements.triggerChangesZoneAll(game, sa);
table.triggerChangesZoneAll(game, sa);
} else if (!sa.usesTargeting() && card.getHaunting() != null) { } else if (!sa.usesTargeting() && card.getHaunting() != null) {
// unhaunt // unhaunt
card.getHaunting().removeHauntedBy(card); card.getHaunting().removeHauntedBy(card);

View File

@@ -65,20 +65,15 @@ public abstract class ManifestBaseEffect extends SpellAbilityEffect {
if (fromLibrary) { if (fromLibrary) {
for (Card c : tgtCards) { for (Card c : tgtCards) {
// CR 701.34d If an effect instructs a player to manifest multiple cards from their library, those cards are manifested one at a time. // CR 701.34d If an effect instructs a player to manifest multiple cards from their library, those cards are manifested one at a time.
CardZoneTable triggerList = new CardZoneTable(game.copyLastStateBattlefield(), game.copyLastStateGraveyard());
Map<AbilityKey, Object> moveParams = AbilityKey.newMap(); Map<AbilityKey, Object> moveParams = AbilityKey.newMap();
AbilityKey.addCardZoneTableParams(moveParams, triggerList); CardZoneTable triggerList = AbilityKey.addCardZoneTableParams(moveParams, sa);
internalEffect(c, p, sa, moveParams); internalEffect(c, p, sa, moveParams);
triggerList.triggerChangesZoneAll(game, sa); triggerList.triggerChangesZoneAll(game, sa);
} }
} else { } else {
// manifest from other zones should be done at the same time // manifest from other zones should be done at the same time
CardZoneTable triggerList = new CardZoneTable(game.copyLastStateBattlefield(), game.copyLastStateGraveyard());
Map<AbilityKey, Object> moveParams = AbilityKey.newMap(); Map<AbilityKey, Object> moveParams = AbilityKey.newMap();
AbilityKey.addCardZoneTableParams(moveParams, triggerList); CardZoneTable triggerList = AbilityKey.addCardZoneTableParams(moveParams, sa);
for (Card c : tgtCards) { for (Card c : tgtCards) {
internalEffect(c, p, sa, moveParams); internalEffect(c, p, sa, moveParams);
} }

View File

@@ -42,11 +42,11 @@ public class MeldEffect extends SpellAbilityEffect {
CardCollection exiled = CardLists.filter(Arrays.asList(hostCard, secondary), CardPredicates.canExiledBy(sa, true)); CardCollection exiled = CardLists.filter(Arrays.asList(hostCard, secondary), CardPredicates.canExiledBy(sa, true));
Map<AbilityKey, Object> moveParams = AbilityKey.newMap(); Map<AbilityKey, Object> moveParams = AbilityKey.newMap();
CardZoneTable table = new CardZoneTable(sa.getLastStateBattlefield(), sa.getLastStateGraveyard()); CardZoneTable zoneMovements = AbilityKey.addCardZoneTableParams(moveParams, sa);
AbilityKey.addCardZoneTableParams(moveParams, table);
exiled = game.getAction().exile(exiled, sa, moveParams); exiled = game.getAction().exile(exiled, sa, moveParams);
table.triggerChangesZoneAll(game, sa);
zoneMovements.triggerChangesZoneAll(game, sa);
if (exiled.size() < 2) { if (exiled.size() < 2) {
return; return;
@@ -78,10 +78,16 @@ public class MeldEffect extends SpellAbilityEffect {
primary.setMeldedWith(secondary); primary.setMeldedWith(secondary);
PlayerZoneBattlefield bf = (PlayerZoneBattlefield)controller.getZone(ZoneType.Battlefield); PlayerZoneBattlefield bf = (PlayerZoneBattlefield)controller.getZone(ZoneType.Battlefield);
bf.addToMelded(secondary); bf.addToMelded(secondary);
Card movedCard = game.getAction().changeZone(primary.getZone(), bf, primary, 0, sa);
moveParams = AbilityKey.newMap();
zoneMovements = AbilityKey.addCardZoneTableParams(moveParams, sa);
Card movedCard = game.getAction().moveToPlay(primary, controller, sa, moveParams);
if (addToCombat(movedCard, sa, "Attacking", "Blocking")) { if (addToCombat(movedCard, sa, "Attacking", "Blocking")) {
game.updateCombatForView(); game.updateCombatForView();
game.fireEvent(new GameEventCombatChanged()); game.fireEvent(new GameEventCombatChanged());
} }
zoneMovements.triggerChangesZoneAll(game, sa);
} }
} }

View File

@@ -3,8 +3,6 @@ package forge.game.ability.effects;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
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.AbilityKey;
@@ -14,7 +12,6 @@ import forge.game.card.Card;
import forge.game.card.CardCollection; import forge.game.card.CardCollection;
import forge.game.card.CardCollectionView; import forge.game.card.CardCollectionView;
import forge.game.card.CardLists; import forge.game.card.CardLists;
import forge.game.card.CardUtil;
import forge.game.card.CardZoneTable; import forge.game.card.CardZoneTable;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
@@ -45,13 +42,13 @@ public class SacrificeAllEffect extends SpellAbilityEffect {
@Override @Override
public void resolve(SpellAbility sa) { public void resolve(SpellAbility sa) {
final Card card = sa.getHostCard(); final Card host = sa.getHostCard();
final Player activator = sa.getActivatingPlayer(); final Player activator = sa.getActivatingPlayer();
final Game game = activator.getGame(); final Game game = activator.getGame();
CardCollectionView list; CardCollectionView list;
if (sa.hasParam("Defined")) { if (sa.hasParam("Defined")) {
list = AbilityUtils.getDefinedCards(card, sa.getParam("Defined"), sa); list = AbilityUtils.getDefinedCards(host, sa.getParam("Defined"), sa);
} else { } else {
list = game.getCardsIn(ZoneType.Battlefield); list = game.getCardsIn(ZoneType.Battlefield);
if (sa.hasParam("ValidCards")) { if (sa.hasParam("ValidCards")) {
@@ -61,7 +58,7 @@ public class SacrificeAllEffect extends SpellAbilityEffect {
final boolean remSacrificed = sa.hasParam("RememberSacrificed"); final boolean remSacrificed = sa.hasParam("RememberSacrificed");
if (remSacrificed) { if (remSacrificed) {
card.clearRemembered(); host.clearRemembered();
} }
// update cards that where using LKI // update cards that where using LKI
@@ -82,23 +79,22 @@ public class SacrificeAllEffect extends SpellAbilityEffect {
list = GameActionUtil.orderCardsByTheirOwners(game, list, ZoneType.Graveyard, sa); list = GameActionUtil.orderCardsByTheirOwners(game, list, ZoneType.Graveyard, sa);
Map<Integer, Card> cachedMap = Maps.newHashMap();
Map<AbilityKey, Object> params = AbilityKey.newMap(); Map<AbilityKey, Object> params = AbilityKey.newMap();
CardZoneTable table = new CardZoneTable(game.copyLastStateBattlefield(), CardCollection.EMPTY); CardZoneTable zoneMovements = AbilityKey.addCardZoneTableParams(params, sa);
AbilityKey.addCardZoneTableParams(params, table);
for (Card sac : list) { for (Card sac : list) {
final Card lKICopy = CardUtil.getLKICopy(sac, cachedMap); final Card lKICopy = zoneMovements.getLastStateBattlefield().get(sac);
if (game.getAction().sacrifice(sac, sa, true, params) != null) { if (game.getAction().sacrifice(sac, sa, true, params) != null) {
if (remSacrificed) { if (remSacrificed) {
card.addRemembered(lKICopy); host.addRemembered(lKICopy);
} }
if (sa.hasParam("ImprintSacrificed")) { if (sa.hasParam("ImprintSacrificed")) {
card.addImprintedCard(lKICopy); host.addImprintedCard(lKICopy);
} }
} }
} }
table.triggerChangesZoneAll(game, sa);
zoneMovements.triggerChangesZoneAll(game, sa);
} }
} }

View File

@@ -10,8 +10,6 @@ import forge.card.CardType;
import forge.util.Lang; import forge.util.Lang;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import com.google.common.collect.Maps;
import forge.card.mana.ManaCost; import forge.card.mana.ManaCost;
import forge.game.Game; import forge.game.Game;
import forge.game.GameActionUtil; import forge.game.GameActionUtil;
@@ -24,7 +22,6 @@ import forge.game.card.CardCollection;
import forge.game.card.CardCollectionView; import forge.game.card.CardCollectionView;
import forge.game.card.CardLists; import forge.game.card.CardLists;
import forge.game.card.CardPredicates; import forge.game.card.CardPredicates;
import forge.game.card.CardUtil;
import forge.game.card.CardZoneTable; import forge.game.card.CardZoneTable;
import forge.game.card.CounterEnumType; import forge.game.card.CounterEnumType;
import forge.game.cost.Cost; import forge.game.cost.Cost;
@@ -43,7 +40,7 @@ public class SacrificeEffect extends SpellAbilityEffect {
public void resolve(SpellAbility sa) { public void resolve(SpellAbility sa) {
final Player activator = sa.getActivatingPlayer(); final Player activator = sa.getActivatingPlayer();
final Game game = activator.getGame(); final Game game = activator.getGame();
final Card card = sa.getHostCard(); final Card host = sa.getHostCard();
if (sa.hasParam("Echo")) { if (sa.hasParam("Echo")) {
boolean isPaid; boolean isPaid;
@@ -51,45 +48,45 @@ public class SacrificeEffect extends SpellAbilityEffect {
&& activator.getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblDoYouWantPayEcho") + " {0}?", null)) { && activator.getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblDoYouWantPayEcho") + " {0}?", null)) {
isPaid = true; isPaid = true;
} else { } else {
isPaid = activator.getController().payManaOptional(card, new Cost(sa.getParam("Echo"), true), isPaid = activator.getController().payManaOptional(host, new Cost(sa.getParam("Echo"), true),
sa, Localizer.getInstance().getMessage("lblPayEcho"), ManaPaymentPurpose.Echo); sa, Localizer.getInstance().getMessage("lblPayEcho"), ManaPaymentPurpose.Echo);
} }
final Map<AbilityKey, Object> runParams = AbilityKey.mapFromCard(card); final Map<AbilityKey, Object> runParams = AbilityKey.mapFromCard(host);
runParams.put(AbilityKey.EchoPaid, isPaid); runParams.put(AbilityKey.EchoPaid, isPaid);
game.getTriggerHandler().runTrigger(TriggerType.PayEcho, runParams, false); game.getTriggerHandler().runTrigger(TriggerType.PayEcho, runParams, false);
if (isPaid || !card.getController().equals(activator)) { if (isPaid || !host.getController().equals(activator)) {
return; return;
} }
} else if (sa.hasParam("CumulativeUpkeep")) { } else if (sa.hasParam("CumulativeUpkeep")) {
GameEntityCounterTable table = new GameEntityCounterTable(); GameEntityCounterTable table = new GameEntityCounterTable();
card.addCounter(CounterEnumType.AGE, 1, activator, table); host.addCounter(CounterEnumType.AGE, 1, activator, table);
table.replaceCounterEffect(game, sa, true); table.replaceCounterEffect(game, sa, true);
Cost payCost = new Cost(ManaCost.ZERO, true); Cost payCost = new Cost(ManaCost.ZERO, true);
int n = card.getCounters(CounterEnumType.AGE); int n = host.getCounters(CounterEnumType.AGE);
if (n > 0) { if (n > 0) {
Cost cumCost = new Cost(sa.getParam("CumulativeUpkeep"), true); Cost cumCost = new Cost(sa.getParam("CumulativeUpkeep"), true);
payCost.mergeTo(cumCost, n, sa); payCost.mergeTo(cumCost, n, sa);
} }
game.updateLastStateForCard(card); game.updateLastStateForCard(host);
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
sb.append("Cumulative upkeep for ").append(card); sb.append("Cumulative upkeep for ").append(host);
boolean isPaid = activator.getController().payManaOptional(card, payCost, sa, sb.toString(), ManaPaymentPurpose.CumulativeUpkeep); boolean isPaid = activator.getController().payManaOptional(host, payCost, sa, sb.toString(), ManaPaymentPurpose.CumulativeUpkeep);
final Map<AbilityKey, Object> runParams = AbilityKey.mapFromCard(card); final Map<AbilityKey, Object> runParams = AbilityKey.mapFromCard(host);
runParams.put(AbilityKey.CumulativeUpkeepPaid, isPaid); runParams.put(AbilityKey.CumulativeUpkeepPaid, isPaid);
runParams.put(AbilityKey.PayingMana, StringUtils.join(sa.getPayingMana(), "")); runParams.put(AbilityKey.PayingMana, StringUtils.join(sa.getPayingMana(), ""));
game.getTriggerHandler().runTrigger(TriggerType.PayCumulativeUpkeep, runParams, false); game.getTriggerHandler().runTrigger(TriggerType.PayCumulativeUpkeep, runParams, false);
if (isPaid || !card.getController().equals(activator)) { if (isPaid || !host.getController().equals(activator)) {
return; return;
} }
} }
// Expand Sacrifice keyword here depending on what we need out of it. // Expand Sacrifice keyword here depending on what we need out of it.
final int amount = AbilityUtils.calculateAmount(card, sa.getParamOrDefault("Amount", "1"), sa); final int amount = AbilityUtils.calculateAmount(host, sa.getParamOrDefault("Amount", "1"), sa);
final boolean devour = sa.isKeyword(Keyword.DEVOUR); final boolean devour = sa.isKeyword(Keyword.DEVOUR);
final boolean exploit = sa.isKeyword(Keyword.EXPLOIT); final boolean exploit = sa.isKeyword(Keyword.EXPLOIT);
final boolean sacEachValid = sa.hasParam("SacEachValid"); final boolean sacEachValid = sa.hasParam("SacEachValid");
@@ -102,17 +99,14 @@ public class SacrificeEffect extends SpellAbilityEffect {
final boolean remSacrificed = sa.hasParam("RememberSacrificed"); final boolean remSacrificed = sa.hasParam("RememberSacrificed");
final boolean optional = sa.hasParam("Optional"); final boolean optional = sa.hasParam("Optional");
Map<AbilityKey, Object> params = AbilityKey.newMap(); Map<AbilityKey, Object> params = AbilityKey.newMap();
CardZoneTable table = new CardZoneTable(game.copyLastStateBattlefield(), CardCollection.EMPTY); CardZoneTable zoneMovements = AbilityKey.addCardZoneTableParams(params, sa);
AbilityKey.addCardZoneTableParams(params, table);
if (valid.equals("Self") && game.getZoneOf(card) != null) { if (valid.equals("Self") && game.getZoneOf(host) != null) {
if (game.getZoneOf(card).is(ZoneType.Battlefield)) { if (game.getZoneOf(host).is(ZoneType.Battlefield)) {
if (!optional || activator.getController().confirmAction(sa, null, if (!optional || activator.getController().confirmAction(sa, null,
Localizer.getInstance().getMessage("lblDoYouWantSacrificeThis", card.getName()), null)) { Localizer.getInstance().getMessage("lblDoYouWantSacrificeThis", host.getName()), null)) {
if (game.getAction().sacrifice(card, sa, true, params) != null) { if (game.getAction().sacrifice(host, sa, true, params) != null && remSacrificed) {
if (remSacrificed) { host.addRemembered(host);
card.addRemembered(card);
}
} }
} }
} }
@@ -165,35 +159,31 @@ public class SacrificeEffect extends SpellAbilityEffect {
choosenToSacrifice = GameActionUtil.orderCardsByTheirOwners(game, choosenToSacrifice, ZoneType.Graveyard, sa); choosenToSacrifice = GameActionUtil.orderCardsByTheirOwners(game, choosenToSacrifice, ZoneType.Graveyard, sa);
Map<Integer, Card> cachedMap = Maps.newHashMap();
for (Card sac : choosenToSacrifice) { for (Card sac : choosenToSacrifice) {
Card lKICopy = null; Card lKICopy = zoneMovements.getLastStateBattlefield().get(sac);
if (devour || exploit || remSacrificed) {
lKICopy = CardUtil.getLKICopy(sac, cachedMap);
}
boolean wasSacrificed = !destroy && game.getAction().sacrifice(sac, sa, true, params) != null; boolean wasSacrificed = !destroy && game.getAction().sacrifice(sac, sa, true, params) != null;
boolean wasDestroyed = destroy && game.getAction().destroy(sac, sa, true, params); boolean wasDestroyed = destroy && game.getAction().destroy(sac, sa, true, params);
// Run Devour Trigger // Run Devour Trigger
if (devour) { if (devour) {
card.addDevoured(lKICopy); host.addDevoured(lKICopy);
final Map<AbilityKey, Object> runParams = AbilityKey.newMap(); final Map<AbilityKey, Object> runParams = AbilityKey.newMap();
runParams.put(AbilityKey.Devoured, lKICopy); runParams.put(AbilityKey.Devoured, lKICopy);
game.getTriggerHandler().runTrigger(TriggerType.Devoured, runParams, false); game.getTriggerHandler().runTrigger(TriggerType.Devoured, runParams, false);
} }
if (exploit) { if (exploit) {
card.addExploited(lKICopy); host.addExploited(lKICopy);
final Map<AbilityKey, Object> runParams = AbilityKey.mapFromCard(card); final Map<AbilityKey, Object> runParams = AbilityKey.mapFromCard(host);
runParams.put(AbilityKey.Exploited, lKICopy); runParams.put(AbilityKey.Exploited, lKICopy);
game.getTriggerHandler().runTrigger(TriggerType.Exploited, runParams, false); game.getTriggerHandler().runTrigger(TriggerType.Exploited, runParams, false);
} }
if ((wasDestroyed || wasSacrificed) && remSacrificed) { if ((wasDestroyed || wasSacrificed) && remSacrificed) {
card.addRemembered(lKICopy); host.addRemembered(lKICopy);
} }
} }
} }
} }
table.triggerChangesZoneAll(game, sa); zoneMovements.triggerChangesZoneAll(game, sa);
} }
@Override @Override

View File

@@ -6155,11 +6155,6 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
damageType = DamageType.Deathtouch; damageType = DamageType.Deathtouch;
} }
// 704.8: if it looks like the creature might die from SBA make sure the LKI is refreshed
if (hasBeenDealtDeathtouchDamage() || (getDamage() > 0 && getLethal() <= getDamage())) {
game.updateLastStateForCard(this);
}
// Play the Damage sound // Play the Damage sound
game.fireEvent(new GameEventCardDamaged(this, source, damageIn, damageType)); game.fireEvent(new GameEventCardDamaged(this, source, damageIn, damageType));
} }

View File

@@ -113,12 +113,11 @@ public class CardFactoryUtil {
} }
final Game game = hostCard.getGame(); final Game game = hostCard.getGame();
CardZoneTable table = new CardZoneTable(game.copyLastStateBattlefield(), game.copyLastStateBattlefield());
Map<AbilityKey, Object> params = AbilityKey.newMap(); Map<AbilityKey, Object> params = AbilityKey.newMap();
AbilityKey.addCardZoneTableParams(params, table); CardZoneTable zoneMovements = AbilityKey.addCardZoneTableParams(params, this);
hostCard.getGame().getAction().moveToPlay(hostCard, this, params); hostCard.getGame().getAction().moveToPlay(hostCard, this, params);
table.triggerChangesZoneAll(game, this); zoneMovements.triggerChangesZoneAll(game, this);
} }
@Override @Override
@@ -3036,11 +3035,10 @@ public class CardFactoryUtil {
public void resolve() { public void resolve() {
final Game game = getHostCard().getGame(); final Game game = getHostCard().getGame();
Map<AbilityKey, Object> moveParams = AbilityKey.newMap(); Map<AbilityKey, Object> moveParams = AbilityKey.newMap();
CardZoneTable table = new CardZoneTable(this.getLastStateBattlefield(), this.getLastStateGraveyard()); CardZoneTable zoneMovements = AbilityKey.addCardZoneTableParams(moveParams, this);
AbilityKey.addCardZoneTableParams(moveParams, table);
final Card c = game.getAction().exile(getHostCard(), this, moveParams); final Card c = game.getAction().exile(getHostCard(), this, moveParams);
table.triggerChangesZoneAll(game, this); zoneMovements.triggerChangesZoneAll(game, this);
c.setForetold(true); c.setForetold(true);
c.turnFaceDown(true); c.turnFaceDown(true);
@@ -3500,11 +3498,10 @@ public class CardFactoryUtil {
public void resolve() { public void resolve() {
final Game game = this.getHostCard().getGame(); final Game game = this.getHostCard().getGame();
Map<AbilityKey, Object> moveParams = AbilityKey.newMap(); Map<AbilityKey, Object> moveParams = AbilityKey.newMap();
CardZoneTable moveTable = new CardZoneTable(this.getLastStateBattlefield(), this.getLastStateGraveyard()); CardZoneTable zoneMovements = AbilityKey.addCardZoneTableParams(moveParams, this);
AbilityKey.addCardZoneTableParams(moveParams, moveTable);
final Card c = game.getAction().exile(getHostCard(), this, moveParams); final Card c = game.getAction().exile(getHostCard(), this, moveParams);
moveTable.triggerChangesZoneAll(game, this); zoneMovements.triggerChangesZoneAll(game, this);
int counters = AbilityUtils.calculateAmount(c, k[1], this); int counters = AbilityUtils.calculateAmount(c, k[1], this);
GameEntityCounterTable table = new GameEntityCounterTable(); GameEntityCounterTable table = new GameEntityCounterTable();

View File

@@ -11,8 +11,10 @@ import com.google.common.collect.*;
import forge.game.CardTraitBase; import forge.game.CardTraitBase;
import forge.game.Game; import forge.game.Game;
import forge.game.GameAction;
import forge.game.ability.AbilityKey; import forge.game.ability.AbilityKey;
import forge.game.player.PlayerCollection; import forge.game.player.PlayerCollection;
import forge.game.replacement.ReplacementType;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.game.trigger.TriggerType; import forge.game.trigger.TriggerType;
import forge.game.zone.ZoneType; import forge.game.zone.ZoneType;
@@ -27,12 +29,6 @@ public class CardZoneTable extends ForwardingTable<ZoneType, ZoneType, CardColle
private CardCollectionView lastStateBattlefield; private CardCollectionView lastStateBattlefield;
private CardCollectionView lastStateGraveyard; private CardCollectionView lastStateGraveyard;
public CardZoneTable(CardZoneTable cardZoneTable) {
this.putAll(cardZoneTable);
lastStateBattlefield = cardZoneTable.getLastStateBattlefield();
lastStateGraveyard = cardZoneTable.getLastStateGraveyard();
}
public CardZoneTable() { public CardZoneTable() {
this(null, null); this(null, null);
} }
@@ -42,6 +38,24 @@ public class CardZoneTable extends ForwardingTable<ZoneType, ZoneType, CardColle
setLastStateGraveyard(ObjectUtils.firstNonNull(lastStateGraveyard, CardCollection.EMPTY)); setLastStateGraveyard(ObjectUtils.firstNonNull(lastStateGraveyard, CardCollection.EMPTY));
} }
public CardZoneTable(CardZoneTable cardZoneTable) {
this.putAll(cardZoneTable);
lastStateBattlefield = cardZoneTable.getLastStateBattlefield();
lastStateGraveyard = cardZoneTable.getLastStateGraveyard();
}
public static CardZoneTable getSimultaneousInstance(SpellAbility sa) {
if (sa.isReplacementAbility() && sa.getReplacementEffect().getMode() == ReplacementType.Moved
&& sa.getReplacingObject(AbilityKey.InternalTriggerTable) != null) {
// if a RE changes the destination zone try to make it simultaneous
return (CardZoneTable) sa.getReplacingObject(AbilityKey.InternalTriggerTable);
}
GameAction ga = sa.getHostCard().getGame().getAction();
return new CardZoneTable(
ga.getLastState(AbilityKey.LastStateBattlefield, sa, null, true),
ga.getLastState(AbilityKey.LastStateGraveyard, sa, null, true));
}
public CardCollectionView getLastStateBattlefield() { public CardCollectionView getLastStateBattlefield() {
return lastStateBattlefield; return lastStateBattlefield;
} }

View File

@@ -908,10 +908,6 @@ public class Combat {
return assignedDamage; return assignedDamage;
} }
public final CardDamageMap getDamageMap() {
return damageMap;
}
public void dealAssignedDamage() { public void dealAssignedDamage() {
final Game game = playerWhoAttacks.getGame(); final Game game = playerWhoAttacks.getGame();
game.copyLastState(); game.copyLastState();

View File

@@ -123,11 +123,11 @@ public class CostExileFromStack extends CostPart {
return true; return true;
} }
CardZoneTable table = new CardZoneTable(game.copyLastStateBattlefield(), game.copyLastStateGraveyard());
Map<AbilityKey, Object> moveParams = AbilityKey.newMap(); Map<AbilityKey, Object> moveParams = AbilityKey.newMap();
AbilityKey.addCardZoneTableParams(moveParams, table); CardZoneTable zoneMovements = AbilityKey.addCardZoneTableParams(moveParams, ability);
CardCollection moved = game.getAction().exile(list, ability, moveParams); CardCollection moved = game.getAction().exile(list, ability, moveParams);
SpellAbilityEffect.handleExiledWith(moved, ability); SpellAbilityEffect.handleExiledWith(moved, ability);
zoneMovements.triggerChangesZoneAll(game, ability);
return true; return true;
} }

View File

@@ -19,7 +19,6 @@ package forge.game.cost;
import java.util.Map; import java.util.Map;
import forge.game.Game;
import forge.game.ability.AbilityKey; 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;
@@ -91,12 +90,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) {
final Game game = ai.getGame();
CardZoneTable table = new CardZoneTable(game.getLastStateBattlefield(), game.getLastStateGraveyard());
Map<AbilityKey, Object> moveParams = AbilityKey.newMap(); Map<AbilityKey, Object> moveParams = AbilityKey.newMap();
AbilityKey.addCardZoneTableParams(moveParams, table); CardZoneTable zoneMovements = AbilityKey.addCardZoneTableParams(moveParams, ability);
ability.getPaidHash().put("Milled", true, (CardCollection) ai.mill(decision.c, ZoneType.Graveyard, ability, moveParams)); ability.getPaidHash().put("Milled", true, (CardCollection) ai.mill(decision.c, ZoneType.Graveyard, ability, moveParams));
table.triggerChangesZoneAll(ai.getGame(), ability); zoneMovements.triggerChangesZoneAll(ai.getGame(), ability);
return true; return true;
} }