diff --git a/Mage.Sets/src/mage/sets/magic2014/BanisherPriest.java b/Mage.Sets/src/mage/sets/magic2014/BanisherPriest.java index cfd44d060e0..9e103441f3b 100644 --- a/Mage.Sets/src/mage/sets/magic2014/BanisherPriest.java +++ b/Mage.Sets/src/mage/sets/magic2014/BanisherPriest.java @@ -79,6 +79,8 @@ public class BanisherPriest extends CardImpl { // Implemented as triggered effect that doesn't uses the stack (implementation with watcher does not work correctly because if the returned creature // has a DiesTriggeredAll ability it triggers for the dying Banish Priest, what shouldn't happen) this.addAbility(new BanisherPriestReturnExiledAbility()); + + } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/EvolveTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/EvolveTest.java index 02f4baece8c..3472d6e139f 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/EvolveTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/EvolveTest.java @@ -145,4 +145,83 @@ public class EvolveTest extends CardTestPlayerBase { assertPowerToughness(playerA, "Experiment One", 3, 3); } + + @Test + public void testMultipleCreaturesComeIntoPlay() { + + // Cloudfin Raptor gets one +1/+1 because itself and other creatur return from exile + + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 6); + addCard(Zone.BATTLEFIELD, playerA, "Judge's Familiar", 1); + addCard(Zone.BATTLEFIELD, playerA, "Cloudfin Raptor", 1); + addCard(Zone.HAND, playerA, "Mizzium Mortars", 1); + + addCard(Zone.BATTLEFIELD, playerB, "Plains", 6); + addCard(Zone.HAND, playerB, "Banisher Priest", 2); + + + castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Banisher Priest"); + addTarget(playerB, "Cloudfin Raptor"); + castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Banisher Priest"); + addTarget(playerB, "Judge's Familiar"); + + castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, "Mizzium Mortars with overload"); + + setStopAt(3, PhaseStep.END_TURN); + execute(); + + assertLife(playerA, 20); + assertLife(playerB, 20); + + assertPermanentCount(playerB, "Banisher Priest", 0); + + assertGraveyardCount(playerB, 2); + assertGraveyardCount(playerA, 1); + + assertPermanentCount(playerA, "Cloudfin Raptor", 1); + assertPermanentCount(playerA, "Judge's Familiar", 1); + + assertPowerToughness(playerA, "Cloudfin Raptor", 1, 2); + + + } + + @Test + public void testMultipleCreaturesComeIntoPlaySuddenDisappearance() { + + // Sudden Disappearance + // Sorcery {5}{W} + // Exile all nonland permanents target player controls. Return the exiled cards + // to the battlefield under their owner's control at the beginning of the next end step. + + // Battering Krasis (2/1) and Crocanura (1/3) get both a +1/+1 counter each other because they come into play at the same time + + addCard(Zone.BATTLEFIELD, playerA, "Battering Krasis", 1); + addCard(Zone.BATTLEFIELD, playerA, "Crocanura", 1); + + addCard(Zone.BATTLEFIELD, playerB, "Plains", 6); + addCard(Zone.HAND, playerB, "Sudden Disappearance", 2); + + + castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Sudden Disappearance", playerA); + + setStopAt(3, PhaseStep.PRECOMBAT_MAIN); + execute(); + + assertLife(playerA, 20); + assertLife(playerB, 20); + + assertGraveyardCount(playerB, 1); + assertGraveyardCount(playerA, 0); + + assertPermanentCount(playerA, "Battering Krasis", 1); + assertPermanentCount(playerA, "Crocanura", 1); + + assertPowerToughness(playerA, "Battering Krasis", 3, 2); + assertPowerToughness(playerA, "Crocanura", 2, 4); + + + } + + } diff --git a/Mage.Tests/src/test/java/org/mage/test/serverside/base/CardTestPlayerBase.java b/Mage.Tests/src/test/java/org/mage/test/serverside/base/CardTestPlayerBase.java index 94e32f095fb..e5da7ed42cd 100644 --- a/Mage.Tests/src/test/java/org/mage/test/serverside/base/CardTestPlayerBase.java +++ b/Mage.Tests/src/test/java/org/mage/test/serverside/base/CardTestPlayerBase.java @@ -42,6 +42,7 @@ public abstract class CardTestPlayerBase extends CardTestPlayerAPIImpl { public CardTestPlayerBase() { } + @Override protected TestPlayer createNewPlayer(String playerName) { return createPlayer(playerName); } diff --git a/Mage/src/mage/abilities/AbilityImpl.java b/Mage/src/mage/abilities/AbilityImpl.java index 0dc5ed073fb..c4805c97834 100644 --- a/Mage/src/mage/abilities/AbilityImpl.java +++ b/Mage/src/mage/abilities/AbilityImpl.java @@ -178,8 +178,6 @@ public abstract class AbilityImpl> implements Ability { if (effect.applyEffectsAfter()) { game.applyEffects(); } - // effects like entersBattlefield have to trigger simultanously so objects see each other - game.getState().handleSimultaneousEvent(game); } } return result; diff --git a/Mage/src/mage/abilities/keyword/EvolveAbility.java b/Mage/src/mage/abilities/keyword/EvolveAbility.java index 3710d1368be..5265a9f9ed7 100644 --- a/Mage/src/mage/abilities/keyword/EvolveAbility.java +++ b/Mage/src/mage/abilities/keyword/EvolveAbility.java @@ -28,17 +28,17 @@ package mage.abilities.keyword; -import java.util.UUID; -import mage.constants.CardType; -import mage.constants.Outcome; -import mage.constants.Zone; import mage.abilities.Ability; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.effects.OneShotEffect; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.Zone; import mage.counters.CounterType; import mage.game.Game; import mage.game.events.GameEvent; import mage.game.permanent.Permanent; +import mage.target.targetpointer.FixedTarget; /** * FAQ 2013/01/11 @@ -52,6 +52,34 @@ import mage.game.permanent.Permanent; * * 702.98b If a creature has multiple instances of evolve, each triggers separately * + * Rulings + * + * When comparing the stats of the two creatures, you always compare power to power and toughness to toughness. + * Whenever a creature enters the battlefield under your control, check its power and toughness against + * the power and toughness of the creature with evolve. If neither stat of the new creature is greater, + * evolve won't trigger at all. For example, if you control a 2/3 creature with evolve and a 2/2 creature + * enters the battlefield under your control, you won't have the opportunity to cast a spell like Giant Growth + * to make the 2/2 creature large enough to cause evolve to trigger. + * If evolve triggers, the stat comparison will happen again when the ability tries to resolve. If + * neither stat of the new creature is greater, the ability will do nothing. If the creature that + * entered the battlefield leaves the battlefield before evolve tries to resolve, use its last known + * power and toughness to compare the stats. + * If a creature enters the battlefield with +1/+1 counters on it, consider those counters when determining + * if evolve will trigger. For example, a 1/1 creature that enters the battlefield with two +1/+1 counters + * on it will cause the evolve ability of a 2/2 creature to trigger. + * If multiple creatures enter the battlefield at the same time, evolve may trigger multiple times, although the stat + * comparison will take place each time one of those abilities tries to resolve. For example, if you control a 2/2 + * creature with evolve and two 3/3 creatures enter the battlefield, evolve will trigger twice. The first ability + * will resolve and put a +1/+1 counter on the creature with evolve. When the second ability tries to resolve, + * neither the power nor the toughness of the new creature is greater than that of the creature with evolve, + * so that ability does nothing. + * When comparing the stats as the evolve ability resolves, it's possible that the stat that's greater changes + * from power to toughness or vice versa. If this happens, the ability will still resolve and you'll put a +1/+1 + * counter on the creature with evolve. For example, if you control a 2/2 creature with evolve and a 1/3 creature + * enters the battlefield under your control, it toughness is greater so evolve will trigger. In response, the 1/3 + * creature gets +2/-2. When the evolve trigger tries to resolve, its power is greater. You'll put a +1/+1 + * counter on the creature with evolve. + * * @author LevelX2 */ @@ -68,15 +96,17 @@ public class EvolveAbility extends TriggeredAbilityImpl { @Override public boolean checkTrigger(GameEvent event, Game game) { - if (event.getType() == GameEvent.EventType.ENTERS_THE_BATTLEFIELD && !event.getTargetId().equals(this.getSourceId())) { - Permanent triggeringCreature = game.getPermanent(event.getTargetId()); - if (triggeringCreature != null - && triggeringCreature.getCardType().contains(CardType.CREATURE) - && triggeringCreature.getControllerId().equals(this.controllerId)) { - Permanent sourceCreature = game.getPermanent(sourceId); - if (sourceCreature != null && isPowerOrThoughnessGreater(sourceCreature, triggeringCreature)) { - this.getEffects().get(0).setValue("triggeringCreature", event.getTargetId()); - return true; + if (event.getType() == GameEvent.EventType.ENTERS_THE_BATTLEFIELD) { + if (!event.getTargetId().equals(this.getSourceId())) { + Permanent triggeringCreature = game.getPermanent(event.getTargetId()); + if (triggeringCreature != null + && triggeringCreature.getCardType().contains(CardType.CREATURE) + && triggeringCreature.getControllerId().equals(this.controllerId)) { + Permanent sourceCreature = game.getPermanent(sourceId); + if (sourceCreature != null && isPowerOrThoughnessGreater(sourceCreature, triggeringCreature)) { + this.getEffects().get(0).setTargetPointer(new FixedTarget(event.getTargetId())); + return true; + } } } } @@ -93,10 +123,7 @@ public class EvolveAbility extends TriggeredAbilityImpl { if (newCreature.getPower().getValue() > sourceCreature.getPower().getValue()) { return true; } - if (newCreature.getToughness().getValue() > sourceCreature.getToughness().getValue()) { - return true; - } - return false; + return newCreature.getToughness().getValue() > sourceCreature.getToughness().getValue(); } @Override @@ -127,11 +154,7 @@ class EvolveEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - UUID triggeringCreatureId = (UUID) getValue("triggeringCreature"); - Permanent triggeringCreature = game.getPermanent(triggeringCreatureId); - if (triggeringCreature == null) { - triggeringCreature = (Permanent) game.getLastKnownInformation(triggeringCreatureId, Zone.BATTLEFIELD); - } + Permanent triggeringCreature = game.getPermanentOrLKIBattlefield(getTargetPointer().getFirst(game, source)); if (triggeringCreature != null) { Permanent sourceCreature = game.getPermanent(source.getSourceId()); if (sourceCreature != null && EvolveAbility.isPowerOrThoughnessGreater(sourceCreature, triggeringCreature)) { diff --git a/Mage/src/mage/cards/CardImpl.java b/Mage/src/mage/cards/CardImpl.java index 9d24423dced..b28c7c31557 100644 --- a/Mage/src/mage/cards/CardImpl.java +++ b/Mage/src/mage/cards/CardImpl.java @@ -376,7 +376,7 @@ public abstract class CardImpl> extends MageObjectImpl } setControllerId(ownerId); game.setZone(objectId, event.getToZone()); - game.fireEvent(event); + game.addSimultaneousEvent(event); return game.getState().getZone(objectId) == toZone; } return false; diff --git a/Mage/src/mage/game/GameImpl.java b/Mage/src/mage/game/GameImpl.java index ef68f7e4695..cda1cff1aaf 100644 --- a/Mage/src/mage/game/GameImpl.java +++ b/Mage/src/mage/game/GameImpl.java @@ -986,7 +986,10 @@ public abstract class GameImpl> implements Game, Serializa while (!player.isPassed() && player.isInGame() && !isPaused() && !gameOver(null)) { if (!resuming) { if (checkStateAndTriggered()) { - applyEffects(); + do { + state.handleSimultaneousEvent(this); + applyEffects(); + } while (state.hasSimultaneousEvents()); } //resetLKI(); applyEffects(); @@ -1057,6 +1060,7 @@ public abstract class GameImpl> implements Game, Serializa } finally { if (top != null) { state.getStack().remove(top); + state.handleSimultaneousEvent(this); } } } diff --git a/Mage/src/mage/game/GameState.java b/Mage/src/mage/game/GameState.java index 015c3b06575..03f0da33952 100644 --- a/Mage/src/mage/game/GameState.java +++ b/Mage/src/mage/game/GameState.java @@ -494,12 +494,18 @@ public class GameState implements Serializable, Copyable { public void handleSimultaneousEvent(Game game) { if (!simultaneousEvents.isEmpty()) { - for (GameEvent event:simultaneousEvents) { + // it can happen, that the events add new simultaneous events, so copy the list before + List eventsToHandle = new ArrayList<>(); + eventsToHandle.addAll(simultaneousEvents); + simultaneousEvents.clear(); + for (GameEvent event:eventsToHandle) { this.handleEvent(event, game); } - simultaneousEvents.clear(); } } + public boolean hasSimultaneousEvents() { + return !simultaneousEvents.isEmpty(); + } public void handleEvent(GameEvent event, Game game) { watchers.watch(event, game); diff --git a/Mage/src/mage/game/permanent/PermanentCard.java b/Mage/src/mage/game/permanent/PermanentCard.java index bad546062d6..335a64f68a4 100644 --- a/Mage/src/mage/game/permanent/PermanentCard.java +++ b/Mage/src/mage/game/permanent/PermanentCard.java @@ -161,7 +161,7 @@ public class PermanentCard extends PermanentImpl { break; } game.setZone(objectId, event.getToZone()); - game.fireEvent(event); + game.addSimultaneousEvent(event); if (event.getFromZone().equals(Zone.BATTLEFIELD)) { game.resetForSourceId(getId()); game.applyEffects(); // LevelX2: needed to execute isInactive for of custom duration copy effect if source returns directly (e.g. cloudshifted clone)