From 340398fb7409fe6686f4b585098070a49bcdb02a Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Wed, 9 Sep 2015 00:51:41 +0200 Subject: [PATCH] * Fixed that state triggered abilities were not checked at the correct times. --- .../sets/alliances/PhyrexianDevourer.java | 13 +--- .../mage/sets/avacynrestored/Cloudshift.java | 13 ++-- .../sets/shadowmoor/LureboundScarecrow.java | 21 ++--- .../mage/sets/shardsofalara/ImmortalCoil.java | 24 +++--- .../triggers/state/PhyrexianDevourerTest.java | 71 +++++++++++++++++ .../test/commander/duel/AnafenzaTest.java | 47 +++++++++++ .../base/impl/CardTestPlayerAPIImpl.java | 4 +- Mage/src/mage/abilities/AbilityImpl.java | 1 + .../mage/abilities/StateTriggeredAbility.java | 22 +++--- .../mage/abilities/TriggeredAbilities.java | 78 +++++++++++-------- ...ThisOrAnotherCreatureTriggeredAbility.java | 19 ++--- Mage/src/mage/game/GameImpl.java | 1 + 12 files changed, 217 insertions(+), 97 deletions(-) create mode 100644 Mage.Tests/src/test/java/org/mage/test/cards/triggers/state/PhyrexianDevourerTest.java diff --git a/Mage.Sets/src/mage/sets/alliances/PhyrexianDevourer.java b/Mage.Sets/src/mage/sets/alliances/PhyrexianDevourer.java index a7ba4876f7..36a4e5d6da 100644 --- a/Mage.Sets/src/mage/sets/alliances/PhyrexianDevourer.java +++ b/Mage.Sets/src/mage/sets/alliances/PhyrexianDevourer.java @@ -27,8 +27,6 @@ */ package mage.sets.alliances; -import java.util.ArrayList; -import java.util.List; import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; @@ -100,10 +98,7 @@ class PhyrexianDevourerStateTriggeredAbility extends StateTriggeredAbility { @Override public boolean checkTrigger(GameEvent event, Game game) { Permanent permanent = game.getPermanent(getSourceId()); - if(permanent != null && permanent.getPower().getValue() >= 7){ - return true; - } - return false; + return permanent != null && permanent.getPower().getValue() >= 7; } @Override @@ -134,9 +129,9 @@ class PhyrexianDevourerEffect extends OneShotEffect { Player controller = game.getPlayer(source.getControllerId()); if (controller != null) { Card card = null; - for (Cost cost: source.getCosts()) { + for (Cost cost : source.getCosts()) { if (cost instanceof ExileTopCardLibraryCost) { - card = ((ExileTopCardLibraryCost)cost).getCard(); + card = ((ExileTopCardLibraryCost) cost).getCard(); } } if (card != null) { @@ -170,7 +165,7 @@ class ExileTopCardLibraryCost extends CostImpl { if (controller != null) { card = controller.getLibrary().getFromTop(game); if (card != null) { - paid = controller.moveCardToExileWithInfo(card, null, "", sourceId, game, Zone.LIBRARY, true); + paid = controller.moveCards(card, null, Zone.EXILED, ability, game); } } return paid; diff --git a/Mage.Sets/src/mage/sets/avacynrestored/Cloudshift.java b/Mage.Sets/src/mage/sets/avacynrestored/Cloudshift.java index 49d0848213..42c785000e 100644 --- a/Mage.Sets/src/mage/sets/avacynrestored/Cloudshift.java +++ b/Mage.Sets/src/mage/sets/avacynrestored/Cloudshift.java @@ -27,15 +27,15 @@ */ package mage.sets.avacynrestored; -import mage.constants.CardType; -import mage.constants.Rarity; +import java.util.UUID; +import mage.abilities.effects.Effect; import mage.abilities.effects.common.ExileTargetForSourceEffect; import mage.abilities.effects.common.ReturnToBattlefieldUnderYourControlTargetEffect; import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Rarity; import mage.target.common.TargetControlledCreaturePermanent; -import java.util.UUID; - /** * * @author noxx @@ -46,10 +46,11 @@ public class Cloudshift extends CardImpl { super(ownerId, 12, "Cloudshift", Rarity.COMMON, new CardType[]{CardType.INSTANT}, "{W}"); this.expansionSetCode = "AVR"; - // Exile target creature you control, then return that card to the battlefield under your control. this.getSpellAbility().addTarget(new TargetControlledCreaturePermanent()); - this.getSpellAbility().addEffect(new ExileTargetForSourceEffect()); + Effect effect = new ExileTargetForSourceEffect(); + effect.setApplyEffectsAfter(); + this.getSpellAbility().addEffect(effect); this.getSpellAbility().addEffect(new ReturnToBattlefieldUnderYourControlTargetEffect(true)); } diff --git a/Mage.Sets/src/mage/sets/shadowmoor/LureboundScarecrow.java b/Mage.Sets/src/mage/sets/shadowmoor/LureboundScarecrow.java index fc6cd8c6ce..9ab3c91bb7 100644 --- a/Mage.Sets/src/mage/sets/shadowmoor/LureboundScarecrow.java +++ b/Mage.Sets/src/mage/sets/shadowmoor/LureboundScarecrow.java @@ -35,7 +35,6 @@ import mage.abilities.StateTriggeredAbility; import mage.abilities.common.AsEntersBattlefieldAbility; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.SacrificeSourceEffect; -import mage.cards.Card; import mage.cards.CardImpl; import mage.choices.ChoiceColor; import mage.constants.CardType; @@ -127,20 +126,16 @@ class LureboundScarecrowTriggeredAbility extends StateTriggeredAbility { @Override public boolean checkTrigger(GameEvent event, Game game) { - if (event.getType() == GameEvent.EventType.ZONE_CHANGE || event.getType() == GameEvent.EventType.LOST_CONTROL - || event.getType() == GameEvent.EventType.COLOR_CHANGED - || event.getType() == GameEvent.EventType.SPELL_CAST) { - Card card = game.getCard(this.getSourceId()); - if (card != null) { - ObjectColor color = (ObjectColor) game.getState().getValue(card.getId() + "_color"); - if (color != null) { - for (Permanent perm : game.getBattlefield().getAllActivePermanents(controllerId)) { - if (perm.getColor(game).contains(color)) { - return false; - } + Permanent permanent = game.getPermanent(getSourceId()); + if (permanent != null) { + ObjectColor color = (ObjectColor) game.getState().getValue(getSourceId() + "_color"); + if (color != null) { + for (Permanent perm : game.getBattlefield().getAllActivePermanents(controllerId)) { + if (perm.getColor(game).contains(color)) { + return false; } - return true; } + return true; } } return false; diff --git a/Mage.Sets/src/mage/sets/shardsofalara/ImmortalCoil.java b/Mage.Sets/src/mage/sets/shardsofalara/ImmortalCoil.java index 1b3d3fb54a..b8307e5cc7 100644 --- a/Mage.Sets/src/mage/sets/shardsofalara/ImmortalCoil.java +++ b/Mage.Sets/src/mage/sets/shardsofalara/ImmortalCoil.java @@ -28,8 +28,6 @@ package mage.sets.shardsofalara; import java.util.UUID; - -import mage.constants.*; import mage.abilities.Ability; import mage.abilities.StateTriggeredAbility; import mage.abilities.common.SimpleActivatedAbility; @@ -42,6 +40,11 @@ import mage.abilities.effects.common.DrawCardSourceControllerEffect; import mage.abilities.effects.common.SacrificeSourceEffect; import mage.cards.Card; import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.Rarity; +import mage.constants.Zone; import mage.filter.FilterCard; import mage.game.Game; import mage.game.events.GameEvent; @@ -58,7 +61,6 @@ public class ImmortalCoil extends CardImpl { super(ownerId, 79, "Immortal Coil", Rarity.RARE, new CardType[]{CardType.ARTIFACT}, "{2}{B}{B}"); this.expansionSetCode = "ALA"; - // {tap}, Exile two cards from your graveyard: Draw a card. Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new DrawCardSourceControllerEffect(1), new TapSourceCost()); ability.addCost(new ExileFromGraveCost(new TargetCardInYourGraveyard(2, new FilterCard("cards from your graveyard")))); @@ -98,10 +100,7 @@ class ImmortalCoilAbility extends StateTriggeredAbility { @Override public boolean checkTrigger(GameEvent event, Game game) { Player player = game.getPlayer(this.getControllerId()); - if(player != null && player.getGraveyard().size() == 0){ - return true; - } - return false; + return player != null && player.getGraveyard().size() == 0; } @Override @@ -130,7 +129,7 @@ class LoseGameEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Player player = game.getPlayer(source.getControllerId()); - if (player != null) { + if (player != null) { player.lost(game); return true; } @@ -140,7 +139,6 @@ class LoseGameEffect extends OneShotEffect { class PreventAllDamageToControllerEffect extends PreventionEffectImpl { - public PreventAllDamageToControllerEffect() { super(Duration.WhileOnBattlefield); staticText = "If damage would be dealt to you, prevent that damage. Exile a card from your graveyard for each 1 damage prevented this way"; @@ -166,10 +164,10 @@ class PreventAllDamageToControllerEffect extends PreventionEffectImpl { if (!game.replaceEvent(preventEvent)) { int damage = event.getAmount(); Player player = game.getPlayer(source.getControllerId()); - if(player != null){ - TargetCardInYourGraveyard target = new TargetCardInYourGraveyard(Math.min(damage, player.getGraveyard().size()), new FilterCard()); + if (player != null) { + TargetCardInYourGraveyard target = new TargetCardInYourGraveyard(Math.min(damage, player.getGraveyard().size()), new FilterCard()); if (target.choose(Outcome.Exile, source.getControllerId(), source.getSourceId(), game)) { - for (UUID targetId: target.getTargets()) { + for (UUID targetId : target.getTargets()) { Card card = player.getGraveyard().get(targetId, game); if (card != null) { card.moveToZone(Zone.EXILED, source.getSourceId(), game, false); @@ -186,7 +184,7 @@ class PreventAllDamageToControllerEffect extends PreventionEffectImpl { @Override public boolean applies(GameEvent event, Ability source, Game game) { if (super.applies(event, source, game)) { - if (event.getTargetId().equals(source.getControllerId())){ + if (event.getTargetId().equals(source.getControllerId())) { return true; } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/triggers/state/PhyrexianDevourerTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/state/PhyrexianDevourerTest.java new file mode 100644 index 0000000000..8b62413c14 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/state/PhyrexianDevourerTest.java @@ -0,0 +1,71 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package org.mage.test.cards.triggers.state; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * + * @author LevelX2 + */ +public class PhyrexianDevourerTest extends CardTestPlayerBase { + + /** + * Check that Phyrexian Devourer is sacrifriced as soon as the counters are + * added + * + */ + @Test + public void testBoostChecked() { + // When Phyrexian Devourer's power is 7 or greater, sacrifice it. + // Exile the top card of your library: Put X +1/+1 counters on Phyrexian Devourer, where X is the exiled card's converted mana cost. + addCard(Zone.BATTLEFIELD, playerA, "Phyrexian Devourer"); + addCard(Zone.LIBRARY, playerA, "Phyrexian Devourer"); + skipInitShuffling(); + + addCard(Zone.BATTLEFIELD, playerB, "Silvercoat Lion"); + + attack(2, playerB, "Silvercoat Lion"); + block(2, playerA, "Phyrexian Devourer", "Silvercoat Lion"); + + activateAbility(2, PhaseStep.DECLARE_BLOCKERS, playerA, "Exile the top card of your library"); + + setStopAt(2, PhaseStep.POSTCOMBAT_MAIN); + execute(); + + assertGraveyardCount(playerA, "Phyrexian Devourer", 1); + assertExileCount("Phyrexian Devourer", 1); + + assertPermanentCount(playerB, "Silvercoat Lion", 1); + + } + +} diff --git a/Mage.Tests/src/test/java/org/mage/test/commander/duel/AnafenzaTest.java b/Mage.Tests/src/test/java/org/mage/test/commander/duel/AnafenzaTest.java index aa0eda13c9..109b704982 100644 --- a/Mage.Tests/src/test/java/org/mage/test/commander/duel/AnafenzaTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/commander/duel/AnafenzaTest.java @@ -92,4 +92,51 @@ public class AnafenzaTest extends CardTestCommanderDuelBase { } + /** + * Token don't go to exile because they are no creature cards + */ + @Test + public void testAnafenzaExileInCombatOmnathToken() { + addCard(Zone.BATTLEFIELD, playerA, "Plains", 1); + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 1); + addCard(Zone.BATTLEFIELD, playerA, "Forest", 1); + addCard(Zone.BATTLEFIELD, playerA, "Acidic Slime", 1); + + addCard(Zone.HAND, playerB, "Forest", 2); + // Landfall - Whenever a land enters the battlefield under your control, put a 5/5 red and green Elemental creature token onto the battlefield. + // Whenever Omnath, Locus of Rage or another Elemental you control dies, Omnath deals 3 damage to target creature or player. + addCard(Zone.BATTLEFIELD, playerB, "Omnath, Locus of Rage", 1); + + // Whenever Anafenza, the Foremost attacks, put a +1/+1 counter on another target tapped creature you control. + // If a creature card would be put into an opponent's graveyard from anywhere, exile it instead. + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Anafenza, the Foremost"); + + playLand(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Forest"); + playLand(4, PhaseStep.PRECOMBAT_MAIN, playerB, "Forest"); + + attack(5, playerA, "Acidic Slime"); + block(5, playerB, "Elemental", "Acidic Slime"); + attack(5, playerA, "Anafenza, the Foremost"); + block(5, playerB, "Elemental", "Anafenza, the Foremost"); + addTarget(playerB, playerA); + + setStopAt(5, PhaseStep.POSTCOMBAT_MAIN); + execute(); + + assertExileCount(playerA, 0); + assertExileCount(playerB, 0); + + assertPermanentCount(playerB, "Elemental", 1); + + assertGraveyardCount(playerA, "Acidic Slime", 1); + assertGraveyardCount(playerA, "Anafenza, the Foremost", 0); + assertCommandZoneCount(playerA, "Anafenza, the Foremost", 1); + + assertHandCount(playerA, 2); // turn 3 + 5 draw + assertHandCount(playerB, 2); // turn 2 + 4 draw + + assertLife(playerA, 37); + assertLife(playerB, 40); + } + } diff --git a/Mage.Tests/src/test/java/org/mage/test/serverside/base/impl/CardTestPlayerAPIImpl.java b/Mage.Tests/src/test/java/org/mage/test/serverside/base/impl/CardTestPlayerAPIImpl.java index 664d665ecf..1114d2d073 100644 --- a/Mage.Tests/src/test/java/org/mage/test/serverside/base/impl/CardTestPlayerAPIImpl.java +++ b/Mage.Tests/src/test/java/org/mage/test/serverside/base/impl/CardTestPlayerAPIImpl.java @@ -585,7 +585,7 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement actualCount++; } } - Assert.assertEquals("(Battlefield) Card counts are not equal (" + commandZoneObjectName + ")", count, actualCount); + Assert.assertEquals("(Command Zone) Card counts are not equal (" + commandZoneObjectName + ")", count, actualCount); } /** @@ -777,7 +777,7 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement */ public void assertHandCount(Player player, int count) throws AssertionError { int actual = currentGame.getPlayer(player.getId()).getHand().size(); - Assert.assertEquals("(Hand) Card counts are not equal ", count, actual); + Assert.assertEquals("(Hand " + player.getName() + ") Card counts are not equal ", count, actual); } /** diff --git a/Mage/src/mage/abilities/AbilityImpl.java b/Mage/src/mage/abilities/AbilityImpl.java index d9723850a2..7006eeebdc 100644 --- a/Mage/src/mage/abilities/AbilityImpl.java +++ b/Mage/src/mage/abilities/AbilityImpl.java @@ -237,6 +237,7 @@ public abstract class AbilityImpl implements Ability { */ if (effect.applyEffectsAfter()) { game.applyEffects(); + game.getState().getTriggers().checkStateTriggers(game); } } } diff --git a/Mage/src/mage/abilities/StateTriggeredAbility.java b/Mage/src/mage/abilities/StateTriggeredAbility.java index 1c8cfadfb3..45425d8b32 100644 --- a/Mage/src/mage/abilities/StateTriggeredAbility.java +++ b/Mage/src/mage/abilities/StateTriggeredAbility.java @@ -1,16 +1,16 @@ /* * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. - * + * * Redistribution and use in source and binary forms, with or without modification, are * permitted provided that the following conditions are met: - * + * * 1. Redistributions of source code must retain the above copyright notice, this list of * conditions and the following disclaimer. - * + * * 2. Redistributions in binary form must reproduce the above copyright notice, this list * of conditions and the following disclaimer in the documentation and/or other materials * provided with the distribution. - * + * * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR @@ -20,12 +20,11 @@ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * + * * The views and conclusions contained in the software and documentation are those of the * authors and should not be interpreted as representing official policies, either expressed * or implied, of BetaSteward_at_googlemail.com. */ - package mage.abilities; import java.util.UUID; @@ -48,8 +47,7 @@ public abstract class StateTriggeredAbility extends TriggeredAbilityImpl { super(ability); } - @Override - public final boolean checkEventType(GameEvent event, Game game) { + public boolean canTrigger(Game game) { //20100716 - 603.8 Boolean triggered = (Boolean) game.getState().getValue(getSourceId().toString() + "triggered"); if (triggered == null) { @@ -58,7 +56,11 @@ public abstract class StateTriggeredAbility extends TriggeredAbilityImpl { return !triggered; } - + @Override + public final boolean checkEventType(GameEvent event, Game game) { + return false; + } + @Override public void trigger(Game game, UUID controllerId) { //20100716 - 603.8 @@ -71,7 +73,7 @@ public abstract class StateTriggeredAbility extends TriggeredAbilityImpl { //20100716 - 603.8 boolean result = super.resolve(game); game.getState().setValue(this.getSourceId().toString() + "triggered", Boolean.FALSE); - return result; + return result; } public void counter(Game game) { diff --git a/Mage/src/mage/abilities/TriggeredAbilities.java b/Mage/src/mage/abilities/TriggeredAbilities.java index 969e3cb78f..599e44068c 100644 --- a/Mage/src/mage/abilities/TriggeredAbilities.java +++ b/Mage/src/mage/abilities/TriggeredAbilities.java @@ -69,45 +69,57 @@ public class TriggeredAbilities extends ConcurrentHashMap it = this.values().iterator(); it.hasNext();) { + TriggeredAbility ability = it.next(); + if (ability instanceof StateTriggeredAbility && ((StateTriggeredAbility) ability).canTrigger(game)) { + checkTrigger(ability, null, game); + } + } + } + public void checkTriggers(GameEvent event, Game game) { for (Iterator it = this.values().iterator(); it.hasNext();) { TriggeredAbility ability = it.next(); - if (!ability.checkEventType(event, game)) { - continue; + if (ability.checkEventType(event, game)) { + checkTrigger(ability, event, game); } - // for effects like when leaves battlefield or destroyed use ShortLKI to check if permanent was in the correct zone before (e.g. Oblivion Ring or Karmic Justice) - MageObject object = game.getObject(ability.getSourceId()); - if (ability.isInUseableZone(game, object, event)) { - if (!game.getContinuousEffects().preventedByRuleModification(event, ability, game, false)) { - if (object != null) { - boolean controllerSet = false; - if (!ability.getZone().equals(Zone.COMMAND) && event.getTargetId() != null && event.getTargetId().equals(ability.getSourceId()) - && (event.getType().equals(EventType.ZONE_CHANGE) || event.getType().equals(EventType.DESTROYED_PERMANENT))) { - // need to check if object was face down for dies and destroy events because the ability triggers in the new zone, zone counter -1 is used - Permanent permanent = (Permanent) game.getLastKnownInformation(ability.getSourceId(), Zone.BATTLEFIELD, ability.getSourceObjectZoneChangeCounter() - 1); - if (permanent != null) { - if (!ability.getWorksFaceDown() && permanent.isFaceDown(game)) { - continue; - } - controllerSet = true; - ability.setControllerId(permanent.getControllerId()); - } - } - if (!controllerSet) { - if (object instanceof Permanent) { - ability.setControllerId(((Permanent) object).getControllerId()); - } else if (object instanceof Spell) { - // needed so that cast triggered abilities have to correct controller (e.g. Ulamog, the Infinite Gyre). - ability.setControllerId(((Spell) object).getControllerId()); - } else if (object instanceof Card) { - ability.setControllerId(((Card) object).getOwnerId()); - } - } - } + } + } - if (ability.checkTrigger(event, game)) { - ability.trigger(game, ability.getControllerId()); + private void checkTrigger(TriggeredAbility ability, GameEvent event, Game game) { + // for effects like when leaves battlefield or destroyed use ShortLKI to check if permanent was in the correct zone before (e.g. Oblivion Ring or Karmic Justice) + MageObject object = game.getObject(ability.getSourceId()); + if (ability.isInUseableZone(game, object, event)) { + if (event == null || !game.getContinuousEffects().preventedByRuleModification(event, ability, game, false)) { + if (object != null) { + boolean controllerSet = false; + if (!ability.getZone().equals(Zone.COMMAND) && event != null && event.getTargetId() != null && event.getTargetId().equals(ability.getSourceId()) + && (event.getType().equals(EventType.ZONE_CHANGE) || event.getType().equals(EventType.DESTROYED_PERMANENT))) { + // need to check if object was face down for dies and destroy events because the ability triggers in the new zone, zone counter -1 is used + Permanent permanent = (Permanent) game.getLastKnownInformation(ability.getSourceId(), Zone.BATTLEFIELD, ability.getSourceObjectZoneChangeCounter() - 1); + if (permanent != null) { + if (!ability.getWorksFaceDown() && permanent.isFaceDown(game)) { + return; + } + controllerSet = true; + ability.setControllerId(permanent.getControllerId()); + } } + if (!controllerSet) { + if (object instanceof Permanent) { + ability.setControllerId(((Permanent) object).getControllerId()); + } else if (object instanceof Spell) { + // needed so that cast triggered abilities have to correct controller (e.g. Ulamog, the Infinite Gyre). + ability.setControllerId(((Spell) object).getControllerId()); + } else if (object instanceof Card) { + ability.setControllerId(((Card) object).getOwnerId()); + } + } + } + + if (ability.checkTrigger(event, game)) { + ability.trigger(game, ability.getControllerId()); } } } diff --git a/Mage/src/mage/abilities/common/DiesThisOrAnotherCreatureTriggeredAbility.java b/Mage/src/mage/abilities/common/DiesThisOrAnotherCreatureTriggeredAbility.java index 2e8aefd917..494c05fa7b 100644 --- a/Mage/src/mage/abilities/common/DiesThisOrAnotherCreatureTriggeredAbility.java +++ b/Mage/src/mage/abilities/common/DiesThisOrAnotherCreatureTriggeredAbility.java @@ -28,9 +28,9 @@ package mage.abilities.common; import mage.MageObject; -import mage.constants.Zone; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.effects.Effect; +import mage.constants.Zone; import mage.filter.common.FilterCreaturePermanent; import mage.game.Game; import mage.game.events.GameEvent; @@ -67,7 +67,7 @@ public class DiesThisOrAnotherCreatureTriggeredAbility extends TriggeredAbilityI public boolean checkEventType(GameEvent event, Game game) { return event.getType() == GameEvent.EventType.ZONE_CHANGE; } - + @Override public boolean isInUseableZone(Game game, MageObject source, GameEvent event) { Permanent sourcePermanent; @@ -81,24 +81,21 @@ public class DiesThisOrAnotherCreatureTriggeredAbility extends TriggeredAbilityI } return hasSourceObjectAbility(game, sourcePermanent, event); } - + @Override public boolean checkTrigger(GameEvent event, Game game) { ZoneChangeEvent zEvent = (ZoneChangeEvent) event; - if (game.getPermanent(sourceId) == null) { - if (game.getLastKnownInformation(sourceId, Zone.BATTLEFIELD) == null) { - return false; - } + if (game.getPermanentOrLKIBattlefield(getSourceId()) == null) { + return false; } if (zEvent.getFromZone() == Zone.BATTLEFIELD && zEvent.getToZone() == Zone.GRAVEYARD) { - Permanent permanent = (Permanent) game.getLastKnownInformation(event.getTargetId(), Zone.BATTLEFIELD); - if (permanent != null) { - if (permanent.getId().equals(this.getSourceId())) { + if (zEvent.getTarget() != null) { + if (zEvent.getTarget().getId().equals(this.getSourceId())) { return true; } else { - if (filter.match(permanent, sourceId, controllerId, game)) { + if (filter.match(zEvent.getTarget(), sourceId, controllerId, game)) { return true; } } diff --git a/Mage/src/mage/game/GameImpl.java b/Mage/src/mage/game/GameImpl.java index 92014988e0..f1d6cf8b25 100644 --- a/Mage/src/mage/game/GameImpl.java +++ b/Mage/src/mage/game/GameImpl.java @@ -1464,6 +1464,7 @@ public abstract class GameImpl implements Game, Serializable { */ public boolean checkTriggered() { boolean played = false; + state.getTriggers().checkStateTriggers(this); for (UUID playerId : state.getPlayerList(state.getActivePlayerId())) { Player player = getPlayer(playerId); while (player.isInGame()) { // player can die or win caused by triggered abilities or leave the game