diff --git a/Mage.Sets/src/mage/cards/k/KarnLiberated.java b/Mage.Sets/src/mage/cards/k/KarnLiberated.java index 9733ed5755e..a8b86091f7e 100644 --- a/Mage.Sets/src/mage/cards/k/KarnLiberated.java +++ b/Mage.Sets/src/mage/cards/k/KarnLiberated.java @@ -102,6 +102,9 @@ class KarnLiberatedEffect extends OneShotEffect { for (Card card : game.getCards()) { game.getState().addCard(card); } + // TODO: miss meld cards? + // TODO: miss copied cards? + for (Player player : game.getPlayers().values()) { if (player.canRespond()) { // only players alive are in the restarted game player.getGraveyard().clear(); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/watchers/TempleOfPowerTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/watchers/TempleOfPowerTest.java new file mode 100644 index 00000000000..6d0e075b19b --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/watchers/TempleOfPowerTest.java @@ -0,0 +1,70 @@ +package org.mage.test.cards.watchers; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import mage.view.GameView; +import org.junit.Assert; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author JayDi85 + */ +public class TempleOfPowerTest extends CardTestPlayerBase { + + private void checkGameView() { + // game view uses rules text generating, so TempleOfPowerWatcher will be called too + // original watcher code don't raise game error on miss watcher, but test must fail - so it uses direct key search here + String needWatcherKey = "TempleOfPowerWatcher"; + GameView gameView = getGameView(playerA); + Assert.assertNotNull(gameView); + Assert.assertNotNull("Watchers must be init with game card all the time, miss " + needWatcherKey, currentGame.getState().getWatcher(needWatcherKey)); + } + + @Test + public void test_TransformRevert() { + // Ojer Axonil, Deepest Might + // Temple of Power + // When Ojer Axonil dies, return it to the battlefield tapped and transformed under its owner’s control. + addCard(Zone.BATTLEFIELD, playerA, "Ojer Axonil, Deepest Might", 1); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 4 + 3); + addCard(Zone.HAND, playerA, "Lightning Bolt", 4); + + // turn 1 + + // kill and transform to back side + runCode("check", 1, PhaseStep.PRECOMBAT_MAIN, playerA, (info, player, game) -> checkGameView()); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", "Ojer Axonil, Deepest Might"); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", "Ojer Axonil, Deepest Might"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + checkPermanentCount("after transform to back", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Temple of Power", 1); + runCode("check", 1, PhaseStep.PRECOMBAT_MAIN, playerA, (info, player, game) -> checkGameView()); + + // turn 3 + + // damage and prepare condition for transform + runCode("check", 3, PhaseStep.PRECOMBAT_MAIN, playerA, (info, player, game) -> checkGameView()); + checkPermanentCount("before condition", 3, PhaseStep.PRECOMBAT_MAIN, playerA, "Ojer Axonil, Deepest Might", 0); + checkPlayableAbility("before condition", 3, PhaseStep.PRECOMBAT_MAIN, playerA, "{2}{R}, {T}: Transform", false); + castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", playerA); + castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", playerA); + waitStackResolved(3, PhaseStep.PRECOMBAT_MAIN); + checkPermanentCount("after condition", 3, PhaseStep.PRECOMBAT_MAIN, playerA, "Ojer Axonil, Deepest Might", 0); + checkPlayableAbility("after condition", 3, PhaseStep.PRECOMBAT_MAIN, playerA, "{2}{R}, {T}: Transform", true); + runCode("check", 3, PhaseStep.PRECOMBAT_MAIN, playerA, (info, player, game) -> checkGameView()); + + // transform to front side + activateAbility(3, PhaseStep.PRECOMBAT_MAIN, playerA, "{2}{R}, {T}: Transform"); + waitStackResolved(3, PhaseStep.PRECOMBAT_MAIN); + checkPermanentCount("after transform", 3, PhaseStep.PRECOMBAT_MAIN, playerA, "Ojer Axonil, Deepest Might", 1); + checkPermanentCount("after transform", 3, PhaseStep.PRECOMBAT_MAIN, playerA, "Temple of Power ", 0); + checkPlayableAbility("after transform", 3, PhaseStep.PRECOMBAT_MAIN, playerA, "{2}{R}, {T}: Transform", false); + runCode("check", 3, PhaseStep.PRECOMBAT_MAIN, playerA, (info, player, game) -> checkGameView()); + + setStrictChooseMode(true); + setStopAt(3, PhaseStep.END_TURN); + execute(); + + assertPermanentCount(playerA, "Ojer Axonil, Deepest Might", 1); + } +} diff --git a/Mage/src/main/java/mage/game/GameImpl.java b/Mage/src/main/java/mage/game/GameImpl.java index 8153a0f52b1..b36ca501bd8 100644 --- a/Mage/src/main/java/mage/game/GameImpl.java +++ b/Mage/src/main/java/mage/game/GameImpl.java @@ -318,8 +318,11 @@ public abstract class GameImpl implements Game { card = ((PermanentCard) card).getCard(); } - // init each card by parts... if you add new type here then getInitAbilities must be - // implemented too (it allows to split abilities between card and parts) + // usage hints: + // - each card and parts must be initialized here before usage + // - it add card/part to starting zone, assign abilities and init watchers + // - warning, if you add new type here then getInitAbilities must be + // implemented too (it allows to split abilities between card and parts) // main card card.setOwnerId(ownerId); @@ -348,6 +351,14 @@ public abstract class GameImpl implements Game { Card spellCard = ((AdventureCard) card).getSpellCard(); spellCard.setOwnerId(ownerId); addCardToState(spellCard); + } else if (card.isTransformable() && card.getSecondCardFace() != null) { + Card nightCard = card.getSecondCardFace(); + nightCard.setOwnerId(ownerId); + addCardToState(nightCard); + } else if (card.getMeldsToClazz() != null) { + // meld card will be added and init on meld effect resolve, so ignore it here + // TODO: rework meld logic cause card with watchers must be added on game init + // (possible bugs: miss watcher related data in meld cards/rules/hints) } } } diff --git a/Mage/src/main/java/mage/game/GameState.java b/Mage/src/main/java/mage/game/GameState.java index 5ca06a6b860..312aeb36009 100644 --- a/Mage/src/main/java/mage/game/GameState.java +++ b/Mage/src/main/java/mage/game/GameState.java @@ -634,16 +634,23 @@ public class GameState implements Serializable, Copyable { return this.turnMods; } + /** + * Find game scope watcher + */ public T getWatcher(Class watcherClass) { - return watcherClass.cast(watchers.get(watcherClass.getSimpleName())); + return getWatcher(watcherClass, null); } + /** + * Find card/player scope watcher + */ public T getWatcher(Class watcherClass, UUID uuid) { - return watcherClass.cast(watchers.get(watcherClass.getSimpleName(), uuid.toString())); + String watcherKey = (uuid == null ? "" : uuid.toString()) + watcherClass.getSimpleName(); + return watcherClass.cast(getWatcher(watcherKey)); } - public T getWatcher(Class watcherClass, String prefix) { - return watcherClass.cast(watchers.get(watcherClass.getSimpleName(), prefix)); + public Watcher getWatcher(String key) { + return watchers.get(key); } public SpecialActions getSpecialActions() { diff --git a/Mage/src/main/java/mage/watchers/Watchers.java b/Mage/src/main/java/mage/watchers/Watchers.java index 7fba3a54deb..79058d08171 100644 --- a/Mage/src/main/java/mage/watchers/Watchers.java +++ b/Mage/src/main/java/mage/watchers/Watchers.java @@ -12,7 +12,7 @@ import java.util.HashMap; */ public class Watchers extends HashMap { - private static Logger logger = LogManager.getLogger(Watcher.class.getSimpleName()); + private static final Logger logger = LogManager.getLogger(Watcher.class.getSimpleName()); public Watchers() { } @@ -39,15 +39,12 @@ public class Watchers extends HashMap { this.values().forEach(Watcher::reset); } - public Watcher get(String key, String id) { - return get(id + key); - } - @Override public Watcher get(Object key) { if (containsKey(key)) { return super.get(key); } + // can't add game exeption here because it's an easy way to ruin any game with bugged card logger.error(key + " not found in watchers", new Throwable()); return null; }