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

View File

@@ -1,6 +1,5 @@
package forge.game.ability;
import forge.game.Game;
import forge.game.GameEntity;
import forge.game.card.Card;
import forge.game.card.CardZoneTable;
@@ -207,19 +206,14 @@ public enum AbilityKey {
return runParams;
}
public static void addCardZoneTableParams(Map<AbilityKey, Object> map, CardZoneTable table) {
map.put(AbilityKey.LastStateBattlefield, table.getLastStateBattlefield());
map.put(AbilityKey.LastStateGraveyard, table.getLastStateGraveyard());
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);
public static CardZoneTable addCardZoneTableParams(Map<AbilityKey, Object> params, SpellAbility sa) {
CardZoneTable table = CardZoneTable.getSimultaneousInstance(sa);
addCardZoneTableParams(params, table);
return table;
}
public static CardZoneTable addCardZoneTableParams(Map<AbilityKey, Object> map, Game game) {
CardZoneTable table = new CardZoneTable(game.getLastStateBattlefield(), game.getLastStateGraveyard());
addCardZoneTableParams(map, table);
return table;
public static void addCardZoneTableParams(Map<AbilityKey, Object> params, CardZoneTable table) {
params.put(AbilityKey.LastStateBattlefield, table.getLastStateBattlefield());
params.put(AbilityKey.LastStateGraveyard, table.getLastStateGraveyard());
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.ReplacementHandler;
import forge.game.replacement.ReplacementLayer;
import forge.game.replacement.ReplacementType;
import forge.game.spellability.AbilitySub;
import forge.game.spellability.SpellAbility;
import forge.game.trigger.Trigger;
@@ -971,15 +970,6 @@ public abstract class SpellAbilityEffect {
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) {
return new GameCommand() {
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.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardCollectionView;
import forge.game.card.CardFactoryUtil;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates;
@@ -55,8 +54,6 @@ public class ChangeZoneAllEffect extends SpellAbilityEffect {
CardCollection cards;
List<Player> tgtPlayers = getTargetPlayers(sa);
final Game game = sa.getActivatingPlayer().getGame();
CardCollectionView lastStateBattlefield = game.copyLastStateBattlefield();
CardCollectionView lastStateGraveyard = game.copyLastStateGraveyard();
if ((!sa.usesTargeting() && !sa.hasParam("Defined")) || sa.hasParam("UseAllOriginZones")) {
cards = new CardCollection(game.getCardsIn(origin));
@@ -154,7 +151,8 @@ public class ChangeZoneAllEffect extends SpellAbilityEffect {
CardLists.shuffle(cards);
}
final CardZoneTable triggerList = getChangeZoneTable(sa, lastStateBattlefield, lastStateGraveyard);
final CardZoneTable triggerList = CardZoneTable.getSimultaneousInstance(sa);
for (final Card c : cards) {
final Zone originZone = game.getZoneOf(c);

View File

@@ -470,11 +470,10 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
libraryPosition = pair.getValue();
}
CardCollectionView lastStateBattlefield = game.copyLastStateBattlefield();
CardCollectionView lastStateGraveyard = game.copyLastStateGraveyard();
final GameEntityCounterTable counterTable = new GameEntityCounterTable();
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
for (final SpellAbility tgtSA : getTargetSpells(sa)) {
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");
boolean combatChanged = false;
CardCollectionView lastStateBattlefield = game.copyLastStateBattlefield();
CardCollectionView lastStateGraveyard = game.copyLastStateGraveyard();
final CardZoneTable triggerList = getChangeZoneTable(sa, lastStateBattlefield, lastStateGraveyard);
final CardZoneTable triggerList = CardZoneTable.getSimultaneousInstance(sa);
for (Player player : HiddenOriginChoicesMap.keySet()) {
boolean searchedLibrary = HiddenOriginChoicesMap.get(player).searchedLibrary;
@@ -1281,6 +1277,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
Map<AbilityKey, Object> moveParams = AbilityKey.newMap();
moveParams.put(AbilityKey.FoundSearchingLibrary, searchedLibrary);
AbilityKey.addCardZoneTableParams(moveParams, triggerList);
if (destination.equals(ZoneType.Library)) {
movedCard = game.getAction().moveToLibrary(c, libraryPos, sa, moveParams);
}
@@ -1330,7 +1327,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
if (sa.hasParam("AttachedTo") && c.isAttachment()) {
CardCollection list = AbilityUtils.getDefinedCards(source, sa.getParam("AttachedTo"), sa);
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
// 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) {
CardCollection connivers = CardLists.filterControlledBy(toConnive, p);
final CardCollection connivers = CardLists.filterControlledBy(toConnive, p);
while (!connivers.isEmpty()) {
GameEntityCounterTable table = new GameEntityCounterTable();
final CardZoneTable triggerList = new CardZoneTable(game.copyLastStateBattlefield(), game.copyLastStateGraveyard());
Map<Player, CardCollectionView> discardedMap = Maps.newHashMap();
Map<AbilityKey, Object> moveParams = AbilityKey.newMap();
AbilityKey.addCardZoneTableParams(moveParams, triggerList);
final Map<Player, CardCollectionView> discardedMap = Maps.newHashMap();
final Map<AbilityKey, Object> moveParams = AbilityKey.newMap();
final CardZoneTable zoneMovements = AbilityKey.addCardZoneTableParams(moveParams, sa);
final GameEntityCounterTable counterPlacements = new GameEntityCounterTable();
Card conniver = connivers.size() > 1 ? p.getController().chooseSingleEntityForEffect(connivers, sa,
Localizer.getInstance().getMessage("lblChooseConniver"), null) : connivers.get(0);
@@ -96,12 +95,12 @@ public class ConniveEffect extends SpellAbilityEffect {
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 (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));
discard(sa, true, discardedMap, moveParams);
table.replaceCounterEffect(game, sa, true);
triggerList.triggerChangesZoneAll(game, sa);
counterPlacements.replaceCounterEffect(game, sa, true);
zoneMovements.triggerChangesZoneAll(game, sa);
}
}
}

View File

@@ -53,7 +53,7 @@ public class CounterEffect extends SpellAbilityEffect {
public void resolve(SpellAbility sa) {
final Game game = sa.getActivatingPlayer().getGame();
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)) {
final Card tgtSACard = tgtSA.getHostCard();
@@ -103,8 +103,8 @@ public class CounterEffect extends SpellAbilityEffect {
sa.getHostCard().addRemembered(tgtSACard);
}
}
table.triggerChangesZoneAll(game, sa);
} // end counterResolve
zoneMovements.triggerChangesZoneAll(game, sa);
}
public static boolean checkForConditionWouldDestroy(SpellAbility sa, SpellAbility tgtSA) {
List<SpellAbility> testChain = Lists.newArrayList();

View File

@@ -2,8 +2,6 @@ package forge.game.ability.effects;
import java.util.Map;
import com.google.common.collect.Maps;
import forge.game.Game;
import forge.game.GameActionUtil;
import forge.game.ability.AbilityKey;
@@ -13,7 +11,6 @@ import forge.game.card.Card;
import forge.game.card.CardCollectionView;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates;
import forge.game.card.CardUtil;
import forge.game.card.CardZoneTable;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
@@ -90,16 +87,15 @@ public class DestroyAllEffect extends SpellAbilityEffect {
list = GameActionUtil.orderCardsByTheirOwners(game, list, ZoneType.Graveyard, sa);
Map<AbilityKey, Object> params = AbilityKey.newMap();
CardZoneTable table = new CardZoneTable(game.copyLastStateBattlefield(), game.getLastStateGraveyard());
AbilityKey.addCardZoneTableParams(params, table);
CardZoneTable zoneMovements = AbilityKey.addCardZoneTableParams(params, sa);
Map<Integer, Card> cachedMap = Maps.newHashMap();
for (Card c : list) {
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.Map;
import com.google.common.collect.Maps;
import forge.game.Game;
import forge.game.GameActionUtil;
import forge.game.ability.AbilityKey;
import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardCollectionView;
import forge.game.card.CardUtil;
import forge.game.card.CardZoneTable;
@@ -47,23 +44,22 @@ public class DestroyEffect extends SpellAbilityEffect {
@Override
public void resolve(SpellAbility sa) {
final Card card = sa.getHostCard();
final Game game = card.getGame();
final Card host = sa.getHostCard();
final Game game = host.getGame();
if (sa.hasParam("RememberDestroyed")) {
card.clearRemembered();
host.clearRemembered();
}
CardCollectionView tgtCards = getTargetCards(sa);
CardCollectionView untargetedCards = CardUtil.getRadiance(sa);
CardCollectionView tgtCards = getTargetCards(sa);
tgtCards = GameActionUtil.orderCardsByTheirOwners(game, tgtCards, ZoneType.Graveyard, sa);
untargetedCards = GameActionUtil.orderCardsByTheirOwners(game, untargetedCards, ZoneType.Graveyard, sa);
Map<AbilityKey, Object> params = AbilityKey.newMap();
CardZoneTable table = getChangeZoneTable(sa, game.copyLastStateBattlefield(), CardCollection.EMPTY);
AbilityKey.addCardZoneTableParams(params, table);
CardZoneTable zoneMovements = AbilityKey.addCardZoneTableParams(params, sa);
Map<Integer, Card> cachedMap = Maps.newHashMap();
for (final Card tgtC : tgtCards) {
if (!tgtC.isInPlay()) {
continue;
@@ -75,29 +71,26 @@ public class DestroyEffect extends SpellAbilityEffect {
if (gameCard == null || !tgtC.equalsWithTimestamp(gameCard)) {
continue;
}
internalDestroy(gameCard, sa, cachedMap, params);
internalDestroy(gameCard, sa, params, zoneMovements);
}
untargetedCards = GameActionUtil.orderCardsByTheirOwners(game, untargetedCards, ZoneType.Graveyard, sa);
for (final Card unTgtC : untargetedCards) {
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) {
final Card card = sa.getHostCard();
final Game game = card.getGame();
protected void internalDestroy(Card gameCard, SpellAbility sa, Map<AbilityKey, Object> params, CardZoneTable zoneMovements) {
final Card host = sa.getHostCard();
final Game game = host.getGame();
final boolean remDestroyed = sa.hasParam("RememberDestroyed");
final boolean noRegen = sa.hasParam("NoRegen");
final boolean sac = sa.hasParam("Sacrifice");
final boolean alwaysRem = sa.hasParam("AlwaysRemember");
boolean destroyed = false;
final Card lki = sa.hasParam("RememberLKI") ? CardUtil.getLKICopy(gameCard, cachedMap) : null;
SpellAbility cause = sa;
if (sa.isReplacementAbility()) {
@@ -110,10 +103,10 @@ public class DestroyEffect extends SpellAbilityEffect {
destroyed = game.getAction().destroy(gameCard, cause, !noRegen, params);
}
if (destroyed && remDestroyed) {
card.addRemembered(gameCard);
host.addRemembered(gameCard);
}
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 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;
String changeValid = sa.getParamOrDefault("ChangeValid", "");
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 noMove = sa.hasParam("NoMove");
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();
boolean combatChanged = false;
@@ -375,7 +376,7 @@ public class DigEffect extends SpellAbilityEffect {
}
Map<AbilityKey, Object> moveParams = AbilityKey.newMap();
AbilityKey.addCardZoneTableParams(moveParams, table);
AbilityKey.addCardZoneTableParams(moveParams, zoneMovements);
for (Card c : movedCards) {
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());
}
table.triggerChangesZoneAll(game, sa);
zoneMovements.triggerChangesZoneAll(game, sa);
counterTable.replaceCounterEffect(game, sa, true);
}

View File

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

View File

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

View File

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

View File

@@ -65,20 +65,15 @@ public abstract class ManifestBaseEffect extends SpellAbilityEffect {
if (fromLibrary) {
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.
CardZoneTable triggerList = new CardZoneTable(game.copyLastStateBattlefield(), game.copyLastStateGraveyard());
Map<AbilityKey, Object> moveParams = AbilityKey.newMap();
AbilityKey.addCardZoneTableParams(moveParams, triggerList);
CardZoneTable triggerList = AbilityKey.addCardZoneTableParams(moveParams, sa);
internalEffect(c, p, sa, moveParams);
triggerList.triggerChangesZoneAll(game, sa);
}
} else {
// 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();
AbilityKey.addCardZoneTableParams(moveParams, triggerList);
CardZoneTable triggerList = AbilityKey.addCardZoneTableParams(moveParams, sa);
for (Card c : tgtCards) {
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));
Map<AbilityKey, Object> moveParams = AbilityKey.newMap();
CardZoneTable table = new CardZoneTable(sa.getLastStateBattlefield(), sa.getLastStateGraveyard());
AbilityKey.addCardZoneTableParams(moveParams, table);
CardZoneTable zoneMovements = AbilityKey.addCardZoneTableParams(moveParams, sa);
exiled = game.getAction().exile(exiled, sa, moveParams);
table.triggerChangesZoneAll(game, sa);
zoneMovements.triggerChangesZoneAll(game, sa);
if (exiled.size() < 2) {
return;
@@ -78,10 +78,16 @@ public class MeldEffect extends SpellAbilityEffect {
primary.setMeldedWith(secondary);
PlayerZoneBattlefield bf = (PlayerZoneBattlefield)controller.getZone(ZoneType.Battlefield);
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")) {
game.updateCombatForView();
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.Map;
import com.google.common.collect.Maps;
import forge.game.Game;
import forge.game.GameActionUtil;
import forge.game.ability.AbilityKey;
@@ -14,7 +12,6 @@ import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardCollectionView;
import forge.game.card.CardLists;
import forge.game.card.CardUtil;
import forge.game.card.CardZoneTable;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
@@ -45,13 +42,13 @@ public class SacrificeAllEffect extends SpellAbilityEffect {
@Override
public void resolve(SpellAbility sa) {
final Card card = sa.getHostCard();
final Card host = sa.getHostCard();
final Player activator = sa.getActivatingPlayer();
final Game game = activator.getGame();
CardCollectionView list;
if (sa.hasParam("Defined")) {
list = AbilityUtils.getDefinedCards(card, sa.getParam("Defined"), sa);
list = AbilityUtils.getDefinedCards(host, sa.getParam("Defined"), sa);
} else {
list = game.getCardsIn(ZoneType.Battlefield);
if (sa.hasParam("ValidCards")) {
@@ -61,7 +58,7 @@ public class SacrificeAllEffect extends SpellAbilityEffect {
final boolean remSacrificed = sa.hasParam("RememberSacrificed");
if (remSacrificed) {
card.clearRemembered();
host.clearRemembered();
}
// update cards that where using LKI
@@ -82,23 +79,22 @@ public class SacrificeAllEffect extends SpellAbilityEffect {
list = GameActionUtil.orderCardsByTheirOwners(game, list, ZoneType.Graveyard, sa);
Map<Integer, Card> cachedMap = Maps.newHashMap();
Map<AbilityKey, Object> params = AbilityKey.newMap();
CardZoneTable table = new CardZoneTable(game.copyLastStateBattlefield(), CardCollection.EMPTY);
AbilityKey.addCardZoneTableParams(params, table);
CardZoneTable zoneMovements = AbilityKey.addCardZoneTableParams(params, sa);
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 (remSacrificed) {
card.addRemembered(lKICopy);
host.addRemembered(lKICopy);
}
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 org.apache.commons.lang3.StringUtils;
import com.google.common.collect.Maps;
import forge.card.mana.ManaCost;
import forge.game.Game;
import forge.game.GameActionUtil;
@@ -24,7 +22,6 @@ import forge.game.card.CardCollection;
import forge.game.card.CardCollectionView;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates;
import forge.game.card.CardUtil;
import forge.game.card.CardZoneTable;
import forge.game.card.CounterEnumType;
import forge.game.cost.Cost;
@@ -43,7 +40,7 @@ public class SacrificeEffect extends SpellAbilityEffect {
public void resolve(SpellAbility sa) {
final Player activator = sa.getActivatingPlayer();
final Game game = activator.getGame();
final Card card = sa.getHostCard();
final Card host = sa.getHostCard();
if (sa.hasParam("Echo")) {
boolean isPaid;
@@ -51,45 +48,45 @@ public class SacrificeEffect extends SpellAbilityEffect {
&& activator.getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblDoYouWantPayEcho") + " {0}?", null)) {
isPaid = true;
} 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);
}
final Map<AbilityKey, Object> runParams = AbilityKey.mapFromCard(card);
final Map<AbilityKey, Object> runParams = AbilityKey.mapFromCard(host);
runParams.put(AbilityKey.EchoPaid, isPaid);
game.getTriggerHandler().runTrigger(TriggerType.PayEcho, runParams, false);
if (isPaid || !card.getController().equals(activator)) {
if (isPaid || !host.getController().equals(activator)) {
return;
}
} else if (sa.hasParam("CumulativeUpkeep")) {
GameEntityCounterTable table = new GameEntityCounterTable();
card.addCounter(CounterEnumType.AGE, 1, activator, table);
host.addCounter(CounterEnumType.AGE, 1, activator, table);
table.replaceCounterEffect(game, sa, true);
Cost payCost = new Cost(ManaCost.ZERO, true);
int n = card.getCounters(CounterEnumType.AGE);
int n = host.getCounters(CounterEnumType.AGE);
if (n > 0) {
Cost cumCost = new Cost(sa.getParam("CumulativeUpkeep"), true);
payCost.mergeTo(cumCost, n, sa);
}
game.updateLastStateForCard(card);
game.updateLastStateForCard(host);
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);
final Map<AbilityKey, Object> runParams = AbilityKey.mapFromCard(card);
boolean isPaid = activator.getController().payManaOptional(host, payCost, sa, sb.toString(), ManaPaymentPurpose.CumulativeUpkeep);
final Map<AbilityKey, Object> runParams = AbilityKey.mapFromCard(host);
runParams.put(AbilityKey.CumulativeUpkeepPaid, isPaid);
runParams.put(AbilityKey.PayingMana, StringUtils.join(sa.getPayingMana(), ""));
game.getTriggerHandler().runTrigger(TriggerType.PayCumulativeUpkeep, runParams, false);
if (isPaid || !card.getController().equals(activator)) {
if (isPaid || !host.getController().equals(activator)) {
return;
}
}
// 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 exploit = sa.isKeyword(Keyword.EXPLOIT);
final boolean sacEachValid = sa.hasParam("SacEachValid");
@@ -102,17 +99,14 @@ public class SacrificeEffect extends SpellAbilityEffect {
final boolean remSacrificed = sa.hasParam("RememberSacrificed");
final boolean optional = sa.hasParam("Optional");
Map<AbilityKey, Object> params = AbilityKey.newMap();
CardZoneTable table = new CardZoneTable(game.copyLastStateBattlefield(), CardCollection.EMPTY);
AbilityKey.addCardZoneTableParams(params, table);
CardZoneTable zoneMovements = AbilityKey.addCardZoneTableParams(params, sa);
if (valid.equals("Self") && game.getZoneOf(card) != null) {
if (game.getZoneOf(card).is(ZoneType.Battlefield)) {
if (valid.equals("Self") && game.getZoneOf(host) != null) {
if (game.getZoneOf(host).is(ZoneType.Battlefield)) {
if (!optional || activator.getController().confirmAction(sa, null,
Localizer.getInstance().getMessage("lblDoYouWantSacrificeThis", card.getName()), null)) {
if (game.getAction().sacrifice(card, sa, true, params) != null) {
if (remSacrificed) {
card.addRemembered(card);
}
Localizer.getInstance().getMessage("lblDoYouWantSacrificeThis", host.getName()), null)) {
if (game.getAction().sacrifice(host, sa, true, params) != null && remSacrificed) {
host.addRemembered(host);
}
}
}
@@ -165,35 +159,31 @@ public class SacrificeEffect extends SpellAbilityEffect {
choosenToSacrifice = GameActionUtil.orderCardsByTheirOwners(game, choosenToSacrifice, ZoneType.Graveyard, sa);
Map<Integer, Card> cachedMap = Maps.newHashMap();
for (Card sac : choosenToSacrifice) {
Card lKICopy = null;
if (devour || exploit || remSacrificed) {
lKICopy = CardUtil.getLKICopy(sac, cachedMap);
}
Card lKICopy = zoneMovements.getLastStateBattlefield().get(sac);
boolean wasSacrificed = !destroy && game.getAction().sacrifice(sac, sa, true, params) != null;
boolean wasDestroyed = destroy && game.getAction().destroy(sac, sa, true, params);
// Run Devour Trigger
if (devour) {
card.addDevoured(lKICopy);
host.addDevoured(lKICopy);
final Map<AbilityKey, Object> runParams = AbilityKey.newMap();
runParams.put(AbilityKey.Devoured, lKICopy);
game.getTriggerHandler().runTrigger(TriggerType.Devoured, runParams, false);
}
if (exploit) {
card.addExploited(lKICopy);
final Map<AbilityKey, Object> runParams = AbilityKey.mapFromCard(card);
host.addExploited(lKICopy);
final Map<AbilityKey, Object> runParams = AbilityKey.mapFromCard(host);
runParams.put(AbilityKey.Exploited, lKICopy);
game.getTriggerHandler().runTrigger(TriggerType.Exploited, runParams, false);
}
if ((wasDestroyed || wasSacrificed) && remSacrificed) {
card.addRemembered(lKICopy);
host.addRemembered(lKICopy);
}
}
}
}
table.triggerChangesZoneAll(game, sa);
zoneMovements.triggerChangesZoneAll(game, sa);
}
@Override

View File

@@ -6155,11 +6155,6 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
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
game.fireEvent(new GameEventCardDamaged(this, source, damageIn, damageType));
}

View File

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

View File

@@ -11,8 +11,10 @@ import com.google.common.collect.*;
import forge.game.CardTraitBase;
import forge.game.Game;
import forge.game.GameAction;
import forge.game.ability.AbilityKey;
import forge.game.player.PlayerCollection;
import forge.game.replacement.ReplacementType;
import forge.game.spellability.SpellAbility;
import forge.game.trigger.TriggerType;
import forge.game.zone.ZoneType;
@@ -27,12 +29,6 @@ public class CardZoneTable extends ForwardingTable<ZoneType, ZoneType, CardColle
private CardCollectionView lastStateBattlefield;
private CardCollectionView lastStateGraveyard;
public CardZoneTable(CardZoneTable cardZoneTable) {
this.putAll(cardZoneTable);
lastStateBattlefield = cardZoneTable.getLastStateBattlefield();
lastStateGraveyard = cardZoneTable.getLastStateGraveyard();
}
public CardZoneTable() {
this(null, null);
}
@@ -42,6 +38,24 @@ public class CardZoneTable extends ForwardingTable<ZoneType, ZoneType, CardColle
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() {
return lastStateBattlefield;
}

View File

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

View File

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

View File

@@ -19,7 +19,6 @@ package forge.game.cost;
import java.util.Map;
import forge.game.Game;
import forge.game.ability.AbilityKey;
import forge.game.card.CardCollection;
import forge.game.card.CardZoneTable;
@@ -91,12 +90,10 @@ public class CostMill extends CostPart {
@Override
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();
AbilityKey.addCardZoneTableParams(moveParams, table);
CardZoneTable zoneMovements = AbilityKey.addCardZoneTableParams(moveParams, ability);
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;
}