From e062c83c02a42e4f0a61ad049b1132daf6b2bab7 Mon Sep 17 00:00:00 2001 From: Hans Mackowiak Date: Fri, 7 Feb 2020 17:29:54 +0000 Subject: [PATCH] Fixes graf cage --- .../src/main/java/forge/game/GameAction.java | 182 +++++++++--------- .../java/forge/game/GlobalRuleChange.java | 3 +- .../forge/game/ability/AbilityFactory.java | 3 +- .../ability/effects/ChangeZoneAllEffect.java | 22 ++- .../ability/effects/ChangeZoneEffect.java | 32 ++- .../game/ability/effects/FlipCoinEffect.java | 4 +- .../game/ability/effects/ManifestEffect.java | 18 +- .../src/main/java/forge/game/card/Card.java | 99 ++++++++-- .../game/replacement/ReplacementHandler.java | 3 + .../ai/simulation/GameSimulatorTest.java | 37 ++++ .../res/cardsfolder/b/bronzehide_lion.txt | 6 +- .../cardsfolder/c/chainer_dementia_master.txt | 4 +- .../res/cardsfolder/d/dance_of_the_manse.txt | 4 +- forge-gui/res/cardsfolder/d/dread_slaver.txt | 6 +- forge-gui/res/cardsfolder/e/ever_after.txt | 6 +- .../res/cardsfolder/g/grafdiggers_cage.txt | 6 +- .../res/cardsfolder/g/grave_betrayal.txt | 13 +- .../cardsfolder/g/grimoire_of_the_dead.txt | 5 +- .../k/kunoros_hound_of_athreos.txt | 2 +- .../cardsfolder/l/liliana_deaths_majesty.txt | 5 +- .../cardsfolder/l/lim_dul_the_necromancer.txt | 5 +- .../cardsfolder/n/necromantic_selection.txt | 13 +- .../res/cardsfolder/r/rise_from_the_grave.txt | 5 +- .../s/sorin_vengeful_bloodlord.txt | 4 +- .../res/cardsfolder/w/worms_of_the_earth.txt | 2 +- 25 files changed, 302 insertions(+), 187 deletions(-) diff --git a/forge-game/src/main/java/forge/game/GameAction.java b/forge-game/src/main/java/forge/game/GameAction.java index 871984d3a4a..aaa787bd082 100644 --- a/forge-game/src/main/java/forge/game/GameAction.java +++ b/forge-game/src/main/java/forge/game/GameAction.java @@ -6,12 +6,12 @@ * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * + * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ @@ -57,7 +57,7 @@ import java.util.*; /** * Methods for common actions performed during a game. - * + * * @author Forge * @version $Id$ */ @@ -80,7 +80,7 @@ public class GameAction { public Card changeZone(final Zone zoneFrom, Zone zoneTo, final Card c, Integer position, SpellAbility cause) { return changeZone(zoneFrom, zoneTo, c, position, cause, null); } - + private Card changeZone(final Zone zoneFrom, Zone zoneTo, final Card c, Integer position, SpellAbility cause, Map params) { if (c.isCopiedSpell() || (c.isImmutable() && zoneTo.is(ZoneType.Exile))) { // Remove Effect from command immediately, this is essential when some replacement @@ -125,11 +125,16 @@ public class GameAction { Card copied = null; Card lastKnownInfo = null; + // get the LKI from above like ChangeZoneEffect + if (params != null && params.containsKey(AbilityKey.CardLKI)) { + lastKnownInfo = (Card) params.get(AbilityKey.CardLKI); + } + if (c.isSplitCard()) { boolean resetToOriginal = false; if (c.isManifested()) { - if (zoneFrom.is(ZoneType.Battlefield)) { + if (fromBattlefield) { // Make sure the card returns from the battlefield as the original card with two halves resetToOriginal = true; } @@ -164,8 +169,22 @@ public class GameAction { // Don't copy Tokens, copy only cards leaving the battlefield // and returning to hand (to recreate their spell ability information) if (suppress || (!fromBattlefield && !toHand)) { - lastKnownInfo = c; copied = c; + + // if to Battlefield and it is caused by an replacement effect, + // try to get previous LKI if able + if (zoneTo.is(ZoneType.Battlefield)) { + if (cause != null && cause.isReplacementAbility()) { + ReplacementEffect re = cause.getReplacementEffect(); + if (ReplacementType.Moved.equals(re.getMode())) { + lastKnownInfo = (Card) cause.getReplacingObject(AbilityKey.CardLKI); + } + } + } + + if (lastKnownInfo == null) { + lastKnownInfo = CardUtil.getLKICopy(c); + } } else { // if from Battlefield to Graveyard and Card does exist in LastStateBattlefield // use that instead @@ -189,15 +208,17 @@ public class GameAction { } if (!c.isToken()) { - if (c.isCloned() || c.hasTextChangeState()) { - c.removeCloneStates(); - c.removeTextChangeStates(); + boolean updateState = false; + updateState |= c.removeCloneStates(); + updateState |= c.removeTextChangeStates(); + + if (updateState) { c.updateStateForView(); } copied = CardFactory.copyCard(c, false); - if (fromBattlefield && copied.getCurrentStateName() != CardStateName.Original) { + if (fromBattlefield) { // when a card leaves the battlefield, ensure it's in its original state // (we need to do this on the object before copying it, or it won't work correctly e.g. // on Transformed objects) @@ -227,59 +248,6 @@ public class GameAction { } } - // special rule for Worms of the Earth - if (toBattlefield && game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noLandBattlefield)) { - // something that is already a Land cant enter the battlefield - Card noLandLKI = c; - if (!c.isLand()) { - // check if something would be a land - noLandLKI = CardUtil.getLKICopy(c); - // this check needs to check if this card would be on the battlefield - noLandLKI.setLastKnownZone(zoneTo); - - CardCollection preList = new CardCollection(noLandLKI); - checkStaticAbilities(false, Sets.newHashSet(noLandLKI), preList); - - // fake etb counters thing, then if something changed, - // need to apply checkStaticAbilities again - if(!noLandLKI.isLand()) { - if (noLandLKI.putEtbCounters(null)) { - // counters are added need to check again - checkStaticAbilities(false, Sets.newHashSet(noLandLKI), preList); - } - } - } - if(noLandLKI.isLand()) { - // if it isn't on the Stack, it stays in that Zone - if (!c.isInZone(ZoneType.Stack)) { - return c; - } - // if something would only be a land when entering the battlefield and not before - // put it into the graveyard instead - zoneTo = c.getOwner().getZone(ZoneType.Graveyard); - - // reset facedown - copied.setState(CardStateName.Original, false); - copied.setManifested(false); - copied.updateStateForView(); - - // not to battlefield anymore! - toBattlefield = false; - - if (c.isCloned() || c.hasTextChangeState()) { - c.removeCloneStates(); - c.removeTextChangeStates(); - c.updateStateForView(); - } - - if (copied.getCurrentStateName() != CardStateName.Original) { - copied.setState(CardStateName.Original, false); - } - - copied.updateStateForView(); - } - } - if (!suppress) { if (zoneFrom == null) { copied.getOwner().addInboundToken(copied); @@ -298,15 +266,29 @@ public class GameAction { ReplacementResult repres = game.getReplacementHandler().run(ReplacementType.Moved, repParams); if (repres != ReplacementResult.NotReplaced) { // reset failed manifested Cards back to original - if (c.isManifested()) { + if (c.isManifested() && !c.isInZone(ZoneType.Battlefield)) { c.turnFaceUp(false, false); } - if (game.getStack().isResolving(c) && !zoneTo.is(ZoneType.Graveyard) && repres == ReplacementResult.Prevented) { - copied.getOwner().removeInboundToken(copied); - return moveToGraveyard(c, cause, params); - } copied.getOwner().removeInboundToken(copied); + + if (repres == ReplacementResult.Prevented) { + if (game.getStack().isResolving(c) && !zoneTo.is(ZoneType.Graveyard)) { + return moveToGraveyard(c, cause, params); + } + + copied.clearDevoured(); + copied.clearDelved(); + copied.clearConvoked(); + copied.clearExploited(); + } + + // was replaced with another Zone Change + if (toBattlefield && c.isInZone(ZoneType.Battlefield)) { + if (c.removeChangedState()) { + c.updateStateForView(); + } + } return c; } } @@ -370,7 +352,7 @@ public class GameAction { // do ETB counters after zone add if (!suppress) { - if (toBattlefield ) { + if (toBattlefield) { copied.putEtbCounters(table); // enable replacement effects again for (final ReplacementEffect re : copied.getReplacementEffects()) { @@ -393,6 +375,14 @@ public class GameAction { c.setMustAttackEntity(null); } + // for ETB trigger to work correct, + // the LKI needs to be the Card itself, + // or it might not updated correctly + // TODO be reworked when ZoneTrigger Update is done + if (toBattlefield) { + lastKnownInfo = c; + } + // Need to apply any static effects to produce correct triggers checkStaticAbilities(); game.getTriggerHandler().clearInstrinsicActiveTriggers(c, zoneFrom); @@ -415,7 +405,7 @@ public class GameAction { } game.getTriggerHandler().runTrigger(TriggerType.ChangesZone, runParams, true); - if (zoneFrom != null && zoneFrom.is(ZoneType.Battlefield) && !zoneFrom.getPlayer().equals(zoneTo.getPlayer())) { + if (fromBattlefield && !zoneFrom.getPlayer().equals(zoneTo.getPlayer())) { final Map runParams2 = AbilityKey.mapFromCard(lastKnownInfo); runParams2.put(AbilityKey.OriginalController, zoneFrom.getPlayer()); if(params != null) { @@ -447,7 +437,7 @@ public class GameAction { copied.clearExploited(); } - // rule 504.6: reveal a face-down card leaving the stack + // rule 504.6: reveal a face-down card leaving the stack if (zoneFrom != null && zoneTo != null && zoneFrom.is(ZoneType.Stack) && !zoneTo.is(ZoneType.Battlefield) && wasFacedown) { Card revealLKI = CardUtil.getLKICopy(c); revealLKI.turnFaceUp(true, false); @@ -491,7 +481,7 @@ public class GameAction { // Remove all changed keywords copied.removeAllChangedText(game.getNextTimestamp()); } else if (toBattlefield) { - // reset timestamp in changezone effects so they have same timestamp if ETB simutaneously + // reset timestamp in changezone effects so they have same timestamp if ETB simutaneously copied.setTimestamp(game.getNextTimestamp()); for (Player p : game.getPlayers()) { copied.getDamageHistory().setNotAttackedSinceLastUpkeepOf(p); @@ -540,7 +530,7 @@ public class GameAction { return moveTo(zoneTo, c, position, cause, null); } - private Card moveTo(final Zone zoneTo, Card c, SpellAbility cause, Map params) { + public final Card moveTo(final Zone zoneTo, Card c, SpellAbility cause, Map params) { // FThreads.assertExecutedByEdt(false); // This code must never be executed from EDT, // use FThreads.invokeInNewThread to run code in a pooled thread return moveTo(zoneTo, c, null, cause, params); @@ -618,8 +608,12 @@ public class GameAction { } public final Card moveToStack(final Card c, SpellAbility cause) { + return moveToStack(c, cause, null); + } + + public final Card moveToStack(final Card c, SpellAbility cause, Map params) { final Zone stack = game.getStackZone(); - return moveTo(stack, c, cause); + return moveTo(stack, c, cause, params); } public final Card moveToGraveyard(final Card c, SpellAbility cause) { @@ -634,7 +628,7 @@ public class GameAction { public final Card moveToHand(final Card c, SpellAbility cause) { return moveToHand(c, cause, null); } - + public final Card moveToHand(final Card c, SpellAbility cause, Map params) { final PlayerZone hand = c.getOwner().getZone(ZoneType.Hand); return moveTo(hand, c, cause, params); @@ -648,7 +642,7 @@ public class GameAction { public final Card moveToPlay(final Card c, final Player p, SpellAbility cause) { return moveToPlay(c, p, cause, null); } - + public final Card moveToPlay(final Card c, final Player p, SpellAbility cause, Map params) { // move to a specific player's Battlefield final PlayerZone play = p.getZone(ZoneType.Battlefield); @@ -658,7 +652,7 @@ public class GameAction { public final Card moveToBottomOfLibrary(final Card c, SpellAbility cause) { return moveToBottomOfLibrary(c, cause, null); } - + public final Card moveToBottomOfLibrary(final Card c, SpellAbility cause, Map params) { return moveToLibrary(c, -1, cause, params); } @@ -666,7 +660,7 @@ public class GameAction { public final Card moveToLibrary(final Card c, SpellAbility cause) { return moveToLibrary(c, cause, null); } - + public final Card moveToLibrary(final Card c, SpellAbility cause, Map params) { return moveToLibrary(c, 0, cause, params); } @@ -674,7 +668,7 @@ public class GameAction { public final Card moveToLibrary(Card c, int libPosition, SpellAbility cause) { return moveToLibrary(c, libPosition, cause, null); } - + public final Card moveToLibrary(Card c, int libPosition, SpellAbility cause, Map params) { final PlayerZone library = c.getOwner().getZone(ZoneType.Library); if (libPosition == -1 || libPosition > library.size()) { @@ -684,11 +678,15 @@ public class GameAction { } public final Card moveToVariantDeck(Card c, ZoneType zone, int deckPosition, SpellAbility cause) { + return moveToVariantDeck(c, zone, deckPosition, cause, null); + } + + public final Card moveToVariantDeck(Card c, ZoneType zone, int deckPosition, SpellAbility cause, Map params) { final PlayerZone deck = c.getOwner().getZone(zone); if (deckPosition == -1 || deckPosition > deck.size()) { deckPosition = deck.size(); } - return changeZone(game.getZoneOf(c), deck, c, deckPosition, cause); + return changeZone(game.getZoneOf(c), deck, c, deckPosition, cause, params); } public final Card exile(final Card c, SpellAbility cause) { @@ -723,16 +721,20 @@ public class GameAction { } public final Card moveTo(final ZoneType name, final Card c, final int libPosition, SpellAbility cause) { + return moveTo(name, c, libPosition, cause, null); + } + + public final Card moveTo(final ZoneType name, final Card c, final int libPosition, SpellAbility cause, Map params) { // Call specific functions to set PlayerZone, then move onto moveTo switch(name) { - case Hand: return moveToHand(c, cause); - case Library: return moveToLibrary(c, libPosition, cause); - case Battlefield: return moveToPlay(c, cause); - case Graveyard: return moveToGraveyard(c, cause); - case Exile: return exile(c, cause); - case Stack: return moveToStack(c, cause); - case PlanarDeck: return moveToVariantDeck(c, ZoneType.PlanarDeck, libPosition, cause); - case SchemeDeck: return moveToVariantDeck(c, ZoneType.SchemeDeck, libPosition, cause); + case Hand: return moveToHand(c, cause, params); + case Library: return moveToLibrary(c, libPosition, cause, params); + case Battlefield: return moveToPlay(c, c.getController(), cause, params); + case Graveyard: return moveToGraveyard(c, cause, params); + case Exile: return exile(c, cause, params); + case Stack: return moveToStack(c, cause, params); + case PlanarDeck: return moveToVariantDeck(c, ZoneType.PlanarDeck, libPosition, cause, params); + case SchemeDeck: return moveToVariantDeck(c, ZoneType.SchemeDeck, libPosition, cause, params); default: // sideboard will also get there return moveTo(c.getOwner().getZone(name), c, cause); } @@ -793,7 +795,7 @@ public class GameAction { } }); - + final Comparator comp = new Comparator() { @Override public int compare(final StaticAbility a, final StaticAbility b) { @@ -1073,7 +1075,7 @@ public class GameAction { if (!game.isGameOver()) { checkGameOverCondition(); } - + if (game.getAge() != GameStage.Play) { return; } diff --git a/forge-game/src/main/java/forge/game/GlobalRuleChange.java b/forge-game/src/main/java/forge/game/GlobalRuleChange.java index ac75f132904..6a95f71a0ef 100644 --- a/forge-game/src/main/java/forge/game/GlobalRuleChange.java +++ b/forge-game/src/main/java/forge/game/GlobalRuleChange.java @@ -34,8 +34,7 @@ public enum GlobalRuleChange { onlyOneBlockerPerOpponent ("Each opponent can't block with more than one creature."), onlyTwoBlockers ("No more than two creatures can block each combat."), toughnessAssignsDamage ("Each creature assigns combat damage equal to its toughness rather than its power."), - blankIsChaos("Each blank roll of the planar dice is a {CHAOS} roll."), - noLandBattlefield("Lands can't enter the battlefield."); + blankIsChaos("Each blank roll of the planar dice is a {CHAOS} roll."); private final String ruleText; diff --git a/forge-game/src/main/java/forge/game/ability/AbilityFactory.java b/forge-game/src/main/java/forge/game/ability/AbilityFactory.java index a09664512c8..55bdfd789e6 100644 --- a/forge-game/src/main/java/forge/game/ability/AbilityFactory.java +++ b/forge-game/src/main/java/forge/game/ability/AbilityFactory.java @@ -54,7 +54,8 @@ public final class AbilityFactory { "Execute", // DelayedTrigger "FallbackAbility", // Complex Unless costs which can be unpayable "ChooseSubAbility", // Can choose a player via ChoosePlayer - "CantChooseSubAbility" // Can't choose a player via ChoosePlayer + "CantChooseSubAbility", // Can't choose a player via ChoosePlayer + "AnimateSubAbility" // For ChangeZone Effects to Animate before ETB ); public enum AbilityRecordType { diff --git a/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneAllEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneAllEffect.java index ef2950ad9bc..c9ad6dd4e02 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneAllEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneAllEffect.java @@ -1,6 +1,8 @@ package forge.game.ability.effects; import com.google.common.collect.Iterables; +import com.google.common.collect.Maps; + import forge.game.Game; import forge.game.GameActionUtil; import forge.game.ability.AbilityKey; @@ -92,7 +94,7 @@ public class ChangeZoneAllEffect extends SpellAbilityEffect { sa.getActivatingPlayer().getController().reveal(handCards, ZoneType.Hand, handCards.get(0).getOwner()); } } - + cards = (CardCollection)AbilityUtils.filterListByType(cards, sa.getParam("ChangeType"), sa); if (sa.hasParam("Optional")) { @@ -150,7 +152,19 @@ public class ChangeZoneAllEffect extends SpellAbilityEffect { } } + Map moveParams = Maps.newEnumMap(AbilityKey.class); + if (destination == ZoneType.Battlefield) { + + if (sa.hasAdditionalAbility("AnimateSubAbility")) { + // need LKI before Animate does apply + moveParams.put(AbilityKey.CardLKI, CardUtil.getLKICopy(c)); + + source.addRemembered(c); + AbilityUtils.resolve(sa.getAdditionalAbility("AnimateSubAbility")); + source.removeRemembered(c); + } + // Auras without Candidates stay in their current location if (c.isAura()) { final SpellAbility saAura = c.getFirstAttachSpell(); @@ -165,9 +179,9 @@ public class ChangeZoneAllEffect extends SpellAbilityEffect { Card movedCard = null; if (sa.hasParam("GainControl")) { c.setController(sa.getActivatingPlayer(), game.getNextTimestamp()); - movedCard = game.getAction().moveToPlay(c, sa.getActivatingPlayer(), sa); + movedCard = game.getAction().moveToPlay(c, sa.getActivatingPlayer(), sa, moveParams); } else { - movedCard = game.getAction().moveTo(destination, c, libraryPos, sa); + movedCard = game.getAction().moveTo(destination, c, libraryPos, sa, moveParams); if (destination == ZoneType.Exile && !c.isToken()) { Card host = sa.getOriginalHost(); if (host == null) { @@ -205,7 +219,7 @@ public class ChangeZoneAllEffect extends SpellAbilityEffect { if (destination == ZoneType.Battlefield) { movedCard.setTimestamp(ts); } - + if (!movedCard.getZone().equals(originZone)) { triggerList.put(originZone.getZoneType(), movedCard.getZone().getZoneType(), movedCard); } diff --git a/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneEffect.java index 7191e7b66d4..c68a48b9836 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneEffect.java @@ -4,6 +4,8 @@ import com.google.common.base.Predicate; import com.google.common.base.Predicates; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; +import com.google.common.collect.Maps; + import forge.GameCommand; import forge.card.CardStateName; import forge.game.Game; @@ -523,6 +525,17 @@ public class ChangeZoneEffect extends SpellAbilityEffect { } } + Map moveParams = Maps.newEnumMap(AbilityKey.class); + + if (sa.hasAdditionalAbility("AnimateSubAbility")) { + // need LKI before Animate does apply + moveParams.put(AbilityKey.CardLKI, CardUtil.getLKICopy(tgtC)); + + hostCard.addRemembered(tgtC); + AbilityUtils.resolve(sa.getAdditionalAbility("AnimateSubAbility")); + hostCard.removeRemembered(tgtC); + } + // Auras without Candidates stay in their current // location if (tgtC.isAura()) { @@ -530,13 +543,16 @@ public class ChangeZoneEffect extends SpellAbilityEffect { if (saAura != null) { saAura.setActivatingPlayer(sa.getActivatingPlayer()); if (!saAura.getTargetRestrictions().hasCandidates(saAura, false)) { + if (sa.hasAdditionalAbility("AnimateSubAbility")) { + tgtC.removeChangedState(); + } continue; } } } movedCard = game.getAction().moveTo( - tgtC.getController().getZone(destination), tgtC, sa); + tgtC.getController().getZone(destination), tgtC, sa, moveParams); if (sa.hasParam("Unearth")) { movedCard.setUnearthed(true); movedCard.addChangedCardKeywords(Lists.newArrayList("Haste"), null, false, false, @@ -983,6 +999,18 @@ public class ChangeZoneEffect extends SpellAbilityEffect { if (sa.hasParam("Tapped")) { c.setTapped(true); } + + Map moveParams = Maps.newEnumMap(AbilityKey.class); + + if (sa.hasAdditionalAbility("AnimateSubAbility")) { + // need LKI before Animate does apply + moveParams.put(AbilityKey.CardLKI, CardUtil.getLKICopy(c)); + + source.addRemembered(c); + AbilityUtils.resolve(sa.getAdditionalAbility("AnimateSubAbility")); + source.removeRemembered(c); + } + if (sa.hasParam("GainControl")) { Player newController = sa.getActivatingPlayer(); if (sa.hasParam("NewController")) { @@ -1109,7 +1137,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect { c.addFaceupCommand(unanimate); } } - movedCard = game.getAction().moveTo(c.getController().getZone(destination), c, sa); + movedCard = game.getAction().moveTo(c.getController().getZone(destination), c, sa, moveParams); if (sa.hasParam("Tapped")) { movedCard.setTapped(true); } diff --git a/forge-game/src/main/java/forge/game/ability/effects/FlipCoinEffect.java b/forge-game/src/main/java/forge/game/ability/effects/FlipCoinEffect.java index 5a22c55676c..e16f59a92c5 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/FlipCoinEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/FlipCoinEffect.java @@ -117,7 +117,7 @@ public class FlipCoinEffect extends SpellAbilityEffect { } else { if (victory) { if (sa.getParam("RememberWinner") != null) { - host.addRemembered(host); + host.addRemembered(flipper); } if (sa.hasAdditionalAbility("WinSubAbility")) { @@ -126,7 +126,7 @@ public class FlipCoinEffect extends SpellAbilityEffect { // runParams.put("Won","True"); } else { if (sa.getParam("RememberLoser") != null) { - host.addRemembered(host); + host.addRemembered(flipper); } if (sa.hasAdditionalAbility("LoseSubAbility")) { diff --git a/forge-game/src/main/java/forge/game/ability/effects/ManifestEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ManifestEffect.java index 3a5d74e5d27..fed406415c2 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/ManifestEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/ManifestEffect.java @@ -1,15 +1,11 @@ package forge.game.ability.effects; -import com.google.common.collect.Sets; - import forge.game.Game; -import forge.game.GlobalRuleChange; import forge.game.ability.AbilityUtils; import forge.game.ability.SpellAbilityEffect; import forge.game.card.Card; import forge.game.card.CardCollection; import forge.game.card.CardLists; -import forge.game.card.CardUtil; import forge.game.player.Player; import forge.game.spellability.SpellAbility; import forge.game.zone.ZoneType; @@ -56,20 +52,8 @@ public class ManifestEffect extends SpellAbilityEffect { } for(Card c : tgtCards) { - //check if lki would be a land entering the battlefield - if (game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noLandBattlefield)) { - Card lki = CardUtil.getLKICopy(c); - lki.turnFaceDownNoUpdate(); - lki.setManifested(true); - lki.setLastKnownZone(p.getZone(ZoneType.Battlefield)); - CardCollection preList = new CardCollection(lki); - game.getAction().checkStaticAbilities(false, Sets.newHashSet(lki), preList); - if (lki.isLand()) { - continue; - } - } Card rem = c.manifest(p, sa); - if (sa.hasParam("RememberManifested") && rem != null) { + if (sa.hasParam("RememberManifested") && rem != null && rem.isManifested()) { source.addRemembered(rem); } } diff --git a/forge-game/src/main/java/forge/game/card/Card.java b/forge-game/src/main/java/forge/game/card/Card.java index bf91cfabb11..5c4c8eaa2f3 100644 --- a/forge-game/src/main/java/forge/game/card/Card.java +++ b/forge-game/src/main/java/forge/game/card/Card.java @@ -633,7 +633,7 @@ public class Card extends GameEntity implements Comparable { Card c = game.getAction().moveToPlay(this, p, sa); // Add manifest demorph static ability for creatures - if (isCreature && !cost.isNoCost()) { + if (c.isManifested() && isCreature && !cost.isNoCost()) { // Add Manifest to original State c.getState(CardStateName.Original).addSpellAbility(CardFactoryUtil.abilityManifestFaceUp(c, cost)); c.updateStateForView(); @@ -1168,10 +1168,10 @@ public class Card extends GameEntity implements Comparable { return mustAttackEntity; } public final void clearMustAttackEntity(final Player playerturn) { - if (getController().equals(playerturn)) { - mustAttackEntity = null; - } - mustAttackEntityThisTurn = null; + if (getController().equals(playerturn)) { + mustAttackEntity = null; + } + mustAttackEntityThisTurn = null; } public final GameEntity getMustAttackEntityThisTurn() { return mustAttackEntityThisTurn; } public final void setMustAttackEntityThisTurn(GameEntity entThisTurn) { mustAttackEntityThisTurn = entThisTurn; } @@ -2841,10 +2841,10 @@ public class Card extends GameEntity implements Comparable { } public final CardPlayOption mayPlay(final StaticAbility sta) { - if (sta == null) { - return null; - } - return mayPlay.get(sta); + if (sta == null) { + return null; + } + return mayPlay.get(sta); } public final List mayPlay(final Player player) { @@ -3078,6 +3078,27 @@ public class Card extends GameEntity implements Comparable { return Collections.unmodifiableMap(changedCardTypes); } + public boolean clearChangedCardTypes() { + if (changedCardTypes.isEmpty()) + return false; + changedCardTypes.clear(); + return true; + } + + public boolean clearChangedCardKeywords() { + if (changedCardKeywords.isEmpty()) + return false; + changedCardKeywords.clear(); + return true; + } + + public boolean clearChangedCardColors() { + if (changedCardColors.isEmpty()) + return false; + changedCardColors.clear(); + return true; + } + public Map getChangedCardKeywords() { return changedCardKeywords; } @@ -3281,8 +3302,13 @@ public class Card extends GameEntity implements Comparable { return clStates.getHost(); } - public final void removeCloneStates() { + public final boolean removeCloneStates() { + if (clonedStates.isEmpty()) { + return false; + } clonedStates.clear(); + updateCloneState(false); + return true; } public final Map getCloneStates() { @@ -3334,8 +3360,13 @@ public class Card extends GameEntity implements Comparable { } return false; } - public final void removeTextChangeStates() { + public final boolean removeTextChangeStates() { + if (textChangeStates.isEmpty()) { + return false; + } textChangeStates.clear(); + updateCloneState(false); + return true; } private final CardCloneStates getLastTextChangeState() { @@ -3502,8 +3533,8 @@ public class Card extends GameEntity implements Comparable { } public final boolean toughnessAssignsDamage() { - return getGame().getStaticEffects().getGlobalRuleChange(GlobalRuleChange.toughnessAssignsDamage) - || hasKeyword("CARDNAME assigns combat damage equal to its toughness rather than its power"); + return getGame().getStaticEffects().getGlobalRuleChange(GlobalRuleChange.toughnessAssignsDamage) + || hasKeyword("CARDNAME assigns combat damage equal to its toughness rather than its power"); } // How much combat damage does the card deal @@ -3646,6 +3677,14 @@ public class Card extends GameEntity implements Comparable { } } + public boolean clearChangedCardTraits() { + if (changedCardTraits.isEmpty()) { + return false; + } + changedCardTraits.clear(); + return true; + } + // keywords are like flying, fear, first strike, etc... public final List getKeywords() { return getKeywords(currentState); @@ -3687,7 +3726,7 @@ public class Card extends GameEntity implements Comparable { } public final void updateKeywords() { - currentState.getView().updateKeywords(this, currentState); + currentState.getView().updateKeywords(this, currentState); } public final void addChangedCardKeywords(final List keywords, final List removeKeywords, @@ -4694,8 +4733,8 @@ public class Card extends GameEntity implements Comparable { public final boolean hasDealtDamageToOpponentThisTurn() { for (final GameEntity e : getDamageHistory().getThisTurnDamaged()) { - if (e instanceof Player) { - final Player p = (Player) e; + if (e instanceof Player) { + final Player p = (Player) e; if (getController().isOpponentOf(p)) { return true; } @@ -5132,7 +5171,7 @@ public class Card extends GameEntity implements Comparable { return eternalized; } public final void setEternalized(final boolean b) { - eternalized = b; + eternalized = b; } public final int getExertedThisTurn() { @@ -5835,10 +5874,10 @@ public class Card extends GameEntity implements Comparable { continue; } if (drawbackOnly && params.containsKey("Execute")){ - String exec = this.getSVar(params.get("Execute")); - if (exec.contains("AB$")) { - continue; - } + String exec = this.getSVar(params.get("Execute")); + if (exec.contains("AB$")) { + continue; + } } return true; } @@ -6482,4 +6521,22 @@ public class Card extends GameEntity implements Comparable { public boolean canBlockAny() { return !canBlockAny.isEmpty(); } + + public boolean removeChangedState() { + boolean updateState = false; + updateState |= removeCloneStates(); + updateState |= removeTextChangeStates(); + + updateState |= clearChangedCardTypes(); + updateState |= clearChangedCardKeywords(); + updateState |= clearChangedCardColors(); + updateState |= clearChangedCardTraits(); + + newPT.clear(); + newPTCharacterDefining.clear(); + + clearEtbCounters(); + + return updateState; + } } diff --git a/forge-game/src/main/java/forge/game/replacement/ReplacementHandler.java b/forge-game/src/main/java/forge/game/replacement/ReplacementHandler.java index d2f9cd9e063..fb9be6b94f2 100644 --- a/forge-game/src/main/java/forge/game/replacement/ReplacementHandler.java +++ b/forge-game/src/main/java/forge/game/replacement/ReplacementHandler.java @@ -84,6 +84,9 @@ public class ReplacementHandler { affectedCard = (Card) runParams.get(AbilityKey.Affected); affectedLKI = CardUtil.getLKICopy(affectedCard); affectedLKI.setLastKnownZone(affectedCard.getController().getZone(ZoneType.Battlefield)); + + // need to apply Counters to check its future state on the battlefield + affectedLKI.putEtbCounters(null); preList.add(affectedLKI); game.getAction().checkStaticAbilities(false, Sets.newHashSet(affectedLKI), preList); checkAgain = true; diff --git a/forge-gui-desktop/src/test/java/forge/ai/simulation/GameSimulatorTest.java b/forge-gui-desktop/src/test/java/forge/ai/simulation/GameSimulatorTest.java index 38c36aed3b6..288b62b0fe7 100644 --- a/forge-gui-desktop/src/test/java/forge/ai/simulation/GameSimulatorTest.java +++ b/forge-gui-desktop/src/test/java/forge/ai/simulation/GameSimulatorTest.java @@ -2121,6 +2121,43 @@ public class GameSimulatorTest extends SimulationTestCase { assertEquals(1, simWC.getPowerBonusFromCounters()); assertEquals(3, simGame.getPlayers().get(0).getCreaturesInPlay().size()); + } + public void testEverAfterWithWaywardServant() { + Game game = initAndCreateGame(); + Player p = game.getPlayers().get(0); + String everAfter = "Ever After"; + String waywardServant = "Wayward Servant"; + String goblin = "Raging Goblin"; + + for (int i = 0; i < 8; i++) + addCard("Swamp", p); + + Card cardEverAfter = addCardToZone(everAfter, p, ZoneType.Hand); + Card cardWaywardServant = addCardToZone(waywardServant, p, ZoneType.Graveyard); + Card cardRagingGoblin = addCardToZone(goblin, p, ZoneType.Graveyard); + + game.getPhaseHandler().devModeSet(PhaseType.MAIN1, p); + game.getAction().checkStateEffects(true); + + SpellAbility playSa = cardEverAfter.getSpellAbilities().get(0); + playSa.setActivatingPlayer(p); + playSa.getTargets().add(cardWaywardServant); + playSa.getTargets().add(cardRagingGoblin); + + GameSimulator sim = createSimulator(game, p); + int origScore = sim.getScoreForOrigGame().value; + int score = sim.simulateSpellAbility(playSa).value; + assertTrue(String.format("score=%d vs. origScore=%d", score, origScore), score > origScore); + + Game simGame = sim.getSimulatedGameState(); + + Card simGoblin = findCardWithName(simGame, goblin); + + simGame.getAction().checkStateEffects(true); + simGame.getPhaseHandler().devAdvanceToPhase(PhaseType.MAIN2); + + assertEquals(21, simGame.getPlayers().get(0).getLife()); + assertEquals(true, simGoblin.getType().hasSubtype("Zombie")); } } diff --git a/forge-gui/res/cardsfolder/b/bronzehide_lion.txt b/forge-gui/res/cardsfolder/b/bronzehide_lion.txt index b241fcd59bc..d746fd6bfad 100644 --- a/forge-gui/res/cardsfolder/b/bronzehide_lion.txt +++ b/forge-gui/res/cardsfolder/b/bronzehide_lion.txt @@ -3,9 +3,9 @@ ManaCost:G W Types:Creature Cat PT:3/3 A:AB$ Pump | Cost$ G W | KW$ Indestructible | Defined$ Self | SpellDescription$ CARDNAME gains indestructible until end of turn. -T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Self | TriggerController$ TriggeredCardController | Execute$ DBAnimate | TriggerDescription$ When CARDNAME dies, return it to the battlefield. It's an Aura enchantment with enchant creature you control and CARDNAME has "{G}{W}: Enchanted creature gains indestructible until end of turn," and it loses all other abilities. -SVar:DBAnimate:DB$ Animate | Defined$ TriggeredCard | Types$ Enchantment,Aura | RemoveCardTypes$ True | RemoveAllAbilities$ True | Keywords$ Enchant creature you control | Abilities$ SPAttach,ABPump | Permanent$ True | SubAbility$ DBReturn -SVar:DBReturn:DB$ ChangeZone | Defined$ TriggeredCard | Origin$ Graveyard | Destination$ Battlefield +T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Self | TriggerController$ TriggeredCardController | Execute$ DBReturn | TriggerDescription$ When CARDNAME dies, return it to the battlefield. It's an Aura enchantment with enchant creature you control and CARDNAME has "{G}{W}: Enchanted creature gains indestructible until end of turn," and it loses all other abilities. +SVar:DBReturn:DB$ ChangeZone | Defined$ TriggeredCard | Origin$ Graveyard | Destination$ Battlefield | AnimateSubAbility$ DBAnimate +SVar:DBAnimate:DB$ Animate | Defined$ Remembered | Types$ Enchantment,Aura | RemoveCardTypes$ True | RemoveAllAbilities$ True | Keywords$ Enchant creature you control | Abilities$ SPAttach,ABPump | Permanent$ True SVar:SPAttach:SP$ Attach | Cost$ 0 | ValidTgts$ Creature.YouCtrl | AILogic$ Pump SVar:ABPump:AB$ Pump | Cost$ G W | KW$ Indestructible | Defined$ Enchanted | SpellDescription$ Enchanted creature gains indestructible until end of turn. Oracle:{G}{W}: Bronzehide Lion gains indestructible until end of turn.\nWhen Bronzehide Lion dies, return it to the battlefield. It's an Aura enchantment with enchant creature you control and "{G}{W}: Enchanted creature gains indestructible until end of turn," and it loses all other abilities. diff --git a/forge-gui/res/cardsfolder/c/chainer_dementia_master.txt b/forge-gui/res/cardsfolder/c/chainer_dementia_master.txt index d9dc7150103..d3552564736 100644 --- a/forge-gui/res/cardsfolder/c/chainer_dementia_master.txt +++ b/forge-gui/res/cardsfolder/c/chainer_dementia_master.txt @@ -3,8 +3,8 @@ ManaCost:3 B B Types:Legendary Creature Human Minion PT:3/3 S:Mode$ Continuous | Affected$ Creature.Nightmare | AddPower$ 1 | AddToughness$ 1 | Description$ Nightmare creatures get +1/+1. -A:AB$ ChangeZone | Cost$ B B B PayLife<3> | Origin$ Graveyard | Destination$ Battlefield | GainControl$ True | TgtPrompt$ Select target creature card in a graveyard | ValidTgts$ Creature | ChangeNum$ 1 | SubAbility$ DBAnimate| SpellDescription$ Put target creature card from a graveyard onto the battlefield under your control. That creature is black and is a Nightmare in addition to its other creature types. -SVar:DBAnimate:DB$Animate | Defined$ Targeted | Types$ Nightmare | Colors$ Black | Permanent$ True | OverwriteColors$ True +A:AB$ ChangeZone | Cost$ B B B PayLife<3> | Origin$ Graveyard | Destination$ Battlefield | GainControl$ True | TgtPrompt$ Select target creature card in a graveyard | ValidTgts$ Creature | ChangeNum$ 1 | AnimateSubAbility$ DBAnimate | SpellDescription$ Put target creature card from a graveyard onto the battlefield under your control. That creature is black and is a Nightmare in addition to its other creature types. +SVar:DBAnimate:DB$ Animate | Defined$ Targeted | Types$ Nightmare | Colors$ Black | Permanent$ True | OverwriteColors$ True T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Any | ValidCard$ Card.Self | Execute$ TrigExile | TriggerController$ TriggeredCardController | TriggerDescription$ When CARDNAME leaves the battlefield, exile all Nightmares. SVar:TrigExile:DB$ ChangeZoneAll | Origin$ Battlefield | Destination$ Exile | ChangeType$ Nightmare SVar:PlayMain1:TRUE diff --git a/forge-gui/res/cardsfolder/d/dance_of_the_manse.txt b/forge-gui/res/cardsfolder/d/dance_of_the_manse.txt index a999663808e..8d5f6a376a7 100644 --- a/forge-gui/res/cardsfolder/d/dance_of_the_manse.txt +++ b/forge-gui/res/cardsfolder/d/dance_of_the_manse.txt @@ -1,8 +1,8 @@ Name:Dance of the Manse ManaCost:X W U Types:Sorcery -A:SP$ ChangeZone | Cost$ X W U | Announce$ X | Origin$ Graveyard | Destination$ Battlefield | ValidTgts$ Artifact.cmcLEX+YouOwn,Enchantment.cmcLEX+YouOwn+nonAura | TgtPrompt$ Select target artifact or non-Aura enchantment in your graveyard | TargetMin$ 0 | TargetMax$ X | SubAbility$ DBAnimate | SpellDescription$ Return up to X target artifact and/or non-Aura enchantment cards with converted mana cost X or less from your graveyard to the battlefield. If X is 6 or more, those permanents are 4/4 creatures in addition to their other types. -SVar:DBAnimate:DB$ Animate | Defined$ Targeted | Types$ Creature | Power$ 4 | Toughness$ 4 | Permanent$ True | ConditionCheckSVar$ X | ConditionSVarCompare$ GE6 | References$ X +A:SP$ ChangeZone | Cost$ X W U | Announce$ X | Origin$ Graveyard | Destination$ Battlefield | ValidTgts$ Artifact.cmcLEX+YouOwn,Enchantment.cmcLEX+YouOwn+nonAura | TgtPrompt$ Select target artifact or non-Aura enchantment in your graveyard | TargetMin$ 0 | TargetMax$ X | AnimateSubAbility$ DBAnimate | SpellDescription$ Return up to X target artifact and/or non-Aura enchantment cards with converted mana cost X or less from your graveyard to the battlefield. If X is 6 or more, those permanents are 4/4 creatures in addition to their other types. +SVar:DBAnimate:DB$ Animate | Defined$ Remembered | Types$ Creature | Power$ 4 | Toughness$ 4 | Permanent$ True | ConditionCheckSVar$ X | ConditionSVarCompare$ GE6 | References$ X SVar:X:Count$xPaid AI:RemoveDeck:All Oracle:Return up to X target artifact and/or non-Aura enchantment cards each with converted mana cost X or less from your graveyard to the battlefield. If X is 6 or more, those permanents are 4/4 creatures in addition to their other types. diff --git a/forge-gui/res/cardsfolder/d/dread_slaver.txt b/forge-gui/res/cardsfolder/d/dread_slaver.txt index 1102019c201..6c55c77bc19 100644 --- a/forge-gui/res/cardsfolder/d/dread_slaver.txt +++ b/forge-gui/res/cardsfolder/d/dread_slaver.txt @@ -3,8 +3,6 @@ ManaCost:3 B B Types:Creature Zombie Horror PT:3/5 T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Creature.DamagedBy | Execute$ TrigChange | TriggerDescription$ Whenever a creature dealt damage by CARDNAME this turn dies, return it to the battlefield under your control. That creature is a black Zombie in addition to its other colors and types. -SVar:TrigChange:DB$ ChangeZone | Origin$ Graveyard | Destination$ Battlefield | GainControl$ True | Defined$ TriggeredCard | RememberChanged$ True | SubAbility$ DBAnimate -SVar:DBAnimate:DB$ Animate | Defined$ Remembered | Types$ Zombie | Colors$ Black | Permanent$ True | SubAbility$ DBCleanup -SVar:DBCleanup:DB$Cleanup | ClearRemembered$ True -SVar:Picture:http://www.wizards.com/global/images/magic/general/dread_slaver.jpg +SVar:TrigChange:DB$ ChangeZone | Origin$ Graveyard | Destination$ Battlefield | GainControl$ True | Defined$ TriggeredCard | AnimateSubAbility$ DBAnimate +SVar:DBAnimate:DB$ Animate | Defined$ Remembered | Types$ Zombie | Colors$ Black | Permanent$ True Oracle:Whenever a creature dealt damage by Dread Slaver this turn dies, return it to the battlefield under your control. That creature is a black Zombie in addition to its other colors and types. diff --git a/forge-gui/res/cardsfolder/e/ever_after.txt b/forge-gui/res/cardsfolder/e/ever_after.txt index 23f9690a315..831f5aa0798 100644 --- a/forge-gui/res/cardsfolder/e/ever_after.txt +++ b/forge-gui/res/cardsfolder/e/ever_after.txt @@ -1,9 +1,9 @@ Name:Ever After ManaCost:4 B B Types:Sorcery -A:SP$ ChangeZone | Cost$ 4 B B | Origin$ Graveyard | Destination$ Battlefield | TgtPrompt$ Choose target creature card in your graveyard | ValidTgts$ Creature.YouOwn | TargetMin$ 0 | TargetMax$ 2 | SubAbility$ Animate | SpellDescription$ Return up to two target creature cards from your graveyard onto the battlefield. -SVar:Animate:DB$Animate | Defined$ Targeted | Types$ Zombie | Colors$ Black | Permanent$ True | SubAbility$ DBPut | SpellDescription$ Each of those creatures is a black Zombie in addition to its other colors and types. +A:SP$ ChangeZone | Cost$ 4 B B | Origin$ Graveyard | Destination$ Battlefield | TgtPrompt$ Choose target creature card in your graveyard | ValidTgts$ Creature.YouOwn | TargetMin$ 0 | TargetMax$ 2 | AnimateSubAbility$ Animate | SubAbility$ DBPut | SpellDescription$ Return up to two target creature cards from your graveyard onto the battlefield. +SVar:Animate:DB$Animate | Defined$ Remembered | Types$ Zombie | Colors$ Black | Permanent$ True | SpellDescription$ Each of those creatures is a black Zombie in addition to its other colors and types. SVar:DBPut:DB$ChangeZone | Origin$ Stack | Destination$ Library | LibraryPosition$ -1 | SpellDescription$ Put Ever After on the bottom of its owner's library. -DeckHints:Ability$Graveyard & Ability$Discard +DeckHints:Ability$Graveyard & Ability$Discard & Type$Zombie SVar:Picture:http://www.wizards.com/global/images/magic/general/ever_after.jpg Oracle:Return up to two target creature cards from your graveyard to the battlefield. Each of those creatures is a black Zombie in addition to its other colors and types. Put Ever After on the bottom of its owner's library. diff --git a/forge-gui/res/cardsfolder/g/grafdiggers_cage.txt b/forge-gui/res/cardsfolder/g/grafdiggers_cage.txt index d736b534973..e3d09b80855 100644 --- a/forge-gui/res/cardsfolder/g/grafdiggers_cage.txt +++ b/forge-gui/res/cardsfolder/g/grafdiggers_cage.txt @@ -1,10 +1,8 @@ Name:Grafdigger's Cage ManaCost:1 Types:Artifact -R:Event$Moved | ActiveZones$ Battlefield | Origin$ Graveyard | Destination$ Battlefield | ValidCard$ Creature | Prevent$ True | Description$ Creature cards in graveyards and libraries can't enter the battlefield. -R:Event$Moved | ActiveZones$ Battlefield | Origin$ Library | Destination$ Battlefield | ValidCard$ Creature | Prevent$ True -S:Mode$ CantBeCast | Origin$ Graveyard | Description$ Players can't cast spells from graveyards or libraries. -S:Mode$ CantBeCast | Origin$ Library +R:Event$ Moved | ActiveZones$ Battlefield | Origin$ Graveyard,Library | Destination$ Battlefield | ValidLKI$ Creature.Other | Prevent$ True | Description$ Creature cards in graveyards and libraries can't enter the battlefield. +S:Mode$ CantBeCast | Origin$ Graveyard,Library | Description$ Players can't cast spells from graveyards or libraries. SVar:NonStackingEffect:True AI:RemoveDeck:Random SVar:Picture:http://www.wizards.com/global/images/magic/general/grafdiggers_cage.jpg diff --git a/forge-gui/res/cardsfolder/g/grave_betrayal.txt b/forge-gui/res/cardsfolder/g/grave_betrayal.txt index 4f47935ce00..a8157f7ffc2 100644 --- a/forge-gui/res/cardsfolder/g/grave_betrayal.txt +++ b/forge-gui/res/cardsfolder/g/grave_betrayal.txt @@ -1,12 +1,9 @@ Name:Grave Betrayal ManaCost:5 B B Types:Enchantment -T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Creature.YouDontCtrl | TriggerZones$ Battlefield | Execute$ TrigEffect | TriggerDescription$ Whenever a creature you don't control dies, return it to the battlefield under your control with an additional +1/+1 counter on it at the beginning of the next end step. That creature is a black Zombie in addition to its other colors and types. -SVar:TrigEffect:DB$ Effect | Name$ Grave Betrayal Effect | Triggers$ TrigEOT | SVars$ GBReturn,GBCounter,GBZombify,GBCleanup | References$ GBReturn,GBCounter,GBZombify,GBCleanup | RememberObjects$ TriggeredCard | Duration$ Permanent -SVar:TrigEOT:Mode$ Phase | Phase$ End of Turn | Execute$ GBReturn | TriggerDescription$ Return creature to the battlefield under your control with an additional +1/+1 counter on it at the beginning of the next end step. It is a black Zombie in addition to its other colors and types. -SVar:GBReturn:DB$ ChangeZone | Defined$ Remembered | Origin$ Graveyard | Destination$ Battlefield | GainControl$ True | RememberChanged$ True | ForgetOtherRemembered$ True | SubAbility$ GBCounter -SVar:GBCounter:DB$ PutCounter | Defined$ Remembered | CounterType$ P1P1 | CounterNum$ 1 | SubAbility$ GBZombify -SVar:GBZombify:DB$ Animate | Defined$ Remembered | Types$ Zombie | Colors$ Black | Permanent$ True | SubAbility$ GBCleanup -SVar:GBCleanup:DB$ ChangeZone | Defined$ Self | Origin$ Command | Destination$ Exile -SVar:Picture:http://www.wizards.com/global/images/magic/general/grave_betrayal.jpg +T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Creature.YouDontCtrl | TriggerZones$ Battlefield | Execute$ DelTrig | TriggerDescription$ Whenever a creature you don't control dies, return it to the battlefield under your control with an additional +1/+1 counter on it at the beginning of the next end step. That creature is a black Zombie in addition to its other colors and types. +SVar:DelTrig:DB$ DelayedTrigger | Mode$ Phase | Phase$ End of Turn | Execute$ GBReturn | RememberObjects$ Remembered | SubAbility$ DBCleanup | TriggerDescription$ Return creature to the battlefield under your control with an additional +1/+1 counter on it at the beginning of the next end step. It is a black Zombie in addition to its other colors and types. +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True +SVar:GBReturn:DB$ ChangeZone | Defined$ DelayTriggerRemembered | Origin$ Graveyard | Destination$ Battlefield | GainControl$ True | WithCounters$ P1P1_1 | AnimateSubAbility$ GBZombify +SVar:GBZombify:DB$ Animate | Defined$ Remembered | Types$ Zombie | Colors$ Black | Permanent$ True Oracle:Whenever a creature you don't control dies, return it to the battlefield under your control with an additional +1/+1 counter on it at the beginning of the next end step. That creature is a black Zombie in addition to its other colors and types. diff --git a/forge-gui/res/cardsfolder/g/grimoire_of_the_dead.txt b/forge-gui/res/cardsfolder/g/grimoire_of_the_dead.txt index e549db6be35..77d0777360b 100644 --- a/forge-gui/res/cardsfolder/g/grimoire_of_the_dead.txt +++ b/forge-gui/res/cardsfolder/g/grimoire_of_the_dead.txt @@ -2,9 +2,8 @@ Name:Grimoire of the Dead ManaCost:4 Types:Legendary Artifact A:AB$ PutCounter | Cost$ 1 T Discard<1/Card> | Defined$ Self | CounterType$ STUDY | CounterNum$ 1 | SpellDescription$ Put a study counter on Grimoire of the Dead. -A:AB$ ChangeZoneAll | Cost$ T SubCounter<3/STUDY> Sac<1/CARDNAME> | ChangeType$ Creature | Origin$ Graveyard | Destination$ Battlefield | GainControl$ True | RememberChanged$ True | SubAbility$ DBAnimate | SpellDescription$ Put all creature cards in all graveyards onto the battlefield under your control. They are black Zombies in addition to their other colors and types. -SVar:DBAnimate:DB$ AnimateAll | ValidCards$ Creature.IsRemembered | Colors$ Black | Types$ Zombie | Permanent$ True | SubAbility$ DBCleanup -SVar:DBCleanup:DB$Cleanup | ClearRemembered$ True +A:AB$ ChangeZoneAll | Cost$ T SubCounter<3/STUDY> Sac<1/CARDNAME> | ChangeType$ Creature | Origin$ Graveyard | Destination$ Battlefield | GainControl$ True | AnimateSubAbility$ DBAnimate | SpellDescription$ Put all creature cards in all graveyards onto the battlefield under your control. They are black Zombies in addition to their other colors and types. +SVar:DBAnimate:DB$ Animate | Defined$ Remembered | Colors$ Black | Types$ Zombie | Permanent$ True AI:RemoveDeck:All SVar:IsReanimatorCard:TRUE SVar:Picture:http://www.wizards.com/global/images/magic/general/grimoire_of_the_dead.jpg diff --git a/forge-gui/res/cardsfolder/k/kunoros_hound_of_athreos.txt b/forge-gui/res/cardsfolder/k/kunoros_hound_of_athreos.txt index d4493caebf9..6a483991c7b 100644 --- a/forge-gui/res/cardsfolder/k/kunoros_hound_of_athreos.txt +++ b/forge-gui/res/cardsfolder/k/kunoros_hound_of_athreos.txt @@ -5,7 +5,7 @@ PT:3/3 K:Vigilance K:Menace K:Lifelink -R:Event$Moved | ActiveZones$ Battlefield | Origin$ Graveyard | Destination$ Battlefield | ValidCard$ Creature | Prevent$ True | Description$ Creature cards in graveyards can't enter the battlefield. +R:Event$Moved | ActiveZones$ Battlefield | Origin$ Graveyard | Destination$ Battlefield | ValidLKI$ Creature.Other | Prevent$ True | Description$ Creature cards in graveyards can't enter the battlefield. S:Mode$ CantBeCast | Origin$ Graveyard | Description$ Players can't cast spells from graveyards. SVar:NonStackingEffect:True Oracle:Vigilance, menace, lifelink\nCreature cards in graveyards can't enter the battlefield.\nPlayers can't cast spells from graveyards. diff --git a/forge-gui/res/cardsfolder/l/liliana_deaths_majesty.txt b/forge-gui/res/cardsfolder/l/liliana_deaths_majesty.txt index 7b60b8f9b3d..5c758b41e9e 100644 --- a/forge-gui/res/cardsfolder/l/liliana_deaths_majesty.txt +++ b/forge-gui/res/cardsfolder/l/liliana_deaths_majesty.txt @@ -4,8 +4,7 @@ Types:Legendary Planeswalker Liliana Loyalty:5 A:AB$ Token | Cost$ AddCounter<1/LOYALTY> | TokenAmount$ 1 | TokenScript$ b_2_2_zombie | TokenOwner$ You | LegacyImage$ b 2 2 zombie akh | Planeswalker$ True | SubAbility$ DBMill | SpellDescription$ Create a 2/2 black Zombie creature token. Put the top two cards of your library into your graveyard. SVar:DBMill:DB$Mill | Defined$ You | NumCards$ 2 -A:AB$ ChangeZone | Cost$ SubCounter<3/LOYALTY> | Origin$ Graveyard | Destination$ Battlefield | ValidTgts$ Creature.YouCtrl | SubAbility$ Animate | TgtPrompt$ Select target creature card from your graveyard | Planeswalker$ True | SpellDescription$ Return target creature card from your graveyard to the battlefield. That creature is a black Zombie in addition to its other colors and types. -SVar:Animate:DB$Animate | Defined$ Targeted | Types$ Zombie | Colors$ Black | Permanent$ True +A:AB$ ChangeZone | Cost$ SubCounter<3/LOYALTY> | Origin$ Graveyard | Destination$ Battlefield | ValidTgts$ Creature.YouCtrl | AnimateSubAbility$ Animate | TgtPrompt$ Select target creature card from your graveyard | Planeswalker$ True | SpellDescription$ Return target creature card from your graveyard to the battlefield. That creature is a black Zombie in addition to its other colors and types. +SVar:Animate:DB$Animate | Defined$ Remembered | Types$ Zombie | Colors$ Black | Permanent$ True A:AB$ DestroyAll | Cost$ SubCounter<7/LOYALTY> | Ultimate$ True | ValidCards$ Creature.nonZombie | Planeswalker$ True | SpellDescription$ Destroy all non-Zombie creatures. -SVar:Picture:http://www.wizards.com/global/images/magic/general/liliana_deaths_majesty.jpg Oracle:[+1]: Create a 2/2 black Zombie creature token. Put the top two cards of your library into your graveyard.\n[-3]: Return target creature card from your graveyard to the battlefield. That creature is a black Zombie in addition to its other colors and types.\n[-7]: Destroy all non-Zombie creatures. diff --git a/forge-gui/res/cardsfolder/l/lim_dul_the_necromancer.txt b/forge-gui/res/cardsfolder/l/lim_dul_the_necromancer.txt index 74a0afd484a..f01cf65e29c 100644 --- a/forge-gui/res/cardsfolder/l/lim_dul_the_necromancer.txt +++ b/forge-gui/res/cardsfolder/l/lim_dul_the_necromancer.txt @@ -3,9 +3,8 @@ ManaCost:5 B B Types:Legendary Creature Human Wizard PT:4/4 T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Creature.OppCtrl | TriggerZones$ Battlefield | Execute$ TrigReturn | OptionalDecider$ You | TriggerDescription$ Whenever a creature an opponent controls dies, you may pay {1}{B}. If you do, return that card to the battlefield under your control. If it's a creature, it's a Zombie in addition to its other creature types. -SVar:TrigReturn:AB$ ChangeZone | Cost$ 1 B | Origin$ Graveyard | Destination$ Battlefield | GainControl$ True | Defined$ TriggeredCard | RememberChanged$ True | SubAbility$ Animate -SVar:Animate:DB$ Animate | Defined$ Remembered | Types$ Zombie | Permanent$ True | ConditionDefined$ Remembered | ConditionPresent$ Creature | SubAbility$ DBCleanup -SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True +SVar:TrigReturn:AB$ ChangeZone | Cost$ 1 B | Origin$ Graveyard | Destination$ Battlefield | GainControl$ True | Defined$ TriggeredCard | AnimateSubAbility$ Animate +SVar:Animate:DB$ Animate | Defined$ Remembered | Types$ Zombie | Permanent$ True | ConditionDefined$ Remembered | ConditionPresent$ Creature A:AB$ Regenerate | ValidTgts$ Zombie | TgtPrompt$ Select target Zombie | Cost$ 1 B | SpellDescription$ Regenerate target Zombie. SVar:Picture:http://www.wizards.com/global/images/magic/general/lim_dul_the_necromancer.jpg Oracle:Whenever a creature an opponent controls dies, you may pay {1}{B}. If you do, return that card to the battlefield under your control. If it's a creature, it's a Zombie in addition to its other creature types.\n{1}{B}: Regenerate target Zombie. diff --git a/forge-gui/res/cardsfolder/n/necromantic_selection.txt b/forge-gui/res/cardsfolder/n/necromantic_selection.txt index 6fae2656b76..777556b2fd4 100644 --- a/forge-gui/res/cardsfolder/n/necromantic_selection.txt +++ b/forge-gui/res/cardsfolder/n/necromantic_selection.txt @@ -1,10 +1,11 @@ Name:Necromantic Selection ManaCost:4 B B B Types:Sorcery -A:SP$ DestroyAll | Cost$ 4 B B B | ValidCards$ Creature | RememberDestroyed$ True | SubAbility$ DBReturn | SpellDescription$ Destroy all creatures, then return a creature card put into a graveyard this way to the battlefield under your control. It's a black Zombie in addition to its other colors and types. Exile CARDNAME. -SVar:DBReturn:DB$ ChangeZone | ChangeType$ Creature.nonToken+IsRemembered | ChangeNum$ 1 | Hidden$ True | Origin$ Graveyard | Destination$ Battlefield | GainControl$ True | RememberChanged$ True | ForgetOtherRemembered$ True | SubAbility$ DBZombify -SVar:DBZombify:DB$ Animate | Defined$ Remembered | Types$ Zombie | Colors$ Black | Permanent$ True | SubAbility$ DBCleanup -SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True | SubAbility$ DBChange -SVar:DBChange:DB$ ChangeZone | Origin$ Stack | Destination$ Exile -SVar:Picture:http://www.wizards.com/global/images/magic/general/necromantic_selection.jpg +A:SP$ DestroyAll | Cost$ 4 B B B | ValidCards$ Creature | RememberDestroyed$ True | SubAbility$ TrigImprint | SpellDescription$ Destroy all creatures, then return a creature card put into a graveyard this way to the battlefield under your control. It's a black Zombie in addition to its other colors and types. Exile CARDNAME. +SVar:TrigImprint:DB$ Pump | ImprintCards$ Remembered | SubAbility$ DBClearRemember | StackDescription$ None +SVar:DBClearRemember:DB$ Cleanup | ClearRemembered$ True | SubAbility$ DBReturn +SVar:DBReturn:DB$ ChangeZone | ChangeType$ Creature.nonToken+IsImprinted | ChangeNum$ 1 | Hidden$ True | Origin$ Graveyard | Destination$ Battlefield | GainControl$ True | AnimateSubAbility$ DBZombify | SubAbility$ DBCleanup +SVar:DBZombify:DB$ Animate | Defined$ Remembered | Types$ Zombie | Colors$ Black | Permanent$ True +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True | ClearImprinted$ True | SubAbility$ DBChange +SVar:DBChange:DB$ ChangeZone | Origin$ Stack | Destination$ Exile | StackDescription$ None Oracle:Destroy all creatures, then return a creature card put into a graveyard this way to the battlefield under your control. It's a black Zombie in addition to its other colors and types. Exile Necromantic Selection. diff --git a/forge-gui/res/cardsfolder/r/rise_from_the_grave.txt b/forge-gui/res/cardsfolder/r/rise_from_the_grave.txt index 8354f264af9..3a0886fbacd 100644 --- a/forge-gui/res/cardsfolder/r/rise_from_the_grave.txt +++ b/forge-gui/res/cardsfolder/r/rise_from_the_grave.txt @@ -1,7 +1,6 @@ Name:Rise from the Grave ManaCost:4 B Types:Sorcery -A:SP$ ChangeZone | Cost$ 4 B | Origin$ Graveyard | Destination$ Battlefield | GainControl$ True | TgtPrompt$ Choose target creature card in a graveyard | ValidTgts$ Creature | SubAbility$ Animate | SpellDescription$ Put target creature card from a graveyard onto the battlefield under your control. That creature is a black Zombie in addition to its other colors and types. -SVar:Animate:DB$Animate | Defined$ Targeted | Types$ Zombie | Colors$ Black | Permanent$ True -SVar:Picture:http://www.wizards.com/global/images/magic/general/rise_from_the_grave.jpg +A:SP$ ChangeZone | Cost$ 4 B | Origin$ Graveyard | Destination$ Battlefield | GainControl$ True | TgtPrompt$ Choose target creature card in a graveyard | ValidTgts$ Creature | AnimateSubAbility$ Animate | SpellDescription$ Put target creature card from a graveyard onto the battlefield under your control. That creature is a black Zombie in addition to its other colors and types. +SVar:Animate:DB$Animate | Defined$ Remembered | Types$ Zombie | Colors$ Black | Permanent$ True Oracle:Put target creature card from a graveyard onto the battlefield under your control. That creature is a black Zombie in addition to its other colors and types. diff --git a/forge-gui/res/cardsfolder/s/sorin_vengeful_bloodlord.txt b/forge-gui/res/cardsfolder/s/sorin_vengeful_bloodlord.txt index dc657b9e48c..b50ed11b96c 100644 --- a/forge-gui/res/cardsfolder/s/sorin_vengeful_bloodlord.txt +++ b/forge-gui/res/cardsfolder/s/sorin_vengeful_bloodlord.txt @@ -5,7 +5,7 @@ Loyalty:4 S:Mode$ Continuous | Affected$ Creature.YouCtrl,Planeswalker.YouCtrl | AddKeyword$ Lifelink | Condition$ PlayerTurn | Description$ As long as it's your turn, creatures and planeswalkers you control have lifelink. SVar:NonStackingEffect:True A:AB$ DealDamage | Cost$ AddCounter<2/LOYALTY> | Planeswalker$ True | ValidTgts$ Player,Planeswalker | TgtPrompt$ Select target player or planeswalker | NumDmg$ 1 | SpellDescription$ CARDNAME deals 1 damage to target player or planeswalker. -A:AB$ ChangeZone | Cost$ SubCounter | Planeswalker$ True | Origin$ Graveyard | Destination$ Battlefield | References$ X | ValidTgts$ Creature.YouOwn | AILogic$ SorinVengefulBloodlord | TgtPrompt$ Select target creature with converted mana cost X from your graveyard | SubAbility$ Animate | SpellDescription$ Return target creature card with converted mana cost X from your graveyard to the battlefield. That creature is a Vampire in addition to its other types. -SVar:Animate:DB$ Animate | Defined$ Targeted | Types$ Vampire | Permanent$ True +A:AB$ ChangeZone | Cost$ SubCounter | Planeswalker$ True | Origin$ Graveyard | Destination$ Battlefield | References$ X | ValidTgts$ Creature.YouOwn | AILogic$ SorinVengefulBloodlord | TgtPrompt$ Select target creature with converted mana cost X from your graveyard | AnimateSubAbility$ Animate | SpellDescription$ Return target creature card with converted mana cost X from your graveyard to the battlefield. That creature is a Vampire in addition to its other types. +SVar:Animate:DB$ Animate | Defined$ Remembered | Types$ Vampire | Permanent$ True SVar:X:Targeted$CardManaCost Oracle:As long as it's your turn, creatures and planeswalkers you control have lifelink.\n[+2]: Sorin, Vengeful Bloodlord deals 1 damage to target player or planeswalker.\n-X: Return target creature card with converted mana cost X from your graveyard to the battlefield. That creature is a Vampire in addition to its other types. diff --git a/forge-gui/res/cardsfolder/w/worms_of_the_earth.txt b/forge-gui/res/cardsfolder/w/worms_of_the_earth.txt index 136b72cdd85..e2af0854bcb 100644 --- a/forge-gui/res/cardsfolder/w/worms_of_the_earth.txt +++ b/forge-gui/res/cardsfolder/w/worms_of_the_earth.txt @@ -2,7 +2,7 @@ Name:Worms of the Earth ManaCost:2 B B B Types:Enchantment S:Mode$ CantPlayLand | Description$ Players can't play lands. -S:Mode$ Continuous | EffectZone$ Battlefield | GlobalRule$ Lands can't enter the battlefield. | Description$ Lands can't enter the battlefield. +R:Event$ Moved | ActiveZones$ Battlefield | Destination$ Battlefield | ValidCard$ Land | Prevent$ True | Description$ Lands can't enter the battlefield. T:Mode$ Phase | Phase$ Upkeep | TriggerZones$ Battlefield | Execute$ RepeatAbility | TriggerDescription$ At the beginning of each upkeep, any player may sacrifice two lands or have CARDNAME deal 5 damage to that player. If a player does either, destroy CARDNAME. SVar:RepeatAbility:DB$ RepeatEach | RepeatPlayers$ Player | RepeatSubAbility$ DBChoose SVar:DBChoose:DB$ GenericChoice | Defined$ Player.IsRemembered | Choices$ SacTwoLands,DealDmg | AILogic$ PayUnlessCost