From 12ee3f7e54879731e8af15ea0abf1462789ada13 Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Wed, 6 Apr 2016 15:09:39 +0200 Subject: [PATCH] * Prized Amalgam - Fixed that it was not returned to battlefield under some conditions (fixes #1704). --- .../shadowsoverinnistrad/PrizedAmalgam.java | 26 ++++- .../cards/single/soi/PrizedAmalgamTest.java | 56 ++++++----- .../common/CastFromGraveyardWatcher.java | 99 +++++++++++++++++++ 3 files changed, 150 insertions(+), 31 deletions(-) create mode 100644 Mage/src/main/java/mage/watchers/common/CastFromGraveyardWatcher.java diff --git a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/PrizedAmalgam.java b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/PrizedAmalgam.java index d7474149370..1f8bc6cacce 100644 --- a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/PrizedAmalgam.java +++ b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/PrizedAmalgam.java @@ -41,20 +41,22 @@ import mage.constants.TargetController; import mage.constants.Zone; import mage.filter.FilterPermanent; import mage.filter.common.FilterCreaturePermanent; -import mage.filter.predicate.permanent.ControllerPredicate; +import mage.filter.predicate.other.OwnerPredicate; import mage.game.Game; import mage.game.events.EntersTheBattlefieldEvent; import mage.game.events.GameEvent; +import mage.watchers.common.CastFromGraveyardWatcher; /** * * @author LevelX2 */ public class PrizedAmalgam extends CardImpl { - + private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("a creature"); + static { - filter.add(new ControllerPredicate(TargetController.YOU)); + filter.add(new OwnerPredicate(TargetController.YOU)); } public PrizedAmalgam(UUID ownerId) { @@ -66,7 +68,8 @@ public class PrizedAmalgam extends CardImpl { // Whenever a creature enters the battlefield, if it entered from your graveyard or you cast it from your graveyard, return Prized Amalgam from your graveyard to the battlefield tapped at the beginning of the next end step. this.addAbility(new PrizedAmalgamTriggerdAbility(new CreateDelayedTriggeredAbilityEffect( - new AtTheBeginOfNextEndStepDelayedTriggeredAbility(new ReturnSourceFromGraveyardToBattlefieldEffect(true))), filter)); + new AtTheBeginOfNextEndStepDelayedTriggeredAbility(new ReturnSourceFromGraveyardToBattlefieldEffect(true))), filter), + new CastFromGraveyardWatcher()); } public PrizedAmalgam(final PrizedAmalgam card) { @@ -96,7 +99,20 @@ class PrizedAmalgamTriggerdAbility extends EntersBattlefieldAllTriggeredAbility @Override public boolean checkTrigger(GameEvent event, Game game) { - return super.checkTrigger(event, game) && ((EntersTheBattlefieldEvent) event).getFromZone().equals(Zone.GRAVEYARD); + if (super.checkTrigger(event, game)) { + EntersTheBattlefieldEvent entersEvent = (EntersTheBattlefieldEvent) event; + if (entersEvent.getFromZone().equals(Zone.GRAVEYARD)) { + return true; + } + if (entersEvent.getFromZone().equals(Zone.STACK) && entersEvent.getTarget().getControllerId().equals(getControllerId())) { + CastFromGraveyardWatcher watcher = (CastFromGraveyardWatcher) game.getState().getWatchers().get(CastFromGraveyardWatcher.class.getName()); + if (watcher != null) { + int zcc = game.getState().getZoneChangeCounter(event.getSourceId()); + return watcher.spellWasCastFromGraveyard(event.getSourceId(), zcc - 1); + } + } + } + return false; } @Override diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/soi/PrizedAmalgamTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/soi/PrizedAmalgamTest.java index f7b070caa75..0cd8182c34f 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/soi/PrizedAmalgamTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/soi/PrizedAmalgamTest.java @@ -6,96 +6,100 @@ import org.junit.Test; import org.mage.test.serverside.base.CardTestPlayerBase; /** - *1UB -* Creature - Zombie -* Whenever a creature enters the battlefield, if it entered from your graveyard or you cast it from your graveyard, -* return Prized Amalgam from your graveyard to the battlefield tapped at the beginning of the next end step. + * 1UB Creature - Zombie Whenever a creature enters the battlefield, if it + * entered from your graveyard or you cast it from your graveyard, return Prized + * Amalgam from your graveyard to the battlefield tapped at the beginning of the + * next end step. + * * @author escplan9 (Derek Monturo - dmontur1 at gmail dot com) */ public class PrizedAmalgamTest extends CardTestPlayerBase { - + /** * Reanimated creature recurs Prized Amalgam */ @Test public void testReanimation() { - + addCard(Zone.HAND, playerA, "Reanimate", 1); // {B} Put target creature card from a graveyard onto the battlefield under your control. You lose life equal to its converted mana cost. addCard(Zone.GRAVEYARD, playerA, "Bronze Sable", 1); // (2) 2/1 addCard(Zone.GRAVEYARD, playerA, "Prized Amalgam", 1); addCard(Zone.BATTLEFIELD, playerA, "Swamp", 1); - + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Reanimate"); addTarget(playerA, "Bronze Sable"); setStopAt(1, PhaseStep.END_TURN); execute(); - + assertLife(playerA, 18); // loss of 2 from reanimate assertGraveyardCount(playerA, "Reanimate", 1); assertPermanentCount(playerA, "Bronze Sable", 1); assertPermanentCount(playerA, "Prized Amalgam", 1); assertTapped("Prized Amalgam", true); } - + /** - * Reported bug - Gravecrawler cast from grave does not trigger Prized Amalgam. + * Reported bug - Gravecrawler cast from grave does not trigger Prized + * Amalgam. */ @Test public void testGravecrawlerCastFromGrave() { - + addCard(Zone.GRAVEYARD, playerA, "Gravecrawler", 1); addCard(Zone.GRAVEYARD, playerA, "Prized Amalgam", 1); addCard(Zone.BATTLEFIELD, playerA, "Gnawing Zombie", 1); addCard(Zone.BATTLEFIELD, playerA, "Swamp", 1); - + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Gravecrawler"); setStopAt(1, PhaseStep.END_TURN); execute(); - + assertPermanentCount(playerA, "Gravecrawler", 1); assertPermanentCount(playerA, "Prized Amalgam", 1); assertTapped("Prized Amalgam", true); } - + /** - * Reported bug - creature returned from opponent's graveyard to battlefield by Ojutai's Command incorrectly triggers Prized Amalgam + * Reported bug - creature returned from opponent's graveyard to battlefield + * by Ojutai's Command incorrectly triggers Prized Amalgam */ @Test public void testOpponentReturnsCreatureFromGrave() { - + addCard(Zone.HAND, playerA, "Reanimate", 1); addCard(Zone.GRAVEYARD, playerA, "Hill Giant", 1); // {3}{R} 3/3 addCard(Zone.BATTLEFIELD, playerA, "Swamp", 1); addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3); addCard(Zone.GRAVEYARD, playerB, "Prized Amalgam", 1); - + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Reanimate"); addTarget(playerA, "Hill Giant"); setStopAt(1, PhaseStep.END_TURN); execute(); - + assertLife(playerA, 16); // lose 4 life from reanimate 4 CMC assertPermanentCount(playerA, "Hill Giant", 1); assertPermanentCount(playerB, "Prized Amalgam", 0); // should not recur assertGraveyardCount(playerB, "Prized Amalgam", 1); // stays in grave } - - /* + + /* * Test opponent returning a card from your graveyard to battlefield. - */ + */ @Test public void testOpponentReturnsCreatureFromYourGrave() { - - addCard(Zone.HAND, playerA, "Necromantic Summons", 1); // Put target creature card from a graveyard onto the battlefield under your control. + + addCard(Zone.HAND, playerA, "Necromantic Summons", 1); // Put target creature card from a graveyard onto the battlefield under your control. addCard(Zone.BATTLEFIELD, playerA, "Swamp", 5); - addCard(Zone.GRAVEYARD, playerB, "Merfolk Looter", 1); // {U} 1/1 + addCard(Zone.GRAVEYARD, playerB, "Merfolk Looter", 1); // {U} 1/1 + // Whenever a creature enters the battlefield, if it entered from your graveyard or you cast it from your graveyard, return Prized Amalgam from your graveyard to the battlefield tapped at the beginning of the next end step. addCard(Zone.GRAVEYARD, playerB, "Prized Amalgam", 1); - + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Necromantic Summons"); addTarget(playerA, "Merfolk Looter"); setStopAt(1, PhaseStep.END_TURN); execute(); - + assertPermanentCount(playerA, "Merfolk Looter", 1); assertPermanentCount(playerB, "Prized Amalgam", 1); assertGraveyardCount(playerB, "Prized Amalgam", 0); diff --git a/Mage/src/main/java/mage/watchers/common/CastFromGraveyardWatcher.java b/Mage/src/main/java/mage/watchers/common/CastFromGraveyardWatcher.java new file mode 100644 index 00000000000..056950c5693 --- /dev/null +++ b/Mage/src/main/java/mage/watchers/common/CastFromGraveyardWatcher.java @@ -0,0 +1,99 @@ +/* + * 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 mage.watchers.common; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import mage.constants.WatcherScope; +import mage.constants.Zone; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.stack.Spell; +import mage.watchers.Watcher; + +/** + * + * @author LevelX2 + */ +public class CastFromGraveyardWatcher extends Watcher { + + // holds which spell with witch zone change counter was cast from graveyard + private final Map> spellsCastFromGraveyard = new HashMap<>(); + + public CastFromGraveyardWatcher() { + super(CastFromGraveyardWatcher.class.getName(), WatcherScope.GAME); + } + + public CastFromGraveyardWatcher(final CastFromGraveyardWatcher watcher) { + super(watcher); + } + + @Override + public void watch(GameEvent event, Game game) { + /** + * This does still not handle if a spell is cast from hand and comes to + * play from other zones during the same step. But at least the state is + * reset if the game comes to a new step + */ + if (event.getType() == GameEvent.EventType.SPELL_CAST && event.getZone().equals(Zone.GRAVEYARD)) { + Spell spell = (Spell) game.getObject(event.getTargetId()); + if (spell != null) { + HashSet zcc = spellsCastFromGraveyard.get(spell.getSourceId()); + if (zcc == null) { + zcc = new HashSet<>(); + spellsCastFromGraveyard.put(spell.getSourceId(), zcc); + } + zcc.add(spell.getZoneChangeCounter(game)); + } + + } + } + + public boolean spellWasCastFromGraveyard(UUID sourceId, int zcc) { + Set zccSet = spellsCastFromGraveyard.get(sourceId); + if (zccSet != null) { + return zccSet.contains(zcc); + } + return false; + + } + + @Override + public void reset() { + super.reset(); + spellsCastFromGraveyard.clear(); + } + + @Override + public CastFromGraveyardWatcher copy() { + return new CastFromGraveyardWatcher(this); + } +}