From 7575510c858bf6e3d6d8ca57edfc89e30b78f21e Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Tue, 14 Jan 2020 23:26:04 +0100 Subject: [PATCH] * Fixed that Soulfire Grand Master did not work for adventure or split cards (fixes #6182). --- .../src/mage/cards/s/SoulfireGrandMaster.java | 27 +++--- .../cards/cost/splitcards/SplitCardsTest.java | 52 ++++++++++++ .../java/org/mage/test/player/TestPlayer.java | 83 ++++++++++--------- 3 files changed, 111 insertions(+), 51 deletions(-) create mode 100644 Mage.Tests/src/test/java/org/mage/test/cards/cost/splitcards/SplitCardsTest.java diff --git a/Mage.Sets/src/mage/cards/s/SoulfireGrandMaster.java b/Mage.Sets/src/mage/cards/s/SoulfireGrandMaster.java index 07ed9c44b8..5fd5de0fe7 100644 --- a/Mage.Sets/src/mage/cards/s/SoulfireGrandMaster.java +++ b/Mage.Sets/src/mage/cards/s/SoulfireGrandMaster.java @@ -1,5 +1,6 @@ package mage.cards.s; +import java.util.UUID; import mage.MageInt; import mage.MageObject; import mage.abilities.Ability; @@ -10,9 +11,11 @@ import mage.abilities.effects.Effect; import mage.abilities.effects.GainAbilitySpellsEffect; import mage.abilities.effects.ReplacementEffectImpl; import mage.abilities.keyword.LifelinkAbility; +import mage.cards.AdventureCardSpell; import mage.cards.Card; import mage.cards.CardImpl; import mage.cards.CardSetInfo; +import mage.cards.SplitCardHalf; import mage.constants.*; import mage.filter.FilterCard; import mage.filter.FilterObject; @@ -23,8 +26,6 @@ import mage.game.events.ZoneChangeEvent; import mage.game.stack.Spell; import mage.players.Player; -import java.util.UUID; - /** * @author LevelX2 */ @@ -99,12 +100,10 @@ class SoulfireGrandMasterCastFromHandReplacementEffect extends ReplacementEffect @Override public boolean replaceEvent(GameEvent event, Ability source, Game game) { - MageObject mageObject = game.getObject(spellId); - if (!(mageObject instanceof Spell) || mageObject.isCopy()) { - return false; - } else { + Spell spell = (Spell) game.getStack().getFirst(); + if (!spell.isCopy() && !spell.isCountered()) { Card sourceCard = game.getCard(spellId); - if (sourceCard != null) { + if (sourceCard != null && Zone.STACK.equals(game.getState().getZone(spellId))) { Player player = game.getPlayer(sourceCard.getOwnerId()); if (player != null) { player.moveCards(sourceCard, Zone.HAND, source, game); @@ -141,9 +140,17 @@ class SoulfireGrandMasterCastFromHandReplacementEffect extends ReplacementEffect if (zEvent.getFromZone() == Zone.STACK && zEvent.getToZone() == Zone.GRAVEYARD && event.getTargetId().equals(spellId)) { - Spell spell = game.getStack().getSpell(spellId); - if (spell != null && !spell.isCountered()) { - return true; + if (game.getStack().getFirst() instanceof Spell) { + Card cardOfSpell = ((Spell) game.getStack().getFirst()).getCard(); + if (cardOfSpell instanceof SplitCardHalf) { + return ((SplitCardHalf) cardOfSpell).getParentCard().getId().equals(spellId); + } else if (cardOfSpell instanceof AdventureCardSpell) { + return (((AdventureCardSpell) cardOfSpell).getParentCard().getId().equals(spellId)); + } else { + if (cardOfSpell.getId().equals(spellId)) { + return true; + } + } } } } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/cost/splitcards/SplitCardsTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/cost/splitcards/SplitCardsTest.java new file mode 100644 index 0000000000..7f6ec618af --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/cost/splitcards/SplitCardsTest.java @@ -0,0 +1,52 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package org.mage.test.cards.cost.splitcards; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * + * @author LevelX2 + */ +public class SplitCardsTest extends CardTestPlayerBase { + + @Test + public void testReturnCardFromSoulfireGrandMaster() { + // Total CMC of Failure // Comply is 3, so should be exiled by Transgress the Mind. + addCard(Zone.BATTLEFIELD, playerA, "Volcanic Island", 6); + + // Lifelink + // Instant and sorcery spells you control have lifelink. + // {2}{U/R}{U/R}: The next time you cast an instant or sorcery spell from your hand this turn, put that card into your hand instead of your graveyard as it resolves. + addCard(Zone.BATTLEFIELD, playerA, "Soulfire Grand Master"); + + // Fire - Instant {1}{R} + // Fire deals 2 damage divided as you choose among one or two target creatures and/or players. + // Ice - Instant {1}{U} + // Tap target permanent. + // Draw a card. + addCard(Zone.HAND, playerA, "Fire // Ice"); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{2}{U/R}{U/R}"); + + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Fire", playerB); + addTargetAmount(playerA, "targetPlayer=PlayerB", 2); + + setStopAt(1, PhaseStep.END_TURN); + setStrictChooseMode(true); + execute(); + assertAllCommandsUsed(); + + assertLife(playerA, 22); + assertLife(playerB, 18); + + assertHandCount(playerA, "Fire // Ice", 1); + } + +} diff --git a/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java b/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java index 1383163221..de3381f1ef 100644 --- a/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java +++ b/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java @@ -1,5 +1,10 @@ package org.mage.test.player; +import java.io.Serializable; +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; import mage.MageItem; import mage.MageObject; import mage.MageObjectReference; @@ -56,13 +61,6 @@ import mage.util.CardUtil; import org.apache.log4j.Logger; import org.junit.Assert; import org.junit.Ignore; - -import java.io.Serializable; -import java.util.*; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import java.util.stream.Collectors; - import static org.mage.test.serverside.base.impl.CardTestPlayerAPIImpl.*; /** @@ -179,7 +177,7 @@ public class TestPlayer implements Player { /** * @param maxCallsWithoutAction max number of priority passes a player may - * have for this test (default = 100) + * have for this test (default = 100) */ public void setMaxCallsWithoutAction(int maxCallsWithoutAction) { this.maxCallsWithoutAction = maxCallsWithoutAction; @@ -333,6 +331,9 @@ public class TestPlayer implements Player { if (ability.getTargets().isEmpty()) { throw new UnsupportedOperationException("Ability has no targets, but there is a player target set - " + ability.toString()); } + if (ability.getTargets().get(0) instanceof TargetAmount) { + return true; // targetAmount have to be set by setTargetAmount in the test script + } ability.getTargets().get(0).addTarget(player.getId(), ability, game); targetsSet++; break; @@ -900,12 +901,12 @@ public class TestPlayer implements Player { List data = cards.stream() .map(c -> (((c instanceof PermanentToken) ? "[T] " : "[C] ") - + c.getIdName() - + (c.isCopy() ? " [copy of " + c.getCopyFrom().getId().toString().substring(0, 3) + "]" : "") - + " - " + c.getPower().getValue() + "/" + c.getToughness().getValue() - + (c.isPlaneswalker() ? " - L" + c.getCounters(game).getCount(CounterType.LOYALTY) : "") - + ", " + (c.isTapped() ? "Tapped" : "Untapped") - + (c.getAttachedTo() == null ? "" : ", attached to " + game.getPermanent(c.getAttachedTo()).getIdName()))) + + c.getIdName() + + (c.isCopy() ? " [copy of " + c.getCopyFrom().getId().toString().substring(0, 3) + "]" : "") + + " - " + c.getPower().getValue() + "/" + c.getToughness().getValue() + + (c.isPlaneswalker() ? " - L" + c.getCounters(game).getCount(CounterType.LOYALTY) : "") + + ", " + (c.isTapped() ? "Tapped" : "Untapped") + + (c.getAttachedTo() == null ? "" : ", attached to " + game.getPermanent(c.getAttachedTo()).getIdName()))) .sorted() .collect(Collectors.toList()); @@ -929,11 +930,11 @@ public class TestPlayer implements Player { List data = abilities.stream() .map(a -> (a.getZone() + " -> " - + a.getSourceObject(game).getIdName() + " -> " - + (a.toString().length() > 0 - ? a.toString().substring(0, Math.min(20, a.toString().length()) - 1) - : a.getClass().getSimpleName()) - + "...")) + + a.getSourceObject(game).getIdName() + " -> " + + (a.toString().length() > 0 + ? a.toString().substring(0, Math.min(20, a.toString().length()) - 1) + : a.getClass().getSimpleName()) + + "...")) .sorted() .collect(Collectors.toList()); @@ -1287,7 +1288,7 @@ public class TestPlayer implements Player { UUID defenderId = null; boolean mustAttackByAction = false; boolean madeAttackByAction = false; - for (Iterator it = actions.iterator(); it.hasNext(); ) { + for (Iterator it = actions.iterator(); it.hasNext();) { PlayerAction action = it.next(); if (action.getTurnNum() == game.getTurnNum() && action.getAction().startsWith("attack:")) { mustAttackByAction = true; @@ -1769,7 +1770,7 @@ public class TestPlayer implements Player { // skip targets if (targets.get(0).equals(TARGET_SKIP)) { Assert.assertTrue("found skip target, but it require more targets, needs " - + (target.getMinNumberOfTargets() - target.getTargets().size()) + " more", + + (target.getMinNumberOfTargets() - target.getTargets().size()) + " more", target.getTargets().size() >= target.getMinNumberOfTargets()); targets.remove(0); return true; @@ -3243,7 +3244,7 @@ public class TestPlayer implements Player { @Override public boolean choose(Outcome outcome, Target target, - UUID sourceId, Game game + UUID sourceId, Game game ) { // needed to call here the TestPlayer because it's overwitten return choose(outcome, target, sourceId, game, null); @@ -3251,7 +3252,7 @@ public class TestPlayer implements Player { @Override public boolean choose(Outcome outcome, Cards cards, - TargetCard target, Game game + TargetCard target, Game game ) { if (!choices.isEmpty()) { for (String choose2 : choices) { @@ -3287,7 +3288,7 @@ public class TestPlayer implements Player { @Override public boolean chooseTargetAmount(Outcome outcome, TargetAmount target, - Ability source, Game game + Ability source, Game game ) { // chooseTargetAmount calls for EACH target cycle (e.g. one target per click, see TargetAmount) // if use want to stop choosing then chooseTargetAmount must return false (example: up to xxx) @@ -3299,7 +3300,7 @@ public class TestPlayer implements Player { // skip targets if (targets.get(0).equals(TARGET_SKIP)) { Assert.assertTrue("found skip target, but it require more targets, needs " - + (target.getMinNumberOfTargets() - target.getTargets().size()) + " more", + + (target.getMinNumberOfTargets() - target.getTargets().size()) + " more", target.getTargets().size() >= target.getMinNumberOfTargets()); targets.remove(0); return false; // false in chooseTargetAmount = stop to choose @@ -3352,15 +3353,15 @@ public class TestPlayer implements Player { @Override public boolean choosePile(Outcome outcome, String message, - List pile1, List pile2, - Game game + List pile1, List pile2, + Game game ) { return computerPlayer.choosePile(outcome, message, pile1, pile2, game); } @Override public boolean playMana(Ability ability, ManaCost unpaid, - String promptText, Game game + String promptText, Game game ) { groupsForTargetHandling = null; return computerPlayer.playMana(ability, unpaid, promptText, game); @@ -3374,15 +3375,15 @@ public class TestPlayer implements Player { @Override public UUID chooseBlockerOrder(List blockers, CombatGroup combatGroup, - List blockerOrder, Game game + List blockerOrder, Game game ) { return computerPlayer.chooseBlockerOrder(blockers, combatGroup, blockerOrder, game); } @Override public void assignDamage(int damage, List targets, - String singleTargetName, UUID sourceId, - Game game + String singleTargetName, UUID sourceId, + Game game ) { computerPlayer.assignDamage(damage, targets, singleTargetName, sourceId, game); } @@ -3401,14 +3402,14 @@ public class TestPlayer implements Player { @Override public void pickCard(List cards, Deck deck, - Draft draft + Draft draft ) { computerPlayer.pickCard(cards, deck, draft); } @Override public boolean scry(int value, Ability source, - Game game + Game game ) { // Don't scry at the start of the game. if (game.getTurnNum() == 1 && game.getStep() == null) { @@ -3419,44 +3420,44 @@ public class TestPlayer implements Player { @Override public boolean surveil(int value, Ability source, - Game game + Game game ) { return computerPlayer.surveil(value, source, game); } @Override public boolean moveCards(Card card, Zone toZone, - Ability source, Game game + Ability source, Game game ) { return computerPlayer.moveCards(card, toZone, source, game); } @Override public boolean moveCards(Card card, Zone toZone, - Ability source, Game game, - boolean tapped, boolean faceDown, boolean byOwner, List appliedEffects + Ability source, Game game, + boolean tapped, boolean faceDown, boolean byOwner, List appliedEffects ) { return computerPlayer.moveCards(card, toZone, source, game, tapped, faceDown, byOwner, appliedEffects); } @Override public boolean moveCards(Cards cards, Zone toZone, - Ability source, Game game + Ability source, Game game ) { return computerPlayer.moveCards(cards, toZone, source, game); } @Override public boolean moveCards(Set cards, Zone toZone, - Ability source, Game game + Ability source, Game game ) { return computerPlayer.moveCards(cards, toZone, source, game); } @Override public boolean moveCards(Set cards, Zone toZone, - Ability source, Game game, - boolean tapped, boolean faceDown, boolean byOwner, List appliedEffects + Ability source, Game game, + boolean tapped, boolean faceDown, boolean byOwner, List appliedEffects ) { return computerPlayer.moveCards(cards, toZone, source, game, tapped, faceDown, byOwner, appliedEffects); }