diff --git a/Mage.Sets/src/mage/sets/fatereforged/GhastlyConscription.java b/Mage.Sets/src/mage/sets/fatereforged/GhastlyConscription.java index a1e5cedd51e..667318aa6d7 100644 --- a/Mage.Sets/src/mage/sets/fatereforged/GhastlyConscription.java +++ b/Mage.Sets/src/mage/sets/fatereforged/GhastlyConscription.java @@ -107,8 +107,10 @@ class GhastlyConscriptionEffect extends OneShotEffect { } Collections.shuffle(cardsToManifest); game.informPlayers(controller.getName() + " shuffles the face-down pile"); + Ability newSource = source.copy(); + newSource.setWorksFaceDown(true); for (Card card: cardsToManifest) { - if (card.moveToZone(Zone.BATTLEFIELD, source.getSourceId(), game, false)) { + if (card.moveToZone(Zone.BATTLEFIELD, newSource.getSourceId(), game, false)) { game.informPlayers(new StringBuilder(controller.getName()) .append(" puts facedown card from exile onto the battlefield").toString()); ManaCosts manaCosts = null; @@ -120,7 +122,7 @@ class GhastlyConscriptionEffect extends OneShotEffect { } ContinuousEffect effect = new BecomesFaceDownCreatureEffect(manaCosts, true, Duration.Custom, FaceDownType.MANIFESTED); effect.setTargetPointer(new FixedTarget(card.getId())); - game.addEffect(effect, source); + game.addEffect(effect, newSource); } } return true; diff --git a/Mage.Sets/src/mage/sets/fatereforged/HungeringYeti.java b/Mage.Sets/src/mage/sets/fatereforged/HungeringYeti.java index 35b88614dbc..d29bc53609f 100644 --- a/Mage.Sets/src/mage/sets/fatereforged/HungeringYeti.java +++ b/Mage.Sets/src/mage/sets/fatereforged/HungeringYeti.java @@ -65,7 +65,7 @@ public class HungeringYeti extends CardImpl { // As long as you control a green or blue permanent, you may cast Hungering Yeti as though it had flash. AsThoughEffect effect = new CastAsThoughItHadFlashSourceEffect(Duration.EndOfGame); - effect.setText("As long as you control a green or blue permanent, you may cast Hungering Yeti as though it had flash"); + effect.setText("As long as you control a green or blue permanent, you may cast {this} as though it had flash"); this.addAbility(new SimpleStaticAbility(Zone.ALL, new ConditionalAsThoughEffect(effect, new PermanentsOnTheBattlefieldCondition(filter)))); diff --git a/Mage.Sets/src/mage/sets/fatereforged/JeskaiInfiltrator.java b/Mage.Sets/src/mage/sets/fatereforged/JeskaiInfiltrator.java index ef252aeceef..38edb88f163 100644 --- a/Mage.Sets/src/mage/sets/fatereforged/JeskaiInfiltrator.java +++ b/Mage.Sets/src/mage/sets/fatereforged/JeskaiInfiltrator.java @@ -119,7 +119,7 @@ class JeskaiInfiltratorEffect extends OneShotEffect { sourceCard.setFaceDown(true); cardsToManifest.add(sourceCard); } - if (player.getLibrary().size() > 0) { + if (sourcePermanent!= null && player.getLibrary().size() > 0) { Card cardFromLibrary = player.getLibrary().removeFromTop(game); cardFromLibrary.setFaceDown(true); player.moveCardToExileWithInfo(cardFromLibrary, sourcePermanent.getId(), sourcePermanent.getName(), source.getSourceId(), game, Zone.LIBRARY); @@ -127,8 +127,10 @@ class JeskaiInfiltratorEffect extends OneShotEffect { } Collections.shuffle(cardsToManifest); game.fireUpdatePlayersEvent(); // removes Jeskai from Battlefield, so he returns as a fresh permanent to the battlefield with new position + Ability newSource = source.copy(); + newSource.setWorksFaceDown(true); for (Card card : cardsToManifest) { - if (card.moveToZone(Zone.BATTLEFIELD, source.getSourceId(), game, false)) { + if (card.moveToZone(Zone.BATTLEFIELD, newSource.getSourceId(), game, false)) { game.informPlayers(new StringBuilder(player.getName()) .append(" puts facedown card from exile onto the battlefield").toString()); ManaCosts manaCosts = null; @@ -145,7 +147,7 @@ class JeskaiInfiltratorEffect extends OneShotEffect { FaceDownType.MANIFESTED ); effect.setTargetPointer(new FixedTarget(card.getId())); - game.addEffect(effect, source); + game.addEffect(effect, newSource); } } diff --git a/Mage.Sets/src/mage/sets/fatereforged/RallyTheAncestors.java b/Mage.Sets/src/mage/sets/fatereforged/RallyTheAncestors.java index 34470b3dc1c..4d6d0bf6c40 100644 --- a/Mage.Sets/src/mage/sets/fatereforged/RallyTheAncestors.java +++ b/Mage.Sets/src/mage/sets/fatereforged/RallyTheAncestors.java @@ -59,7 +59,8 @@ public class RallyTheAncestors extends CardImpl { super(ownerId, 22, "Rally the Ancestors", Rarity.RARE, new CardType[]{CardType.INSTANT}, "{X}{W}{W}"); this.expansionSetCode = "FRF"; - // Return each creature card with converted mana cost X or less from your graveyard to the battlefield. Exile those creatures at the beginning of your next upkeep. Exile Rally the Ancestors. + // Return each creature card with converted mana cost X or less from your graveyard to the battlefield. + // Exile those creatures at the beginning of your next upkeep. Exile Rally the Ancestors. this.getSpellAbility().addEffect(new RallyTheAncestorsEffect()); this.getSpellAbility().addEffect(ExileSpellEffect.getInstance()); } @@ -101,7 +102,7 @@ class RallyTheAncestorsEffect extends OneShotEffect { for (Card card : cards) { if (card != null) { player.putOntoBattlefieldWithInfo(card, game, Zone.GRAVEYARD, source.getSourceId()); - Effect exileEffect = new ExileTargetEffect(); + Effect exileEffect = new ExileTargetEffect("Exile those creatures at the beginning of your next upkeep"); exileEffect.setTargetPointer(new FixedTarget(card.getId())); DelayedTriggeredAbility delayedAbility = new AtTheBeginOfYourNextUpkeepDelayedTriggeredAbility(exileEffect); delayedAbility.setSourceId(source.getSourceId()); diff --git a/Mage.Sets/src/mage/sets/theros/AshiokNightmareWeaver.java b/Mage.Sets/src/mage/sets/theros/AshiokNightmareWeaver.java index de5dde0e8f1..c6c25653b77 100644 --- a/Mage.Sets/src/mage/sets/theros/AshiokNightmareWeaver.java +++ b/Mage.Sets/src/mage/sets/theros/AshiokNightmareWeaver.java @@ -28,6 +28,7 @@ package mage.sets.theros; import java.util.UUID; +import mage.MageObject; import mage.abilities.Ability; import mage.abilities.LoyaltyAbility; import mage.abilities.common.EntersBattlefieldAbility; @@ -121,11 +122,12 @@ class AshiokNightmareWeaverExileEffect extends OneShotEffect { UUID exileId = CardUtil.getCardExileZoneId(game, source); Player opponent = game.getPlayer(this.getTargetPointer().getFirst(game, source)); Player controller = game.getPlayer(source.getControllerId()); - if (opponent != null && controller != null) { + MageObject sourceObject = game.getObject(source.getSourceId()); + if (sourceObject != null && opponent != null && controller != null) { for (int i = 0; i < 3; i++) { Card card = opponent.getLibrary().getFromTop(game); if (card != null) { - controller.moveCardToExileWithInfo(card, exileId, "Ashiok, Nightmare Weaver", source.getSourceId(), game, Zone.LIBRARY); + controller.moveCardToExileWithInfo(card, exileId, sourceObject.getLogName(), source.getSourceId(), game, Zone.LIBRARY); } } return true; @@ -166,7 +168,8 @@ class AshiokNightmareWeaverPutIntoPlayEffect extends OneShotEffect { FilterCard filter = new FilterCreatureCard(new StringBuilder("creature card with converted mana cost {").append(cmc).append("} exiled with Ashiok, Nightmare Weaver").toString()); filter.add(new ConvertedManaCostPredicate(Filter.ComparisonType.Equal, cmc)); - Target target = new TargetCardInExile(filter, CardUtil.getCardExileZoneId(game, source)); + + Target target = new TargetCardInExile(filter, CardUtil.getCardExileZoneId(game, source.getSourceId(), game.getPermanent(source.getSourceId()) == null)); if (target.canChoose(source.getSourceId(), player.getId(), game)) { if (player.chooseTarget(Outcome.PutCreatureInPlay, target, source, game)) { diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/enters/BanisherPriestTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/enters/BanisherPriestTest.java index bcdce8ffe7a..e364611bc94 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/enters/BanisherPriestTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/enters/BanisherPriestTest.java @@ -83,7 +83,7 @@ public class BanisherPriestTest extends CardTestPlayerBase { * Rockslide Elemental * Creature — Elemental 1/1, 2R (3) * First strike. - * Whenever another creature dies, you may put a +1/+1 counter on Rockslide Elemental.. + * Whenever another creature dies, you may put a +1/+1 counter on Rockslide Elemental. */ addCard(Zone.BATTLEFIELD, playerB, "Rockslide Elemental"); diff --git a/Mage/src/mage/abilities/DelayedTriggeredAbility.java b/Mage/src/mage/abilities/DelayedTriggeredAbility.java index d0457dc5c75..15517e64715 100644 --- a/Mage/src/mage/abilities/DelayedTriggeredAbility.java +++ b/Mage/src/mage/abilities/DelayedTriggeredAbility.java @@ -76,7 +76,11 @@ public abstract class DelayedTriggeredAbility extends TriggeredAbilityImpl { return triggerOnlyOnce; } - public void init(Game game) {}; + public void init(Game game) { + for (Effect effect: this.getEffects()) { + effect.getTargetPointer().init(game, this); + } + }; public boolean isInactive(Game game) { return false; diff --git a/Mage/src/mage/abilities/common/delayed/AtTheBeginOfNextEndStepDelayedTriggeredAbility.java b/Mage/src/mage/abilities/common/delayed/AtTheBeginOfNextEndStepDelayedTriggeredAbility.java index 333f5c1c774..9fee24b35a7 100644 --- a/Mage/src/mage/abilities/common/delayed/AtTheBeginOfNextEndStepDelayedTriggeredAbility.java +++ b/Mage/src/mage/abilities/common/delayed/AtTheBeginOfNextEndStepDelayedTriggeredAbility.java @@ -61,14 +61,6 @@ public class AtTheBeginOfNextEndStepDelayedTriggeredAbility extends DelayedTrigg this.targetController = ability.targetController; } - @Override - public void init(Game game) { - super.init(game); - for (Effect effect: this.getEffects()) { - effect.getTargetPointer().init(game, this); - } - } - @Override public boolean checkTrigger(GameEvent event, Game game) { if (event.getType() == EventType.END_TURN_STEP_PRE) { diff --git a/Mage/src/mage/abilities/common/delayed/OnLeaveReturnExiledToBattlefieldAbility.java b/Mage/src/mage/abilities/common/delayed/OnLeaveReturnExiledToBattlefieldAbility.java index c2b3fdfd842..8040de94858 100644 --- a/Mage/src/mage/abilities/common/delayed/OnLeaveReturnExiledToBattlefieldAbility.java +++ b/Mage/src/mage/abilities/common/delayed/OnLeaveReturnExiledToBattlefieldAbility.java @@ -100,7 +100,8 @@ class ReturnExiledPermanentsEffect extends OneShotEffect { public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); if (controller != null) { - ExileZone exile = game.getExile().getExileZone(CardUtil.getCardExileZoneId(game, source)); + // because source already changed zone we have to get previous related exile zone + ExileZone exile = game.getExile().getExileZone(CardUtil.getCardExileZoneId(game, source.getSourceId(), true)); if (exile != null) { LinkedList cards = new LinkedList<>(exile); for (UUID cardId : cards) { diff --git a/Mage/src/mage/abilities/effects/keyword/ManifestEffect.java b/Mage/src/mage/abilities/effects/keyword/ManifestEffect.java index 66a91b550f2..db40f3b4b68 100644 --- a/Mage/src/mage/abilities/effects/keyword/ManifestEffect.java +++ b/Mage/src/mage/abilities/effects/keyword/ManifestEffect.java @@ -74,10 +74,12 @@ public class ManifestEffect extends OneShotEffect { public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); if (controller != null) { + Ability newSource = source.copy(); + newSource.setWorksFaceDown(true); List cards = controller.getLibrary().getTopCards(game, amount); for (Card card: cards) { card.setFaceDown(true); - controller.putOntoBattlefieldWithInfo(card, game, Zone.LIBRARY, source.getSourceId()); + controller.putOntoBattlefieldWithInfo(card, game, Zone.LIBRARY, newSource.getSourceId()); Permanent permanent = game.getPermanent(card.getId()); if (permanent != null) { permanent.setManifested(true); @@ -90,7 +92,7 @@ public class ManifestEffect extends OneShotEffect { } ContinuousEffect effect = new BecomesFaceDownCreatureEffect(manaCosts, true, Duration.Custom, FaceDownType.MANIFESTED); effect.setTargetPointer(new FixedTarget(card.getId())); - game.addEffect(effect, source); + game.addEffect(effect, newSource); } } game.applyEffects(); // to apply before ETB triggered or replace Effects are executed diff --git a/Mage/src/mage/abilities/effects/keyword/ManifestTargetPlayerEffect.java b/Mage/src/mage/abilities/effects/keyword/ManifestTargetPlayerEffect.java index 4b395e9c155..1432f39a98e 100644 --- a/Mage/src/mage/abilities/effects/keyword/ManifestTargetPlayerEffect.java +++ b/Mage/src/mage/abilities/effects/keyword/ManifestTargetPlayerEffect.java @@ -78,10 +78,12 @@ public class ManifestTargetPlayerEffect extends OneShotEffect { public boolean apply(Game game, Ability source) { Player targetPlayer = game.getPlayer(getTargetPointer().getFirst(game, source)); if (targetPlayer != null) { + Ability newSource = source.copy(); + newSource.setWorksFaceDown(true); List cards = targetPlayer.getLibrary().getTopCards(game, amount); for (Card card: cards) { card.setFaceDown(true); - targetPlayer.putOntoBattlefieldWithInfo(card, game, Zone.LIBRARY, source.getSourceId()); + targetPlayer.putOntoBattlefieldWithInfo(card, game, Zone.LIBRARY, newSource.getSourceId()); Permanent permanent = game.getPermanent(card.getId()); if (permanent != null) { permanent.setManifested(true); @@ -94,7 +96,7 @@ public class ManifestTargetPlayerEffect extends OneShotEffect { } ContinuousEffect effect = new BecomesFaceDownCreatureEffect(manaCosts, true, Duration.Custom, FaceDownType.MANIFESTED); effect.setTargetPointer(new FixedTarget(card.getId())); - game.addEffect(effect, source); + game.addEffect(effect, newSource); } } return true; } diff --git a/Mage/src/mage/abilities/keyword/MorphAbility.java b/Mage/src/mage/abilities/keyword/MorphAbility.java index a2c86d32bdc..7ea4aedb550 100644 --- a/Mage/src/mage/abilities/keyword/MorphAbility.java +++ b/Mage/src/mage/abilities/keyword/MorphAbility.java @@ -126,6 +126,7 @@ public class MorphAbility extends StaticAbility implements AlternativeSourceCost ruleText = sb.toString(); Ability ability = new SimpleStaticAbility(Zone.BATTLEFIELD, new BecomesFaceDownCreatureEffect(morphCosts, FaceDownType.MORPHED)); + ability.setWorksFaceDown(true); ability.setRuleVisible(false); card.addAbility(ability); diff --git a/Mage/src/mage/abilities/keyword/UndyingAbility.java b/Mage/src/mage/abilities/keyword/UndyingAbility.java index 95e87dd44a2..796ae53ad8d 100644 --- a/Mage/src/mage/abilities/keyword/UndyingAbility.java +++ b/Mage/src/mage/abilities/keyword/UndyingAbility.java @@ -1,5 +1,6 @@ package mage.abilities.keyword; +import java.util.UUID; import mage.constants.Duration; import mage.constants.Outcome; import mage.abilities.Ability; @@ -36,9 +37,9 @@ public class UndyingAbility extends DiesTriggeredAbility { @Override public boolean checkTrigger(GameEvent event, Game game) { if (super.checkTrigger(event, game)) { - Permanent p = (Permanent) game.getLastKnownInformation(event.getTargetId(), Zone.BATTLEFIELD); - if (!p.getCounters().containsKey(CounterType.P1P1) || p.getCounters().getCount(CounterType.P1P1) == 0) { - game.getState().setValue(new StringBuilder("undying").append(getSourceId()).toString(), new FixedTarget(p.getId())); + Permanent permanent = (Permanent) game.getLastKnownInformation(event.getTargetId(), Zone.BATTLEFIELD); + if (!permanent.getCounters().containsKey(CounterType.P1P1) || permanent.getCounters().getCount(CounterType.P1P1) == 0) { + game.getState().setValue("undying" + getSourceId(),permanent.getId()); return true; } } @@ -109,8 +110,10 @@ class UndyingReplacementEffect extends ReplacementEffectImpl { @Override public boolean applies(GameEvent event, Ability source, Game game) { if (event.getTargetId().equals(source.getSourceId())) { - Object fixedTarget = game.getState().getValue(new StringBuilder("undying").append(source.getSourceId()).toString()); - if (fixedTarget instanceof FixedTarget && ((FixedTarget) fixedTarget).getFirst(game, source).equals(source.getSourceId())) { + // Check if undying condition is true + UUID target = (UUID) game.getState().getValue("undying" + source.getSourceId()); + if (target.equals(source.getSourceId())) { + game.getState().setValue("undying" + source.getSourceId(), null); return true; } } diff --git a/Mage/src/mage/cards/Card.java b/Mage/src/mage/cards/Card.java index fbc46b116ef..e91f5ed8d1a 100644 --- a/Mage/src/mage/cards/Card.java +++ b/Mage/src/mage/cards/Card.java @@ -77,6 +77,7 @@ public interface Card extends MageObject { void assignNewId(); int getZoneChangeCounter(); + void updateZoneChangeCounter(); void addInfo(String key, String value); diff --git a/Mage/src/mage/cards/CardImpl.java b/Mage/src/mage/cards/CardImpl.java index 94edeefb395..aada14c563c 100644 --- a/Mage/src/mage/cards/CardImpl.java +++ b/Mage/src/mage/cards/CardImpl.java @@ -665,7 +665,8 @@ public abstract class CardImpl extends MageObjectImpl implements Card { return zoneChangeCounter; } - private void updateZoneChangeCounter() { + @Override + public void updateZoneChangeCounter() { zoneChangeCounter++; } diff --git a/Mage/src/mage/game/permanent/PermanentCard.java b/Mage/src/mage/game/permanent/PermanentCard.java index 12a3cf61901..1c9910abac0 100644 --- a/Mage/src/mage/game/permanent/PermanentCard.java +++ b/Mage/src/mage/game/permanent/PermanentCard.java @@ -135,9 +135,10 @@ public class PermanentCard extends PermanentImpl { Zone fromZone = game.getState().getZone(objectId); Player controller = game.getPlayer(controllerId); if (controller != null && controller.removeFromBattlefield(this, game)) { + Card originalCard = game.getCard(this.getId()); if (isFaceDown()) { setFaceDown(false); - game.getCard(this.getId()).setFaceDown(false); //TODO: Do this in a better way + originalCard.setFaceDown(false); //TODO: Do this in a better way } ZoneChangeEvent event = new ZoneChangeEvent(this, sourceId, controllerId, fromZone, toZone, appliedEffects); if (!game.replaceEvent(event)) { @@ -145,6 +146,7 @@ public class PermanentCard extends PermanentImpl { game.rememberLKI(objectId, Zone.BATTLEFIELD, this); if (owner != null) { this.setControllerId(ownerId); // neccessary for e.g. abilities in graveyard or hand to not have a controller != owner + originalCard.updateZoneChangeCounter(); switch (event.getToZone()) { case GRAVEYARD: owner.putInGraveyard(card, game, !flag); @@ -195,6 +197,8 @@ public class PermanentCard extends PermanentImpl { ZoneChangeEvent event = new ZoneChangeEvent(this, sourceId, ownerId, fromZone, Zone.EXILED, appliedEffects); if (!game.replaceEvent(event)) { game.rememberLKI(objectId, Zone.BATTLEFIELD, this); + // update zone change counter of original card + game.getCard(this.getId()).updateZoneChangeCounter(); if (exileId == null) { game.getExile().getPermanentExile().add(card); } else { diff --git a/Mage/src/mage/target/targetpointer/FirstTargetPointer.java b/Mage/src/mage/target/targetpointer/FirstTargetPointer.java index bf9ebb7dfa9..ce66a348cca 100644 --- a/Mage/src/mage/target/targetpointer/FirstTargetPointer.java +++ b/Mage/src/mage/target/targetpointer/FirstTargetPointer.java @@ -7,7 +7,9 @@ import java.util.Map; import java.util.UUID; import mage.abilities.Ability; import mage.cards.Card; +import mage.constants.Zone; import mage.game.Game; +import mage.game.permanent.Permanent; public class FirstTargetPointer implements TargetPointer { @@ -47,7 +49,12 @@ public class FirstTargetPointer implements TargetPointer { Card card = game.getCard(targetId); if (card != null && zoneChangeCounter.containsKey(targetId) && card.getZoneChangeCounter() != zoneChangeCounter.get(targetId)) { - continue; + // because if dies trigger has to trigger as permanent has already moved zone, we have to check if target was on the battlefield immed. before + // but no longer if new permanent is already on the battlefield + Permanent permanent = game.getPermanentOrLKIBattlefield(targetId); + if (permanent == null || permanent.getZoneChangeCounter() != zoneChangeCounter.get(targetId)) { + continue; + } } target.add(targetId); } @@ -62,7 +69,12 @@ public class FirstTargetPointer implements TargetPointer { Card card = game.getCard(targetId); if (card != null && zoneChangeCounter.containsKey(targetId) && card.getZoneChangeCounter() != zoneChangeCounter.get(targetId)) { - return null; + // because if dies trigger has to trigger as permanent has already moved zone, we have to check if target was on the battlefield immed. before + // but no longer if new permanent is already on the battlefield + Permanent permanent = game.getPermanentOrLKIBattlefield(targetId); + if (permanent == null || permanent.getZoneChangeCounter() != zoneChangeCounter.get(targetId)) { + return null; + } } } return targetId; diff --git a/Mage/src/mage/util/CardUtil.java b/Mage/src/mage/util/CardUtil.java index 4b7886d1c39..449275739fd 100644 --- a/Mage/src/mage/util/CardUtil.java +++ b/Mage/src/mage/util/CardUtil.java @@ -479,7 +479,11 @@ public class CardUtil { } public static UUID getCardExileZoneId(Game game, UUID sourceId) { - String key = getCardZoneString("SourceExileZone", sourceId, game); + return getCardExileZoneId(game, sourceId, false); + } + + public static UUID getCardExileZoneId(Game game, UUID sourceId, boolean previous) { + String key = getCardZoneString("SourceExileZone", sourceId, game, previous); UUID exileId = (UUID) game.getState().getValue(key); if (exileId == null) { exileId = UUID.randomUUID(); @@ -499,6 +503,11 @@ public class CardUtil { * @return */ public static String getCardZoneString(String text, UUID cardId, Game game) { + return getCardZoneString(text, cardId, game, false); + } + + public static String getCardZoneString(String text, UUID cardId, Game game, boolean previous) { + StringBuilder uniqueString = new StringBuilder(); if (text != null) { uniqueString.append(text); @@ -506,7 +515,7 @@ public class CardUtil { uniqueString.append(cardId); Card card = game.getCard(cardId); // if called for a token, the id is enough if (card != null) { - uniqueString.append(card.getZoneChangeCounter()); + uniqueString.append(previous ? card.getZoneChangeCounter() - 1: card.getZoneChangeCounter()); } return uniqueString.toString(); }