From 09c3e2d70004ad1a9da15462291d40ca6f006c91 Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Wed, 21 Nov 2018 16:32:44 +0400 Subject: [PATCH 01/42] Tests: added checks for wrong addTarget definitions: * non supported target types; * non supported target definition; * detail info about problem ablities; --- .../java/org/mage/test/player/TestPlayer.java | 54 +++++++++++++++++-- 1 file changed, 51 insertions(+), 3 deletions(-) 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 ae5cdf3ca3..92cb940f3b 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 @@ -72,8 +72,8 @@ public class TestPlayer implements Player { private int foundNoAction = 0; private boolean AIPlayer; private final List actions = new ArrayList<>(); - private final List choices = new ArrayList<>(); - private final List targets = new ArrayList<>(); + private final List choices = new ArrayList<>(); // choices stack for choice + private final List targets = new ArrayList<>(); // targets stack for choose (it's uses on empty direct target by cast command) private final List modesSet = new ArrayList<>(); private final ComputerPlayer computerPlayer; @@ -528,7 +528,7 @@ public class TestPlayer implements Player { checkProccessed = true; } - // check PT: life + // check life: life if (params[0].equals(CHECK_COMMAND_LIFE) && params.length == 2) { assertLife(action, game, computerPlayer, Integer.parseInt(params[1])); actions.remove(action); @@ -1130,6 +1130,28 @@ public class TestPlayer implements Player { return computerPlayer.choose(outcome, target, sourceId, game, options); } + private void checkTargetDefinitionMarksSupport(Target needTarget, String targetDefinition, String canSupportChars) { + // fail on wrong chars in definition + // ^ - multiple targets + // [] - special option like [no copy] + // = - target type like targetPlayer=PlayerA + Boolean foundMulti = targetDefinition.contains("^"); + Boolean foundSpecialStart = targetDefinition.contains("["); + Boolean foundSpecialClose = targetDefinition.contains("]"); + Boolean foundEquals = targetDefinition.contains("="); + + Boolean canMulti = canSupportChars.contains("^"); + Boolean canSpecialStart = canSupportChars.contains("["); + Boolean canSpecialClose = canSupportChars.contains("]"); + Boolean canEquals = canSupportChars.contains("="); + + // how to fix: change target definition for addTarget in test's code or update choose from targets implementation in TestPlayer + if((foundMulti && !canMulti) || (foundSpecialStart && !canSpecialStart) || (foundSpecialClose && !canSpecialClose)|| (foundEquals && !canEquals)) { + Assert.fail("Targets list was setup by addTarget with " + targets + ", but target definition [" + targetDefinition + "]" + + " is not supported by ["+ canSupportChars + "] for target class " + needTarget.getClass().getSimpleName()); + } + } + @Override public boolean chooseTarget(Outcome outcome, Target target, Ability source, Game game) { if (!targets.isEmpty()) { @@ -1142,6 +1164,7 @@ public class TestPlayer implements Player { || target instanceof TargetCreatureOrPlayer || target instanceof TargetPermanentOrPlayer) { for (String targetDefinition : targets) { + checkTargetDefinitionMarksSupport(target, targetDefinition, "="); if (targetDefinition.startsWith("targetPlayer=")) { String playerName = targetDefinition.substring(targetDefinition.indexOf("targetPlayer=") + 13); for (Player player : game.getPlayers().values()) { @@ -1161,6 +1184,7 @@ public class TestPlayer implements Player { || (target instanceof TargetAnyTarget) || (target instanceof TargetCreatureOrPlayer)) { for (String targetDefinition : targets) { + checkTargetDefinitionMarksSupport(target, targetDefinition, "^[]"); String[] targetList = targetDefinition.split("\\^"); boolean targetFound = false; for (String targetName : targetList) { @@ -1208,6 +1232,7 @@ public class TestPlayer implements Player { if (target instanceof TargetCardInHand) { for (String targetDefinition : targets) { + checkTargetDefinitionMarksSupport(target, targetDefinition, "^"); String[] targetList = targetDefinition.split("\\^"); boolean targetFound = false; for (String targetName : targetList) { @@ -1230,6 +1255,7 @@ public class TestPlayer implements Player { } if (target instanceof TargetCardInYourGraveyard) { for (String targetDefinition : targets) { + checkTargetDefinitionMarksSupport(target, targetDefinition, "^"); String[] targetList = targetDefinition.split("\\^"); boolean targetFound = false; for (String targetName : targetList) { @@ -1252,6 +1278,8 @@ public class TestPlayer implements Player { } if (target instanceof TargetCardInOpponentsGraveyard) { for (String targetDefinition : targets) { + checkTargetDefinitionMarksSupport(target, targetDefinition, "^"); + String[] targetList = targetDefinition.split("\\^"); boolean targetFound = false; @@ -1281,6 +1309,7 @@ public class TestPlayer implements Player { } if (target instanceof TargetSpell) { for (String targetDefinition : targets) { + checkTargetDefinitionMarksSupport(target, targetDefinition, "^"); String[] targetList = targetDefinition.split("\\^"); boolean targetFound = false; for (String targetName : targetList) { @@ -1299,7 +1328,26 @@ public class TestPlayer implements Player { } } + // wrong target settings by addTarget + // how to fix: implement target class processing above + if(!targets.isEmpty()) { + String message; + + if(source != null) { + message = "Targets list was setup by addTarget with " + targets + ", but not used in [" + + "card " + source.getSourceObject(game) + + " -> ability " + source.getClass().getSimpleName() + " (" + source.getRule().substring(0, Math.min(20, source.getRule().length()) - 1) + "..." + ")" + + " -> target " + target.getClass().getSimpleName() + " (" + target.getMessage() + ")" + + "]"; + } else { + message = "Targets list was setup by addTarget with " + targets + ", but not used in [" + + "card XXX" + + " -> target " + target.getClass().getSimpleName() + " (" + target.getMessage() + ")" + + "]"; + } + Assert.fail(message); } + return computerPlayer.chooseTarget(outcome, target, source, game); } From 21d7207551e4e0c9617b5441b524e2e9bbdd7edd Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Wed, 21 Nov 2018 16:38:26 +0400 Subject: [PATCH 02/42] Tests: added support of TargetDefender; --- .../java/org/mage/test/player/TestPlayer.java | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) 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 92cb940f3b..ec33efbe92 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 @@ -1159,10 +1159,13 @@ public class TestPlayer implements Player { if (target.getTargetController() != null && target.getAbilityController() != null) { abilityControllerId = target.getAbilityController(); } + + // player if (target instanceof TargetPlayer || target instanceof TargetAnyTarget || target instanceof TargetCreatureOrPlayer - || target instanceof TargetPermanentOrPlayer) { + || target instanceof TargetPermanentOrPlayer + || target instanceof TargetDefender) { for (String targetDefinition : targets) { checkTargetDefinitionMarksSupport(target, targetDefinition, "="); if (targetDefinition.startsWith("targetPlayer=")) { @@ -1179,10 +1182,13 @@ public class TestPlayer implements Player { } } + + // permanent in battlefield if ((target instanceof TargetPermanent) || (target instanceof TargetPermanentOrPlayer) || (target instanceof TargetAnyTarget) - || (target instanceof TargetCreatureOrPlayer)) { + || (target instanceof TargetCreatureOrPlayer) + || (target instanceof TargetDefender)) { for (String targetDefinition : targets) { checkTargetDefinitionMarksSupport(target, targetDefinition, "^[]"); String[] targetList = targetDefinition.split("\\^"); @@ -1207,8 +1213,11 @@ public class TestPlayer implements Player { if (filter instanceof FilterCreaturePlayerOrPlaneswalker) { filter = ((FilterCreaturePlayerOrPlaneswalker) filter).getCreatureFilter(); } - if (filter instanceof TargetPermanentOrPlayer) { - filter = ((TargetPermanentOrPlayer) filter).getFilterPermanent(); + if (filter instanceof FilterPermanentOrPlayer) { + filter = ((FilterPermanentOrPlayer) filter).getPermanentFilter(); + } + if (filter instanceof FilterPlaneswalkerOrPlayer) { + filter = ((FilterPlaneswalkerOrPlayer) filter).getFilterPermanent(); } for (Permanent permanent : game.getBattlefield().getAllActivePermanents((FilterPermanent) filter, game)) { if (permanent.getName().equals(targetName) || (permanent.getName() + '-' + permanent.getExpansionSetCode()).equals(targetName)) { @@ -1230,6 +1239,7 @@ public class TestPlayer implements Player { } } + // card in hand if (target instanceof TargetCardInHand) { for (String targetDefinition : targets) { checkTargetDefinitionMarksSupport(target, targetDefinition, "^"); @@ -1251,6 +1261,7 @@ public class TestPlayer implements Player { return true; } } + } } if (target instanceof TargetCardInYourGraveyard) { @@ -1307,6 +1318,8 @@ public class TestPlayer implements Player { } } + + // stack if (target instanceof TargetSpell) { for (String targetDefinition : targets) { checkTargetDefinitionMarksSupport(target, targetDefinition, "^"); @@ -1327,6 +1340,7 @@ public class TestPlayer implements Player { } } } + } // wrong target settings by addTarget // how to fix: implement target class processing above From a9451711bbf6e23c6a259e52a6a319cd45dee37e Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Wed, 21 Nov 2018 16:42:00 +0400 Subject: [PATCH 03/42] Tests: added support of TargetCardInExile, TargetCardInGraveyard and variations; --- .../java/org/mage/test/player/TestPlayer.java | 87 +++++++++++++++---- .../common/FilterPlaneswalkerOrPlayer.java | 8 ++ Mage/src/main/java/mage/game/Exile.java | 8 ++ 3 files changed, 84 insertions(+), 19 deletions(-) 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 ec33efbe92..4751f33588 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 @@ -1263,17 +1263,18 @@ public class TestPlayer implements Player { } } - } - if (target instanceof TargetCardInYourGraveyard) { + // card in exile + if (target instanceof TargetCardInExile) { + TargetCardInExile targetFull = (TargetCardInExile) target; for (String targetDefinition : targets) { checkTargetDefinitionMarksSupport(target, targetDefinition, "^"); String[] targetList = targetDefinition.split("\\^"); boolean targetFound = false; for (String targetName : targetList) { - for (Card card : computerPlayer.getGraveyard().getCards(((TargetCardInYourGraveyard) target).getFilter(), game)) { + for (Card card : game.getExile().getCards(targetFull.getFilter(), game)) { if (card.getName().equals(targetName) || (card.getName() + '-' + card.getExpansionSetCode()).equals(targetName)) { - if (((TargetCardInYourGraveyard) target).canTarget(abilityControllerId, card.getId(), source, game) && !target.getTargets().contains(card.getId())) { - target.add(card.getId(), game); + if (targetFull.canTarget(abilityControllerId, card.getId(), source, game) && !targetFull.getTargets().contains(card.getId())) { + targetFull.add(card.getId(), game); targetFound = true; break; } @@ -1285,32 +1286,80 @@ public class TestPlayer implements Player { return true; } } - } - if (target instanceof TargetCardInOpponentsGraveyard) { + + // card in battlefield + if (target instanceof TargetCardInGraveyardOrBattlefield) { + TargetCard targetFull = (TargetCard) target; + for (String targetDefinition : targets) { + checkTargetDefinitionMarksSupport(target, targetDefinition, "^"); + String[] targetList = targetDefinition.split("\\^"); + boolean targetFound = false; + for (String targetName : targetList) { + for (Card card : game.getBattlefield().getAllActivePermanents()) { + if (card.getName().equals(targetName) || (card.getName() + '-' + card.getExpansionSetCode()).equals(targetName)) { + if (targetFull.canTarget(abilityControllerId, card.getId(), source, game) && !targetFull.getTargets().contains(card.getId())) { + targetFull.add(card.getId(), game); + targetFound = true; + break; + } + } + } + } + if (targetFound) { + targets.remove(targetDefinition); + return true; + } + } + } + + + // card in graveyard + if (target instanceof TargetCardInOpponentsGraveyard + || target instanceof TargetCardInYourGraveyard + || target instanceof TargetCardInGraveyard + || target instanceof TargetCardInGraveyardOrBattlefield) { + TargetCard targetFull = (TargetCard) target; + + List needPlayers = game.getState().getPlayersInRange(getId(), game).toList(); + // fix for opponent graveyard + if(target instanceof TargetCardInOpponentsGraveyard) { + // current player remove + Assert.assertTrue(needPlayers.contains(getId())); + needPlayers.remove(getId()); + Assert.assertFalse(needPlayers.contains(getId())); + } + // fix for your graveyard + if(target instanceof TargetCardInYourGraveyard) { + // only current player + Assert.assertTrue(needPlayers.contains(getId())); + needPlayers.clear(); + needPlayers.add(getId()); + Assert.assertFalse(needPlayers.contains(getId())); + } + for (String targetDefinition : targets) { checkTargetDefinitionMarksSupport(target, targetDefinition, "^"); String[] targetList = targetDefinition.split("\\^"); boolean targetFound = false; - for (String targetName : targetList) { - IterateOpponentsGraveyards: - for (UUID opponentId : game.getState().getPlayersInRange(getId(), game)) { - if (computerPlayer.hasOpponent(opponentId, game)) { - Player opponent = game.getPlayer(opponentId); - for (Card card : opponent.getGraveyard().getCards(((TargetCardInOpponentsGraveyard) target).getFilter(), game)) { - if (card.getName().equals(targetName) || (card.getName() + '-' + card.getExpansionSetCode()).equals(targetName)) { - if (((TargetCardInOpponentsGraveyard) target).canTarget(abilityControllerId, card.getId(), source, game) && !target.getTargets().contains(card.getId())) { - target.add(card.getId(), game); - targetFound = true; - break IterateOpponentsGraveyards; - } + IterateGraveyards: + for (UUID playerId : needPlayers) { + Player player = game.getPlayer(playerId); + for (Card card : player.getGraveyard().getCards(targetFull.getFilter(), game)) { + if (card.getName().equals(targetName) || (card.getName() + '-' + card.getExpansionSetCode()).equals(targetName)) { + if (targetFull.canTarget(abilityControllerId, card.getId(), source, game) && !target.getTargets().contains(card.getId())) { + target.add(card.getId(), game); + targetFound = true; + break IterateGraveyards; } } } + } } + if (targetFound) { targets.remove(targetDefinition); return true; diff --git a/Mage/src/main/java/mage/filter/common/FilterPlaneswalkerOrPlayer.java b/Mage/src/main/java/mage/filter/common/FilterPlaneswalkerOrPlayer.java index f587236ac4..601bb952bc 100644 --- a/Mage/src/main/java/mage/filter/common/FilterPlaneswalkerOrPlayer.java +++ b/Mage/src/main/java/mage/filter/common/FilterPlaneswalkerOrPlayer.java @@ -47,6 +47,14 @@ public class FilterPlaneswalkerOrPlayer extends FilterImpl { this.playerFilter = filter.playerFilter.copy(); } + public FilterPlaneswalkerPermanent getFilterPermanent() { + return this.planeswalkerFilter; + } + + public FilterPlayer getFilterPlayer() { + return this.playerFilter; + } + @Override public boolean checkObjectClass(Object object) { return true; diff --git a/Mage/src/main/java/mage/game/Exile.java b/Mage/src/main/java/mage/game/Exile.java index 3fc2a7ab7b..304bbe07ec 100644 --- a/Mage/src/main/java/mage/game/Exile.java +++ b/Mage/src/main/java/mage/game/Exile.java @@ -9,7 +9,10 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.UUID; +import java.util.stream.Collectors; + import mage.cards.Card; +import mage.filter.FilterCard; import mage.util.Copyable; /** @@ -70,6 +73,11 @@ public class Exile implements Serializable, Copyable { return null; } + public List getCards(FilterCard filter, Game game) { + List allCards = getAllCards(game); + return allCards.stream().filter(card -> filter.match(card, game)).collect(Collectors.toList()); + } + public List getAllCards(Game game) { List cards = new ArrayList<>(); for (ExileZone exile : exileZones.values()) { From be225f7def463648ad49380e6fdd1677e29e73f4 Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Wed, 21 Nov 2018 16:54:05 +0400 Subject: [PATCH 04/42] Tests: fixed CipherTest --- .../cards/abilities/keywords/CipherTest.java | 134 +++++++++--------- 1 file changed, 66 insertions(+), 68 deletions(-) diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/CipherTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/CipherTest.java index a1e4302162..2f109ed839 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/CipherTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/CipherTest.java @@ -1,68 +1,66 @@ - -package org.mage.test.cards.abilities.keywords; - -import mage.constants.PhaseStep; -import mage.constants.Zone; -import org.junit.Test; -import org.mage.test.serverside.base.CardTestPlayerBase; - -/** - * - * @author LevelX2 - */ -public class CipherTest extends CardTestPlayerBase { - - /** - * Produced a copy of the opponents Roil Elemental with Stolen Identity and - * used Cipher on that same token. The token's landfall ability then did - * trigger normally up to the point where a target creature could be - * selected. The selection was logged by XMage, but the effect simply did - * not work. The original Roil Elemental controlled by the other player - * worked as intended, though. - * - * Edit: Opponent was AI, if that helps. - */ - @Test - public void testStolenIdentity() { - addCard(Zone.BATTLEFIELD, playerA, "Island", 6); - addCard(Zone.HAND, playerA, "Mountain", 1); - - // Create a token that's a copy of target artifact or creature. - // Cipher (Then you may exile this spell card encoded on a creature you control. Whenever that creature deals combat damage to a player, its controller may cast a copy of the encoded card without paying its mana cost.) - addCard(Zone.HAND, playerA, "Stolen Identity"); // Sorcery {4}{U}{U} - - // Flying - // Landfall - Whenever a land enters the battlefield under your control, you may gain control of target creature for as long as you control Roil Elemental. - addCard(Zone.BATTLEFIELD, playerB, "Roil Elemental"); - addCard(Zone.BATTLEFIELD, playerB, "Pillarfield Ox"); - addCard(Zone.BATTLEFIELD, playerB, "Silvercoat Lion"); - - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Stolen Identity", "Roil Elemental"); - setChoice(playerA, "Yes"); - - playLand(3, PhaseStep.PRECOMBAT_MAIN, playerA, "Mountain"); - addTarget(playerA, "Silvercoat Lion"); // Triggered ability of copied Roil Elemental to gain control - - attack(3, playerA, "Roil Elemental"); // Creature 3/2 - addTarget(playerA, "Pillarfield Ox"); - - setStopAt(3, PhaseStep.POSTCOMBAT_MAIN); - execute(); - - assertLife(playerB, 17); - - assertExileCount(playerA, "Stolen Identity", 1); - - assertPermanentCount(playerA, "Mountain", 1); - - assertPermanentCount(playerB, "Pillarfield Ox", 1); - assertPermanentCount(playerA, "Pillarfield Ox", 1); // a copy from the cipered Stolen Identity caused by the Roil Elelemtal Attack - - assertPermanentCount(playerB, "Silvercoat Lion", 0); - assertPermanentCount(playerA, "Silvercoat Lion", 1); // Gain control from triggered ability of the copied Roil Elemental ????? TARGET ??? - - assertPermanentCount(playerB, "Roil Elemental", 1); - assertPermanentCount(playerA, "Roil Elemental", 1); - - } -} + +package org.mage.test.cards.abilities.keywords; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * + * @author LevelX2 + */ +public class CipherTest extends CardTestPlayerBase { + + /** + * Produced a copy of the opponents Roil Elemental with Stolen Identity and + * used Cipher on that same token. The token's landfall ability then did + * trigger normally up to the point where a target creature could be + * selected. The selection was logged by XMage, but the effect simply did + * not work. The original Roil Elemental controlled by the other player + * worked as intended, though. + * + * Edit: Opponent was AI, if that helps. + */ + @Test + public void testStolenIdentity() { + addCard(Zone.BATTLEFIELD, playerA, "Island", 6); + addCard(Zone.HAND, playerA, "Mountain", 1); + + // Create a token that's a copy of target artifact or creature. + // Cipher (Then you may exile this spell card encoded on a creature you control. Whenever that creature deals combat damage to a player, its controller may cast a copy of the encoded card without paying its mana cost.) + addCard(Zone.HAND, playerA, "Stolen Identity"); // Sorcery {4}{U}{U} + + // Flying + // Landfall - Whenever a land enters the battlefield under your control, you may gain control of target creature for as long as you control Roil Elemental. + addCard(Zone.BATTLEFIELD, playerB, "Roil Elemental"); + addCard(Zone.BATTLEFIELD, playerB, "Pillarfield Ox"); + addCard(Zone.BATTLEFIELD, playerB, "Silvercoat Lion"); + + // cast spell, create copy token, exile spell card and encode it to that token of Roil Elemental + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Stolen Identity", "Roil Elemental"); + setChoice(playerA, "Yes"); // Cipher activate + addTarget(playerA, "Roil Elemental"); // Cipher target for encode + checkPermanentCount("playerA must have Roil Elemental", 2, PhaseStep.PRECOMBAT_MAIN, playerA, "Roil Elemental", 1); + checkPermanentCount("playerB must have Roil Elemental", 2, PhaseStep.PRECOMBAT_MAIN, playerB, "Roil Elemental", 1); + checkExileCount("Stolen Identity must be in exile zone", 2, PhaseStep.PRECOMBAT_MAIN, playerA, "Stolen Identity", 1); + + // Roil Elemental must activated on new land + playLand(3, PhaseStep.PRECOMBAT_MAIN, playerA, "Mountain"); + setChoice(playerA, "Yes"); // activate landfall to control opponent creature + addTarget(playerA, "Silvercoat Lion"); // Triggered ability of copied Roil Elemental to gain control + checkPermanentCount("must gain control of Lion", 3, PhaseStep.POSTCOMBAT_MAIN, playerA, "Silvercoat Lion", 1); + checkPermanentCount("must lose control of Lion", 3, PhaseStep.POSTCOMBAT_MAIN, playerB, "Silvercoat Lion", 0); + + // on attack must activated ability to free cast + attack(5, playerA, "Roil Elemental"); + setChoice(playerA, "Yes"); // activate free cast of encoded card + checkPermanentCount("playerA must have 2 Roil Elemental", 5, PhaseStep.POSTCOMBAT_MAIN, playerA, "Roil Elemental", 2); + checkPermanentCount("playerB must have Roil Elemental", 5, PhaseStep.POSTCOMBAT_MAIN, playerB, "Roil Elemental", 1); + + setStopAt(5, PhaseStep.POSTCOMBAT_MAIN); + execute(); + + assertLife(playerB, 17); // -3 by Roil + } +} \ No newline at end of file From f86cfe9e3db33704ccbe65855157260c4f574684 Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Wed, 21 Nov 2018 16:56:31 +0400 Subject: [PATCH 05/42] Tests: fixed EchoTest --- .../cards/abilities/keywords/EchoTest.java | 34 +++++++++++-------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/EchoTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/EchoTest.java index c75ebd9b23..07cf2e1902 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/EchoTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/EchoTest.java @@ -1,4 +1,3 @@ - package org.mage.test.cards.abilities.keywords; import mage.constants.PhaseStep; @@ -7,29 +6,27 @@ import org.junit.Test; import org.mage.test.serverside.base.CardTestPlayerBase; /** - * * @author LevelX2 */ public class EchoTest extends CardTestPlayerBase { - /* - * I flickered an Avalanche Riders with its Echo trigger on the stack with Restoration Angel. - * When the trigger resolved, my Riders was sacrificed, even though it should have been - * considered a new permanent. - */ + /* + * I flickered an Avalanche Riders with its Echo trigger on the stack with Restoration Angel. + * When the trigger resolved, my Riders was sacrificed, even though it should have been + * considered a new permanent. + */ @Test public void testEchoTriggerChecksIdentity() { - - addCard(Zone.BATTLEFIELD, playerA, "Plains", 2); - addCard(Zone.BATTLEFIELD, playerA, "Mountain", 2); - // Avalanche Riders Creature - Human Nomad 2/2 + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 4); + // Avalanche Riders Creature - Human Nomad 2/2 {3}{R} // Haste // Echo (At the beginning of your upkeep, if this came under your control since the beginning of your last upkeep, sacrifice it unless you pay its echo cost.) // When Avalanche Riders enters the battlefield, destroy target land. addCard(Zone.HAND, playerA, "Avalanche Riders"); + addCard(Zone.BATTLEFIELD, playerA, "Plains", 4); // Restoration Angel {3}{W} // Flash // Flying @@ -37,12 +34,19 @@ public class EchoTest extends CardTestPlayerBase { // then return that card to the battlefield under your control. addCard(Zone.HAND, playerA, "Restoration Angel"); - addCard(Zone.BATTLEFIELD, playerB, "Mountain", 1); + // cast Avalanche Riders and destroy forest + addCard(Zone.BATTLEFIELD, playerA, "Forest", 1); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Avalanche Riders"); + addTarget(playerA, "Forest"); + // Avalanche Riders go to echo, cast Restoration Angel to restore rider (do not apply echo with 4 mana) + activateManaAbility(3, PhaseStep.UPKEEP, playerA, "{T}: Add {W}"); + activateManaAbility(3, PhaseStep.UPKEEP, playerA, "{T}: Add {W}"); + activateManaAbility(3, PhaseStep.UPKEEP, playerA, "{T}: Add {W}"); + activateManaAbility(3, PhaseStep.UPKEEP, playerA, "{T}: Add {W}"); castSpell(3, PhaseStep.UPKEEP, playerA, "Restoration Angel", null, "Echo {3}{R} (At the beginning of your upkeep, if this came under your control since the beginning of your last upkeep, sacrifice it unless you pay its echo cost.)"); - addTarget(playerA, "Avalanche Riders"); + setChoice(playerA, "Yes"); // raider do restore setStopAt(3, PhaseStep.PRECOMBAT_MAIN); execute(); @@ -52,7 +56,9 @@ public class EchoTest extends CardTestPlayerBase { assertPermanentCount(playerA, "Avalanche Riders", 1); assertPermanentCount(playerA, "Restoration Angel", 1); - assertPermanentCount(playerB, "Mountain", 0); + assertPermanentCount(playerB, "Forest", 0); + assertTappedCount("Plains", true, 4); + assertTappedCount("Mountain", true, 0); } From 92afbb3bc6ea06450b7d04f7a75e4906fe3abfd5 Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Wed, 21 Nov 2018 16:58:42 +0400 Subject: [PATCH 06/42] Tests: fixed EquipRestrictedTest --- .../test/cards/abilities/equipped/EquipRestrictedTest.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/equipped/EquipRestrictedTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/equipped/EquipRestrictedTest.java index e6013f7d61..0f2193091e 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/equipped/EquipRestrictedTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/equipped/EquipRestrictedTest.java @@ -1,4 +1,3 @@ - package org.mage.test.cards.abilities.equipped; import mage.constants.PhaseStep; @@ -9,7 +8,6 @@ import org.junit.Test; import org.mage.test.serverside.base.CardTestPlayerBase; /** - * * @author LevelX2 */ public class EquipRestrictedTest extends CardTestPlayerBase { @@ -21,7 +19,7 @@ public class EquipRestrictedTest extends CardTestPlayerBase { addCard(Zone.BATTLEFIELD, playerB, "Leonin Scimitar"); activateAbility(2, PhaseStep.PRECOMBAT_MAIN, playerB, "{T}: Attach target Equipment you control to target creature you control.", "Leonin Scimitar"); - addTarget(playerB, "Silvercout Lion"); + addTarget(playerB, "Silvercoat Lion"); setStopAt(2, PhaseStep.BEGIN_COMBAT); execute(); @@ -43,7 +41,7 @@ public class EquipRestrictedTest extends CardTestPlayerBase { addCard(Zone.BATTLEFIELD, playerB, "Konda's Banner"); activateAbility(2, PhaseStep.PRECOMBAT_MAIN, playerB, "{T}: Attach target Equipment you control to target creature you control.", "Konda's Banner"); - addTarget(playerB, "Silvercout Lion"); + addTarget(playerB, "Silvercoat Lion"); setStopAt(2, PhaseStep.BEGIN_COMBAT); execute(); From 079baa94957eefbd44a1982db8f5c724a900b065 Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Wed, 21 Nov 2018 17:00:38 +0400 Subject: [PATCH 07/42] Tests: fixed MyriadTest --- .../org/mage/test/multiplayer/MyriadTest.java | 24 +++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/Mage.Tests/src/test/java/org/mage/test/multiplayer/MyriadTest.java b/Mage.Tests/src/test/java/org/mage/test/multiplayer/MyriadTest.java index 8f93c6ebee..0538e0c3d6 100644 --- a/Mage.Tests/src/test/java/org/mage/test/multiplayer/MyriadTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/multiplayer/MyriadTest.java @@ -1,7 +1,5 @@ - package org.mage.test.multiplayer; -import java.io.FileNotFoundException; import mage.constants.MultiplayerAttackOption; import mage.constants.PhaseStep; import mage.constants.RangeOfInfluence; @@ -12,8 +10,9 @@ import mage.game.GameException; import org.junit.Test; import org.mage.test.serverside.base.CardTestMultiPlayerBase; +import java.io.FileNotFoundException; + /** - * * @author LevelX2 */ public class MyriadTest extends CardTestMultiPlayerBase { @@ -72,27 +71,32 @@ public class MyriadTest extends CardTestMultiPlayerBase { // Myriad (Whenever this creature attacks, for each opponent other than the defending player, put a token that's a copy of this creature onto the battlefield tapped and attacking that player or a planeswalker he or she controls. Exile those tokens at the end of combat.) addCard(Zone.BATTLEFIELD, playerD, "Caller of the Pack"); // 8/6 + // turns: A, D, C, B + // +1: You gain 2 life. // -1: Put a +1/+1 counter on each creature you control. Those creatures gain vigilance until end of turn. // -6: Create a white Avatar creature token. It has "This creature's power and toughness are each equal to your life total." addCard(Zone.BATTLEFIELD, playerA, "Ajani Goldmane"); + // +2 life activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "+1:"); + checkLife("must have +2 life", 2, PhaseStep.PRECOMBAT_MAIN, playerA, 40 + 2); + // D attack C, create 2 copy of caller (for each opponent exclude defender = 2) and attack to ajani attack(2, playerD, "Caller of the Pack", playerC); - addTarget(playerD, "Ajani Goldmane"); + addTarget(playerD, "Ajani Goldmane"); // select ajani instead playerA for pack attack + checkPermanentCount("must have 3 packs", 2, PhaseStep.END_COMBAT, playerD, "Caller of the Pack", 3); + checkPermanentCount("ajani must die", 2, PhaseStep.END_COMBAT, playerA, "Ajani Goldmane", 0); + checkLife("pack must not damage playerA", 2, PhaseStep.END_COMBAT, playerA, 40 + 2); + checkLife("pack must damage playerB by 8", 2, PhaseStep.END_COMBAT, playerB, 40 - 8); + checkLife("pack must damage playerC", 2, PhaseStep.END_COMBAT, playerC, 40 - 8); + checkLife("pack must not damage playerD", 2, PhaseStep.END_COMBAT, playerD, 40); setStopAt(2, PhaseStep.POSTCOMBAT_MAIN); execute(); assertPermanentCount(playerD, "Caller of the Pack", 1); assertGraveyardCount(playerA, "Ajani Goldmane", 1); - - assertLife(playerA, 42); - assertLife(playerB, 32); - assertLife(playerC, 32); - assertLife(playerD, 40); - } /** From c2158a2d9c1b3670426d076c2aab337a3244ac66 Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Wed, 21 Nov 2018 17:01:56 +0400 Subject: [PATCH 08/42] Tests: fixed PrizedAmalgamTest --- .../mage/test/cards/single/soi/PrizedAmalgamTest.java | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) 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 807859f205..6dc75feb43 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 @@ -66,23 +66,24 @@ public class PrizedAmalgamTest extends CardTestPlayerBase { public void testOpponentReturnsCreatureFromGrave() { addCard(Zone.HAND, playerA, "Reanimate", 1); - addCard(Zone.GRAVEYARD, playerA, "Hill Giant", 1); // {3}{R} 3/3 + addCard(Zone.GRAVEYARD, playerA, "Hill Giant", 1); // {3}{R} 3/3, 4 CMC addCard(Zone.BATTLEFIELD, playerA, "Swamp", 1); addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3); - addCard(Zone.GRAVEYARD, playerB, "Prized Amalgam", 1); + addCard(Zone.GRAVEYARD, playerB, "Prized Amalgam", 1); // {1}{U}{B}, 3 CMC - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Reanimate", "Hill Giant"); + 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 + assertLife(playerA, 16); // lose 4 life from reanimate 4 CMC by Hill Giant 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 opponent returning a card from your graveyard to battlefield. */ @Test public void testOpponentReturnsCreatureFromYourGrave() { From 62c156edb11f1a9fe982221146aac3ce10ea14e6 Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Wed, 21 Nov 2018 17:07:14 +0400 Subject: [PATCH 09/42] Tests: fixed FlashbackTest --- .../abilities/keywords/FlashbackTest.java | 19 +++++++------------ .../java/org/mage/test/player/TestPlayer.java | 2 +- 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/FlashbackTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/FlashbackTest.java index a1a900ba11..23abdec9c3 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/FlashbackTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/FlashbackTest.java @@ -1,4 +1,3 @@ - package org.mage.test.cards.abilities.keywords; import mage.abilities.keyword.TrampleAbility; @@ -9,7 +8,6 @@ import org.junit.Test; import org.mage.test.serverside.base.CardTestPlayerBase; /** - * * @author LevelX2 */ public class FlashbackTest extends CardTestPlayerBase { @@ -71,10 +69,9 @@ public class FlashbackTest extends CardTestPlayerBase { } /** - * * Test Granting Flashback to spells with X in manacost which have targeting * requirements depending on the choice of X - * + *

* Specific instance: Snapcaster Mage granting Flashback to Repeal */ @Test @@ -84,7 +81,7 @@ public class FlashbackTest extends CardTestPlayerBase { addCard(Zone.GRAVEYARD, playerA, "Repeal", 1); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Snapcaster Mage"); - setChoice(playerA, "Repeal"); + addTarget(playerA, "Repeal"); activateAbility(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Flashback"); setChoice(playerA, "X=2"); @@ -100,10 +97,9 @@ public class FlashbackTest extends CardTestPlayerBase { } /** - * * Test Granting Flashback to spells with X in mana cost, where X has no * influence on targeting requirements - * + *

* Specific instance: Snapcaster Mage granting Flashback to Blaze */ @Test @@ -115,7 +111,7 @@ public class FlashbackTest extends CardTestPlayerBase { addCard(Zone.GRAVEYARD, playerA, "Blaze", 1); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Snapcaster Mage"); - setChoice(playerA, "Blaze"); + addTarget(playerA, "Blaze"); activateAbility(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Flashback"); setChoice(playerA, "X=1"); @@ -133,7 +129,6 @@ public class FlashbackTest extends CardTestPlayerBase { /** * My opponent put Iona on the battlefield using Unburial Rites, but my game * log didn't show me the color he has chosen. - * */ @Test public void testUnburialRites() { @@ -238,7 +233,7 @@ public class FlashbackTest extends CardTestPlayerBase { * Ancestral Vision has no casting cost (this is different to a casting cost * of {0}). Snapcaster Mage, for example, is able to give it flashback * whilst it is in the graveyard. - * + *

* However the controller should not be able to cast Ancestral Visions from * the graveyard for {0} mana. */ @@ -348,7 +343,7 @@ public class FlashbackTest extends CardTestPlayerBase { // When Snapcaster Mage enters the battlefield, target instant or sorcery card in your graveyard gains flashback until end of turn. The flashback cost is equal to its mana cost. castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Snapcaster Mage"); - setChoice(playerA, "Terminate"); + addTarget(playerA, "Terminate"); activateAbility(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Flashback"); // Flashback Terminate addTarget(playerA, "Icefall Regent"); @@ -537,7 +532,7 @@ public class FlashbackTest extends CardTestPlayerBase { addCard(Zone.BATTLEFIELD, playerB, "Mountain", 1); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Snapcaster Mage"); - setChoice(playerA, "Force of Will"); + addTarget(playerA, "Force of Will"); castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerB, "Lightning Bolt", "Snapcaster Mage"); activateAbility(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Flashback", null, "Lightning Bolt"); 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 4751f33588..30e83c5c63 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 @@ -1335,7 +1335,7 @@ public class TestPlayer implements Player { Assert.assertTrue(needPlayers.contains(getId())); needPlayers.clear(); needPlayers.add(getId()); - Assert.assertFalse(needPlayers.contains(getId())); + Assert.assertEquals(1, needPlayers.size()); } for (String targetDefinition : targets) { From d9e79c20f97a3e44d8c5a0f157b1bb88701de769 Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Wed, 21 Nov 2018 17:33:10 +0400 Subject: [PATCH 10/42] Tests: fixed SpliceOnArcaneTest --- .../keywords/SpliceOnArcaneTest.java | 29 ++++++++++++------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/SpliceOnArcaneTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/SpliceOnArcaneTest.java index a5abc4c3f3..48f7759381 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/SpliceOnArcaneTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/SpliceOnArcaneTest.java @@ -1,15 +1,14 @@ - package org.mage.test.cards.abilities.keywords; import mage.abilities.keyword.HasteAbility; import mage.constants.PhaseStep; import mage.constants.Zone; import org.junit.Assert; +import org.junit.Ignore; import org.junit.Test; import org.mage.test.serverside.base.CardTestPlayerBase; /** - * * @author LevelX2 */ public class SpliceOnArcaneTest extends CardTestPlayerBase { @@ -17,7 +16,6 @@ public class SpliceOnArcaneTest extends CardTestPlayerBase { /** * Test that it works to cast Through the Breach by slicing it on an arcane * spell - * */ @Test public void testSpliceThroughTheBreach() { @@ -32,7 +30,10 @@ public class SpliceOnArcaneTest extends CardTestPlayerBase { addCard(Zone.HAND, playerA, "Silvercoat Lion", 1); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lava Spike", playerB); - setChoice(playerA, "Silvercoat Lion"); + // activate splice: yes -> card with splice ability -> new target for spliced ability + setChoice(playerA, "Yes"); + addTarget(playerA, "Through the Breach"); + addTarget(playerA, "Silvercoat Lion"); // target for spliced ability: put from hand to battlefield setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); @@ -60,8 +61,12 @@ public class SpliceOnArcaneTest extends CardTestPlayerBase { addCard(Zone.BATTLEFIELD, playerB, "Silvercoat Lion", 1); + // cast arcane Lava Spike castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lava Spike", playerB); - addTarget(playerA, "Silvercoat Lion"); + // activate splice: yes -> card with splice ability -> new target for spliced ability + setChoice(playerA, "Yes"); + addTarget(playerA, "Torrent of Stone"); + addTarget(playerA, "Silvercoat Lion"); // target for spliced ability: 4 damage setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); @@ -97,8 +102,10 @@ public class SpliceOnArcaneTest extends CardTestPlayerBase { addCard(Zone.HAND, playerA, "Silvercoat Lion", 1); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Nourishing Shoal"); + // activate splice: yes -> card with splice ability -> new target for spliced ability setChoice(playerA, "Yes"); - setChoice(playerA, "Silvercoat Lion"); + addTarget(playerA, "Through the Breach"); + addTarget(playerA, "Silvercoat Lion"); // target for spliced ability: put from hand to battlefield setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); @@ -117,12 +124,12 @@ public class SpliceOnArcaneTest extends CardTestPlayerBase { /** * Cards involved: Nourishing Shoal, Goryo's Vengeance, Griselbrand, * Terminate - * + *

* I actually noticed this bug on the 1.4.3 client, but I didn't see it in * the change log for 1.4.4, so I assume it is still unknown. Also, it is a * bit of a rules corner case and I haven't seen anyone else report it, so * the players of this deck may actually not realize it's incorrect. - * + *

* The scenario was that I cast a Nourishing Shoal with a Goryo's Vengeance * spliced to it targeting Griselbrand in my graveyard and exiling * Worldspine Wurm. My opponent responded with a Snapcaster Mage, so to @@ -132,7 +139,7 @@ public class SpliceOnArcaneTest extends CardTestPlayerBase { * resolve, it should have been countered due to no legal target. However, * it caused me to gain 11 life. It did not resurrect Griselbrand * (correctly), but it should have done nothing at all. - * + *

* I include the info about the Terminate because thinking through, it could * be pertinent. I would guess what is going on here is one of two things. * Either the client doesn't recognize the Shoal with a spliced Vengeance as @@ -145,7 +152,10 @@ public class SpliceOnArcaneTest extends CardTestPlayerBase { * the error against a bot and update this report. */ @Test + @Ignore public void testCounteredBecauseOfNoLegalTarget() { + // TODO: rewrite test, it's wrong and misleading-- user report about Griselbrand was destroyed by Terminate after splice anounce, but tests don't use it at all (Griselbrand legal target all the time) + addCard(Zone.BATTLEFIELD, playerA, "Forest", 2); addCard(Zone.BATTLEFIELD, playerA, "Swamp", 8); // You may exile a green card with converted mana cost X from your hand rather than pay Nourishing Shoal's mana cost. @@ -174,5 +184,4 @@ public class SpliceOnArcaneTest extends CardTestPlayerBase { assertLife(playerB, 20); } - } From 81942aa3001a708cdd3ee18f76189980f3f6a223 Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Thu, 22 Nov 2018 04:57:51 +0400 Subject: [PATCH 11/42] Tests: fixed OblivionSowerTest --- .../test/cards/abilities/oneshot/exile/OblivionSowerTest.java | 1 + 1 file changed, 1 insertion(+) diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/oneshot/exile/OblivionSowerTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/oneshot/exile/OblivionSowerTest.java index 3dcb48df92..19680d1380 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/oneshot/exile/OblivionSowerTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/oneshot/exile/OblivionSowerTest.java @@ -30,6 +30,7 @@ public class OblivionSowerTest extends CardTestPlayerBase { skipInitShuffling(); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Oblivion Sower"); + addTarget(playerA, playerB); addTarget(playerA, "Canopy Vista^Canopy Vista^Canopy Vista"); From ea65ed736750b42d275c389e2d970e097a412244 Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Thu, 22 Nov 2018 05:05:45 +0400 Subject: [PATCH 12/42] Tests: fixed TragicSlipTest --- .../mage/test/cards/conditional/TragicSlipTest.java | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/conditional/TragicSlipTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/conditional/TragicSlipTest.java index 0bbcfee3ab..8d272382fb 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/conditional/TragicSlipTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/conditional/TragicSlipTest.java @@ -1,4 +1,4 @@ - package org.mage.test.cards.conditional; +package org.mage.test.cards.conditional; import mage.constants.PhaseStep; import mage.constants.Zone; @@ -6,12 +6,8 @@ import org.junit.Test; import org.mage.test.serverside.base.CardTestPlayerBase; /** - * * @author LevelX2 */ - - - public class TragicSlipTest extends CardTestPlayerBase { @Test @@ -108,7 +104,7 @@ public class TragicSlipTest extends CardTestPlayerBase { castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Tragic Slip", "Silvercoat Lion"); castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Snapcaster Mage"); - setChoice(playerA, "Tragic Slip"); + addTarget(playerA, "Tragic Slip"); castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Lightning Bolt", "Snapcaster Mage"); @@ -122,7 +118,7 @@ public class TragicSlipTest extends CardTestPlayerBase { assertPermanentCount(playerA, "Snapcaster Mage", 0); assertExileCount("Tragic Slip", 1); assertPermanentCount(playerB, "Silvercoat Lion", 1); - assertPowerToughness(playerB, "Silvercoat Lion", 1,1); + assertPowerToughness(playerB, "Silvercoat Lion", 1, 1); assertGraveyardCount(playerB, "Tarmogoyf", 1); } } From 5d6cc6dc72386c5860cc4ebccc71f4e7f27affaa Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Thu, 22 Nov 2018 05:29:56 +0400 Subject: [PATCH 13/42] Tests: fixed MasterThiefTest --- .../org/mage/test/cards/continuous/MasterThiefTest.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/continuous/MasterThiefTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/continuous/MasterThiefTest.java index ba821b9a54..8bfe2420bd 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/continuous/MasterThiefTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/continuous/MasterThiefTest.java @@ -1,13 +1,12 @@ - package org.mage.test.cards.continuous; +import mage.abilities.keyword.VigilanceAbility; import mage.constants.PhaseStep; import mage.constants.Zone; import org.junit.Test; import org.mage.test.serverside.base.CardTestPlayerBase; /** - * * @author JayDi85 */ public class MasterThiefTest extends CardTestPlayerBase { @@ -42,7 +41,7 @@ public class MasterThiefTest extends CardTestPlayerBase { // cast and get control of shield castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Master Thief"); - addTarget(playerB, "Accorder's Shield"); + addTarget(playerA, "Accorder's Shield"); // sacrifice Master Thief -- must lost control activateAbility(3, PhaseStep.PRECOMBAT_MAIN, playerA, "Sacrifice a creature"); @@ -72,11 +71,13 @@ public class MasterThiefTest extends CardTestPlayerBase { // cast and get control of shield castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Master Thief"); - addTarget(playerB, "Accorder's Shield"); + addTarget(playerA, "Accorder's Shield"); + checkPermanentCount("must control shield", 1, PhaseStep.BEGIN_COMBAT, playerA, "Accorder's Shield", 1); // attach and boost activateAbility(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Equip {3}"); addTarget(playerA, "Bearer of the Heavens"); + checkAbility("bear must have boost", 1, PhaseStep.END_TURN, playerA, "Bearer of the Heavens", VigilanceAbility.class, true); // sacrifice Master Thief -- must lost control, but attached and boosted activateAbility(3, PhaseStep.PRECOMBAT_MAIN, playerA, "Sacrifice a creature"); From 243ad8cdddcac9f30c340c6db76d4f6ad76a3a9a Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Thu, 22 Nov 2018 06:21:35 +0400 Subject: [PATCH 14/42] Tests: added support of zero targets in TestPlayer (empty string in addTarget); Tests: fixed CopySpellTest; --- .../java/org/mage/test/cards/copy/CopySpellTest.java | 10 ++++++++-- .../src/test/java/org/mage/test/player/TestPlayer.java | 7 +++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/copy/CopySpellTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/copy/CopySpellTest.java index 2ec1c261ed..fb3892ecbb 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/copy/CopySpellTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/copy/CopySpellTest.java @@ -24,9 +24,15 @@ public class CopySpellTest extends CardTestPlayerBase { addCard(Zone.BATTLEFIELD, playerB, "Pillarfield Ox", 1); addCard(Zone.BATTLEFIELD, playerB, "Island", 1); + // start chain from A - return pillar to hand castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Chain of Vapor", "Pillarfield Ox"); - setChoice(playerB, "Yes"); - addTarget(playerB, "Silvercoat Lion"); + //setChoice(playerB, "Yes"); // want to sacrifice + addTarget(playerB, "Island"); // select a land to sacrifice + setChoice(playerB, "Yes"); // want to copy spell + setChoice(playerB, "Yes"); // want to change target + addTarget(playerB, "Silvercoat Lion"); // new target after copy + // stop the chain on 0 land + addTarget(playerB, ""); setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); 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 30e83c5c63..932c63de59 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 @@ -1160,6 +1160,13 @@ public class TestPlayer implements Player { abilityControllerId = target.getAbilityController(); } + // do not select + if (targets.get(0).equals("")) { + Assert.assertEquals("found empty choice, but target is not support 0 choice", 0, target.getMinNumberOfTargets()); + targets.remove(0); + return true; + } + // player if (target instanceof TargetPlayer || target instanceof TargetAnyTarget From 511b4b28c368c4a192d81547e3b24e4dd4d2cf5f Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Thu, 22 Nov 2018 06:52:28 +0400 Subject: [PATCH 15/42] Tests: fixed CryptoplasmTest; --- .../org/mage/test/cards/copy/CryptoplasmTest.java | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/copy/CryptoplasmTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/copy/CryptoplasmTest.java index ec85a76ada..34c014aed2 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/copy/CryptoplasmTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/copy/CryptoplasmTest.java @@ -52,15 +52,24 @@ public class CryptoplasmTest extends CardTestPlayerBase { // At the beginning of your upkeep, you may have Cryptoplasm become a copy of another target creature. If you do, Cryptoplasm gains this ability. addCard(Zone.BATTLEFIELD, playerB, "Cryptoplasm", 1); // {1}{U}{U} + // turn 2 - prepare (crypto to paladin, footsteps to crypto) + // crypto: copy as paladin on upkeep + setChoice(playerB, "Yes"); + addTarget(playerB, "Sigiled Paladin"); + // footsteps: enchant copy of paladin (crypto) castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Followed Footsteps"); addTarget(playerB, "Sigiled Paladin[only copy]"); + // turn 4 - ignore crypto ask for new copy + setChoice(playerB, "No"); + setStopAt(4, PhaseStep.END_TURN); execute(); assertPermanentCount(playerB, "Followed Footsteps", 1); - assertPermanentCount(playerB, "Cryptoplasm", 0); - assertPermanentCount(playerB, "Sigiled Paladin", 2); + assertPermanentCount(playerB, "Cryptoplasm", 0); // it's a copy + assertPermanentCount(playerB, "Sigiled Paladin", 2); // crypto as copy + footstep token as copy + assertPermanentCount(playerA, "Sigiled Paladin", 1); // original } /** From b834094921535e2c56536bcaa456d48f7209ebb6 Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Thu, 22 Nov 2018 19:36:45 +0400 Subject: [PATCH 16/42] Tests: fixed IsochronScepterTest; --- .../java/org/mage/test/cards/copy/IsochronScepterTest.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/copy/IsochronScepterTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/copy/IsochronScepterTest.java index c49391815c..810e5ea628 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/copy/IsochronScepterTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/copy/IsochronScepterTest.java @@ -44,10 +44,12 @@ public class IsochronScepterTest extends CardTestPlayerBase { addCard(Zone.HAND, playerA, "Lightning Bolt"); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Isochron Scepter"); - addTarget(playerA, "Lightning Bolt"); + setChoice(playerA, "Yes"); // use imprint + setChoice(playerA, "Lightning Bolt"); // target for imprint (excile from hand) + + // copy and cast imprinted card activateAbility(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "{2}, {T}:"); setChoice(playerA, "Yes"); - setChoice(playerA, "Yes"); setStopAt(1, PhaseStep.END_TURN); execute(); From fb8c7c51287fccbda4ed3da6667605d4a26595cb Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Thu, 22 Nov 2018 19:38:01 +0400 Subject: [PATCH 17/42] Tests: fixed ReversalOfFortuneTest; --- .../java/org/mage/test/cards/copy/ReversalOfFortuneTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/copy/ReversalOfFortuneTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/copy/ReversalOfFortuneTest.java index ad5641993a..a47e832365 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/copy/ReversalOfFortuneTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/copy/ReversalOfFortuneTest.java @@ -28,8 +28,8 @@ public class ReversalOfFortuneTest extends CardTestPlayerBase { addCard(Zone.HAND, playerB, "Lightning Bolt"); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Reversal of Fortune", playerB); - addTarget(playerA, "Lightning Bolt"); - setChoice(playerA, "Yes"); + setChoice(playerA, "Lightning Bolt"); // select to copy + setChoice(playerA, "Yes"); // cast copy setStopAt(1, PhaseStep.END_TURN); execute(); From 9d7c59fed986413af1b00aa548f02ac08d11184b Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Thu, 22 Nov 2018 20:05:29 +0400 Subject: [PATCH 18/42] Tests: fixed SharuumTheHegemonTest; --- .../cards/copy/SharuumTheHegemonTest.java | 71 ++++++++++--------- 1 file changed, 38 insertions(+), 33 deletions(-) diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/copy/SharuumTheHegemonTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/copy/SharuumTheHegemonTest.java index 5421dfca18..0ac2a0db44 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/copy/SharuumTheHegemonTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/copy/SharuumTheHegemonTest.java @@ -1,4 +1,3 @@ - package org.mage.test.cards.copy; import mage.constants.PhaseStep; @@ -7,7 +6,6 @@ import org.junit.Test; import org.mage.test.serverside.base.CardTestPlayerBase; /** - * * @author LevelX2 */ @@ -15,34 +13,33 @@ public class SharuumTheHegemonTest extends CardTestPlayerBase { /** * http://www.slightlymagic.net/forum/viewtopic.php?f=70&t=16732&p=172937&hilit=Sharuum+the+Hegemon#p172920 - * - * My Sharuum EDH deck uses the standard Sharuum + Clone Effect + Blood Artist as one of the win - * conditions, but when I have Sharuum in plan and play a Clever Impersonator, targetting Sharuum - * and choose to keep the Clever Impersonator and send the original Sharuum to the graveyard Xmage - * never gives me the option to use the Sharuum Ability that the Clever Impersonator should get, + *

+ * My Sharuum EDH deck uses the standard Sharuum + Clone Effect + Blood Artist as one of the win + * conditions, but when I have Sharuum in plan and play a Clever Impersonator, targetting Sharuum + * and choose to keep the Clever Impersonator and send the original Sharuum to the graveyard Xmage + * never gives me the option to use the Sharuum Ability that the Clever Impersonator should get, * making the combo not work. - * - * I run a Sharuum EDH deck that wins by cloning Sharuum for infinite death triggers. I know the rules + *

+ * I run a Sharuum EDH deck that wins by cloning Sharuum for infinite death triggers. I know the rules * check out on this combo irl, but no matter how I stack the triggers for cloning Sharuum and her enter * the battlefield effect it does not work. It either ends with Sharuum in my graveyard or the reanimate * effect hits the stack before the legend rule applies - * - [1] Sharuum the Hegemon is on the battlefield. - [2] You cast Clone (or any other Clone-like card). - [3] When Clone resolves, you choose Sharuum for the replacement effect. - [4] Since fake-Sharuum entered the battlefield, its EtB ability triggers. - [5] State-based actions are checked and you are prompted to keep one Sharuum. You sacrifice real-Sharuum. - * 116.2a Triggered abilities can trigger at any time, including while a spell is being cast, an ability is being activated, or a spell or - * ability is resolving. (See rule 603, "Handling Triggered Abilities.") However, nothing actually happens at the time an ability triggers. - * Each time a player would receive priority, each ability that has triggered but hasn't yet been put on the stack is put on the stack. See rule 116.5 - * 116.5. Each time a player would get priority, the game first performs all applicable state-based actions as a single event (see rule 704, - * "State-Based Actions"), then repeats this process until no state-based actions are performed. Then triggered abilities are put on the stack - * (see rule 603, "Handling Triggered Abilities"). These steps repeat in order until no further state-based actions are performed and no abilities - * trigger. Then the player who would have received priority does so. - [6] Once State-based actions are finished, triggered abilities go on the stack. You put the EtB from [4] choosing real-Sharuum. - [7] Real-Sharuum enters the battlefield. - [8] Rinse and repeat. - * + *

+ * [1] Sharuum the Hegemon is on the battlefield. + * [2] You cast Clone (or any other Clone-like card). + * [3] When Clone resolves, you choose Sharuum for the replacement effect. + * [4] Since fake-Sharuum entered the battlefield, its EtB ability triggers. + * [5] State-based actions are checked and you are prompted to keep one Sharuum. You sacrifice real-Sharuum. + * 116.2a Triggered abilities can trigger at any time, including while a spell is being cast, an ability is being activated, or a spell or + * ability is resolving. (See rule 603, "Handling Triggered Abilities.") However, nothing actually happens at the time an ability triggers. + * Each time a player would receive priority, each ability that has triggered but hasn't yet been put on the stack is put on the stack. See rule 116.5 + * 116.5. Each time a player would get priority, the game first performs all applicable state-based actions as a single event (see rule 704, + * "State-Based Actions"), then repeats this process until no state-based actions are performed. Then triggered abilities are put on the stack + * (see rule 603, "Handling Triggered Abilities"). These steps repeat in order until no further state-based actions are performed and no abilities + * trigger. Then the player who would have received priority does so. + * [6] Once State-based actions are finished, triggered abilities go on the stack. You put the EtB from [4] choosing real-Sharuum. + * [7] Real-Sharuum enters the battlefield. + * [8] Rinse and repeat. */ @Test public void testCloneTriggered() { @@ -59,17 +56,26 @@ public class SharuumTheHegemonTest extends CardTestPlayerBase { setChoice(playerA, "Sharuum the Hegemon"); // what creature to clone addTarget(playerA, "Sharuum the Hegemon[only copy]"); // which legend to keep - setChoice(playerA, "Yes"); - - addTarget(playerA, "Sharuum the Hegemon[only copy]"); // which legend to keep - setChoice(playerA, "Yes"); + setChoice(playerA, "Whenever {this} or another creature dies"); // blood first + addTarget(playerA, playerB); // damage by blood + setChoice(playerA, "Yes"); // return + addTarget(playerA, "Sharuum the Hegemon"); // return real sharuum addTarget(playerA, "Sharuum the Hegemon[only copy]"); // which legend to keep - setChoice(playerA, "Yes"); + setChoice(playerA, "Whenever {this} or another creature dies"); // blood first + addTarget(playerA, playerB); // damage by blood + setChoice(playerA, "Yes"); // return + addTarget(playerA, "Sharuum the Hegemon"); // return real sharuum + + addTarget(playerA, "Sharuum the Hegemon[only copy]"); // which legend to keep + setChoice(playerA, "Whenever {this} or another creature dies"); // blood first + addTarget(playerA, playerB); // damage by blood + setChoice(playerA, "Yes"); // return + addTarget(playerA, "Sharuum the Hegemon"); // return real sharuum addTarget(playerA, "Sharuum the Hegemon[only copy]"); // which legend to keep setChoice(playerA, "No"); // Don't use it anymore - + setStopAt(1, PhaseStep.END_TURN); execute(); @@ -77,7 +83,6 @@ public class SharuumTheHegemonTest extends CardTestPlayerBase { assertLife(playerB, 16); - } } \ No newline at end of file From e5bdb850764d8d8644582fd72693494d201a1a69 Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Thu, 22 Nov 2018 21:53:32 +0400 Subject: [PATCH 19/42] * Mirari - Fixed that it can select any spell instead triggered use. --- Mage.Sets/src/mage/cards/m/Mirari.java | 19 +++++++++++-------- .../effects/common/CopyTargetSpellEffect.java | 11 ++++++++--- .../effects/common/DoIfCostPaid.java | 16 +++++++++++++--- 3 files changed, 32 insertions(+), 14 deletions(-) diff --git a/Mage.Sets/src/mage/cards/m/Mirari.java b/Mage.Sets/src/mage/cards/m/Mirari.java index 0c0b0fe580..da05fc00c0 100644 --- a/Mage.Sets/src/mage/cards/m/Mirari.java +++ b/Mage.Sets/src/mage/cards/m/Mirari.java @@ -1,7 +1,5 @@ - package mage.cards.m; -import java.util.UUID; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.costs.mana.GenericManaCost; import mage.abilities.effects.Effect; @@ -19,17 +17,17 @@ import mage.game.Game; import mage.game.events.GameEvent; import mage.game.events.GameEvent.EventType; import mage.game.stack.Spell; -import mage.target.TargetSpell; import mage.target.targetpointer.FixedTarget; +import java.util.UUID; + /** - * * @author LevelX2 */ public final class Mirari extends CardImpl { public Mirari(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.ARTIFACT},"{5}"); + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{5}"); addSuperType(SuperType.LEGENDARY); // Whenever you cast an instant or sorcery spell, you may pay {3}. If you do, copy that spell. You may choose new targets for the copy. @@ -58,8 +56,9 @@ class MirariTriggeredAbility extends TriggeredAbilityImpl { } MirariTriggeredAbility() { - super(Zone.BATTLEFIELD, new DoIfCostPaid(new CopyTargetSpellEffect(true), new GenericManaCost(3)), false); - this.addTarget(new TargetSpell(filter)); + super(Zone.BATTLEFIELD, new DoIfCostPaid( + new CopyTargetSpellEffect(true), + new GenericManaCost(3)), false); } MirariTriggeredAbility(final MirariTriggeredAbility ability) { @@ -82,7 +81,11 @@ class MirariTriggeredAbility extends TriggeredAbilityImpl { Spell spell = game.getStack().getSpell(event.getTargetId()); if (isControlledInstantOrSorcery(spell)) { for (Effect effect : getEffects()) { - effect.setTargetPointer(new FixedTarget(spell.getId())); + if (effect instanceof DoIfCostPaid) { + for (Effect execEffect : ((DoIfCostPaid) effect).getExecutingEffects()) { + execEffect.setTargetPointer(new FixedTarget(spell.getId())); + } + } } return true; } diff --git a/Mage/src/main/java/mage/abilities/effects/common/CopyTargetSpellEffect.java b/Mage/src/main/java/mage/abilities/effects/common/CopyTargetSpellEffect.java index 040be4c85d..93c6712292 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/CopyTargetSpellEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/CopyTargetSpellEffect.java @@ -1,4 +1,3 @@ - package mage.abilities.effects.common; import mage.abilities.Ability; @@ -13,7 +12,6 @@ import mage.players.Player; /** * @author BetaSteward_at_googlemail.com - * */ public class CopyTargetSpellEffect extends OneShotEffect { @@ -79,7 +77,14 @@ public class CopyTargetSpellEffect extends OneShotEffect { return staticText; } StringBuilder sb = new StringBuilder(); - sb.append("copy target ").append(mode.getTargets().get(0).getTargetName()).append(". You may choose new targets for the copy"); + sb.append("copy "); + if (!mode.getTargets().isEmpty()) { + sb.append("target ").append(mode.getTargets().get(0).getTargetName()); + } else { + sb.append("that spell"); + } + sb.append(". You may choose new targets for the copy"); + return sb.toString(); } } diff --git a/Mage/src/main/java/mage/abilities/effects/common/DoIfCostPaid.java b/Mage/src/main/java/mage/abilities/effects/common/DoIfCostPaid.java index 845fe9e5a4..5a8c7f408d 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/DoIfCostPaid.java +++ b/Mage/src/main/java/mage/abilities/effects/common/DoIfCostPaid.java @@ -1,6 +1,5 @@ package mage.abilities.effects.common; -import java.util.Locale; import mage.MageObject; import mage.abilities.Ability; import mage.abilities.Mode; @@ -14,6 +13,8 @@ import mage.game.Game; import mage.players.Player; import mage.util.CardUtil; +import java.util.Locale; + public class DoIfCostPaid extends OneShotEffect { protected Effects executingEffects = new Effects(); @@ -27,9 +28,10 @@ public class DoIfCostPaid extends OneShotEffect { } public DoIfCostPaid(Effect effect, Effect effect2, Cost cost) { - this(effect,effect2,cost,true); + this(effect, effect2, cost, true); } - public DoIfCostPaid(Effect effect, Effect effect2, Cost cost,boolean optional) { + + public DoIfCostPaid(Effect effect, Effect effect2, Cost cost, boolean optional) { this(effect, cost, null, optional); this.otherwiseEffects.add(effect2); } @@ -62,6 +64,14 @@ public class DoIfCostPaid extends OneShotEffect { return this; } + public Effects getExecutingEffects() { + return this.executingEffects; + } + + public Effects getOtherwiseEffects() { + return this.otherwiseEffects; + } + @Override public boolean apply(Game game, Ability source) { Player player = getPayingPlayer(game, source); From 1f1ba7ea8e2098d11018801a99ba050fa3b323ba Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Thu, 22 Nov 2018 21:55:02 +0400 Subject: [PATCH 20/42] Tests: fixed SpelltwineTest (it's ignored and must be rewrited -- see todo); --- .../java/org/mage/test/cards/copy/SpelltwineTest.java | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/copy/SpelltwineTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/copy/SpelltwineTest.java index 548f752852..f4859dc486 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/copy/SpelltwineTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/copy/SpelltwineTest.java @@ -1,8 +1,8 @@ - package org.mage.test.cards.copy; import mage.constants.PhaseStep; import mage.constants.Zone; +import org.junit.Ignore; import org.junit.Test; import org.mage.test.serverside.base.CardTestPlayerBase; @@ -48,6 +48,7 @@ public class SpelltwineTest extends CardTestPlayerBase { * after this, failing to be in the stack box or resolve all. */ @Test + @Ignore // TODO: test is wrong -- mirari exile cards and must cast their copies, on copies cast mirari triggers again (two times). public void testCopyCardsMirari() { addCard(Zone.BATTLEFIELD, playerA, "Island", 9); // Exile target instant or sorcery card from your graveyard and target instant or sorcery card from an opponent's graveyard. @@ -66,9 +67,13 @@ public class SpelltwineTest extends CardTestPlayerBase { // Whenever you cast an instant or sorcery spell, you may pay {3}. If you do, copy that spell. You may choose new targets for the copy. addCard(Zone.BATTLEFIELD, playerA, "Mirari", 1); + // cast spellwin castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Spelltwine"); - addTarget(playerA, "Impulse"); - addTarget(playerA, "Blasphemous Act"); + addTarget(playerA, "Impulse"); // target 1 to excile + addTarget(playerA, "Blasphemous Act"); // target 2 to excile + + + setChoice(playerA, "Yes"); // pay {3} and copy spell setChoice(playerA, "Yes"); // Change targets addTarget(playerA, "Night's Whisper"); From 5cf1e5a7a007032cb52ffeeb395bfd6af4e98e07 Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Fri, 23 Nov 2018 17:02:39 +0400 Subject: [PATCH 21/42] Tests: added support to cast fused cards in tests (any selected side or fused); --- .../mage/test/player/TestComputerPlayer.java | 62 +++++++++++++++++++ .../mage/test/player/TestComputerPlayer7.java | 62 +++++++++++++++++++ .../java/org/mage/test/player/TestPlayer.java | 47 +++++++------- .../serverside/base/CardTestPlayerBaseAI.java | 9 ++- .../serverside/base/MageTestPlayerBase.java | 20 +++--- 5 files changed, 157 insertions(+), 43 deletions(-) create mode 100644 Mage.Tests/src/test/java/org/mage/test/player/TestComputerPlayer.java create mode 100644 Mage.Tests/src/test/java/org/mage/test/player/TestComputerPlayer7.java diff --git a/Mage.Tests/src/test/java/org/mage/test/player/TestComputerPlayer.java b/Mage.Tests/src/test/java/org/mage/test/player/TestComputerPlayer.java new file mode 100644 index 0000000000..cb9a905962 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/player/TestComputerPlayer.java @@ -0,0 +1,62 @@ +package org.mage.test.player; + +import mage.MageObject; +import mage.abilities.ActivatedAbility; +import mage.abilities.SpellAbility; +import mage.constants.RangeOfInfluence; +import mage.game.Game; +import mage.player.ai.ComputerPlayer; + +import java.util.LinkedHashMap; +import java.util.UUID; + +/** + * @author JayDi85 + */ + +// mock class to override to override AI logic for test +public class TestComputerPlayer extends ComputerPlayer { + + private TestPlayer testPlayerLink; + + public TestComputerPlayer(String name, RangeOfInfluence range) { + super(name, range); + } + + public void setTestPlayerLink(TestPlayer testPlayerLink) { + this.testPlayerLink = testPlayerLink; + } + + @Override + public SpellAbility chooseSpellAbilityForCast(SpellAbility ability, Game game, boolean noMana) { + // copy-paste for TestComputerXXX + + // workaround to cast fused cards in tests by it's NAMES (Wear, Tear, Wear // Tear) + // reason: TestPlayer uses outer computerPlayer to cast, not TestPlayer + switch (ability.getSpellAbilityType()) { + case SPLIT: + case SPLIT_FUSED: + case SPLIT_AFTERMATH: + if (!this.testPlayerLink.getChoices().isEmpty()) { + MageObject object = game.getObject(ability.getSourceId()); + if (object != null) { + LinkedHashMap useableAbilities = this.getSpellAbilities(object, game.getState().getZone(object.getId()), game); + + // left, right or fused cast + for (String choose : this.testPlayerLink.getChoices()) { + for (ActivatedAbility activatedAbility : useableAbilities.values()) { + if (activatedAbility instanceof SpellAbility) { + if (((SpellAbility) activatedAbility).getCardName().equals(choose)) { + return (SpellAbility) activatedAbility; + } + } + } + } + } + } + } + + // default implementation by AI + return super.chooseSpellAbilityForCast(ability, game, noMana); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/player/TestComputerPlayer7.java b/Mage.Tests/src/test/java/org/mage/test/player/TestComputerPlayer7.java new file mode 100644 index 0000000000..3bff415070 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/player/TestComputerPlayer7.java @@ -0,0 +1,62 @@ +package org.mage.test.player; + +import mage.MageObject; +import mage.abilities.ActivatedAbility; +import mage.abilities.SpellAbility; +import mage.constants.RangeOfInfluence; +import mage.game.Game; +import mage.player.ai.ComputerPlayer7; + +import java.util.LinkedHashMap; +import java.util.UUID; + +/** + * @author JayDi85 + */ + +// mock class to override AI logic in tests +public class TestComputerPlayer7 extends ComputerPlayer7 { + + private TestPlayer testPlayerLink; + + public TestComputerPlayer7(String name, RangeOfInfluence range, int skill) { + super(name, range, skill); + } + + public void setTestPlayerLink(TestPlayer testPlayerLink) { + this.testPlayerLink = testPlayerLink; + } + + @Override + public SpellAbility chooseSpellAbilityForCast(SpellAbility ability, Game game, boolean noMana) { + // copy-paste for TestComputerXXX + + // workaround to cast fused cards in tests by it's NAMES (Wear, Tear, Wear // Tear) + // reason: TestPlayer uses outer computerPlayer to cast, not TestPlayer + switch (ability.getSpellAbilityType()) { + case SPLIT: + case SPLIT_FUSED: + case SPLIT_AFTERMATH: + if (!this.testPlayerLink.getChoices().isEmpty()) { + MageObject object = game.getObject(ability.getSourceId()); + if (object != null) { + LinkedHashMap useableAbilities = this.getSpellAbilities(object, game.getState().getZone(object.getId()), game); + + // left, right or fused cast + for (String choose : this.testPlayerLink.getChoices()) { + for (ActivatedAbility activatedAbility : useableAbilities.values()) { + if (activatedAbility instanceof SpellAbility) { + if (((SpellAbility) activatedAbility).getCardName().equals(choose)) { + return (SpellAbility) activatedAbility; + } + } + } + } + } + } + } + + // default implementation by AI + return super.chooseSpellAbilityForCast(ability, game, noMana); + } +} 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 932c63de59..c6939d329e 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 @@ -43,7 +43,6 @@ import mage.player.ai.ComputerPlayer; import mage.players.Library; import mage.players.ManaPool; import mage.players.Player; -import mage.players.PlayerList; import mage.players.net.UserData; import mage.target.*; import mage.target.common.*; @@ -84,9 +83,16 @@ public class TestPlayer implements Player { // Before actual turns start. Needed for checking attacker/blocker legality in the tests private static int initialTurns = 0; - public TestPlayer(ComputerPlayer computerPlayer) { + public TestPlayer(TestComputerPlayer computerPlayer) { this.computerPlayer = computerPlayer; AIPlayer = false; + computerPlayer.setTestPlayerLink(this); + } + + public TestPlayer(TestComputerPlayer7 computerPlayer) { + this.computerPlayer = computerPlayer; + AIPlayer = false; + computerPlayer.setTestPlayerLink(this); } public TestPlayer(final TestPlayer testPlayer) { @@ -106,6 +112,10 @@ public class TestPlayer implements Player { choices.add(choice); } + public List getChoices() { + return this.choices; + } + public void addModeChoice(String mode) { modesSet.add(mode); } @@ -1146,9 +1156,9 @@ public class TestPlayer implements Player { Boolean canEquals = canSupportChars.contains("="); // how to fix: change target definition for addTarget in test's code or update choose from targets implementation in TestPlayer - if((foundMulti && !canMulti) || (foundSpecialStart && !canSpecialStart) || (foundSpecialClose && !canSpecialClose)|| (foundEquals && !canEquals)) { + if ((foundMulti && !canMulti) || (foundSpecialStart && !canSpecialStart) || (foundSpecialClose && !canSpecialClose) || (foundEquals && !canEquals)) { Assert.fail("Targets list was setup by addTarget with " + targets + ", but target definition [" + targetDefinition + "]" - + " is not supported by ["+ canSupportChars + "] for target class " + needTarget.getClass().getSimpleName()); + + " is not supported by [" + canSupportChars + "] for target class " + needTarget.getClass().getSimpleName()); } } @@ -1330,14 +1340,14 @@ public class TestPlayer implements Player { List needPlayers = game.getState().getPlayersInRange(getId(), game).toList(); // fix for opponent graveyard - if(target instanceof TargetCardInOpponentsGraveyard) { + if (target instanceof TargetCardInOpponentsGraveyard) { // current player remove Assert.assertTrue(needPlayers.contains(getId())); needPlayers.remove(getId()); Assert.assertFalse(needPlayers.contains(getId())); } // fix for your graveyard - if(target instanceof TargetCardInYourGraveyard) { + if (target instanceof TargetCardInYourGraveyard) { // only current player Assert.assertTrue(needPlayers.contains(getId())); needPlayers.clear(); @@ -1400,10 +1410,10 @@ public class TestPlayer implements Player { // wrong target settings by addTarget // how to fix: implement target class processing above - if(!targets.isEmpty()) { + if (!targets.isEmpty()) { String message; - if(source != null) { + if (source != null) { message = "Targets list was setup by addTarget with " + targets + ", but not used in [" + "card " + source.getSourceObject(game) + " -> ability " + source.getClass().getSimpleName() + " (" + source.getRule().substring(0, Math.min(20, source.getRule().length()) - 1) + "..." + ")" @@ -1792,6 +1802,8 @@ public class TestPlayer implements Player { @Override public boolean cast(SpellAbility ability, Game game, boolean noMana, MageObjectReference reference) { + // TestPlayer, ComputerPlayer always call inherited cast() from PlayerImpl + // that's why chooseSpellAbilityForCast will be ignored in TestPlayer, see workaround with TestComputerPlayerXXX return computerPlayer.cast(ability, game, noMana, reference); } @@ -2586,24 +2598,7 @@ public class TestPlayer implements Player { @Override public SpellAbility chooseSpellAbilityForCast(SpellAbility ability, Game game, boolean noMana) { - switch (ability.getSpellAbilityType()) { - case SPLIT: - case SPLIT_FUSED: - case SPLIT_AFTERMATH: - if (!choices.isEmpty()) { - MageObject object = game.getObject(ability.getSourceId()); - if (object != null) { - LinkedHashMap useableAbilities = computerPlayer.getSpellAbilities(object, game.getState().getZone(object.getId()), game); - for (String choose : choices) { - for (ActivatedAbility actiavtedAbility : useableAbilities.values()) { - if (actiavtedAbility.getRule().startsWith(choose)) { - return (SpellAbility) actiavtedAbility; - } - } - } - } - } - } + Assert.fail("That's method calls only from computerPlayer->cast(), see TestComputerPlayerXXX"); return computerPlayer.chooseSpellAbilityForCast(ability, game, noMana); } diff --git a/Mage.Tests/src/test/java/org/mage/test/serverside/base/CardTestPlayerBaseAI.java b/Mage.Tests/src/test/java/org/mage/test/serverside/base/CardTestPlayerBaseAI.java index fa1a5241d2..89e522fd5a 100644 --- a/Mage.Tests/src/test/java/org/mage/test/serverside/base/CardTestPlayerBaseAI.java +++ b/Mage.Tests/src/test/java/org/mage/test/serverside/base/CardTestPlayerBaseAI.java @@ -1,18 +1,17 @@ - package org.mage.test.serverside.base; -import java.io.FileNotFoundException; import mage.constants.MultiplayerAttackOption; import mage.constants.RangeOfInfluence; import mage.game.Game; import mage.game.GameException; import mage.game.TwoPlayerDuel; -import mage.player.ai.ComputerPlayer7; +import org.mage.test.player.TestComputerPlayer7; import org.mage.test.player.TestPlayer; import org.mage.test.serverside.base.impl.CardTestPlayerAPIImpl; +import java.io.FileNotFoundException; + /** - * * @author LevelX2 */ public abstract class CardTestPlayerBaseAI extends CardTestPlayerAPIImpl { @@ -31,7 +30,7 @@ public abstract class CardTestPlayerBaseAI extends CardTestPlayerAPIImpl { @Override protected TestPlayer createPlayer(String name, RangeOfInfluence rangeOfInfluence) { if (name.equals("PlayerA")) { - TestPlayer testPlayer = new TestPlayer(new ComputerPlayer7("PlayerA", RangeOfInfluence.ONE, skill)); + TestPlayer testPlayer = new TestPlayer(new TestComputerPlayer7("PlayerA", RangeOfInfluence.ONE, skill)); testPlayer.setAIPlayer(true); return testPlayer; } diff --git a/Mage.Tests/src/test/java/org/mage/test/serverside/base/MageTestPlayerBase.java b/Mage.Tests/src/test/java/org/mage/test/serverside/base/MageTestPlayerBase.java index b514cc68b4..c6ad3f2f68 100644 --- a/Mage.Tests/src/test/java/org/mage/test/serverside/base/MageTestPlayerBase.java +++ b/Mage.Tests/src/test/java/org/mage/test/serverside/base/MageTestPlayerBase.java @@ -1,16 +1,6 @@ package org.mage.test.serverside.base; -import java.io.File; -import java.io.FileNotFoundException; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Scanner; -import java.util.regex.Matcher; -import java.util.regex.Pattern; import mage.cards.Card; -import mage.cards.decks.Deck; import mage.cards.decks.DeckCardLists; import mage.cards.repository.CardInfo; import mage.cards.repository.CardRepository; @@ -21,7 +11,6 @@ import mage.game.Game; import mage.game.match.MatchType; import mage.game.permanent.PermanentCard; import mage.game.tournament.TournamentType; -import mage.player.ai.ComputerPlayer; import mage.players.Player; import mage.server.game.GameFactory; import mage.server.util.ConfigSettings; @@ -32,8 +21,15 @@ import mage.util.Copier; import org.apache.log4j.Level; import org.apache.log4j.Logger; import org.junit.BeforeClass; +import org.mage.test.player.TestComputerPlayer; import org.mage.test.player.TestPlayer; +import java.io.File; +import java.io.FileNotFoundException; +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + /** * Base class for all tests. * @@ -334,7 +330,7 @@ public abstract class MageTestPlayerBase { } protected TestPlayer createPlayer(String name, RangeOfInfluence rangeOfInfluence) { - return new TestPlayer(new ComputerPlayer(name, rangeOfInfluence)); + return new TestPlayer(new TestComputerPlayer(name, rangeOfInfluence)); } } From fbdfeba9d8609a5ae26ef4f24e9fff12e98279b5 Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Fri, 23 Nov 2018 17:03:03 +0400 Subject: [PATCH 22/42] Tests: fixed CastSplitCardsFromOtherZonesTest; --- .../CastSplitCardsFromOtherZonesTest.java | 78 +++++++++++-------- 1 file changed, 45 insertions(+), 33 deletions(-) diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/cost/splitcards/CastSplitCardsFromOtherZonesTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/cost/splitcards/CastSplitCardsFromOtherZonesTest.java index ae388836b4..5d0b75df07 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/cost/splitcards/CastSplitCardsFromOtherZonesTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/cost/splitcards/CastSplitCardsFromOtherZonesTest.java @@ -1,4 +1,3 @@ - package org.mage.test.cards.cost.splitcards; import mage.constants.PhaseStep; @@ -7,7 +6,6 @@ import org.junit.Test; import org.mage.test.serverside.base.CardTestPlayerBase; /** - * * @author LevelX2 */ public class CastSplitCardsFromOtherZonesTest extends CardTestPlayerBase { @@ -27,31 +25,6 @@ public class CastSplitCardsFromOtherZonesTest extends CardTestPlayerBase { addCard(Zone.HAND, playerA, "Mindclaw Shaman"); // Creature {4}{R} addCard(Zone.BATTLEFIELD, playerB, "Sanguine Bond", 1); // Enchantment to destroy - // Wear - // Destroy target artifact. - // Tear - // Destroy target enchantment. - addCard(Zone.HAND, playerB, "Wear // Tear"); // Instant {1}{R} // {W} - - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Mindclaw Shaman"); - addTarget(playerA, "Sanguine Bond"); - - setStopAt(1, PhaseStep.BEGIN_COMBAT); - execute(); - - assertPermanentCount(playerA, "Mindclaw Shaman", 1); - assertGraveyardCount(playerB, "Wear // Tear", 1); - assertGraveyardCount(playerB, "Sanguine Bond", 1); - - } - - @Test - public void testCastFearFromOpponentsHand() { - addCard(Zone.BATTLEFIELD, playerA, "Mountain", 5); - // When Mindclaw Shaman enters the battlefield, target opponent reveals their hand. - // You may cast an instant or sorcery card from it without paying its mana cost. - addCard(Zone.HAND, playerA, "Mindclaw Shaman"); // Creature {4}{R} - addCard(Zone.BATTLEFIELD, playerB, "Icy Manipulator", 1); // Artifact to destroy // Wear // Destroy target artifact. @@ -60,7 +33,42 @@ public class CastSplitCardsFromOtherZonesTest extends CardTestPlayerBase { addCard(Zone.HAND, playerB, "Wear // Tear"); // Instant {1}{R} // {W} castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Mindclaw Shaman"); - addTarget(playerA, "Icy Manipulator"); + addTarget(playerA, playerB); + setChoice(playerA, "Wear // Tear"); // select card + setChoice(playerA, "Yes"); // confirm to cast + setChoice(playerA, "Tear"); // select tear side + addTarget(playerA, "Sanguine Bond"); // target for tear + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertPermanentCount(playerA, "Mindclaw Shaman", 1); + assertGraveyardCount(playerB, "Wear // Tear", 1); + assertGraveyardCount(playerB, "Icy Manipulator", 0); + assertGraveyardCount(playerB, "Sanguine Bond", 1); + } + + @Test + public void testCastWearFromOpponentsHand() { + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 5); + // When Mindclaw Shaman enters the battlefield, target opponent reveals their hand. + // You may cast an instant or sorcery card from it without paying its mana cost. + addCard(Zone.HAND, playerA, "Mindclaw Shaman"); // Creature {4}{R} + + addCard(Zone.BATTLEFIELD, playerB, "Sanguine Bond", 1); // Enchantment to destroy + addCard(Zone.BATTLEFIELD, playerB, "Icy Manipulator", 1); // Artifact to destroy + // Wear + // Destroy target artifact. + // Tear + // Destroy target enchantment. + addCard(Zone.HAND, playerB, "Wear // Tear"); // Instant {1}{R} // {W} + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Mindclaw Shaman"); + addTarget(playerA, playerB); + setChoice(playerA, "Wear // Tear"); // select card + setChoice(playerA, "Yes"); // confirm to cast + setChoice(playerA, "Wear"); // select wear side + addTarget(playerA, "Icy Manipulator"); // target for wear setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); @@ -68,7 +76,7 @@ public class CastSplitCardsFromOtherZonesTest extends CardTestPlayerBase { assertPermanentCount(playerA, "Mindclaw Shaman", 1); assertGraveyardCount(playerB, "Wear // Tear", 1); assertGraveyardCount(playerB, "Icy Manipulator", 1); - + assertGraveyardCount(playerB, "Sanguine Bond", 0); } @Test @@ -87,16 +95,20 @@ public class CastSplitCardsFromOtherZonesTest extends CardTestPlayerBase { addCard(Zone.HAND, playerB, "Wear // Tear"); // Instant {1}{R} // {W} castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Mindclaw Shaman"); - addTarget(playerA, "Sanguine Bond"); + addTarget(playerA, playerB); + setChoice(playerA, "Wear // Tear"); // select card + setChoice(playerA, "Yes"); // confirm to cast + setChoice(playerA, "Wear // Tear"); // select fused + addTarget(playerA, "Icy Manipulator"); // target for wear + addTarget(playerA, "Sanguine Bond"); // target for tear setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); assertPermanentCount(playerA, "Mindclaw Shaman", 1); assertGraveyardCount(playerB, "Wear // Tear", 1); - assertGraveyardCount(playerB, "Sanguine Bond", 1); assertGraveyardCount(playerB, "Icy Manipulator", 1); - + assertGraveyardCount(playerB, "Sanguine Bond", 1); } /** @@ -119,7 +131,7 @@ public class CastSplitCardsFromOtherZonesTest extends CardTestPlayerBase { attack(2, playerB, "Etali, Primal Storm"); setChoice(playerB, "Yes"); - setChoice(playerB, "Cast Fire"); + setChoice(playerB, "Fire"); addTarget(playerB, "Silvercoat Lion"); setStopAt(2, PhaseStep.END_COMBAT); From 1e40e6984d2bc1b1b26e3c6eef5c02c37df73cc2 Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Sat, 24 Nov 2018 04:52:05 +0400 Subject: [PATCH 23/42] Tests: fixed SweepTest; --- .../test/cards/dynamicvalue/SweepTest.java | 35 +++++++++---------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/dynamicvalue/SweepTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/dynamicvalue/SweepTest.java index 68788a4429..5be97fe2bc 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/dynamicvalue/SweepTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/dynamicvalue/SweepTest.java @@ -1,5 +1,3 @@ - - package org.mage.test.cards.dynamicvalue; import mage.constants.PhaseStep; @@ -8,7 +6,6 @@ import org.junit.Test; import org.mage.test.serverside.base.CardTestPlayerBase; /** - * * @author BetaSteward */ public class SweepTest extends CardTestPlayerBase { @@ -18,10 +15,9 @@ public class SweepTest extends CardTestPlayerBase { * Plow Through Reito * 1W * Instant -- Arcane - * Sweep -- Return any number of Plains you control to their owner's hand. + * Sweep -- Return any number of Plains you control to their owner's hand. * Target creature gets +1/+1 until end of turn for each Plains returned this way. - * - */ + */ @Test public void testSweep1x() { addCard(Zone.BATTLEFIELD, playerA, "Plains", 5); @@ -29,17 +25,17 @@ public class SweepTest extends CardTestPlayerBase { addCard(Zone.HAND, playerA, "Plow Through Reito"); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Plow Through Reito"); - addTarget(playerA, "Plains"); + addTarget(playerA, "Raging Goblin"); // target to boost + addTarget(playerA, "Plains"); // targets to sweep setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); assertPermanentCount(playerA, "Raging Goblin", 1); assertPermanentCount(playerA, "Plains", 4); - assertPowerToughness(playerA, "Raging Goblin", 2, 2); - + assertPowerToughness(playerA, "Raging Goblin", 2, 2); } - + @Test public void testSweep2x() { addCard(Zone.BATTLEFIELD, playerA, "Plains", 5); @@ -47,15 +43,15 @@ public class SweepTest extends CardTestPlayerBase { addCard(Zone.HAND, playerA, "Plow Through Reito"); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Plow Through Reito"); - addTarget(playerA, "Plains^Plains"); + addTarget(playerA, "Raging Goblin"); // target to boost + addTarget(playerA, "Plains^Plains"); // targets to sweep setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); assertPermanentCount(playerA, "Raging Goblin", 1); assertPermanentCount(playerA, "Plains", 3); - assertPowerToughness(playerA, "Raging Goblin", 3, 3); - + assertPowerToughness(playerA, "Raging Goblin", 3, 3); } @Test @@ -65,15 +61,15 @@ public class SweepTest extends CardTestPlayerBase { addCard(Zone.HAND, playerA, "Plow Through Reito"); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Plow Through Reito"); - addTarget(playerA, "Plains^Plains^Plains"); + addTarget(playerA, "Raging Goblin"); // target to boost + addTarget(playerA, "Plains^Plains^Plains"); // targets to sweep setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); assertPermanentCount(playerA, "Raging Goblin", 1); assertPermanentCount(playerA, "Plains", 2); - assertPowerToughness(playerA, "Raging Goblin", 4, 4); - + assertPowerToughness(playerA, "Raging Goblin", 4, 4); } @Test @@ -83,14 +79,15 @@ public class SweepTest extends CardTestPlayerBase { addCard(Zone.HAND, playerA, "Plow Through Reito"); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Plow Through Reito"); + addTarget(playerA, "Raging Goblin"); // target to boost + addTarget(playerA, ""); // targets to sweep (zero) setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); assertPermanentCount(playerA, "Raging Goblin", 1); assertPermanentCount(playerA, "Plains", 5); - assertPowerToughness(playerA, "Raging Goblin", 1, 1); - + assertPowerToughness(playerA, "Raging Goblin", 1, 1); } - + } From b907d8a75c7fdeb778d212cd601aaff023afeb22 Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Wed, 28 Nov 2018 04:04:49 +0400 Subject: [PATCH 24/42] Tests: fixed MorphTest; --- .../cards/abilities/keywords/MorphTest.java | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/MorphTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/MorphTest.java index c3b430166c..6ecd554716 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/MorphTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/MorphTest.java @@ -1,4 +1,3 @@ - package org.mage.test.cards.abilities.keywords; import mage.cards.Card; @@ -13,7 +12,6 @@ import org.junit.Test; import org.mage.test.serverside.base.CardTestPlayerBase; /** - * * @author levelX2 */ public class MorphTest extends CardTestPlayerBase { @@ -21,7 +19,6 @@ public class MorphTest extends CardTestPlayerBase { /** * Tests if a creature with Morph is cast normal, it behaves as normal * creature - * */ @Test public void testCastMorphCreatureWithoutMorph() { @@ -94,7 +91,6 @@ public class MorphTest extends CardTestPlayerBase { /** * Test that the triggered "turned face up" ability of Pine Walker does not * trigger as long as Pine Walker is not turned face up. - * */ @Test public void testDoesNotTriggerFaceDown() { @@ -132,7 +128,6 @@ public class MorphTest extends CardTestPlayerBase { /** * Test that Morph creature do not trigger abilities with their face up * attributes - * */ @Test public void testMorphedRemovesAttributesCreature() { @@ -163,7 +158,6 @@ public class MorphTest extends CardTestPlayerBase { /** * Test to copy a morphed 2/2 creature - * */ @Test public void testCopyAMorphedCreature() { @@ -241,7 +235,6 @@ public class MorphTest extends CardTestPlayerBase { * morph goes down to 1/1 correctly. If you unmorph the 2/2 and is also a * 2/2 after umorphing, the morph will be erroneously reduced to 0/0 and * die. - * */ @Test public void testDoomwakeGiantEffect() { @@ -283,7 +276,6 @@ public class MorphTest extends CardTestPlayerBase { /** * Clone a Morph creature that was cast face down and meanwhile was turned * face up - * */ @Test public void testCloneFaceUpMorphEffect() { @@ -317,7 +309,6 @@ public class MorphTest extends CardTestPlayerBase { /** * Check that you can't counter a creature cast for it morph costs with * Disdainful Stroke if it's normal cmc > 3 - * */ @Test public void testCounterCastWithMorphEffect() { @@ -355,7 +346,6 @@ public class MorphTest extends CardTestPlayerBase { * the same name" does only effect one face down creature, also if multiple * on the battlefield. Because they have no name, they don't have the same * name. - * */ @Test public void testEchoingDecaySameNameEffect() { @@ -631,7 +621,7 @@ public class MorphTest extends CardTestPlayerBase { * Reflector Mage bouncing a creature that can be played as a morph should * not prevent the card from being replayed as a morph. Morph creatures are * nameless. - * + *

* Reported bug: Face-up morph creatures that are bounced by Reflector Mage * should be able to be replayed as morphs without the "until the next turn" * restriction." @@ -673,11 +663,11 @@ public class MorphTest extends CardTestPlayerBase { * Reflector Mage bouncing a creature that can be played as a morph should * not prevent the card from being replayed as a morph. Morph creatures are * nameless. - * + *

* Reported bug: Face-up morph creatures that are bounced by Reflector Mage * should be able to be replayed as morphs without the "until the next turn" * restriction." - * + *

* Testing bouncing a face-down creature played next turn face-up. */ @Test @@ -701,7 +691,6 @@ public class MorphTest extends CardTestPlayerBase { setChoice(playerA, "Yes"); // cast it face down as 2/2 creature castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Reflector Mage"); - addTarget(playerB, ""); castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, "Rattleclaw Mystic"); setChoice(playerA, "No"); // cast it face down as 2/2 creature From 28ac95cb100d8a26e711920735046f53648e0677 Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Thu, 29 Nov 2018 19:11:20 +0400 Subject: [PATCH 25/42] Test framework: added new real time commands to prints info and checks: * show player's library, hand, battlefield, graveyard (use showXXX); * show exile zone, available abilities to activate (use showXXX); * checks targets, choices and commands in queue (use assert). --- .../java/org/mage/test/player/TestPlayer.java | 185 ++++++++++++++++-- .../base/impl/CardTestPlayerAPIImpl.java | 80 +++++++- 2 files changed, 243 insertions(+), 22 deletions(-) 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 c6939d329e..47f703ec0e 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 @@ -54,6 +54,7 @@ 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.*; @@ -116,6 +117,10 @@ public class TestPlayer implements Player { return this.choices; } + public List getTargets() { + return this.targets; + } + public void addModeChoice(String mode) { modesSet.add(mode); } @@ -445,6 +450,8 @@ public class TestPlayer implements Player { groupsForTargetHandling = null; } } + // TODO: fix wrong commands (on non existing card), it's HUGE (350+ failed tests with wrong commands) + //Assert.fail("Can't find ability to activate command: " + command); } else if (action.getAction().startsWith("manaActivate:")) { String command = action.getAction(); command = command.substring(command.indexOf("manaActivate:") + 13); @@ -525,78 +532,143 @@ public class TestPlayer implements Player { } } else if (action.getAction().startsWith("check:")) { String command = action.getAction(); - command = command.substring(command.indexOf("check:") + 6); + command = command.substring(command.indexOf("check:") + "check:".length()); String[] params = command.split("@"); - boolean checkProccessed = false; + boolean wasProccessed = false; if (params.length > 0) { // check PT: card name, P, T if (params[0].equals(CHECK_COMMAND_PT) && params.length == 4) { assertPT(action, game, computerPlayer, params[1], Integer.parseInt(params[2]), Integer.parseInt(params[3])); actions.remove(action); - checkProccessed = true; + wasProccessed = true; } // check life: life if (params[0].equals(CHECK_COMMAND_LIFE) && params.length == 2) { assertLife(action, game, computerPlayer, Integer.parseInt(params[1])); actions.remove(action); - checkProccessed = true; + wasProccessed = true; } // check ability: card name, ability class, must have if (params[0].equals(CHECK_COMMAND_ABILITY) && params.length == 4) { assertAbility(action, game, computerPlayer, params[1], params[2], Boolean.parseBoolean(params[3])); actions.remove(action); - checkProccessed = true; + wasProccessed = true; } // check battlefield count: card name, count if (params[0].equals(CHECK_COMMAND_PERMANENT_COUNT) && params.length == 3) { assertPermanentCount(action, game, computerPlayer, params[1], Integer.parseInt(params[2])); actions.remove(action); - checkProccessed = true; + wasProccessed = true; } // check exile count: card name, count if (params[0].equals(CHECK_COMMAND_EXILE_COUNT) && params.length == 3) { assertExileCount(action, game, computerPlayer, params[1], Integer.parseInt(params[2])); actions.remove(action); - checkProccessed = true; + wasProccessed = true; } // check hand count: count if (params[0].equals(CHECK_COMMAND_HAND_COUNT) && params.length == 2) { assertHandCount(action, game, computerPlayer, Integer.parseInt(params[1])); actions.remove(action); - checkProccessed = true; + wasProccessed = true; } // check color: card name, colors, must have if (params[0].equals(CHECK_COMMAND_COLOR) && params.length == 4) { assertColor(action, game, computerPlayer, params[1], params[2], Boolean.parseBoolean(params[3])); actions.remove(action); - checkProccessed = true; + wasProccessed = true; } // check subtype: card name, subtype, must have if (params[0].equals(CHECK_COMMAND_SUBTYPE) && params.length == 4) { assertSubType(action, game, computerPlayer, params[1], SubType.fromString(params[2]), Boolean.parseBoolean(params[3])); actions.remove(action); - checkProccessed = true; + wasProccessed = true; } // check mana pool: colors, amount if (params[0].equals(CHECK_COMMAND_MANA_POOL) && params.length == 3) { assertManaPool(action, game, computerPlayer, params[1], Integer.parseInt(params[2])); actions.remove(action); - checkProccessed = true; + wasProccessed = true; + } + } + if (!wasProccessed) { + Assert.fail("Unknow check command or params: " + command); + } + } else if (action.getAction().startsWith("show:")) { + String command = action.getAction(); + command = command.substring(command.indexOf("show:") + "show:".length()); + + String[] params = command.split("@"); + boolean wasProccessed = false; + if (params.length > 0) { + + // show library + if (params[0].equals(SHOW_COMMAND_LIBRARY) && params.length == 1) { + printStart(action.getActionName()); + printCards(computerPlayer.getLibrary().getCards(game)); + printEnd(); + actions.remove(action); + wasProccessed = true; + } + + // show hand + if (params[0].equals(SHOW_COMMAND_HAND) && params.length == 1) { + printStart(action.getActionName()); + printCards(computerPlayer.getHand().getCards(game)); + printEnd(); + actions.remove(action); + wasProccessed = true; + } + + // show battlefield + if (params[0].equals(SHOW_COMMAND_BATTLEFIELD) && params.length == 1) { + printStart(action.getActionName()); + printPermanents(game.getBattlefield().getAllActivePermanents(computerPlayer.getId())); + printEnd(); + actions.remove(action); + wasProccessed = true; + } + + // show graveyard + if (params[0].equals(SHOW_COMMAND_GRAVEYEARD) && params.length == 1) { + printStart(action.getActionName()); + printCards(computerPlayer.getGraveyard().getCards(game)); + printEnd(); + actions.remove(action); + wasProccessed = true; + } + + // show exile + if (params[0].equals(SHOW_COMMAND_EXILE) && params.length == 1) { + printStart(action.getActionName()); + printCards(game.getExile().getAllCards(game)); + printEnd(); + actions.remove(action); + wasProccessed = true; + } + + // show available abilities + if (params[0].equals(SHOW_COMMAND_AVAILABLE_ABILITIES) && params.length == 1) { + printStart(action.getActionName()); + printAbilities(game, computerPlayer.getPlayable(game, true)); + printEnd(); + actions.remove(action); + wasProccessed = true; } } - if (!checkProccessed) { - Assert.fail("Unknow check command or params: " + command); + if (!wasProccessed) { + Assert.fail("Unknow show command or params: " + command); } } } @@ -629,6 +701,73 @@ public class TestPlayer implements Player { return null; } + private void printStart(String name) { + System.out.println("\n" + name + ":"); + } + + private void printEnd() { + System.out.println(); + } + + private void printCards(Set cards) { + printCards(cards.stream().collect(Collectors.toList())); + } + + private void printCards(List cards) { + System.out.println("Total cards: " + cards.size()); + + List data = cards.stream() + .map(Card::getIdName) + .sorted() + .collect(Collectors.toList()); + + for (String s : data) { + System.out.println(s); + } + } + + private void printPermanents(List cards) { + System.out.println("Total permanents: " + cards.size()); + + List data = cards.stream() + .map(c -> (c.getIdName() + + " - " + c.getPower().getValue() + + "/" + c.getToughness().getValue() + + ", " + (c.isTapped() ? "Tapped" : "Untapped") + )) + .sorted() + .collect(Collectors.toList()); + + for (String s : data) { + System.out.println(s); + } + } + + private void printAbilities(Game game, List abilities) { + + + System.out.println("Total abilities: " + (abilities != null ? abilities.size() : 0)); + if (abilities == null) { + return; + } + + List data = abilities.stream() + .map(a -> ( + a.getZone() + " -> " + + a.getSourceObject(game).getIdName() + " -> " + + (a.getRule().length() > 0 + ? a.getRule().substring(0, Math.min(20, a.getRule().length()) - 1) + : a.getClass().getSimpleName()) + + "..." + )) + .sorted() + .collect(Collectors.toList()); + + for (String s : data) { + System.out.println(s); + } + } + private void assertPT(PlayerAction action, Game game, Player player, String permanentName, int Power, int Toughness) { Permanent perm = findPermanentWithAssert(action, game, player, permanentName); @@ -976,6 +1115,8 @@ public class TestPlayer implements Player { if (choice.setChoiceByAnswers(choices, true)) { return true; } + // TODO: enable fail checks and fix tests + //Assert.fail("Wrong choice"); } return computerPlayer.choose(outcome, choice, game); } @@ -991,6 +1132,8 @@ public class TestPlayer implements Player { } } } + // TODO: enable fail checks and fix tests + //Assert.fail("wrong choice"); } return computerPlayer.chooseReplacementEffect(rEffects, game); } @@ -1135,6 +1278,13 @@ public class TestPlayer implements Player { } } } + + // TODO: enable fail checks and fix tests + /* + if (!target.getTargetName().equals("starting player")) { + Assert.fail("Wrong choice"); + } + */ } return computerPlayer.choose(outcome, target, sourceId, game, options); @@ -1451,6 +1601,9 @@ public class TestPlayer implements Player { return true; } } + + // TODO: enable fail checks and fix tests + //Assert.fail("Wrong target"); } return computerPlayer.chooseTarget(outcome, cards, target, source, game); } @@ -1464,6 +1617,8 @@ public class TestPlayer implements Player { return ability; } } + // TODO: enable fail checks and fix tests + //Assert.fail("Wrong choice"); } return computerPlayer.chooseTriggeredAbility(abilities, game); } @@ -1487,6 +1642,8 @@ public class TestPlayer implements Player { choices.remove(0); return true; } + // TODO: enable fail checks and fix tests + //Assert.fail("Wrong choice"); } return computerPlayer.chooseUse(outcome, message, secondMessage, trueText, falseText, source, game); } @@ -2643,6 +2800,8 @@ public class TestPlayer implements Player { return true; } } + // TODO: enable fail checks and fix tests + //Assert.fail("Wrong choice"); } return computerPlayer.choose(outcome, cards, target, game); } 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 b858d8609b..016f1d37fb 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 @@ -26,6 +26,7 @@ import mage.players.ManaPool; import mage.players.Player; import org.junit.Assert; import org.junit.Before; +import org.mage.test.player.PlayerAction; import org.mage.test.player.TestPlayer; import org.mage.test.serverside.base.CardTestAPI; import org.mage.test.serverside.base.MageTestPlayerBase; @@ -48,6 +49,7 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement // Defines the constant if for activate ability is not target but a ability on the stack to define public static final String NO_TARGET = "NO_TARGET"; + // TODO: add target player param to commands public static final String CHECK_COMMAND_PT = "PT"; public static final String CHECK_COMMAND_LIFE = "LIFE"; public static final String CHECK_COMMAND_ABILITY = "ABILITY"; @@ -58,6 +60,14 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement public static final String CHECK_COMMAND_SUBTYPE = "SUBTYPE"; public static final String CHECK_COMMAND_MANA_POOL = "MANA_POOL"; + // TODO: add target player param to commands + public static final String SHOW_COMMAND_LIBRARY = "LIBRARY"; + public static final String SHOW_COMMAND_HAND = "HAND"; + public static final String SHOW_COMMAND_BATTLEFIELD = "BATTLEFIELD"; + public static final String SHOW_COMMAND_GRAVEYEARD = "GRAVEYARD"; + public static final String SHOW_COMMAND_EXILE = "EXILE"; + public static final String SHOW_COMMAND_AVAILABLE_ABILITIES = "AVAILABLE_ABILITIES"; + protected GameOptions gameOptions; protected String deckNameA; @@ -238,6 +248,8 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement return player; } + // check commands + private void check(String checkName, int turnNum, PhaseStep step, TestPlayer player, String command, String... params) { String res = "check:" + command; for (String param : params) { @@ -282,6 +294,40 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement check(checkName, turnNum, step, player, CHECK_COMMAND_MANA_POOL, colors, amount.toString()); } + // show commands + + private void show(String showName, int turnNum, PhaseStep step, TestPlayer player, String command, String... params) { + String res = "show:" + command; + for (String param : params) { + res += "@" + param; + } + player.addAction(showName, turnNum, step, res); + } + + public void showLibrary(String showName, int turnNum, PhaseStep step, TestPlayer player) { + show(showName, turnNum, step, player, SHOW_COMMAND_LIBRARY); + } + + public void showHand(String showName, int turnNum, PhaseStep step, TestPlayer player) { + show(showName, turnNum, step, player, SHOW_COMMAND_HAND); + } + + public void showBattlefield(String showName, int turnNum, PhaseStep step, TestPlayer player) { + show(showName, turnNum, step, player, SHOW_COMMAND_BATTLEFIELD); + } + + public void showGraveyard(String showName, int turnNum, PhaseStep step, TestPlayer player) { + show(showName, turnNum, step, player, SHOW_COMMAND_GRAVEYEARD); + } + + public void showExile(String showName, int turnNum, PhaseStep step, TestPlayer player) { + show(showName, turnNum, step, player, SHOW_COMMAND_EXILE); + } + + public void showAvaileableAbilities(String showName, int turnNum, PhaseStep step, TestPlayer player) { + show(showName, turnNum, step, player, SHOW_COMMAND_AVAILABLE_ABILITIES); + } + /** * Removes all cards from player's library from the game. Usually this * should be used once before initialization to form the library in certain @@ -1080,15 +1126,27 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement Assert.assertEquals("(Library " + player.getName() + ") Card counts are not equal (" + cardName + ')', count, actualCount); } - /** - * Asserts added actions count. Useful to make sure that all actions were - * executed. - * - * @param player - * @param count - */ - public void assertActionCount(TestPlayer player, int count) { - Assert.assertEquals("Actions left are not equal: ", count, player.getActionCount()); + public void assertActionsCount(TestPlayer player, int count) throws AssertionError { + Assert.assertEquals("(Actions " + player.getName() + ") Count are not equel (founded [" + + player.getActions().stream().map(PlayerAction::getAction).collect(Collectors.joining(", ")) + + "])", count, player.getActions().size()); + } + + public void assertChoicesCount(TestPlayer player, int count) throws AssertionError { + Assert.assertEquals("(Choices " + player.getName() + ") Count are not equel (founded " + player.getChoices() + ")", count, player.getChoices().size()); + } + + public void assertTargetsCount(TestPlayer player, int count) throws AssertionError { + Assert.assertEquals("(Targets " + player.getName() + ") Count are not equel (founded " + player.getTargets() + ")", count, player.getTargets().size()); + } + + public void assertAllCommandsUsed() throws AssertionError { + for(Player player : currentGame.getPlayers().values()) { + TestPlayer testPlayer = (TestPlayer) player; + assertActionsCount(testPlayer, 0); + assertChoicesCount(testPlayer, 0); + assertTargetsCount(testPlayer, 0); + } } public void assertActivePlayer(TestPlayer player) { @@ -1243,18 +1301,22 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement } public void activateAbility(int turnNum, PhaseStep step, TestPlayer player, String ability) { + // TODO: it's uses computerPlayer to execute, only ability target will work, but choices and targets commands aren't player.addAction(turnNum, step, "activate:" + ability); } public void activateAbility(int turnNum, PhaseStep step, TestPlayer player, String ability, Player target) { + // TODO: it's uses computerPlayer to execute, only ability target will work, but choices and targets commands aren't player.addAction(turnNum, step, "activate:" + ability + "$targetPlayer=" + target.getName()); } public void activateAbility(int turnNum, PhaseStep step, TestPlayer player, String ability, String... targetNames) { + // TODO: it's uses computerPlayer to execute, only ability target will work, but choices and targets commands aren't player.addAction(turnNum, step, "activate:" + ability + "$target=" + String.join("^", targetNames)); } public void activateAbility(int turnNum, PhaseStep step, TestPlayer player, String ability, String targetName, String spellOnStack) { + // TODO: it's uses computerPlayer to execute, only ability target will work, but choices and targets commands aren't this.activateAbility(turnNum, step, player, ability, targetName, spellOnStack, StackClause.WHILE_ON_STACK); } From 59bda7f1d53a03c8deb47d166a077d05974d7061 Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Thu, 29 Nov 2018 19:29:39 +0400 Subject: [PATCH 26/42] Refactor: added copyFrom info for all objects (original card used for copy, copy of copy and etc); --- .../mage/cards/a/ApproachOfTheSecondSun.java | 9 ++- .../src/mage/cards/c/CopyEnchantment.java | 3 + .../src/mage/cards/k/KheruSpellsnatcher.java | 17 ++---- .../src/mage/cards/s/SoulfireGrandMaster.java | 18 ++---- .../abilities/activated/LicidAbilityTest.java | 2 +- .../test/lki/LastKnownInformationTest.java | 2 +- Mage/src/main/java/mage/MageObject.java | 16 ++--- Mage/src/main/java/mage/MageObjectImpl.java | 26 ++++---- .../abilities/effects/common/CopyEffect.java | 10 +++- .../effects/common/ExileSpellEffect.java | 4 +- .../abilities/keyword/ReboundAbility.java | 6 +- Mage/src/main/java/mage/cards/SplitCard.java | 18 +++--- .../java/mage/designations/CitysBlessing.java | 11 +++- .../java/mage/designations/Designation.java | 50 ++++++++-------- .../main/java/mage/designations/Monarch.java | 11 +++- .../main/java/mage/filter/FilterPlayer.java | 6 +- .../filter/predicate/ObjectSourcePlayer.java | 4 +- Mage/src/main/java/mage/game/GameImpl.java | 4 ++ Mage/src/main/java/mage/game/GameState.java | 20 +++---- .../java/mage/game/command/Commander.java | 42 ++++++++----- .../main/java/mage/game/command/Emblem.java | 36 +++++++---- .../main/java/mage/game/command/Plane.java | 29 ++++++--- Mage/src/main/java/mage/game/stack/Spell.java | 47 ++++++++------- .../java/mage/game/stack/StackAbility.java | 39 +++++++----- .../main/java/mage/players/PlayerImpl.java | 59 ++++++++++--------- 25 files changed, 269 insertions(+), 220 deletions(-) diff --git a/Mage.Sets/src/mage/cards/a/ApproachOfTheSecondSun.java b/Mage.Sets/src/mage/cards/a/ApproachOfTheSecondSun.java index 1877fee325..574c2ce7b7 100644 --- a/Mage.Sets/src/mage/cards/a/ApproachOfTheSecondSun.java +++ b/Mage.Sets/src/mage/cards/a/ApproachOfTheSecondSun.java @@ -1,6 +1,5 @@ package mage.cards.a; -import java.util.*; import mage.abilities.Ability; import mage.abilities.effects.OneShotEffect; import mage.cards.Card; @@ -16,6 +15,10 @@ import mage.game.stack.Spell; import mage.players.Player; import mage.watchers.Watcher; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + /** * @author stravant */ @@ -64,7 +67,7 @@ class ApproachOfTheSecondSunEffect extends OneShotEffect { ApproachOfTheSecondSunWatcher watcher = (ApproachOfTheSecondSunWatcher) game.getState().getWatchers().get(ApproachOfTheSecondSunWatcher.class.getSimpleName()); if (watcher != null - && !spell.isCopiedSpell() + && !spell.isCopy() && watcher.getApproachesCast(controller.getId()) > 1 && spell.getFromZone() == Zone.HAND) { // Win the game @@ -74,7 +77,7 @@ class ApproachOfTheSecondSunEffect extends OneShotEffect { controller.gainLife(7, game, source); // Put this into the library as the 7th from the top - if (spell.isCopiedSpell()) { + if (spell.isCopy()) { return true; } Card spellCard = game.getStack().getSpell(source.getSourceId()).getCard(); diff --git a/Mage.Sets/src/mage/cards/c/CopyEnchantment.java b/Mage.Sets/src/mage/cards/c/CopyEnchantment.java index f84ee371dd..53ecbe3072 100644 --- a/Mage.Sets/src/mage/cards/c/CopyEnchantment.java +++ b/Mage.Sets/src/mage/cards/c/CopyEnchantment.java @@ -2,6 +2,8 @@ package mage.cards.c; import java.util.UUID; + +import mage.MageObject; import mage.abilities.Ability; import mage.abilities.SpellAbility; import mage.abilities.common.EntersBattlefieldAbility; @@ -17,6 +19,7 @@ import mage.filter.FilterPermanent; import mage.filter.StaticFilters; import mage.game.Game; import mage.game.permanent.Permanent; +import mage.game.permanent.PermanentCard; import mage.players.Player; import mage.target.Target; import mage.util.functions.EmptyApplyToPermanent; diff --git a/Mage.Sets/src/mage/cards/k/KheruSpellsnatcher.java b/Mage.Sets/src/mage/cards/k/KheruSpellsnatcher.java index 1576cc697b..d5e5bf5a3d 100644 --- a/Mage.Sets/src/mage/cards/k/KheruSpellsnatcher.java +++ b/Mage.Sets/src/mage/cards/k/KheruSpellsnatcher.java @@ -1,7 +1,5 @@ - package mage.cards.k; -import java.util.UUID; import mage.MageInt; import mage.MageObject; import mage.abilities.Ability; @@ -14,13 +12,7 @@ import mage.abilities.keyword.MorphAbility; import mage.cards.Card; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.AsThoughEffectType; -import mage.constants.CardType; -import mage.constants.SubType; -import mage.constants.Duration; -import mage.constants.Outcome; -import mage.constants.Zone; -import mage.constants.ZoneDetail; +import mage.constants.*; import mage.game.Game; import mage.game.stack.Spell; import mage.game.stack.StackObject; @@ -28,14 +20,15 @@ import mage.players.Player; import mage.target.TargetSpell; import mage.target.targetpointer.FixedTarget; +import java.util.UUID; + /** - * * @author emerald000 */ public final class KheruSpellsnatcher extends CardImpl { public KheruSpellsnatcher(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{3}{U}"); + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{U}"); this.subtype.add(SubType.NAGA); this.subtype.add(SubType.WIZARD); @@ -85,7 +78,7 @@ class KheruSpellsnatcherEffect extends OneShotEffect { StackObject stackObject = game.getStack().getStackObject(objectId); if (stackObject != null && game.getStack().counter(targetPointer.getFirst(game, source), source.getSourceId(), game, Zone.EXILED, false, ZoneDetail.NONE)) { - if (!((Spell) stackObject).isCopiedSpell()) { + if (!((Spell) stackObject).isCopy()) { MageObject card = game.getObject(stackObject.getSourceId()); if (card instanceof Card) { ((Card) card).moveToZone(Zone.EXILED, sourceId, game, true); diff --git a/Mage.Sets/src/mage/cards/s/SoulfireGrandMaster.java b/Mage.Sets/src/mage/cards/s/SoulfireGrandMaster.java index abd70447d8..c7b36889d6 100644 --- a/Mage.Sets/src/mage/cards/s/SoulfireGrandMaster.java +++ b/Mage.Sets/src/mage/cards/s/SoulfireGrandMaster.java @@ -1,14 +1,11 @@ - package mage.cards.s; -import java.util.UUID; import mage.MageInt; import mage.MageObject; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.costs.mana.ManaCostsImpl; -import mage.abilities.effects.ContinuousEffectImpl; import mage.abilities.effects.Effect; import mage.abilities.effects.GainAbilitySpellsEffect; import mage.abilities.effects.ReplacementEffectImpl; @@ -16,13 +13,7 @@ import mage.abilities.keyword.LifelinkAbility; import mage.cards.Card; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.CardType; -import mage.constants.SubType; -import mage.constants.Duration; -import mage.constants.Layer; -import mage.constants.Outcome; -import mage.constants.SubLayer; -import mage.constants.Zone; +import mage.constants.*; import mage.filter.FilterCard; import mage.filter.FilterObject; import mage.filter.predicate.Predicates; @@ -30,13 +21,12 @@ import mage.filter.predicate.mageobject.CardTypePredicate; import mage.game.Game; import mage.game.events.GameEvent; import mage.game.events.ZoneChangeEvent; -import mage.game.permanent.Permanent; import mage.game.stack.Spell; -import mage.game.stack.StackObject; import mage.players.Player; +import java.util.UUID; + /** - * * @author LevelX2 */ public final class SoulfireGrandMaster extends CardImpl { @@ -111,7 +101,7 @@ class SoulfireGrandMasterCastFromHandReplacementEffect extends ReplacementEffect @Override public boolean replaceEvent(GameEvent event, Ability source, Game game) { MageObject mageObject = game.getObject(spellId); - if (!(mageObject instanceof Spell) || ((Spell) mageObject).isCopiedSpell()) { + if (!(mageObject instanceof Spell) || ((Spell) mageObject).isCopy()) { return false; } else { Card sourceCard = game.getCard(spellId); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/activated/LicidAbilityTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/activated/LicidAbilityTest.java index 5d4eca287e..62f5cef145 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/activated/LicidAbilityTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/activated/LicidAbilityTest.java @@ -59,7 +59,7 @@ public class LicidAbilityTest extends CardTestPlayerBase { execute(); - assertActionCount(playerA, 0); + assertActionsCount(playerA, 0); assertAbility(playerA, "Pillarfield Ox", HasteAbility.getInstance(), false); assertAbility(playerA, "Enraging Licid", new LicidAbility(new ColoredManaCost(ColoredManaSymbol.R), new ColoredManaCost(ColoredManaSymbol.R)), true); assertType("Enraging Licid", CardType.ENCHANTMENT, false); diff --git a/Mage.Tests/src/test/java/org/mage/test/lki/LastKnownInformationTest.java b/Mage.Tests/src/test/java/org/mage/test/lki/LastKnownInformationTest.java index ef91492177..352ec36de5 100644 --- a/Mage.Tests/src/test/java/org/mage/test/lki/LastKnownInformationTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/lki/LastKnownInformationTest.java @@ -58,7 +58,7 @@ public class LastKnownInformationTest extends CardTestPlayerBase { assertPermanentCount(playerA, "Soldier", 2); assertGraveyardCount(playerB, "Lightning Bolt", 2); - assertActionCount(playerB, 0); + assertActionsCount(playerB, 0); } diff --git a/Mage/src/main/java/mage/MageObject.java b/Mage/src/main/java/mage/MageObject.java index b65f172173..cb1812c44f 100644 --- a/Mage/src/main/java/mage/MageObject.java +++ b/Mage/src/main/java/mage/MageObject.java @@ -64,20 +64,14 @@ public interface MageObject extends MageItem, Serializable { void adjustTargets(Ability ability, Game game); + // memory object copy (not mtg) MageObject copy(); - /** - * Defines that MageObject is a copy of another object - * - * @param isCopy - */ - void setCopy(boolean isCopy); + // copied card info (mtg) + void setCopy(boolean isCopy, MageObject copiedFrom); + + MageObject getCopyFrom(); - /** - * Checks if current MageObject is a copy of another object - * - * @return - */ boolean isCopy(); int getZoneChangeCounter(Game game); diff --git a/Mage/src/main/java/mage/MageObjectImpl.java b/Mage/src/main/java/mage/MageObjectImpl.java index 051b958457..7592cb770f 100644 --- a/Mage/src/main/java/mage/MageObjectImpl.java +++ b/Mage/src/main/java/mage/MageObjectImpl.java @@ -1,11 +1,5 @@ - package mage; -import java.util.ArrayList; -import java.util.EnumSet; -import java.util.Iterator; -import java.util.List; -import java.util.UUID; import mage.abilities.Abilities; import mage.abilities.AbilitiesImpl; import mage.abilities.Ability; @@ -20,16 +14,14 @@ import mage.abilities.mana.ActivatedManaAbilityImpl; import mage.abilities.text.TextPart; import mage.abilities.text.TextPartSubType; import mage.cards.FrameStyle; -import mage.constants.CardType; -import mage.constants.SubLayer; -import mage.constants.SubType; -import mage.constants.SubTypeSet; -import mage.constants.SuperType; +import mage.constants.*; import mage.game.Game; import mage.game.events.ZoneChangeEvent; import mage.util.GameLog; import mage.util.SubTypeList; +import java.util.*; + public abstract class MageObjectImpl implements MageObject { protected UUID objectId; @@ -48,6 +40,7 @@ public abstract class MageObjectImpl implements MageObject { protected MageInt power; protected MageInt toughness; protected boolean copy; + protected MageObject copyFrom; // copied card INFO (used to call original adjusters) protected List textParts; public MageObjectImpl() { @@ -82,6 +75,7 @@ public abstract class MageObjectImpl implements MageObject { isAllCreatureTypes = object.isAllCreatureTypes; supertype.addAll(object.supertype); this.copy = object.copy; + this.copyFrom = (object.copyFrom != null ? object.copyFrom.copy() : null); textParts = new ArrayList<>(); textParts.addAll(object.textParts); } @@ -263,8 +257,14 @@ public abstract class MageObjectImpl implements MageObject { } @Override - public void setCopy(boolean isCopy) { + public void setCopy(boolean isCopy, MageObject copyFrom) { this.copy = isCopy; + this.copyFrom = (copyFrom != null ? copyFrom.copy() : null); + } + + @Override + public MageObject getCopyFrom() { + return this.copyFrom; } @Override @@ -322,7 +322,7 @@ public abstract class MageObjectImpl implements MageObject { */ @Override public void removePTCDA() { - for (Iterator iter = this.getAbilities().iterator(); iter.hasNext();) { + for (Iterator iter = this.getAbilities().iterator(); iter.hasNext(); ) { Ability ability = iter.next(); for (Effect effect : ability.getEffects()) { if (effect instanceof ContinuousEffect && ((ContinuousEffect) effect).getSublayer() == SubLayer.CharacteristicDefining_7a) { diff --git a/Mage/src/main/java/mage/abilities/effects/common/CopyEffect.java b/Mage/src/main/java/mage/abilities/effects/common/CopyEffect.java index b582f29130..da0131ace9 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/CopyEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/CopyEffect.java @@ -1,4 +1,3 @@ - package mage.abilities.effects.common; import mage.MageObject; @@ -16,7 +15,6 @@ import mage.util.functions.ApplyToPermanent; import java.util.UUID; /** - * * @author BetaSteward_at_googlemail.com */ public class CopyEffect extends ContinuousEffectImpl { @@ -90,7 +88,13 @@ public class CopyEffect extends ContinuousEffectImpl { } protected boolean copyToPermanent(Permanent permanent, Game game, Ability source) { - permanent.setCopy(true); + if (copyFromObject.getCopyFrom() != null) { + // copy from temp blueprints (they are already copies) + permanent.setCopy(true, copyFromObject.getCopyFrom()); + } else { + // copy object to object + permanent.setCopy(true, copyFromObject); + } permanent.setName(copyFromObject.getName()); permanent.getColor(game).setColor(copyFromObject.getColor(game)); permanent.getManaCost().clear(); diff --git a/Mage/src/main/java/mage/abilities/effects/common/ExileSpellEffect.java b/Mage/src/main/java/mage/abilities/effects/common/ExileSpellEffect.java index 0810053005..f828e713ef 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/ExileSpellEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/ExileSpellEffect.java @@ -1,4 +1,3 @@ - package mage.abilities.effects.common; import mage.abilities.Ability; @@ -12,7 +11,6 @@ import mage.game.stack.Spell; import mage.players.Player; /** - * * @author BetaSteward_at_googlemail.com */ public class ExileSpellEffect extends OneShotEffect implements MageSingleton { @@ -38,7 +36,7 @@ public class ExileSpellEffect extends OneShotEffect implements MageSingleton { Player controller = game.getPlayer(source.getControllerId()); if (controller != null) { Spell spell = game.getStack().getSpell(source.getId()); - if (spell != null && !spell.isCopiedSpell()) { + if (spell != null && !spell.isCopy()) { Card spellCard = spell.getCard(); if (spellCard != null) { controller.moveCards(spellCard, Zone.EXILED, source, game); diff --git a/Mage/src/main/java/mage/abilities/keyword/ReboundAbility.java b/Mage/src/main/java/mage/abilities/keyword/ReboundAbility.java index 885d0a3b66..6afa7afdf0 100644 --- a/Mage/src/main/java/mage/abilities/keyword/ReboundAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/ReboundAbility.java @@ -1,7 +1,5 @@ - package mage.abilities.keyword; -import java.util.UUID; import mage.MageObjectReference; import mage.abilities.Ability; import mage.abilities.DelayedTriggeredAbility; @@ -21,6 +19,8 @@ import mage.game.events.ZoneChangeEvent; import mage.game.stack.Spell; import mage.players.Player; +import java.util.UUID; + /** * This ability has no effect by default and will always return false on the * call to apply. This is because of how the {@link ReboundEffect} works. It @@ -93,7 +93,7 @@ class ReboundCastFromHandReplacementEffect extends ReplacementEffectImpl { @Override public boolean replaceEvent(GameEvent event, Ability source, Game game) { Spell sourceSpell = game.getStack().getSpell(source.getSourceId()); - if (sourceSpell != null && sourceSpell.isCopiedSpell()) { + if (sourceSpell != null && sourceSpell.isCopy()) { return false; } else { Card sourceCard = game.getCard(source.getSourceId()); diff --git a/Mage/src/main/java/mage/cards/SplitCard.java b/Mage/src/main/java/mage/cards/SplitCard.java index b8102f7ae4..a602c2a663 100644 --- a/Mage/src/main/java/mage/cards/SplitCard.java +++ b/Mage/src/main/java/mage/cards/SplitCard.java @@ -1,9 +1,6 @@ - package mage.cards; -import java.util.ArrayList; -import java.util.List; -import java.util.UUID; +import mage.MageObject; import mage.abilities.Abilities; import mage.abilities.AbilitiesImpl; import mage.abilities.Ability; @@ -13,8 +10,11 @@ import mage.constants.SpellAbilityType; import mage.constants.Zone; import mage.game.Game; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + /** - * * @author LevelX2 */ public abstract class SplitCard extends CardImpl { @@ -58,10 +58,10 @@ public abstract class SplitCard extends CardImpl { } @Override - public void setCopy(boolean isCopy) { - super.setCopy(isCopy); - leftHalfCard.setCopy(isCopy); - rightHalfCard.setCopy(isCopy); + public void setCopy(boolean isCopy, MageObject copiedFrom) { + super.setCopy(isCopy, copiedFrom); + leftHalfCard.setCopy(isCopy, copiedFrom); + rightHalfCard.setCopy(isCopy, copiedFrom); } @Override diff --git a/Mage/src/main/java/mage/designations/CitysBlessing.java b/Mage/src/main/java/mage/designations/CitysBlessing.java index ca5aaa7476..9e803a6b67 100644 --- a/Mage/src/main/java/mage/designations/CitysBlessing.java +++ b/Mage/src/main/java/mage/designations/CitysBlessing.java @@ -1,8 +1,6 @@ - package mage.designations; /** - * * @author LevelX2 */ public class CitysBlessing extends Designation { @@ -10,4 +8,13 @@ public class CitysBlessing extends Designation { public CitysBlessing() { super(DesignationType.CITYS_BLESSING, "RIX"); } + + private CitysBlessing(final CitysBlessing card) { + super(card); + } + + @Override + public CitysBlessing copy() { + return new CitysBlessing(this); + } } diff --git a/Mage/src/main/java/mage/designations/Designation.java b/Mage/src/main/java/mage/designations/Designation.java index 44d7ba62ab..943e757e77 100644 --- a/Mage/src/main/java/mage/designations/Designation.java +++ b/Mage/src/main/java/mage/designations/Designation.java @@ -1,14 +1,5 @@ -/* - * 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 mage.designations; -import java.util.ArrayList; -import java.util.EnumSet; -import java.util.List; -import java.util.UUID; import mage.MageInt; import mage.MageObject; import mage.ObjectColor; @@ -28,8 +19,12 @@ import mage.game.events.ZoneChangeEvent; import mage.util.GameLog; import mage.util.SubTypeList; +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.List; +import java.util.UUID; + /** - * * @author LevelX2 */ public abstract class Designation implements MageObject { @@ -43,6 +38,8 @@ public abstract class Designation implements MageObject { private DesignationType designationType; private UUID id; private FrameStyle frameStyle; + private boolean copy; + private MageObject copyFrom; // copied card INFO (used to call original adjusters) private Abilities abilites = new AbilitiesImpl<>(); private String expansionSetCodeForImage; private final boolean unique; // can a designation be added multiple times (false) or only once to an object (true) @@ -65,6 +62,8 @@ public abstract class Designation implements MageObject { this.name = designation.name; this.designationType = designation.designationType; this.frameStyle = designation.frameStyle; + this.copy = designation.copy; + this.copyFrom = (designation.copyFrom != null ? designation.copyFrom.copy() : null); this.abilites = designation.abilites.copy(); this.unique = designation.unique; } @@ -78,6 +77,22 @@ public abstract class Designation implements MageObject { this.id = UUID.randomUUID(); } + @Override + public void setCopy(boolean isCopy, MageObject copyFrom) { + this.copy = isCopy; + this.copyFrom = (copyFrom != null ? copyFrom.copy() : null); + } + + @Override + public boolean isCopy() { + return this.copy; + } + + @Override + public MageObject getCopyFrom() { + return this.copyFrom; + } + @Override public String getName() { return name; @@ -198,20 +213,6 @@ public abstract class Designation implements MageObject { public void adjustTargets(Ability ability, Game game) { } - @Override - public void setCopy(boolean isCopy) { - } - - @Override - public boolean isCopy() { - return false; - } - - @Override - public Designation copy() { - return this; - } - @Override public int getZoneChangeCounter(Game game) { return 1; // Emblems can't move zones until now so return always 1 @@ -232,7 +233,6 @@ public abstract class Designation implements MageObject { } /** - * * @param game * @param controllerId */ diff --git a/Mage/src/main/java/mage/designations/Monarch.java b/Mage/src/main/java/mage/designations/Monarch.java index 0281b3c459..d178838970 100644 --- a/Mage/src/main/java/mage/designations/Monarch.java +++ b/Mage/src/main/java/mage/designations/Monarch.java @@ -1,4 +1,3 @@ - package mage.designations; import mage.MageObject; @@ -15,7 +14,6 @@ import mage.game.permanent.Permanent; import mage.target.targetpointer.FixedTarget; /** - * * @author LevelX2 */ public class Monarch extends Designation { @@ -25,6 +23,15 @@ public class Monarch extends Designation { addAbility(new MonarchDrawTriggeredAbility()); addAbility(new MonarchDealsCombatDamageToAPlayerTriggeredAbility()); } + + private Monarch(final Monarch monarch) { + super(monarch); + } + + @Override + public Monarch copy() { + return new Monarch(this); + } } // At the beginning of the monarch's end step, that player draws a card diff --git a/Mage/src/main/java/mage/filter/FilterPlayer.java b/Mage/src/main/java/mage/filter/FilterPlayer.java index 907dfb3434..8f085ddd67 100644 --- a/Mage/src/main/java/mage/filter/FilterPlayer.java +++ b/Mage/src/main/java/mage/filter/FilterPlayer.java @@ -44,12 +44,12 @@ public class FilterPlayer extends FilterImpl { return object instanceof Player; } - public boolean match(Player player, UUID sourceId, UUID playerId, Game game) { - if (!this.match(player, game)) { + public boolean match(Player checkPlayer, UUID sourceId, UUID sourceControllerId, Game game) { + if (!this.match(checkPlayer, game)) { return false; } - return Predicates.and(extraPredicates).apply(new ObjectSourcePlayer(player, sourceId, playerId), game); + return Predicates.and(extraPredicates).apply(new ObjectSourcePlayer(checkPlayer, sourceId, sourceControllerId), game); } @Override diff --git a/Mage/src/main/java/mage/filter/predicate/ObjectSourcePlayer.java b/Mage/src/main/java/mage/filter/predicate/ObjectSourcePlayer.java index aa73a7d466..671c8abf0c 100644 --- a/Mage/src/main/java/mage/filter/predicate/ObjectSourcePlayer.java +++ b/Mage/src/main/java/mage/filter/predicate/ObjectSourcePlayer.java @@ -12,8 +12,8 @@ public class ObjectSourcePlayer extends ObjectPlayer { protected final UUID sourceId; - public ObjectSourcePlayer(T object, UUID sourceId, UUID playerId) { - super(object, playerId); + public ObjectSourcePlayer(T object, UUID sourceId, UUID sourceControllerId) { + super(object, sourceControllerId); this.sourceId = sourceId; } diff --git a/Mage/src/main/java/mage/game/GameImpl.java b/Mage/src/main/java/mage/game/GameImpl.java index c0fa488e20..a6eb99e141 100644 --- a/Mage/src/main/java/mage/game/GameImpl.java +++ b/Mage/src/main/java/mage/game/GameImpl.java @@ -1638,6 +1638,7 @@ public abstract class GameImpl implements Game, Serializable { if (newBluePrint == null) { newBluePrint = copyFromPermanent.copy(); newBluePrint.reset(this); + //getState().addCard(permanent); if (copyFromPermanent.isMorphed() || copyFromPermanent.isManifested()) { MorphAbility.setPermanentToFaceDownCreature(newBluePrint); @@ -1651,6 +1652,9 @@ public abstract class GameImpl implements Game, Serializable { applier.apply(this, newBluePrint, source, copyToPermanentId); } + // save original copy link (handle copy of copies too) + newBluePrint.setCopy(true, (copyFromPermanent.getCopyFrom() != null ? copyFromPermanent.getCopyFrom() : copyFromPermanent)); + CopyEffect newEffect = new CopyEffect(duration, newBluePrint, copyToPermanentId); newEffect.newId(); newEffect.setApplier(applier); diff --git a/Mage/src/main/java/mage/game/GameState.java b/Mage/src/main/java/mage/game/GameState.java index e18fc41b72..b98810d179 100644 --- a/Mage/src/main/java/mage/game/GameState.java +++ b/Mage/src/main/java/mage/game/GameState.java @@ -1,8 +1,5 @@ package mage.game; -import java.io.Serializable; -import java.util.*; -import java.util.stream.Collectors; import mage.MageObject; import mage.abilities.*; import mage.abilities.effects.ContinuousEffect; @@ -35,15 +32,17 @@ import mage.util.ThreadLocalStringBuilder; import mage.watchers.Watcher; import mage.watchers.Watchers; +import java.io.Serializable; +import java.util.*; +import java.util.stream.Collectors; + /** - * * @author BetaSteward_at_googlemail.com - * + *

* since at any time the game state may be copied and restored you cannot rely * on any object maintaining it's instance it then becomes necessary to only * refer to objects by their ids since these will always remain constant * throughout its lifetime - * */ public class GameState implements Serializable, Copyable { @@ -590,6 +589,7 @@ public class GameState implements Serializable, Copyable { // public void addMessage(String message) { // this.messages.add(message); // } + /** * Returns a list of all players of the game ignoring range or if a player * has lost or left the game. @@ -758,7 +758,7 @@ public class GameState implements Serializable, Copyable { } for (Map.Entry> entry : eventsByKey.entrySet()) { Set movedCards = new LinkedHashSet<>(); - for (Iterator it = entry.getValue().iterator(); it.hasNext();) { + for (Iterator it = entry.getValue().iterator(); it.hasNext(); ) { GameEvent event = it.next(); ZoneChangeEvent castEvent = (ZoneChangeEvent) event; UUID targetId = castEvent.getTargetId(); @@ -943,7 +943,7 @@ public class GameState implements Serializable, Copyable { /** * Other abilities are used to implement some special kind of continuous * effects that give abilities to non permanents. - * + *

* Crucible of Worlds - You may play land cards from your graveyard. Past in * Flames - Each instant and sorcery card in your graveyard gains flashback * until end of turn. The flashback cost is equal to its mana cost. Varolz, @@ -984,7 +984,7 @@ public class GameState implements Serializable, Copyable { * @param attachedTo * @param ability * @param copyAbility copies non MageSingleton abilities before adding to - * state + * state */ public void addOtherAbility(Card attachedTo, Ability ability, boolean copyAbility) { Ability newAbility; @@ -1134,7 +1134,7 @@ public class GameState implements Serializable, Copyable { Card copiedCard = cardToCopy.copy(); copiedCard.assignNewId(); copiedCard.setOwnerId(source.getControllerId()); - copiedCard.setCopy(true); + copiedCard.setCopy(true, cardToCopy); copiedCards.put(copiedCard.getId(), copiedCard); addCard(copiedCard); if (copiedCard.isSplitCard()) { diff --git a/Mage/src/main/java/mage/game/command/Commander.java b/Mage/src/main/java/mage/game/command/Commander.java index 966133e5dd..7163f8683e 100644 --- a/Mage/src/main/java/mage/game/command/Commander.java +++ b/Mage/src/main/java/mage/game/command/Commander.java @@ -1,10 +1,7 @@ - package mage.game.command; -import java.util.EnumSet; -import java.util.List; -import java.util.UUID; import mage.MageInt; +import mage.MageObject; import mage.ObjectColor; import mage.abilities.Abilities; import mage.abilities.AbilitiesImpl; @@ -24,9 +21,15 @@ import mage.game.events.ZoneChangeEvent; import mage.util.GameLog; import mage.util.SubTypeList; +import java.util.EnumSet; +import java.util.List; +import java.util.UUID; + public class Commander implements CommandObject { private final Card sourceObject; + private boolean copy; + private MageObject copyFrom; // copied card INFO (used to call original adjusters) private final Abilities abilities = new AbilitiesImpl<>(); public Commander(Card card) { @@ -40,8 +43,10 @@ public class Commander implements CommandObject { } } - private Commander(Commander copy) { - this.sourceObject = copy.sourceObject; + private Commander(final Commander commander) { + this.sourceObject = commander.sourceObject; + this.copy = commander.copy; + this.copyFrom = (commander.copyFrom != null ? commander.copyFrom.copy() : null); } @Override @@ -68,6 +73,22 @@ public class Commander implements CommandObject { return new Commander(this); } + @Override + public void setCopy(boolean isCopy, MageObject copyFrom) { + this.copy = isCopy; + this.copyFrom = (copyFrom != null ? copyFrom.copy() : null); + } + + @Override + public boolean isCopy() { + return this.copy; + } + + @Override + public MageObject getCopyFrom() { + return this.copyFrom; + } + @Override public String getName() { return sourceObject.getName(); @@ -170,15 +191,6 @@ public class Commander implements CommandObject { public void adjustTargets(Ability ability, Game game) { } - @Override - public void setCopy(boolean isCopy) { - } - - @Override - public boolean isCopy() { - return false; - } - @Override public UUID getId() { return sourceObject.getId(); diff --git a/Mage/src/main/java/mage/game/command/Emblem.java b/Mage/src/main/java/mage/game/command/Emblem.java index d95cfb659d..7a4a8896aa 100644 --- a/Mage/src/main/java/mage/game/command/Emblem.java +++ b/Mage/src/main/java/mage/game/command/Emblem.java @@ -1,8 +1,5 @@ package mage.game.command; -import java.util.EnumSet; -import java.util.List; -import java.util.UUID; import mage.MageInt; import mage.MageObject; import mage.ObjectColor; @@ -25,6 +22,10 @@ import mage.game.events.ZoneChangeEvent; import mage.util.GameLog; import mage.util.SubTypeList; +import java.util.EnumSet; +import java.util.List; +import java.util.UUID; + /** * @author nantuko */ @@ -38,6 +39,8 @@ public class Emblem implements CommandObject { private UUID id; private UUID controllerId; private MageObject sourceObject; + private boolean copy; + private MageObject copyFrom; // copied card INFO (used to call original adjusters) private FrameStyle frameStyle; private Abilities abilites = new AbilitiesImpl<>(); private String expansionSetCodeForImage = ""; @@ -52,6 +55,8 @@ public class Emblem implements CommandObject { this.frameStyle = emblem.frameStyle; this.controllerId = emblem.controllerId; this.sourceObject = emblem.sourceObject; + this.copy = emblem.copy; + this.copyFrom = (emblem.copyFrom != null ? emblem.copyFrom : null); this.abilites = emblem.abilites.copy(); this.expansionSetCodeForImage = emblem.expansionSetCodeForImage; } @@ -101,6 +106,22 @@ public class Emblem implements CommandObject { this.abilites.setControllerId(controllerId); } + @Override + public void setCopy(boolean isCopy, MageObject copyFrom) { + this.copy = isCopy; + this.copyFrom = (copyFrom != null ? copyFrom.copy() : null); + } + + @Override + public boolean isCopy() { + return this.copy; + } + + @Override + public MageObject getCopyFrom() { + return this.copyFrom; + } + @Override public String getName() { return name; @@ -204,15 +225,6 @@ public class Emblem implements CommandObject { return this.id; } - @Override - public void setCopy(boolean isCopy) { - } - - @Override - public boolean isCopy() { - return false; - } - @Override public Emblem copy() { return new Emblem(this); diff --git a/Mage/src/main/java/mage/game/command/Plane.java b/Mage/src/main/java/mage/game/command/Plane.java index ae34edade0..d05b0ceed3 100644 --- a/Mage/src/main/java/mage/game/command/Plane.java +++ b/Mage/src/main/java/mage/game/command/Plane.java @@ -41,6 +41,8 @@ public class Plane implements CommandObject { private UUID id; private UUID controllerId; private MageObject sourceObject; + private boolean copy; + private MageObject copyFrom; // copied card INFO (used to call original adjusters) private FrameStyle frameStyle; private Abilities abilites = new AbilitiesImpl<>(); private String expansionSetCodeForImage = ""; @@ -56,6 +58,8 @@ public class Plane implements CommandObject { this.frameStyle = plane.frameStyle; this.controllerId = plane.controllerId; this.sourceObject = plane.sourceObject; + this.copy = plane.copy; + this.copyFrom = (plane.copyFrom != null ? plane.copyFrom.copy() : null); this.abilites = plane.abilites.copy(); this.expansionSetCodeForImage = plane.expansionSetCodeForImage; } @@ -105,6 +109,22 @@ public class Plane implements CommandObject { this.abilites.setControllerId(controllerId); } + @Override + public void setCopy(boolean isCopy, MageObject copyFrom) { + this.copy = isCopy; + this.copyFrom = (copyFrom != null ? copyFrom.copy() : null); + } + + @Override + public boolean isCopy() { + return this.copy; + } + + @Override + public MageObject getCopyFrom() { + return this.copyFrom; + } + @Override public String getName() { return name; @@ -208,15 +228,6 @@ public class Plane implements CommandObject { return this.id; } - @Override - public void setCopy(boolean isCopy) { - } - - @Override - public boolean isCopy() { - return false; - } - @Override public Plane copy() { return new Plane(this); diff --git a/Mage/src/main/java/mage/game/stack/Spell.java b/Mage/src/main/java/mage/game/stack/Spell.java index 281a6485dd..bfc8fce81c 100644 --- a/Mage/src/main/java/mage/game/stack/Spell.java +++ b/Mage/src/main/java/mage/game/stack/Spell.java @@ -1,10 +1,5 @@ - package mage.game.stack; -import java.util.ArrayList; -import java.util.EnumSet; -import java.util.List; -import java.util.UUID; import mage.MageInt; import mage.MageObject; import mage.Mana; @@ -39,8 +34,12 @@ import mage.players.Player; import mage.util.GameLog; import mage.util.SubTypeList; +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.List; +import java.util.UUID; + /** - * * @author BetaSteward_at_googlemail.com */ public class Spell extends StackObjImpl implements Card { @@ -63,7 +62,8 @@ public class Spell extends StackObjImpl implements Card { private final UUID id; private UUID controllerId; - private boolean copiedSpell; + private boolean copy; + private MageObject copyFrom; // copied card INFO (used to call original adjusters) private boolean faceDown; private boolean countered; private boolean resolving = false; @@ -118,7 +118,8 @@ public class Spell extends StackObjImpl implements Card { this.frameStyle = spell.frameStyle; this.controllerId = spell.controllerId; - this.copiedSpell = spell.copiedSpell; + this.copy = spell.copy; + this.copyFrom = (spell.copyFrom != null ? spell.copyFrom.copy() : null); this.faceDown = spell.faceDown; this.countered = spell.countered; this.resolving = spell.resolving; @@ -155,7 +156,7 @@ public class Spell extends StackObjImpl implements Card { public String getActivatedMessage(Game game) { StringBuilder sb = new StringBuilder(); - if (isCopiedSpell()) { + if (isCopy()) { sb.append(" copies "); } else { sb.append(" casts "); @@ -362,7 +363,7 @@ public class Spell extends StackObjImpl implements Card { @Override public void counter(UUID sourceId, Game game, Zone zone, boolean owner, ZoneDetail zoneDetail) { this.countered = true; - if (!isCopiedSpell()) { + if (!isCopy()) { Player player = game.getPlayer(game.getControllerId(sourceId)); if (player == null) { player = game.getPlayer(getControllerId()); @@ -706,7 +707,7 @@ public class Spell extends StackObjImpl implements Card { newAbility.newId(); copy.addSpellAbility(newAbility); } - copy.setCopy(true); + copy.setCopy(true, this); copy.setControllerId(newController); return copy; } @@ -740,7 +741,7 @@ public class Spell extends StackObjImpl implements Card { // 706.10a If a copy of a spell is in a zone other than the stack, it ceases to exist. // If a copy of a card is in any zone other than the stack or the battlefield, it ceases to exist. // These are state-based actions. See rule 704. - if (this.isCopiedSpell() && zone != Zone.STACK) { + if (this.isCopy() && zone != Zone.STACK) { return true; } return card.moveToZone(zone, sourceId, game, flag, appliedEffects); @@ -753,7 +754,7 @@ public class Spell extends StackObjImpl implements Card { @Override public boolean moveToExile(UUID exileId, String name, UUID sourceId, Game game, List appliedEffects) { - if (this.isCopiedSpell()) { + if (this.isCopy()) { game.getStack().remove(this, game); return true; } @@ -835,26 +836,24 @@ public class Spell extends StackObjImpl implements Card { // do nothing } - public void setCopiedSpell(boolean isCopied) { - this.copiedSpell = isCopied; - } - - public boolean isCopiedSpell() { - return this.copiedSpell; - } - public Zone getFromZone() { return this.fromZone; } @Override - public void setCopy(boolean isCopy) { - setCopiedSpell(isCopy); + public void setCopy(boolean isCopy, MageObject copyFrom) { + this.copy = isCopy; + this.copyFrom = (copyFrom != null ? copyFrom.copy() : null); } @Override public boolean isCopy() { - return isCopiedSpell(); + return this.copy; + } + + @Override + public MageObject getCopyFrom() { + return this.copyFrom; } @Override diff --git a/Mage/src/main/java/mage/game/stack/StackAbility.java b/Mage/src/main/java/mage/game/stack/StackAbility.java index 02546c99c2..2fcadf1147 100644 --- a/Mage/src/main/java/mage/game/stack/StackAbility.java +++ b/Mage/src/main/java/mage/game/stack/StackAbility.java @@ -1,9 +1,5 @@ package mage.game.stack; -import java.util.ArrayList; -import java.util.EnumSet; -import java.util.List; -import java.util.UUID; import mage.MageInt; import mage.MageObject; import mage.ObjectColor; @@ -32,8 +28,12 @@ import mage.util.GameLog; import mage.util.SubTypeList; import mage.watchers.Watcher; +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.List; +import java.util.UUID; + /** - * * @author BetaSteward_at_googlemail.com */ public class StackAbility extends StackObjImpl implements Ability { @@ -47,6 +47,8 @@ public class StackAbility extends StackObjImpl implements Ability { private final Ability ability; private UUID controllerId; + private boolean copy; + private MageObject copyFrom; // copied card INFO (used to call original adjusters) private String name; private String expansionSetCode; private TargetAdjuster targetAdjuster = null; @@ -60,6 +62,8 @@ public class StackAbility extends StackObjImpl implements Ability { public StackAbility(final StackAbility stackAbility) { this.ability = stackAbility.ability.copy(); this.controllerId = stackAbility.controllerId; + this.copy = stackAbility.copy; + this.copyFrom = (stackAbility.copyFrom != null ? stackAbility.copyFrom.copy() : null); this.name = stackAbility.name; this.expansionSetCode = stackAbility.expansionSetCode; this.targetAdjuster = stackAbility.targetAdjuster; @@ -104,6 +108,22 @@ public class StackAbility extends StackObjImpl implements Ability { } } + @Override + public void setCopy(boolean isCopy, MageObject copyFrom) { + this.copy = isCopy; + this.copyFrom = (copyFrom != null ? copyFrom.copy() : null); + } + + @Override + public boolean isCopy() { + return this.copy; + } + + @Override + public MageObject getCopyFrom() { + return this.copyFrom; + } + @Override public String getName() { return name; @@ -408,15 +428,6 @@ public class StackAbility extends StackObjImpl implements Ability { throw new UnsupportedOperationException("Not supported."); } - @Override - public void setCopy(boolean isCopy) { - } - - @Override - public boolean isCopy() { - return false; - } - @Override public boolean getRuleAtTheTop() { return this.ability.getRuleAtTheTop(); diff --git a/Mage/src/main/java/mage/players/PlayerImpl.java b/Mage/src/main/java/mage/players/PlayerImpl.java index ec355521d9..cfb0f1ac70 100644 --- a/Mage/src/main/java/mage/players/PlayerImpl.java +++ b/Mage/src/main/java/mage/players/PlayerImpl.java @@ -1,9 +1,5 @@ package mage.players; -import java.io.Serializable; -import java.text.SimpleDateFormat; -import java.util.*; -import java.util.Map.Entry; import mage.ConditionalMana; import mage.MageObject; import mage.MageObjectReference; @@ -72,6 +68,11 @@ import mage.util.GameLog; import mage.util.RandomUtil; import org.apache.log4j.Logger; +import java.io.Serializable; +import java.text.SimpleDateFormat; +import java.util.*; +import java.util.Map.Entry; + public abstract class PlayerImpl implements Player, Serializable { private static final Logger logger = Logger.getLogger(PlayerImpl.class); @@ -2573,7 +2574,7 @@ public abstract class PlayerImpl implements Player, Serializable { /** * @param game * @param appliedEffects - * @param numSides Number of sides the dice has + * @param numSides Number of sides the dice has * @return the number that the player rolled */ @Override @@ -2607,10 +2608,10 @@ public abstract class PlayerImpl implements Player, Serializable { /** * @param game * @param appliedEffects - * @param numberChaosSides The number of chaos sides the planar die - * currently has (normally 1 but can be 5) + * @param numberChaosSides The number of chaos sides the planar die + * currently has (normally 1 but can be 5) * @param numberPlanarSides The number of chaos sides the planar die - * currently has (normally 1) + * currently has (normally 1) * @return the outcome that the player rolled. Either ChaosRoll, PlanarRoll * or NilRoll */ @@ -2767,7 +2768,7 @@ public abstract class PlayerImpl implements Player, Serializable { /** * @param ability - * @param available if null, it won't be checked if enough mana is available + * @param available if null, it won't be checked if enough mana is available * @param sourceObject * @param game * @return @@ -3319,7 +3320,7 @@ public abstract class PlayerImpl implements Player, Serializable { @Override public boolean canPaySacrificeCost(Permanent permanent, UUID sourceId, - UUID controllerId, Game game + UUID controllerId, Game game ) { return sacrificeCostFilter == null || !sacrificeCostFilter.match(permanent, sourceId, controllerId, game); } @@ -3467,8 +3468,8 @@ public abstract class PlayerImpl implements Player, Serializable { @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 ) { Set cardList = new HashSet<>(); if (card != null) { @@ -3479,22 +3480,22 @@ public abstract class PlayerImpl implements Player, Serializable { @Override public boolean moveCards(Cards cards, Zone toZone, - Ability source, Game game + Ability source, Game game ) { return moveCards(cards.getCards(game), toZone, source, game); } @Override public boolean moveCards(Set cards, Zone toZone, - Ability source, Game game + Ability source, Game game ) { return moveCards(cards, toZone, source, game, false, false, false, null); } @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 ) { if (cards.isEmpty()) { return true; @@ -3580,8 +3581,8 @@ public abstract class PlayerImpl implements Player, Serializable { @Override public boolean moveCardsToExile(Card card, Ability source, - Game game, boolean withName, UUID exileId, - String exileZoneName + Game game, boolean withName, UUID exileId, + String exileZoneName ) { Set cards = new HashSet<>(); cards.add(card); @@ -3590,8 +3591,8 @@ public abstract class PlayerImpl implements Player, Serializable { @Override public boolean moveCardsToExile(Set cards, Ability source, - Game game, boolean withName, UUID exileId, - String exileZoneName + Game game, boolean withName, UUID exileId, + String exileZoneName ) { if (cards.isEmpty()) { return true; @@ -3606,14 +3607,14 @@ public abstract class PlayerImpl implements Player, Serializable { @Override public boolean moveCardToHandWithInfo(Card card, UUID sourceId, - Game game + Game game ) { return this.moveCardToHandWithInfo(card, sourceId, game, true); } @Override public boolean moveCardToHandWithInfo(Card card, UUID sourceId, - Game game, boolean withName + Game game, boolean withName ) { boolean result = false; Zone fromZone = game.getState().getZone(card.getId()); @@ -3638,7 +3639,7 @@ public abstract class PlayerImpl implements Player, Serializable { @Override public Set moveCardsToGraveyardWithInfo(Set allCards, Ability source, - Game game, Zone fromZone + Game game, Zone fromZone ) { UUID sourceId = source == null ? null : source.getSourceId(); Set movedCards = new LinkedHashSet<>(); @@ -3646,7 +3647,7 @@ public abstract class PlayerImpl implements Player, Serializable { // identify cards from one owner Cards cards = new CardsImpl(); UUID ownerId = null; - for (Iterator it = allCards.iterator(); it.hasNext();) { + for (Iterator it = allCards.iterator(); it.hasNext(); ) { Card card = it.next(); if (cards.isEmpty()) { ownerId = card.getOwnerId(); @@ -3707,7 +3708,7 @@ public abstract class PlayerImpl implements Player, Serializable { @Override public boolean moveCardToGraveyardWithInfo(Card card, UUID sourceId, - Game game, Zone fromZone + Game game, Zone fromZone ) { if (card == null) { return false; @@ -3736,8 +3737,8 @@ public abstract class PlayerImpl implements Player, Serializable { @Override public boolean moveCardToLibraryWithInfo(Card card, UUID sourceId, - Game game, Zone fromZone, - boolean toTop, boolean withName + Game game, Zone fromZone, + boolean toTop, boolean withName ) { if (card == null) { return false; @@ -3771,7 +3772,7 @@ public abstract class PlayerImpl implements Player, Serializable { @Override public boolean moveCardToExileWithInfo(Card card, UUID exileId, String exileName, UUID sourceId, - Game game, Zone fromZone, boolean withName) { + Game game, Zone fromZone, boolean withName) { if (card == null) { return false; } @@ -3786,7 +3787,7 @@ public abstract class PlayerImpl implements Player, Serializable { } } else if (card instanceof Spell) { final Spell spell = (Spell) card; - if (spell.isCopiedSpell()) { + if (spell.isCopy()) { // Copied spell, only remove from stack game.getStack().remove(spell, game); } From bb18814c8473ce6378eb4d79c2ead1d89c4da79d Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Thu, 29 Nov 2018 19:39:13 +0400 Subject: [PATCH 27/42] * Fixed wrong re-targeting on copy complex cards (fixed adjustTargets for copies) * Oath Of Lieges - Fixed that copy of opponent's card don't work; * Oath Of Lieges - Fixed that it can shuffle lib without search; --- Mage.Sets/src/mage/cards/o/OathOfLieges.java | 44 ++- .../cards/enchantments/OathOfLiegesTest.java | 255 +++++++++++++----- .../mage/game/permanent/PermanentCard.java | 10 +- 3 files changed, 225 insertions(+), 84 deletions(-) diff --git a/Mage.Sets/src/mage/cards/o/OathOfLieges.java b/Mage.Sets/src/mage/cards/o/OathOfLieges.java index be8afeaef3..9fc5d05336 100644 --- a/Mage.Sets/src/mage/cards/o/OathOfLieges.java +++ b/Mage.Sets/src/mage/cards/o/OathOfLieges.java @@ -1,4 +1,3 @@ - package mage.cards.o; import mage.abilities.Ability; @@ -18,6 +17,7 @@ import mage.filter.predicate.ObjectSourcePlayer; import mage.filter.predicate.ObjectSourcePlayerPredicate; import mage.game.Game; import mage.players.Player; +import mage.target.Target; import mage.target.TargetPlayer; import mage.target.common.TargetCardInLibrary; import mage.target.targetpointer.FixedTarget; @@ -25,12 +25,10 @@ import mage.target.targetpointer.FixedTarget; import java.util.UUID; /** - * * @author emerald000 */ public final class OathOfLieges extends CardImpl { - private final UUID originalId; private static final FilterPlayer FILTER = new FilterPlayer("player who controls more lands than you do and is your opponent"); static { @@ -43,26 +41,19 @@ public final class OathOfLieges extends CardImpl { // At the beginning of each player's upkeep, that player chooses target player who controls more lands than he or she does and is their opponent. The first player may search their library for a basic land card, put that card onto the battlefield, then shuffle their library. Ability ability = new BeginningOfUpkeepTriggeredAbility(new OathOfLiegesEffect(), TargetController.ANY, false); ability.addTarget(new TargetPlayer(1, 1, false, FILTER)); - originalId = ability.getOriginalId(); this.addAbility(ability); } @Override public void adjustTargets(Ability ability, Game game) { - if (ability.getOriginalId().equals(originalId)) { - Player activePlayer = game.getPlayer(game.getActivePlayerId()); - if (activePlayer != null) { - ability.getTargets().clear(); - TargetPlayer target = new TargetPlayer(1, 1, false, FILTER); - target.setTargetController(activePlayer.getId()); - ability.getTargets().add(target); - } + // target controller must be active player (even for copies) + for (Target target : ability.getTargets()) { + target.setTargetController(game.getActivePlayerId()); } } public OathOfLieges(final OathOfLieges card) { super(card); - this.originalId = card.originalId; } @Override @@ -93,7 +84,7 @@ class OathOfLiegesEffect extends OneShotEffect { Player activePlayer = game.getPlayer(game.getActivePlayerId()); if (activePlayer != null) { if (activePlayer.chooseUse(outcome, "Search your library for a basic land card, put that card onto the battlefield, then shuffle your library?", source, game)) { - Effect effect = new SearchLibraryPutInPlayTargetPlayerEffect(new TargetCardInLibrary(StaticFilters.FILTER_CARD_BASIC_LAND), false, true, Outcome.PutLandInPlay, true); + Effect effect = new SearchLibraryPutInPlayTargetPlayerEffect(new TargetCardInLibrary(StaticFilters.FILTER_CARD_BASIC_LAND), false, false, Outcome.PutLandInPlay, true); effect.setTargetPointer(new FixedTarget(game.getActivePlayerId())); return effect.apply(game, source); } @@ -110,18 +101,25 @@ class OathOfLiegesPredicate implements ObjectSourcePlayerPredicate input, Game game) { + // input.getPlayerId() -- source controller id + // input.getObject() -- checking player + Player targetPlayer = input.getObject(); //Get active input.playerId because adjust target is used after canTarget function - UUID activePlayerId = game.getActivePlayerId(); - if (targetPlayer == null || activePlayerId == null) { - return false; - } - if (!targetPlayer.hasOpponent(activePlayerId, game)) { - return false; - } - int countTargetPlayer = game.getBattlefield().countAll(FILTER, targetPlayer.getId(), game); - int countActivePlayer = game.getBattlefield().countAll(FILTER, activePlayerId, game); + Player activePlayer = game.getPlayer(game.getActivePlayerId()); + if (targetPlayer == null || activePlayer == null) { + return false; + } + + // must be opponent + if (!activePlayer.hasOpponent(targetPlayer.getId(), game)) { + return false; + } + + // must have more lands than active player + int countTargetPlayer = game.getBattlefield().countAll(FILTER, targetPlayer.getId(), game); + int countActivePlayer = game.getBattlefield().countAll(FILTER, activePlayer.getId(), game); return countTargetPlayer > countActivePlayer; } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/enchantments/OathOfLiegesTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/enchantments/OathOfLiegesTest.java index 2334af2b07..6db1d8e572 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/enchantments/OathOfLiegesTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/enchantments/OathOfLiegesTest.java @@ -1,4 +1,3 @@ - package org.mage.test.cards.enchantments; import mage.constants.PhaseStep; @@ -7,93 +6,229 @@ import org.junit.Test; import org.mage.test.serverside.base.CardTestPlayerBase; /** - * - * @author LevelX2 + * @author LevelX2, JayDi85 */ public class OathOfLiegesTest extends CardTestPlayerBase { + //addCard(Zone.BATTLEFIELD, playerA, "Hypersonic Dragon", 1); // can cast spells at any time + //addCard(Zone.HAND, playerA, "Breath of Life", 1); // {3}{W} // return creatures + //addCard(Zone.HAND, playerA, "Replenish", 1); // {3}{W} // return all enchantments + @Test - public void testSearchLandOwner() { - addCard(Zone.BATTLEFIELD, playerA, "Plains", 2); - // At the beginning of each player's upkeep, that player chooses target player who controls more lands than he or she does and is their opponent. - // The first player may search their library for a basic land card, put that card onto the battlefield, then shuffle their library. - addCard(Zone.HAND, playerA, "Oath of Lieges", 1); // {1}{W} - addCard(Zone.LIBRARY, playerA, "Plains", 1); + public void testOath_OwnCardTriggersOnOwnTurn() { + // A + addCard(Zone.BATTLEFIELD, playerA, "Plains", 4); + addCard(Zone.LIBRARY, playerA, "Plains", 5); + addCard(Zone.BATTLEFIELD, playerA, "Oath of Lieges", 1); // {1}{W} + // B + addCard(Zone.BATTLEFIELD, playerB, "Island", 5); - addCard(Zone.BATTLEFIELD, playerB, "Plains", 3); + // turn 1 - A + // oath A triggers for A and activates + addTarget(playerA, playerB); // who control more lands + setChoice(playerA, "Yes"); // search library + addTarget(playerA, "Plains"); // card from library - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Oath of Lieges"); - addTarget(playerA, playerB); - addTarget(playerA, "Plains"); - - setStopAt(3, PhaseStep.PRECOMBAT_MAIN); + setStopAt(1, PhaseStep.END_TURN); execute(); + assertAllCommandsUsed(); - assertPermanentCount(playerA, "Oath of Lieges", 1); - assertPermanentCount(playerA, "Plains", 3); - + assertPermanentCount(playerA, "Plains", 4 + 1); + assertPermanentCount(playerB, "Island", 5); } @Test - public void testSearchLandOpponent() { - addCard(Zone.BATTLEFIELD, playerA, "Plains", 2); - // At the beginning of each player's upkeep, that player chooses target player who controls more lands than he or she does and is their opponent. - // The first player may search their library for a basic land card, put that card onto the battlefield, then shuffle their library. - addCard(Zone.HAND, playerA, "Oath of Lieges", 1); // {1}{W} + public void testOath_OwnCardTriggersOnOpponentTurn() { + // A + addCard(Zone.BATTLEFIELD, playerA, "Plains", 5); + addCard(Zone.LIBRARY, playerA, "Plains", 5); + addCard(Zone.BATTLEFIELD, playerA, "Hypersonic Dragon", 1); // can cast spells at any time + addCard(Zone.GRAVEYARD, playerA, "Oath of Lieges", 1); // {1}{W} + addCard(Zone.HAND, playerA, "Replenish", 1); // {3}{W} // return all enchantments + // B + addCard(Zone.BATTLEFIELD, playerB, "Plains", 4); + addCard(Zone.LIBRARY, playerB, "Plains", 5); - addCard(Zone.BATTLEFIELD, playerB, "Plains", 1); - addCard(Zone.LIBRARY, playerB, "Plains", 1); + // turn 1 - A (play oath from grave) + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Replenish"); + checkPermanentCount("A have oath", 1, PhaseStep.END_TURN, playerA, "Oath of Lieges", 1); + checkPermanentCount("A have 5 plains", 1, PhaseStep.END_TURN, playerA, "Plains", 5); + checkPermanentCount("B have 4 plains", 1, PhaseStep.END_TURN, playerB, "Plains", 4); - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Oath of Lieges"); - addTarget(playerB, playerA); - addTarget(playerB, "Plains"); + // turn 2 - B + // oath A triggers for B and activates + addTarget(playerB, playerA); // who control more lands + setChoice(playerB, "Yes"); // search library + addTarget(playerB, "Plains"); // card from library - setStopAt(2, PhaseStep.PRECOMBAT_MAIN); + setStopAt(2, PhaseStep.END_TURN); execute(); + assertAllCommandsUsed(); - assertPermanentCount(playerA, "Oath of Lieges", 1); - assertPermanentCount(playerA, "Plains", 2); - assertPermanentCount(playerB, "Plains", 2); + assertPermanentCount(playerA, "Plains", 5); + assertPermanentCount(playerB, "Plains", 4 + 1); } @Test - public void testSearchLandOwnerCopy() { - addCard(Zone.BATTLEFIELD, playerA, "Plains", 2); - // At the beginning of each player's upkeep, that player chooses target player who controls more lands than he or she does and is their opponent. - // The first player may search their library for a basic land card, put that card onto the battlefield, then shuffle their library. - addCard(Zone.HAND, playerA, "Oath of Lieges", 1); // {1}{W} - addCard(Zone.LIBRARY, playerA, "Plains", 3); - addCard(Zone.HAND, playerA, "Plains", 1); + public void testOath_OpponentCardTriggersOnOwnTurn() { + // A + addCard(Zone.BATTLEFIELD, playerA, "Plains", 4); + addCard(Zone.LIBRARY, playerA, "Plains", 5); + // B + addCard(Zone.LIBRARY, playerB, "Plains", 5); + addCard(Zone.BATTLEFIELD, playerB, "Plains", 5); + addCard(Zone.BATTLEFIELD, playerB, "Oath of Lieges", 1); // {1}{W} + // turn 1 - A + // oath B triggers for A and activates + addTarget(playerA, playerB); // who control more lands + setChoice(playerA, "Yes"); // search library + addTarget(playerA, "Plains"); // card from library + + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + assertPermanentCount(playerA, "Plains", 4 + 1); + assertPermanentCount(playerB, "Plains", 5); + } + + @Test + public void testOath_DoubleOath() { + // A + addCard(Zone.BATTLEFIELD, playerA, "Plains", 3); + addCard(Zone.LIBRARY, playerA, "Plains", 5); + addCard(Zone.BATTLEFIELD, playerA, "Oath of Lieges", 2); // {1}{W} + // B + addCard(Zone.BATTLEFIELD, playerB, "Plains", 5); + + // turn 1 - A + // oath A triggers for A and activates + // oath B triggers for A and activates + // 1 + addTarget(playerA, playerB); // who control more lands + setChoice(playerA, "Yes"); // search library + addTarget(playerA, "Plains"); // card from library + // 2 + addTarget(playerA, playerB); // who control more lands + setChoice(playerA, "Yes"); // search library + addTarget(playerA, "Plains"); // card from library + + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + assertPermanentCount(playerA, "Plains", 3 + 2); + assertPermanentCount(playerB, "Plains", 5); + } + + @Test + public void testOath_OwnNormalAndOwnCopy() { + // A + addCard(Zone.BATTLEFIELD, playerA, "Plains", 10); + addCard(Zone.BATTLEFIELD, playerA, "Island", 3); // for copy + addCard(Zone.LIBRARY, playerA, "Plains", 5); + addCard(Zone.BATTLEFIELD, playerA, "Hypersonic Dragon", 1); // can cast spells at any time + addCard(Zone.GRAVEYARD, playerA, "Oath of Lieges", 1); // {1}{W} + addCard(Zone.HAND, playerA, "Replenish", 1); // {3}{W} // return all enchantments + addCard(Zone.HAND, playerA, "Copy Enchantment", 1); // {2}{U} // copy target + // B + addCard(Zone.BATTLEFIELD, playerB, "Plains", 12); addCard(Zone.BATTLEFIELD, playerB, "Island", 3); - addCard(Zone.HAND, playerB, "Copy Enchantment", 1); // {2}{U} - addCard(Zone.LIBRARY, playerB, "Plains", 3); + addCard(Zone.LIBRARY, playerB, "Plains", 5); - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Oath of Lieges"); + // turn 1 - A + // cast oath A + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Replenish"); + showBattlefield("A perms", 1, PhaseStep.POSTCOMBAT_MAIN, playerA); + // cast oath copy + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Copy Enchantment"); + setChoice(playerA, "Yes"); // use copy effect + setChoice(playerA, "Oath of Lieges"); // target for copy + checkPermanentCount("A have 2 oath", 1, PhaseStep.END_TURN, playerA, "Oath of Lieges", 2); + checkPermanentCount("A have 10 plains", 1, PhaseStep.END_TURN, playerA, "Plains", 10); + checkPermanentCount("B have 12 plains", 1, PhaseStep.END_TURN, playerB, "Plains", 12); - castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Copy Enchantment"); - setChoice(playerB, "Oath of Lieges"); + // turn 2 - B + // oath A triggers for B and do nothing + // copy oath A triggers for B and do nothing + checkPermanentCount("A have 10 plains", 1, PhaseStep.END_TURN, playerA, "Plains", 10); + checkPermanentCount("B have 12 plains", 1, PhaseStep.END_TURN, playerB, "Plains", 12); - // turn 3 - addTarget(playerA, playerB); - addTarget(playerA, "Plains"); // 3rd land - addTarget(playerA, "Plains"); // second trigger will fail because target player has no longer more lands than controller + // turn 3 - A + // oath A triggers for A and activates + // copy oath A triggers for A and activates + // 1 + addTarget(playerA, playerB); // who control more lands + setChoice(playerA, "Yes"); // search library + addTarget(playerA, "Plains"); // card from library + // 2 + addTarget(playerA, playerB); // who control more lands + setChoice(playerA, "Yes"); // search library + addTarget(playerA, "Plains"); // card from library - playLand(3, PhaseStep.PRECOMBAT_MAIN, playerA, "Plains"); // 4th land - - // turn 4 - addTarget(playerB, playerA); - addTarget(playerB, "Plains"); - addTarget(playerB, "Plains"); // second trigger will fail because target player has no longer more lands than controller - - setStopAt(4, PhaseStep.PRECOMBAT_MAIN); + setStopAt(3, PhaseStep.END_TURN); execute(); + assertAllCommandsUsed(); - assertPermanentCount(playerB, "Oath of Lieges", 1); - assertPermanentCount(playerA, "Oath of Lieges", 1); + assertPermanentCount(playerA, "Plains", 10 + 2); + assertPermanentCount(playerB, "Plains", 12); + } - assertPermanentCount(playerB, "Plains", 1); - assertPermanentCount(playerA, "Plains", 4); + @Test + public void testOath_OwnNormalAndOpponentCopy() { + // special test to check targetadjusters (copy card must used target adjuster from original card, not from copied) + // A + addCard(Zone.BATTLEFIELD, playerA, "Plains", 10); + addCard(Zone.BATTLEFIELD, playerA, "Island", 3); + addCard(Zone.LIBRARY, playerA, "Plains", 5); + addCard(Zone.GRAVEYARD, playerA, "Oath of Lieges", 1); // {1}{W} + addCard(Zone.HAND, playerA, "Replenish", 1); // {3}{W} // return all enchantments + addCard(Zone.BATTLEFIELD, playerA, "Hypersonic Dragon", 1); // can cast spells at any time + // B + addCard(Zone.BATTLEFIELD, playerB, "Plains", 12); + addCard(Zone.BATTLEFIELD, playerB, "Island", 3); // for copy + addCard(Zone.LIBRARY, playerB, "Plains", 5); + addCard(Zone.HAND, playerB, "Copy Enchantment", 1); // {2}{U} // copy target + + // turn 1 - A + // nothing + + // turn 2 - B + // cast oath A + castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerA, "Replenish"); + // cast oath copy by opponent + showBattlefield("A perms", 2, PhaseStep.POSTCOMBAT_MAIN, playerA); + showBattlefield("B perms", 2, PhaseStep.POSTCOMBAT_MAIN, playerB); + showAvaileableAbilities("B abils", 2, PhaseStep.POSTCOMBAT_MAIN, playerB); + showHand("B hand", 2, PhaseStep.POSTCOMBAT_MAIN, playerB); + castSpell(2, PhaseStep.POSTCOMBAT_MAIN, playerB, "Copy Enchantment"); + setChoice(playerB, "Yes"); // use copy effect + setChoice(playerB, "Oath of Lieges"); // target for copy + checkPermanentCount("A have 1 oath", 2, PhaseStep.END_TURN, playerA, "Oath of Lieges", 1); + checkPermanentCount("B have 1 oath", 2, PhaseStep.END_TURN, playerA, "Oath of Lieges", 1); + checkPermanentCount("A have 10 plains", 2, PhaseStep.END_TURN, playerA, "Plains", 10); + checkPermanentCount("B have 12 plains", 2, PhaseStep.END_TURN, playerB, "Plains", 12); + showLibrary("lib B", 2, PhaseStep.END_TURN, playerB); + + // turn 3 - A + // oath A triggers for A and activates + // copy oath B triggers for A and activates + // 1 + addTarget(playerA, playerB); // who control more lands + setChoice(playerA, "Yes"); // search library + addTarget(playerA, "Plains"); // card from library + // 2 + addTarget(playerA, playerB); // who control more lands + setChoice(playerA, "Yes"); // search library + addTarget(playerA, "Plains"); // card from library + + setStopAt(3, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + assertPermanentCount(playerA, "Plains", 10 + 2); + assertPermanentCount(playerB, "Plains", 12); } } diff --git a/Mage/src/main/java/mage/game/permanent/PermanentCard.java b/Mage/src/main/java/mage/game/permanent/PermanentCard.java index eef8105555..f6436ee451 100644 --- a/Mage/src/main/java/mage/game/permanent/PermanentCard.java +++ b/Mage/src/main/java/mage/game/permanent/PermanentCard.java @@ -21,6 +21,8 @@ public class PermanentCard extends PermanentImpl { protected int maxLevelCounters; // A copy of the origin card that was cast (this is not the original card, so it's possible to chnage some attribute to this blueprint to change attributes to the permanent if it enters the battlefield with e.g. a subtype) protected Card card; + // A copy of original card that was used for copy and create current permanent (used in copy effects and special commands like adjustTargets) + protected Card copiedFromCard; // the number this permanent instance had protected int zoneChangeCounter; @@ -153,7 +155,13 @@ public class PermanentCard extends PermanentImpl { if (this.isTransformed() && card.getSecondCardFace() != null) { card.getSecondCardFace().adjustTargets(ability, game); } else { - card.adjustTargets(ability, game); + if (this.isCopy()) { + // if COPIED card have adjuster then it's must be called instead own -- see OathOfLieges tests + // raise null error on wrong copy + this.getCopyFrom().adjustTargets(ability, game); + } else { + card.adjustTargets(ability, game); + } } } From 6e64e08bd25618a8092812e53724762c0bb6232c Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Thu, 29 Nov 2018 19:40:47 +0400 Subject: [PATCH 28/42] * Pattern Of Rebirth - Fixed that it can shuffle lib without search; --- Mage.Sets/src/mage/cards/p/PatternOfRebirth.java | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/Mage.Sets/src/mage/cards/p/PatternOfRebirth.java b/Mage.Sets/src/mage/cards/p/PatternOfRebirth.java index a9cc52feb6..8184f1d060 100644 --- a/Mage.Sets/src/mage/cards/p/PatternOfRebirth.java +++ b/Mage.Sets/src/mage/cards/p/PatternOfRebirth.java @@ -1,7 +1,5 @@ - package mage.cards.p; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.common.DiesAttachedTriggeredAbility; import mage.abilities.effects.Effect; @@ -11,22 +9,23 @@ import mage.abilities.keyword.EnchantAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.SubType; import mage.constants.Outcome; import mage.constants.SetTargetPointer; +import mage.constants.SubType; import mage.filter.common.FilterCreatureCard; import mage.target.TargetPermanent; import mage.target.common.TargetCardInLibrary; import mage.target.common.TargetCreaturePermanent; +import java.util.UUID; + /** - * * @author LevelX2 */ public final class PatternOfRebirth extends CardImpl { public PatternOfRebirth(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.ENCHANTMENT},"{3}{G}"); + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{3}{G}"); this.subtype.add(SubType.AURA); // Enchant creature @@ -37,7 +36,7 @@ public final class PatternOfRebirth extends CardImpl { this.addAbility(ability); // When enchanted creature dies, that creature's controller may search their library for a creature card and put that card onto the battlefield. If that player does, he or she shuffles their library. - Effect effect = new SearchLibraryPutInPlayTargetPlayerEffect(new TargetCardInLibrary(new FilterCreatureCard()), false, true, Outcome.PutCreatureInPlay); + Effect effect = new SearchLibraryPutInPlayTargetPlayerEffect(new TargetCardInLibrary(new FilterCreatureCard()), false, false, Outcome.PutCreatureInPlay); effect.setText("that creature's controller may search their library for a creature card and put that card onto the battlefield. If that player does, he or she shuffles their library"); this.addAbility(new DiesAttachedTriggeredAbility(effect, "enchanted creature", true, true, SetTargetPointer.ATTACHED_TO_CONTROLLER)); From 8700da94d76d3523b9ae9a4d37a775289271909f Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Thu, 29 Nov 2018 19:47:51 +0400 Subject: [PATCH 29/42] * Added damage info on re-targeting targets (on copy); --- .../java/mage/game/stack/StackObjImpl.java | 54 ++++++++++--------- 1 file changed, 28 insertions(+), 26 deletions(-) diff --git a/Mage/src/main/java/mage/game/stack/StackObjImpl.java b/Mage/src/main/java/mage/game/stack/StackObjImpl.java index 12d49f5749..beadf908f5 100644 --- a/Mage/src/main/java/mage/game/stack/StackObjImpl.java +++ b/Mage/src/main/java/mage/game/stack/StackObjImpl.java @@ -1,12 +1,5 @@ -/* - * 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 mage.game.stack; -import java.util.Set; -import java.util.UUID; import mage.MageObject; import mage.abilities.Abilities; import mage.abilities.AbilitiesImpl; @@ -21,8 +14,10 @@ import mage.players.Player; import mage.target.Target; import mage.target.TargetAmount; +import java.util.Set; +import java.util.UUID; + /** - * * @author LevelX2 */ public abstract class StackObjImpl implements StackObject { @@ -44,47 +39,47 @@ public abstract class StackObjImpl implements StackObject { * 114.6. Some effects allow a player to change the target(s) of a spell or * ability, and other effects allow a player to choose new targets for a * spell or ability. - * + *

* 114.6a If an effect allows a player to "change the target(s)" of a spell * or ability, each target can be changed only to another legal target. If a * target can't be changed to another legal target, the original target is * unchanged, even if the original target is itself illegal by then. If all * the targets aren't changed to other legal targets, none of them are * changed. - * + *

* 114.6b If an effect allows a player to "change a target" of a spell or * ability, the process described in rule 114.6a is followed, except that * only one of those targets may be changed (rather than all of them or none * of them). - * + *

* 114.6c If an effect allows a player to "change any targets" of a spell or * ability, the process described in rule 114.6a is followed, except that * any number of those targets may be changed (rather than all of them or * none of them). - * + *

* 114.6d If an effect allows a player to "choose new targets" for a spell * or ability, the player may leave any number of the targets unchanged, * even if those targets would be illegal. If the player chooses to change * some or all of the targets, the new targets must be legal and must not * cause any unchanged targets to become illegal. - * + *

* 114.6e When changing targets or choosing new targets for a spell or * ability, only the final set of targets is evaluated to determine whether * the change is legal. - * + *

* Example: Arc Trail is a sorcery that reads "Arc Trail deals 2 damage to * any target and 1 damage to another target creature or player." The * current targets of Arc Trail are Runeclaw Bear and Llanowar Elves, in * that order. You cast Redirect, an instant that reads "You may choose new * targets for target spell," targeting Arc Trail. You can change the first * target to Llanowar Elves and change the second target to Runeclaw Bear. - * + *

* 114.7. Modal spells and abilities may have different targeting * requirements for each mode. An effect that allows a player to change the * target(s) of a modal spell or ability, or to choose new targets for a * modal spell or ability, doesn't allow that player to change its mode. * (See rule 700.2.) - * + *

* 706.10c Some effects copy a spell or ability and state that its * controller may choose new targets for the copy. The player may leave any * number of the targets unchanged, even if those targets would be illegal. @@ -94,13 +89,13 @@ public abstract class StackObjImpl implements StackObject { * * @param game * @param targetControllerId - player that can/has to change the target of - * the spell - * @param forceChange - does only work for targets with maximum of one - * targetId - * @param onlyOneTarget - 114.6b one target must be changed to another - * target - * @param filterNewTarget restriction for the new target, if null nothing is - * cheched + * the spell + * @param forceChange - does only work for targets with maximum of one + * targetId + * @param onlyOneTarget - 114.6b one target must be changed to another + * target + * @param filterNewTarget restriction for the new target, if null nothing is + * cheched * @return */ @Override @@ -163,10 +158,15 @@ public abstract class StackObjImpl implements StackObject { newTarget.clearChosen(); for (UUID targetId : target.getTargets()) { String targetNames = getNamesOftargets(targetId, game); + String targetAmount = ""; + if (target.getTargetAmount(targetId) > 0) { + targetAmount = " (amount: " + target.getTargetAmount(targetId) + ")"; + } // change the target? Outcome outcome = mode.getEffects().isEmpty() ? Outcome.Detriment : mode.getEffects().get(0).getOutcome(); + if (targetNames != null - && (forceChange || targetController.chooseUse(outcome, "Change this target: " + targetNames + '?', ability, game))) { + && (forceChange || targetController.chooseUse(outcome, "Change this target: " + targetNames + targetAmount + '?', ability, game))) { Set possibleTargets = target.possibleTargets(this.getSourceId(), getControllerId(), game); // choose exactly one other target - already targeted objects are not counted if (forceChange && possibleTargets != null && possibleTargets.size() > 1) { // controller of spell must be used (e.g. TargetOpponent) @@ -179,7 +179,8 @@ public abstract class StackObjImpl implements StackObject { newTarget.clearChosen(); newTarget.chooseTarget(outcome, getControllerId(), ability, game); - // check target restriction + + // check target restriction TODO: add multiple target checks if (newTarget.getFirstTarget() != null && filterNewTarget != null) { Permanent newTargetPermanent = game.getPermanent(newTarget.getFirstTarget()); if (newTargetPermanent == null || !filterNewTarget.match(newTargetPermanent, game)) { @@ -187,7 +188,8 @@ public abstract class StackObjImpl implements StackObject { newTarget.clearChosen(); } } - } while (targetController.canRespond() && (targetId.equals(newTarget.getFirstTarget()) || newTarget.getTargets().size() != 1)); + } + while (targetController.canRespond() && (targetId.equals(newTarget.getFirstTarget()) || newTarget.getTargets().size() != 1)); // choose a new target } else { // build a target definition with exactly one possible target to select that replaces old target From 4484527d04cc63e1898df5b88fa96ea41c7f62bc Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Fri, 30 Nov 2018 20:26:07 +0400 Subject: [PATCH 30/42] Test framework: add new real time check command to assert hand cards count; --- .../java/org/mage/test/player/TestPlayer.java | 25 ++++++++++++++++--- .../base/impl/CardTestPlayerAPIImpl.java | 7 +++++- 2 files changed, 28 insertions(+), 4 deletions(-) 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 47f703ec0e..7d70c34d4a 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 @@ -580,6 +580,13 @@ public class TestPlayer implements Player { wasProccessed = true; } + // check hand card count: card name, count + if (params[0].equals(CHECK_COMMAND_HAND_CARD_COUNT) && params.length == 3) { + assertHandCardCount(action, game, computerPlayer, params[1], Integer.parseInt(params[2])); + actions.remove(action); + wasProccessed = true; + } + // check color: card name, colors, must have if (params[0].equals(CHECK_COMMAND_COLOR) && params.length == 4) { assertColor(action, game, computerPlayer, params[1], params[2], Boolean.parseBoolean(params[3])); @@ -754,12 +761,12 @@ public class TestPlayer implements Player { List data = abilities.stream() .map(a -> ( a.getZone() + " -> " - + a.getSourceObject(game).getIdName() + " -> " - + (a.getRule().length() > 0 + + a.getSourceObject(game).getIdName() + " -> " + + (a.getRule().length() > 0 ? a.getRule().substring(0, Math.min(20, a.getRule().length()) - 1) : a.getClass().getSimpleName()) + "..." - )) + )) .sorted() .collect(Collectors.toList()); @@ -826,6 +833,18 @@ public class TestPlayer implements Player { Assert.assertEquals(action.getActionName() + " - hand must contain " + count, count, player.getHand().size()); } + private void assertHandCardCount(PlayerAction action, Game game, Player player, String cardName, int count) { + int realCount = 0; + for (UUID cardId : player.getHand()) { + Card card = game.getCard(cardId); + if (card != null && card.getName().equals(cardName)) { + realCount++; + } + } + + Assert.assertEquals(action.getActionName() + " - hand must contain " + count + " cards of " + cardName, count, realCount); + } + private void assertColor(PlayerAction action, Game game, Player player, String permanentName, String colors, boolean mustHave) { Assert.assertNotEquals(action.getActionName() + " - must setup colors", "", colors); 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 016f1d37fb..5a93697bd3 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 @@ -56,6 +56,7 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement public static final String CHECK_COMMAND_PERMANENT_COUNT = "PERMANENT_COUNT"; public static final String CHECK_COMMAND_EXILE_COUNT = "EXILE_COUNT"; public static final String CHECK_COMMAND_HAND_COUNT = "HAND_COUNT"; + public static final String CHECK_COMMAND_HAND_CARD_COUNT = "HAND_CARD_COUNT"; public static final String CHECK_COMMAND_COLOR = "COLOR"; public static final String CHECK_COMMAND_SUBTYPE = "SUBTYPE"; public static final String CHECK_COMMAND_MANA_POOL = "MANA_POOL"; @@ -282,6 +283,10 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement check(checkName, turnNum, step, player, CHECK_COMMAND_HAND_COUNT, count.toString()); } + public void checkHandCardCount(String checkName, int turnNum, PhaseStep step, TestPlayer player, String cardName, Integer count) { + check(checkName, turnNum, step, player, CHECK_COMMAND_HAND_CARD_COUNT, cardName, count.toString()); + } + public void checkColor(String checkName, int turnNum, PhaseStep step, TestPlayer player, String permanentName, String colors, Boolean mustHave) { check(checkName, turnNum, step, player, CHECK_COMMAND_COLOR, permanentName, colors, mustHave.toString()); } @@ -1141,7 +1146,7 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement } public void assertAllCommandsUsed() throws AssertionError { - for(Player player : currentGame.getPlayers().values()) { + for (Player player : currentGame.getPlayers().values()) { TestPlayer testPlayer = (TestPlayer) player; assertActionsCount(testPlayer, 0); assertChoicesCount(testPlayer, 0); From dca5b645aafcef2ab2e01b06d0f9e93c73e09260 Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Sat, 1 Dec 2018 04:19:05 +0400 Subject: [PATCH 31/42] Test framework: * added choices support for discard spells; * fixes TargetCard choices (one choice record per target); --- .../test/cards/single/MisdirectionTest.java | 104 +++++++++++++----- .../mage/test/player/TestComputerPlayer.java | 11 ++ .../mage/test/player/TestComputerPlayer7.java | 11 ++ .../java/org/mage/test/player/TestPlayer.java | 88 ++++++++++----- .../base/impl/CardTestPlayerAPIImpl.java | 6 +- 5 files changed, 167 insertions(+), 53 deletions(-) diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/MisdirectionTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/MisdirectionTest.java index a84870f89d..efb30757b7 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/MisdirectionTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/MisdirectionTest.java @@ -1,4 +1,3 @@ - package org.mage.test.cards.single; import mage.constants.PhaseStep; @@ -7,8 +6,7 @@ import org.junit.Test; import org.mage.test.serverside.base.CardTestPlayerBase; /** - * - * @author LevelX2 + * @author LevelX2, JayDi85 */ public class MisdirectionTest extends CardTestPlayerBase { @@ -17,10 +15,58 @@ public class MisdirectionTest extends CardTestPlayerBase { * Tests if Misdirection for target opponent works correctly * https://github.com/magefree/mage/issues/574 */ + @Test - public void testChangeTargetOpponent() { + public void test_RakshaDiscardWorks() { // Target opponent discards two cards. Put the top two cards of your library into your graveyard. - addCard(Zone.HAND, playerA, "Rakshasa's Secret"); + addCard(Zone.HAND, playerA, "Rakshasa's Secret"); // {2}{B} + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 3); + addCard(Zone.HAND, playerB, "Silvercoat Lion", 2); + addCard(Zone.HAND, playerB, "Ashcoat Bear", 5); + + // A cast discard + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Rakshasa's Secret", playerB); + setChoice(playerB, "Silvercoat Lion"); // select target 1 + setChoice(playerB, "Silvercoat Lion"); // select target 2 + checkHandCardCount("B haven't lions", 1, PhaseStep.POSTCOMBAT_MAIN, playerB, "Silvercoat Lion", 0); + checkHandCardCount("B have 5 bears", 1, PhaseStep.POSTCOMBAT_MAIN, playerB, "Ashcoat Bear", 5); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + } + + @Test + public void test_MisdirectionRetargetWorks() { + // Return target permanent to its owner’s hand. + addCard(Zone.HAND, playerA, "Boomerang", 1); // {U}{U} + addCard(Zone.BATTLEFIELD, playerA, "Island", 2); + addCard(Zone.BATTLEFIELD, playerA, "Ashcoat Bear", 1); + // Change the target of target spell with a single target. + addCard(Zone.HAND, playerB, "Misdirection"); // {3}{U}{U} + addCard(Zone.BATTLEFIELD, playerB, "Island", 5); + addCard(Zone.BATTLEFIELD, playerB, "Silvercoat Lion", 1); + + // A cast Boomerang to remove lion + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Boomerang", "Silvercoat Lion"); + // B counter it by Misdirection and remove bear + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Misdirection", "Boomerang", "Boomerang"); + addTarget(playerB, "Ashcoat Bear"); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + assertGraveyardCount(playerA, "Boomerang", 1); + assertPermanentCount(playerA, "Ashcoat Bear", 0); + assertGraveyardCount(playerB, "Misdirection", 1); + assertPermanentCount(playerB, "Silvercoat Lion", 1); + } + + @Test + public void test_MisdirectionCantTargetToIllegal() { + // Target opponent discards two cards. Put the top two cards of your library into your graveyard. + addCard(Zone.HAND, playerA, "Rakshasa's Secret"); // {2}{B} addCard(Zone.BATTLEFIELD, playerA, "Swamp", 3); /* Misdirection {3}{U}{U} @@ -30,23 +76,31 @@ public class MisdirectionTest extends CardTestPlayerBase { */ addCard(Zone.HAND, playerB, "Misdirection"); addCard(Zone.HAND, playerB, "Silvercoat Lion", 2); + addCard(Zone.HAND, playerB, "Ashcoat Bear", 5); addCard(Zone.BATTLEFIELD, playerB, "Island", 5); - + + // cast Raksha and select B castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Rakshasa's Secret", playerB); + // cast misdir, but it's not apply and taget will be same castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Misdirection", "Rakshasa's Secret", "Rakshasa's Secret"); - addTarget(playerB, playerA); // only legal target is player B as opponent - so player A should not be allowed - - setStopAt(1, PhaseStep.BEGIN_COMBAT); + // B must select cards to discard (2 lions, not bears) + setChoice(playerB, "Silvercoat Lion"); // select target 1 + setChoice(playerB, "Silvercoat Lion"); // select target 2 + checkHandCardCount("B haven't lions", 1, PhaseStep.POSTCOMBAT_MAIN, playerB, "Silvercoat Lion", 0); + checkHandCardCount("B have 5 bears", 1, PhaseStep.POSTCOMBAT_MAIN, playerB, "Ashcoat Bear", 5); + + setStopAt(1, PhaseStep.END_TURN); execute(); + assertAllCommandsUsed(); assertGraveyardCount(playerA, "Rakshasa's Secret", 1); assertGraveyardCount(playerB, "Misdirection", 1); assertHandCount(playerB, "Silvercoat Lion", 0); } - + // check to change target permanent creature legal to to a creature the opponent of the spell controller controls @Test - public void testChangePublicExecution() { + public void test_ChangePublicExecution() { // Destroy target creature an opponent controls. Each other creature that player controls gets -2/-0 until end of turn. addCard(Zone.HAND, playerA, "Public Execution"); addCard(Zone.BATTLEFIELD, playerA, "Swamp", 6); @@ -60,26 +114,27 @@ public class MisdirectionTest extends CardTestPlayerBase { addCard(Zone.BATTLEFIELD, playerB, "Pillarfield Ox", 1); addCard(Zone.BATTLEFIELD, playerB, "Custodian of the Trove", 1); // 4/3 addCard(Zone.BATTLEFIELD, playerB, "Island", 5); - + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Public Execution", "Pillarfield Ox"); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Misdirection", "Public Execution", "Public Execution"); - addTarget(playerB, "Custodian of the Trove"); - + addTarget(playerB, "Custodian of the Trove"); + setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); + assertAllCommandsUsed(); assertGraveyardCount(playerA, "Public Execution", 1); assertGraveyardCount(playerB, "Misdirection", 1); - - assertGraveyardCount(playerB, "Custodian of the Trove",1); + + assertGraveyardCount(playerB, "Custodian of the Trove", 1); assertPermanentCount(playerB, "Pillarfield Ox", 1); assertPowerToughness(playerB, "Pillarfield Ox", 0, 4); - } - + } + // check to change target permanent creature not legal to to a creature the your opponent controls @Test - public void testChangePublicExecution2() { + public void test_ChangePublicExecution2() { // Destroy target creature an opponent controls. Each other creature that player controls gets -2/-0 until end of turn. addCard(Zone.HAND, playerA, "Public Execution"); addCard(Zone.BATTLEFIELD, playerA, "Swamp", 6); @@ -94,13 +149,13 @@ public class MisdirectionTest extends CardTestPlayerBase { addCard(Zone.BATTLEFIELD, playerB, "Pillarfield Ox", 1); addCard(Zone.BATTLEFIELD, playerB, "Custodian of the Trove", 1); // 4/3 addCard(Zone.BATTLEFIELD, playerB, "Island", 5); - + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Public Execution", "Custodian of the Trove"); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Misdirection", "Public Execution", "Public Execution"); - addTarget(playerB, "Keeper of the Lens"); - + setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); + assertAllCommandsUsed(); assertGraveyardCount(playerA, "Public Execution", 1); assertGraveyardCount(playerB, "Misdirection", 1); @@ -108,8 +163,7 @@ public class MisdirectionTest extends CardTestPlayerBase { assertPermanentCount(playerB, "Pillarfield Ox", 1); assertPowerToughness(playerB, "Pillarfield Ox", 0, 4); - - assertGraveyardCount(playerB, "Custodian of the Trove",1); - } + assertGraveyardCount(playerB, "Custodian of the Trove", 1); + } } \ No newline at end of file diff --git a/Mage.Tests/src/test/java/org/mage/test/player/TestComputerPlayer.java b/Mage.Tests/src/test/java/org/mage/test/player/TestComputerPlayer.java index cb9a905962..7e9d8b2cf1 100644 --- a/Mage.Tests/src/test/java/org/mage/test/player/TestComputerPlayer.java +++ b/Mage.Tests/src/test/java/org/mage/test/player/TestComputerPlayer.java @@ -3,9 +3,11 @@ package org.mage.test.player; import mage.MageObject; import mage.abilities.ActivatedAbility; import mage.abilities.SpellAbility; +import mage.constants.Outcome; import mage.constants.RangeOfInfluence; import mage.game.Game; import mage.player.ai.ComputerPlayer; +import mage.target.Target; import java.util.LinkedHashMap; import java.util.UUID; @@ -59,4 +61,13 @@ public class TestComputerPlayer extends ComputerPlayer { // default implementation by AI return super.chooseSpellAbilityForCast(ability, game, noMana); } + + @Override + public boolean choose(Outcome outcome, Target target, UUID sourceId, Game game) { + // copy-paste for TestComputerXXX + + // workaround for discard spells + // reason: TestPlayer uses outer computerPlayer to discard but inner code uses choose + return testPlayerLink.choose(outcome, target, sourceId, game); + } } diff --git a/Mage.Tests/src/test/java/org/mage/test/player/TestComputerPlayer7.java b/Mage.Tests/src/test/java/org/mage/test/player/TestComputerPlayer7.java index 3bff415070..e647128bb6 100644 --- a/Mage.Tests/src/test/java/org/mage/test/player/TestComputerPlayer7.java +++ b/Mage.Tests/src/test/java/org/mage/test/player/TestComputerPlayer7.java @@ -3,9 +3,11 @@ package org.mage.test.player; import mage.MageObject; import mage.abilities.ActivatedAbility; import mage.abilities.SpellAbility; +import mage.constants.Outcome; import mage.constants.RangeOfInfluence; import mage.game.Game; import mage.player.ai.ComputerPlayer7; +import mage.target.Target; import java.util.LinkedHashMap; import java.util.UUID; @@ -59,4 +61,13 @@ public class TestComputerPlayer7 extends ComputerPlayer7 { // default implementation by AI return super.chooseSpellAbilityForCast(ability, game, noMana); } + + @Override + public boolean choose(Outcome outcome, Target target, UUID sourceId, Game game) { + // copy-paste for TestComputerXXX + + // workaround for discard spells + // reason: TestPlayer uses outer computerPlayer to discard but inner code uses choose + return testPlayerLink.choose(outcome, target, sourceId, game); + } } 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 7d70c34d4a..c1aff1cb7f 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 @@ -1160,11 +1160,16 @@ public class TestPlayer implements Player { @Override public boolean choose(Outcome outcome, Target target, UUID sourceId, Game game, Map options) { if (!choices.isEmpty()) { + + List usedChoices = new ArrayList<>(); + List usedTargets = new ArrayList<>(); + Ability source = null; StackObject stackObject = game.getStack().getStackObject(sourceId); if (stackObject != null) { source = stackObject.getStackAbility(); } + if ((target instanceof TargetPermanent) || (target instanceof TargetPermanentOrPlayer)) { // player target not implemted yet FilterPermanent filterPermanent; if (target instanceof TargetPermanentOrPlayer) { @@ -1218,6 +1223,7 @@ public class TestPlayer implements Player { } } } + if (target instanceof TargetPlayer) { for (Player player : game.getPlayers().values()) { for (String choose2 : choices) { @@ -1231,42 +1237,74 @@ public class TestPlayer implements Player { } } } + + // TODO: add same choices fixes for other target types (one choice must uses only one time for one target) if (target instanceof TargetCard) { - TargetCard targetCard = ((TargetCard) target); - Set possibleTargets = targetCard.possibleTargets(sourceId, target.getTargetController() == null ? getId() : target.getTargetController(), game); - for (String choose2 : choices) { - String[] targetList = choose2.split("\\^"); + // one choice per target + // only unique targets + //TargetCard targetFull = ((TargetCard) target); + + usedChoices.clear(); + usedTargets.clear(); + boolean targetCompleted = false; + + CheckAllChoices: + for (String choiceRecord : choices) { + if (targetCompleted) { + break CheckAllChoices; + } + boolean targetFound = false; - Choice: - for (String targetName : targetList) { - for (UUID targetId : possibleTargets) { + String[] possibleChoices = choiceRecord.split("\\^"); + + CheckOneChoice: + for (String possibleChoice : possibleChoices) { + Set possibleCards = target.possibleTargets(sourceId, target.getTargetController() == null ? getId() : target.getTargetController(), game); + CheckTargetsList: + for (UUID targetId : possibleCards) { MageObject targetObject = game.getObject(targetId); - if (targetObject != null) { - if (targetObject.getName().equals(targetName)) { - if (targetCard.canTarget(targetObject.getId(), game)) { - if (targetCard.getTargets() != null && !targetCard.getTargets().contains(targetObject.getId())) { - targetCard.add(targetObject.getId(), game); - targetFound = true; - if (target.getTargets().size() >= target.getMaxNumberOfTargets()) { - break Choice; - } - } + if (targetObject != null && targetObject.getName().equals(possibleChoice)) { + if (target.canTarget(targetObject.getId(), game)) { + // only unique targets + if (usedTargets.contains(targetObject.getId())) { + continue; } + + // OK, can use it + target.add(targetObject.getId(), game); + targetFound = true; + usedTargets.add(targetObject.getId()); + + // break on full targets list + if (target.getTargets().size() >= target.getMaxNumberOfTargets()) { + targetCompleted = true; + break CheckOneChoice; + } + + // restart search + break CheckTargetsList; } } - } } + if (targetFound) { - if (targetCard.isChosen()) { - choices.remove(choose2); - return true; - } else { - target.clearChosen(); - } + usedChoices.add(choiceRecord); + } + } + + // apply only on ALL targets or revert + if (usedChoices.size() > 0) { + if (target.isChosen()) { + choices.removeAll(usedChoices); + return true; + } else { + Assert.fail("Not full targets list."); + target.clearChosen(); } } } + if (target instanceof TargetSource) { Set possibleTargets; TargetSource t = ((TargetSource) target); @@ -2774,7 +2812,7 @@ public class TestPlayer implements Player { @Override public SpellAbility chooseSpellAbilityForCast(SpellAbility ability, Game game, boolean noMana) { - Assert.fail("That's method calls only from computerPlayer->cast(), see TestComputerPlayerXXX"); + Assert.fail("That's method must calls only from computerPlayer->cast(), see TestComputerPlayerXXX"); return computerPlayer.chooseSpellAbilityForCast(ability, game, noMana); } 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 5a93697bd3..9daed70295 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 @@ -1132,17 +1132,17 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement } public void assertActionsCount(TestPlayer player, int count) throws AssertionError { - Assert.assertEquals("(Actions " + player.getName() + ") Count are not equel (founded [" + Assert.assertEquals("(Actions of " + player.getName() + ") Count are not equel (founded [" + player.getActions().stream().map(PlayerAction::getAction).collect(Collectors.joining(", ")) + "])", count, player.getActions().size()); } public void assertChoicesCount(TestPlayer player, int count) throws AssertionError { - Assert.assertEquals("(Choices " + player.getName() + ") Count are not equel (founded " + player.getChoices() + ")", count, player.getChoices().size()); + Assert.assertEquals("(Choices of " + player.getName() + ") Count are not equel (founded " + player.getChoices() + ")", count, player.getChoices().size()); } public void assertTargetsCount(TestPlayer player, int count) throws AssertionError { - Assert.assertEquals("(Targets " + player.getName() + ") Count are not equel (founded " + player.getTargets() + ")", count, player.getTargets().size()); + Assert.assertEquals("(Targets of " + player.getName() + ") Count are not equel (founded " + player.getTargets() + ")", count, player.getTargets().size()); } public void assertAllCommandsUsed() throws AssertionError { From d65dab9a955d10137fe45e6a02659971481f5c71 Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Sat, 1 Dec 2018 08:20:46 +0400 Subject: [PATCH 32/42] Test framework: added choices support for multiple targets with multiple damage spells (targetName^X=3); --- .../java/org/mage/test/player/TestPlayer.java | 45 +++++++++++++++++++ .../base/impl/CardTestPlayerAPIImpl.java | 2 + 2 files changed, 47 insertions(+) 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 c1aff1cb7f..55a79d1781 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 @@ -2867,6 +2867,51 @@ public class TestPlayer implements Player { public boolean chooseTargetAmount(Outcome outcome, TargetAmount target, Ability source, Game game ) { + // command format: targetName^X=3 + + // chooseTargetAmount calls by TargetAmount for EACH target cycle + Assert.assertTrue("chooseTargetAmount supports only one target, but found " + target.getMaxNumberOfTargets(), target.getMaxNumberOfTargets() <= 1); + Assert.assertNotEquals("chooseTargetAmount need remaining > 0", 0, target.getAmountRemaining()); + + if (!targets.isEmpty()) { + + boolean founded = false; + String foundedRecord = ""; + CheckTargets: + for(String targetRecord : targets) { + String[] choiceSettings = targetRecord.split("\\^"); + if (choiceSettings.length == 2 && choiceSettings[1].startsWith("X=")) { + // can choice + String choiceName = choiceSettings[0]; + int choiceAmount = Integer.parseInt(choiceSettings[1].substring(2)); + + Assert.assertNotEquals("choice amount must be not zero", 0, choiceAmount); + Assert.assertTrue("choice amount " + choiceAmount + "must be <= remaining " + target.getAmountRemaining(), choiceAmount <= target.getAmountRemaining()); + + for(UUID possibleTarget : target.possibleTargets(source.getSourceId(), source.getControllerId(), game)) { + MageObject objectPermanent = game.getObject(possibleTarget); + Player objectPlayer = game.getPlayer(possibleTarget); + String objectName = objectPermanent != null ? objectPermanent.getName() : objectPlayer.getName(); + if (objectName.equals(choiceName)) { + if (!target.getTargets().contains(possibleTarget) && target.canTarget(possibleTarget, source, game)) { + // can select + target.addTarget(possibleTarget, choiceAmount, source, game); + founded = true; + foundedRecord = targetRecord; + break CheckTargets; + } + } + } + } + } + + if (founded) { + // all done + targets.remove(foundedRecord); + return true; + } + } + return computerPlayer.chooseTargetAmount(outcome, target, source, game); } 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 9daed70295..e86e9ee26c 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 @@ -222,6 +222,8 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement logger.debug("Winner: " + currentGame.getWinner()); logger.info("Test has been executed. Execution time: " + (t2 - t1) / 1000000 + " ms"); + // TODO: 01.12.2018, JayDi85 - uncomment and fix MANY broken tests with wrong commands + //assertAllCommandsUsed(); } protected TestPlayer createNewPlayer(String playerName, RangeOfInfluence rangeOfInfluence) { From c1e4fce448fa96c400d9c639b8a28dc541794814 Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Sat, 1 Dec 2018 08:21:54 +0400 Subject: [PATCH 33/42] Tests: fixed SpellskiteTest and enabled missing tests; --- .../test/cards/triggers/SpellskiteTest.java | 92 +++++++++++-------- 1 file changed, 56 insertions(+), 36 deletions(-) diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/triggers/SpellskiteTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/SpellskiteTest.java index 9c45cc42e7..af57b93243 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/triggers/SpellskiteTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/SpellskiteTest.java @@ -1,4 +1,3 @@ - package org.mage.test.cards.triggers; import mage.constants.PhaseStep; @@ -7,7 +6,6 @@ import org.junit.Test; import org.mage.test.serverside.base.CardTestPlayerBase; /** - * * @author LevelX2 */ public class SpellskiteTest extends CardTestPlayerBase { @@ -30,6 +28,7 @@ public class SpellskiteTest extends CardTestPlayerBase { setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); + assertAllCommandsUsed(); assertGraveyardCount(playerA, "Lightning Bolt", 1); assertPermanentCount(playerA, "Spellskite", 1); @@ -80,6 +79,7 @@ public class SpellskiteTest extends CardTestPlayerBase { setStopAt(2, PhaseStep.BEGIN_COMBAT); execute(); + assertAllCommandsUsed(); assertPermanentCount(playerA, "Spellskite", 1); assertPermanentCount(playerB, "Frost Titan", 1); @@ -119,6 +119,7 @@ public class SpellskiteTest extends CardTestPlayerBase { setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); + assertAllCommandsUsed(); assertGraveyardCount(playerB, "Lightning Bolt", 1); @@ -160,6 +161,7 @@ public class SpellskiteTest extends CardTestPlayerBase { setStopAt(2, PhaseStep.BEGIN_COMBAT); execute(); + assertAllCommandsUsed(); assertGraveyardCount(playerA, "Cryptic Command", 1); @@ -188,16 +190,16 @@ public class SpellskiteTest extends CardTestPlayerBase { castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", playerB); - activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerB, "{U/P}: Change a target of target spell or ability to {this}.", "Lightning Bolt"); + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerB, "{U/P}: Change a target", "Lightning Bolt"); setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); + assertAllCommandsUsed(); assertGraveyardCount(playerA, "Lightning Bolt", 1); assertLife(playerA, 20); assertLife(playerB, 18); - } /** @@ -219,6 +221,7 @@ public class SpellskiteTest extends CardTestPlayerBase { setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); + assertAllCommandsUsed(); assertGraveyardCount(playerA, "Flame Slash", 1); assertPowerToughness(playerB, "Spellskite", 3, 7); @@ -238,24 +241,36 @@ public class SpellskiteTest extends CardTestPlayerBase { addCard(Zone.BATTLEFIELD, playerB, "Spellskite"); addCard(Zone.BATTLEFIELD, playerB, "Scute Mob"); addCard(Zone.BATTLEFIELD, playerB, "Island"); + // + addCard(Zone.BATTLEFIELD, playerB, "Memnite"); // 1/1 + addCard(Zone.BATTLEFIELD, playerB, "Royal Assassin"); // 1/1 + addCard(Zone.BATTLEFIELD, playerB, "Blinking Spirit"); // 2/2 + addCard(Zone.BATTLEFIELD, playerB, "Pearled Unicorn"); // 2/2 addCard(Zone.HAND, playerA, "Fiery Justice"); + // A cast Fiery Justice castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Fiery Justice"); - addTarget(playerA, "Scute Mob"); - setChoice(playerA, "X=1"); - addTarget(playerA, "Spellskite"); - setChoice(playerA, "X=4"); - - activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerB, "{UP}: Change a target of target spell or ability to {this}.", "Fiery Justice", "Fiery Justice"); - setChoice(playerA, "Yes"); + addTarget(playerA, playerB); // 5 life to B + addTarget(playerA, "Scute Mob^X=1"); // target 1 + addTarget(playerA, "Spellskite^X=4"); // target 2 + // B activate Spellskite, but can't change any targets cause it's already targeted + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerB, "{U/P}: Change a target", "Fiery Justice", "Fiery Justice"); + setChoice(playerB, "Yes"); // pay 2 life + showBattlefield("B battle", 1, PhaseStep.BEGIN_COMBAT, playerB); + showGraveyard("B grave", 1, PhaseStep.BEGIN_COMBAT, playerB); setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); + assertAllCommandsUsed(); + assertLife(playerB, 20 + 5 - 2); assertGraveyardCount(playerB, 2); + assertGraveyardCount(playerB, "Scute Mob", 1); + assertGraveyardCount(playerB, "Spellskite", 1); } + @Test public void testThatSplitDamageCanGetRedirected() { /* Standard redirect test The Spellskite should die from the 5 damage that was redirected to it @@ -267,59 +282,64 @@ public class SpellskiteTest extends CardTestPlayerBase { addCard(Zone.BATTLEFIELD, playerB, "Spellskite");// 0/4 creature addCard(Zone.BATTLEFIELD, playerB, "Scute Mob"); // 1/1 creauture addCard(Zone.BATTLEFIELD, playerB, "Island"); + addCard(Zone.BATTLEFIELD, playerB, "Memnite"); // 1/1 + addCard(Zone.BATTLEFIELD, playerB, "Royal Assassin"); // 1/1 + addCard(Zone.BATTLEFIELD, playerB, "Blinking Spirit"); // 2/2 + addCard(Zone.BATTLEFIELD, playerB, "Pearled Unicorn"); // 2/2 addCard(Zone.HAND, playerA, "Fiery Justice"); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Fiery Justice"); // 5 damage distributed to any number of targets - addTarget(playerA, "Scute Mob"); - setChoice(playerA, "X=5"); + addTarget(playerA, "Scute Mob^X=5"); - activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerB, "{UP}: Change a target of target spell or ability to {this}.", "Fiery Justice", "Fiery Justice"); - setChoice(playerA, "Yes"); + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerB, "{U/P}: Change a target", "Fiery Justice", "Fiery Justice"); + setChoice(playerB, "Yes"); // pay 2 life + setChoice(playerB, "Yes"); // retarget setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); + assertAllCommandsUsed(); + assertLife(playerB, 20 + 5 - 2); assertGraveyardCount(playerB, 1); assertPermanentCount(playerB, "Scute Mob", 1); } + @Test public void testThatSplitDamageGetsRedirectedFromTheCorrectChoice() { addCard(Zone.BATTLEFIELD, playerA, "Plains", 1); addCard(Zone.BATTLEFIELD, playerA, "Mountain", 1); addCard(Zone.BATTLEFIELD, playerA, "Forest", 1); addCard(Zone.BATTLEFIELD, playerB, "Spellskite");// 0/4 creature - addCard(Zone.BATTLEFIELD, playerB, "Memnite"); // 1/1 creauture - addCard(Zone.BATTLEFIELD, playerB, "Royal Assassin"); - addCard(Zone.BATTLEFIELD, playerB, "Blinking Spirit"); - addCard(Zone.BATTLEFIELD, playerB, "Pearled Unicorn"); + addCard(Zone.BATTLEFIELD, playerB, "Memnite"); // 1/1 + addCard(Zone.BATTLEFIELD, playerB, "Royal Assassin"); // 1/1 + addCard(Zone.BATTLEFIELD, playerB, "Blinking Spirit"); // 2/2 + addCard(Zone.BATTLEFIELD, playerB, "Pearled Unicorn"); // 2/2 addCard(Zone.BATTLEFIELD, playerB, "Island"); addCard(Zone.HAND, playerA, "Fiery Justice"); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Fiery Justice"); // 5 damage distributed to any number of targets - addTarget(playerA, "Memnite"); - setChoice(playerA, "X=1"); - addTarget(playerA, "Royal Assassin"); - setChoice(playerA, "X=1"); - addTarget(playerA, "Blinking Spirit"); - setChoice(playerA, "X=1"); - addTarget(playerA, "Pearled Unicorn"); - setChoice(playerA, "X=2");//the unicorn deserves it + addTarget(playerA, "Royal Assassin^X=1"); + addTarget(playerA, "Blinking Spirit^X=2"); + addTarget(playerA, "Pearled Unicorn^X=2"); - - activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerB, "{UP}: Change a target of target spell or ability to {this}.", "Fiery Justice", "Fiery Justice"); - setChoice(playerA, "No"); - setChoice(playerA, "No"); - setChoice(playerA, "No"); - setChoice(playerA, "Yes"); //of course + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerB, "{U/P}: Change a target", "Fiery Justice", "Fiery Justice"); + setChoice(playerB, "Yes"); // pay 2 life + setChoice(playerB, "No"); // skip royal + setChoice(playerB, "No"); // skip blink + setChoice(playerB, "Yes"); // change pearl setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); + assertAllCommandsUsed(); - assertGraveyardCount(playerB, 3); - assertPermanentCount(playerB, "Pearled Unicorn", 1);//it lives on - assertPowerToughness(playerB, "Spellskite", 0, 2); + assertLife(playerB, 20 + 5 - 2); + assertGraveyardCount(playerB, "Memnite", 0); + assertGraveyardCount(playerB, "Royal Assassin", 1); + assertGraveyardCount(playerB, "Blinking Spirit", 1); + assertGraveyardCount(playerB, "Pearled Unicorn", 0); + assertGraveyardCount(playerB, "Spellskite", 0); } } From c1804f810c30bf06e9bf3bafc729114b05caaa32 Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Sat, 1 Dec 2018 09:08:35 +0400 Subject: [PATCH 34/42] Tests: fixed BrainMaggotTest; --- .../cards/triggers/dies/BrainMaggotTest.java | 28 +++++++++++++------ .../java/org/mage/test/player/TestPlayer.java | 4 ++- 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/triggers/dies/BrainMaggotTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/dies/BrainMaggotTest.java index 16973a7acb..f77652b4dd 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/triggers/dies/BrainMaggotTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/dies/BrainMaggotTest.java @@ -1,4 +1,3 @@ - package org.mage.test.cards.triggers.dies; import mage.constants.PhaseStep; @@ -7,7 +6,6 @@ import org.junit.Test; import org.mage.test.serverside.base.CardTestPlayerBase; /** - * * @author LevelX2 */ public class BrainMaggotTest extends CardTestPlayerBase { @@ -16,7 +14,6 @@ public class BrainMaggotTest extends CardTestPlayerBase { * When Brain Maggot enters the battlefield, target opponent reveals his or * her hand and you choose a nonland card from it. Exile that card until * Brain Maggot leaves the battlefield. - * */ @Test public void testCardFromHandWillBeExiled() { @@ -26,10 +23,12 @@ public class BrainMaggotTest extends CardTestPlayerBase { addCard(Zone.HAND, playerB, "Bloodflow Connoisseur", 1); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Brain Maggot"); - addTarget(playerA, "Bloodflow Connoisseur"); + addTarget(playerA, playerB); + setChoice(playerA, "Bloodflow Connoisseur"); setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); + assertAllCommandsUsed(); assertPermanentCount(playerA, "Brain Maggot", 1); assertExileCount("Bloodflow Connoisseur", 1); @@ -45,12 +44,22 @@ public class BrainMaggotTest extends CardTestPlayerBase { addCard(Zone.HAND, playerB, "Lightning Bolt", 1); addCard(Zone.BATTLEFIELD, playerB, "Mountain", 2); + // exile castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Brain Maggot"); - addTarget(playerA, "Bloodflow Connoisseur"); - castSpell(1, PhaseStep.BEGIN_COMBAT, playerB, "Lightning Bolt", "Brain Maggot"); + addTarget(playerA, playerB); + setChoice(playerA, "Bloodflow Connoisseur"); + showExile("exile", 1, PhaseStep.BEGIN_COMBAT, playerB); + checkExileCount("blood must be in exile", 1, PhaseStep.BEGIN_COMBAT, playerB, "Bloodflow Connoisseur", 1); - setStopAt(1, PhaseStep.DECLARE_ATTACKERS); + // return + castSpell(1, PhaseStep.END_COMBAT, playerB, "Lightning Bolt", "Brain Maggot"); + checkPermanentCount("brain must die", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Brain Maggot", 0); + checkExileCount("blood must return from exile", 1, PhaseStep.POSTCOMBAT_MAIN, playerB, "Bloodflow Connoisseur", 0); + checkHandCardCount("blood must be in hand", 1, PhaseStep.POSTCOMBAT_MAIN, playerB, "Bloodflow Connoisseur", 1); + + setStopAt(1, PhaseStep.END_TURN); execute(); + assertAllCommandsUsed(); assertGraveyardCount(playerA, "Brain Maggot", 1); assertGraveyardCount(playerB, "Lightning Bolt", 1); @@ -68,11 +77,14 @@ public class BrainMaggotTest extends CardTestPlayerBase { addCard(Zone.BATTLEFIELD, playerB, "Mountain", 2); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Mesmeric Fiend"); - addTarget(playerA, "Bloodflow Connoisseur"); + addTarget(playerA, playerB); + setChoice(playerA, "Bloodflow Connoisseur"); + // castSpell(1, PhaseStep.BEGIN_COMBAT, playerB, "Lightning Bolt", "Mesmeric Fiend"); setStopAt(1, PhaseStep.DECLARE_ATTACKERS); execute(); + assertAllCommandsUsed(); assertGraveyardCount(playerA, "Mesmeric Fiend", 1); assertGraveyardCount(playerB, "Lightning Bolt", 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 55a79d1781..6bb0eb3e8c 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 @@ -658,7 +658,9 @@ public class TestPlayer implements Player { // show exile if (params[0].equals(SHOW_COMMAND_EXILE) && params.length == 1) { printStart(action.getActionName()); - printCards(game.getExile().getAllCards(game)); + printCards(game.getExile().getAllCards(game).stream() + .filter(card -> card.isOwnedBy(computerPlayer.getId())) + .collect(Collectors.toList())); printEnd(); actions.remove(action); wasProccessed = true; From 86ac1fcb1addad57c754553d72e6b24a19480027 Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Sun, 2 Dec 2018 03:09:27 +0400 Subject: [PATCH 35/42] Tests: fixed OmnathLocusOfRageTest; --- .../test/cards/triggers/dies/OmnathLocusOfRageTest.java | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/triggers/dies/OmnathLocusOfRageTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/dies/OmnathLocusOfRageTest.java index 0b7b8221ea..63bfb1557f 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/triggers/dies/OmnathLocusOfRageTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/dies/OmnathLocusOfRageTest.java @@ -1,4 +1,3 @@ - package org.mage.test.cards.triggers.dies; import mage.constants.PhaseStep; @@ -7,7 +6,6 @@ import org.junit.Test; import org.mage.test.serverside.base.CardTestPlayerBase; /** - * * @author LevelX2 */ public class OmnathLocusOfRageTest extends CardTestPlayerBase { @@ -27,10 +25,10 @@ public class OmnathLocusOfRageTest extends CardTestPlayerBase { addCard(Zone.BATTLEFIELD, playerB, "Swamp", 2); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Diabolic Edict", playerA); - addTarget(playerA, playerB); setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); + assertAllCommandsUsed(); assertGraveyardCount(playerB, "Diabolic Edict", 1); assertGraveyardCount(playerA, "Omnath, Locus of Rage", 1); @@ -53,12 +51,11 @@ public class OmnathLocusOfRageTest extends CardTestPlayerBase { addCard(Zone.BATTLEFIELD, playerB, "Mountain", 7); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Lightning Bolt", "Lightning Elemental"); // Dying Lightning Elemental does no longer trigger ability of Omnath - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Blastfire Bolt", "Omnath, Locus of Rage", "Lightning Bolt"); - addTarget(playerA, playerB); - addTarget(playerA, playerB); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Blastfire Bolt", "Omnath, Locus of Rage"); setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); + assertAllCommandsUsed(); assertGraveyardCount(playerB, "Lightning Bolt", 1); assertGraveyardCount(playerB, "Blastfire Bolt", 1); From b172f3c44a63f6d4f6697b0db431d8d129a97b75 Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Mon, 3 Dec 2018 06:15:54 +0400 Subject: [PATCH 36/42] Test framework: added basic aliases support (see full info at #5451): * generates aliases by addCard command (for one or multiple cards); * added support of `castSpell` command; * added show command to print aliases list with connected objects and zones; * added check command to control alias's object exists; --- .../java/org/mage/test/player/TestPlayer.java | 187 +++++++++++++++--- .../base/impl/CardTestPlayerAPIImpl.java | 39 +++- .../org/mage/test/testapi/TestAliases.java | 92 +++++++++ 3 files changed, 294 insertions(+), 24 deletions(-) create mode 100644 Mage.Tests/src/test/java/org/mage/test/testapi/TestAliases.java 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 6bb0eb3e8c..6573e02f11 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,6 @@ package org.mage.test.player; +import mage.MageItem; import mage.MageObject; import mage.MageObjectReference; import mage.ObjectColor; @@ -74,6 +75,7 @@ public class TestPlayer implements Player { private final List actions = new ArrayList<>(); private final List choices = new ArrayList<>(); // choices stack for choice private final List targets = new ArrayList<>(); // targets stack for choose (it's uses on empty direct target by cast command) + private final Map aliases = new HashMap<>(); // aliases for game objects/players (use it for cards with same name to save and use) private final List modesSet = new ArrayList<>(); private final ComputerPlayer computerPlayer; @@ -102,6 +104,7 @@ public class TestPlayer implements Player { this.actions.addAll(testPlayer.actions); this.choices.addAll(testPlayer.choices); this.targets.addAll(testPlayer.targets); + this.aliases.putAll(testPlayer.aliases); this.modesSet.addAll(testPlayer.modesSet); this.computerPlayer = testPlayer.computerPlayer.copy(); if (testPlayer.groupsForTargetHandling != null) { @@ -121,6 +124,18 @@ public class TestPlayer implements Player { return this.targets; } + public Map getAliases() { + return this.aliases; + } + + public UUID getAliasByName(String searchName) { + if (searchName.startsWith("@")) { + return this.aliases.getOrDefault(searchName.substring(1), null); + } else { + return this.aliases.getOrDefault(searchName, null); + } + } + public void addModeChoice(String mode) { modesSet.add(mode); } @@ -129,6 +144,10 @@ public class TestPlayer implements Player { targets.add(target); } + public void addAlias(String name, UUID Id) { + aliases.put(name, Id); + } + public ManaOptions getAvailableManaTest(Game game) { return computerPlayer.getManaAvailable(game); } @@ -314,6 +333,30 @@ public class TestPlayer implements Player { return result; } + public String generateAliasName(String baseAlias, boolean useMiltiNames, int iteration) { + if (useMiltiNames) { + return baseAlias + "." + iteration; + } else { + return baseAlias; + } + } + + private boolean isObjectHaveTargetNameOrAliase(MageObject object, String nameOrAliase) { + if (object == null || nameOrAliase == null) { + return false; + } + + if (nameOrAliase.startsWith("@") && object.getId().equals(getAliasByName(nameOrAliase))) { + return true; + } + + if (nameOrAliase.isEmpty() && object.getName().isEmpty()) { + return true; + } + + return object.getName().startsWith(nameOrAliase); + } + private boolean handleNonPlayerTargetTarget(String target, Ability ability, Game game) { boolean result = true; if (target == null) { @@ -375,28 +418,46 @@ public class TestPlayer implements Player { for (UUID id : currentTarget.possibleTargets(ability.getSourceId(), ability.getControllerId(), game)) { if (!currentTarget.getTargets().contains(id)) { MageObject object = game.getObject(id); - if (object != null - && ((object.isCopy() && !originOnly) || (!object.isCopy() && !copyOnly)) - && ((!targetName.isEmpty() && object.getName().startsWith(targetName)) || (targetName.isEmpty() && object.getName().isEmpty()))) { - if (currentTarget.getNumberOfTargets() == 1) { - currentTarget.clearChosen(); - } - if (currentTarget instanceof TargetCreaturePermanentAmount) { - // supports only to set the complete amount to one target - TargetCreaturePermanentAmount targetAmount = (TargetCreaturePermanentAmount) currentTarget; - targetAmount.setAmount(ability, game); - int amount = targetAmount.getAmountRemaining(); - targetAmount.addTarget(id, amount, ability, game); - targetsSet++; - } else { - currentTarget.addTarget(id, ability, game); - targetsSet++; - } - if (currentTarget.getTargets().size() == currentTarget.getMaxNumberOfTargets()) { - index++; - } - break; + + if (object == null) { + continue; } + + // only origin + if (originOnly && object.isCopy()) { + continue; + } + + // only copy + if (copyOnly && !object.isCopy()) { + continue; + } + + // need by alias or by name + if (!isObjectHaveTargetNameOrAliase(object, targetName)) { + continue; + } + + // founded, can use as target + + if (currentTarget.getNumberOfTargets() == 1) { + currentTarget.clearChosen(); + } + if (currentTarget instanceof TargetCreaturePermanentAmount) { + // supports only to set the complete amount to one target + TargetCreaturePermanentAmount targetAmount = (TargetCreaturePermanentAmount) currentTarget; + targetAmount.setAmount(ability, game); + int amount = targetAmount.getAmountRemaining(); + targetAmount.addTarget(id, amount, ability, game); + targetsSet++; + } else { + currentTarget.addTarget(id, ability, game); + targetsSet++; + } + if (currentTarget.getTargets().size() == currentTarget.getMaxNumberOfTargets()) { + index++; + } + break; } } } @@ -607,6 +668,13 @@ public class TestPlayer implements Player { actions.remove(action); wasProccessed = true; } + + // check aliase at zone: alias name, zone, must have (only for TestPlayer) + if (params[0].equals(CHECK_COMMAND_ALIAS_ZONE) && params.length == 4) { + assertAliasZone(action, game, this, params[1], Zone.valueOf(params[2]), Boolean.parseBoolean(params[3])); + actions.remove(action); + wasProccessed = true; + } } if (!wasProccessed) { Assert.fail("Unknow check command or params: " + command); @@ -674,6 +742,15 @@ public class TestPlayer implements Player { actions.remove(action); wasProccessed = true; } + + // show aliases + if (params[0].equals(SHOW_COMMAND_ALIASES) && params.length == 1) { + printStart(action.getActionName()); + printAliases(game, this); + printEnd(); + actions.remove(action); + wasProccessed = true; + } } if (!wasProccessed) { @@ -777,6 +854,38 @@ public class TestPlayer implements Player { } } + + private String getAliasInfo(Game game, TestPlayer player, String aliasName) { + MageItem item = findAliasObject(game, player, aliasName); + if (item == null) { + return aliasName + " [not exists]"; + } + + if (item instanceof MageObject) { + Zone zone = game.getState().getZone(item.getId()); + return aliasName + " - " + ((MageObject) item).getIdName() + " - " + (zone != null ? zone.toString() : "null"); + } + + if (item instanceof Player) { + return aliasName + " - " + ((Player) item).getName(); + } + + return aliasName + " [unknown object " + item.getId() + "]"; + } + + private void printAliases(Game game, TestPlayer player) { + System.out.println("Total aliases: " + player.getAliases().size()); + + List data = player.getAliases().entrySet().stream() + .map(entry -> (getAliasInfo(game, player, entry.getKey()))) + .sorted() + .collect(Collectors.toList()); + + for (String s : data) { + System.out.println(s); + } + } + private void assertPT(PlayerAction action, Game game, Player player, String permanentName, int Power, int Toughness) { Permanent perm = findPermanentWithAssert(action, game, player, permanentName); @@ -891,6 +1000,36 @@ public class TestPlayer implements Player { } } + private MageItem findAliasObject(Game game, TestPlayer player, String aliasName) { + UUID objectId = player.getAliasByName(aliasName); + if (objectId == null) { + return null; + } + + MageObject itemObject = game.getObject(objectId); + if (itemObject != null) { + return itemObject; + } + + Player itemPlayer = game.getPlayer(objectId); + if (itemPlayer != null) { + return itemPlayer; + } + + return null; + } + + private void assertAliasZone(PlayerAction action, Game game, TestPlayer player, String aliasName, Zone needZone, boolean mustHave) { + MageItem item = findAliasObject(game, player, aliasName); + Zone currentZone = (item == null ? null : game.getState().getZone(item.getId())); + + if (mustHave) { + Assert.assertEquals(action.getActionName() + " - alias " + aliasName + " must have zone " + needZone.toString(), needZone, currentZone); + } else { + Assert.assertNotEquals(action.getActionName() + " - alias " + aliasName + " must have not zone " + needZone.toString(), needZone, currentZone); + } + } + private void assertManaPoolInner(PlayerAction action, Player player, ManaType manaType, Integer amount) { Integer current = player.getManaPool().get(manaType); Assert.assertEquals(action.getActionName() + " - mana pool must contain [" + amount.toString() + " " + manaType.toString() + "], but found [" + current.toString() + "]", amount, current); @@ -1785,6 +1924,8 @@ public class TestPlayer implements Player { this.choices.addAll(((TestPlayer) player).choices); this.targets.clear(); this.targets.addAll(((TestPlayer) player).targets); + this.aliases.clear(); + this.aliases.putAll(((TestPlayer) player).aliases); computerPlayer.restore(player); } @@ -2880,7 +3021,7 @@ public class TestPlayer implements Player { boolean founded = false; String foundedRecord = ""; CheckTargets: - for(String targetRecord : targets) { + for (String targetRecord : targets) { String[] choiceSettings = targetRecord.split("\\^"); if (choiceSettings.length == 2 && choiceSettings[1].startsWith("X=")) { // can choice @@ -2890,7 +3031,7 @@ public class TestPlayer implements Player { Assert.assertNotEquals("choice amount must be not zero", 0, choiceAmount); Assert.assertTrue("choice amount " + choiceAmount + "must be <= remaining " + target.getAmountRemaining(), choiceAmount <= target.getAmountRemaining()); - for(UUID possibleTarget : target.possibleTargets(source.getSourceId(), source.getControllerId(), game)) { + for (UUID possibleTarget : target.possibleTargets(source.getSourceId(), source.getControllerId(), game)) { MageObject objectPermanent = game.getObject(possibleTarget); Player objectPlayer = game.getPlayer(possibleTarget); String objectName = objectPermanent != null ? objectPermanent.getName() : objectPlayer.getName(); 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 e86e9ee26c..03f6a38ce1 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 @@ -60,6 +60,7 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement public static final String CHECK_COMMAND_COLOR = "COLOR"; public static final String CHECK_COMMAND_SUBTYPE = "SUBTYPE"; public static final String CHECK_COMMAND_MANA_POOL = "MANA_POOL"; + public static final String CHECK_COMMAND_ALIAS_ZONE = "ALIAS_ZONE"; // TODO: add target player param to commands public static final String SHOW_COMMAND_LIBRARY = "LIBRARY"; @@ -68,6 +69,10 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement public static final String SHOW_COMMAND_GRAVEYEARD = "GRAVEYARD"; public static final String SHOW_COMMAND_EXILE = "EXILE"; public static final String SHOW_COMMAND_AVAILABLE_ABILITIES = "AVAILABLE_ABILITIES"; + public static final String SHOW_COMMAND_ALIASES = "ALIASES"; + + // TODO: add target player param to commands + public static final String ALIAS_COMMAND_ADD = "ADD"; protected GameOptions gameOptions; @@ -301,6 +306,14 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement check(checkName, turnNum, step, player, CHECK_COMMAND_MANA_POOL, colors, amount.toString()); } + public void checkAliasZone(String checkName, int turnNum, PhaseStep step, TestPlayer player, String alias, Zone zone) { + checkAliasZone(checkName, turnNum, step, player, alias, zone, true); + } + + public void checkAliasZone(String checkName, int turnNum, PhaseStep step, TestPlayer player, String alias, Zone zone, Boolean mustHave) { + check(checkName, turnNum, step, player, CHECK_COMMAND_ALIAS_ZONE, alias, zone.toString(), mustHave.toString()); + } + // show commands private void show(String showName, int turnNum, PhaseStep step, TestPlayer player, String command, String... params) { @@ -335,6 +348,10 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement show(showName, turnNum, step, player, SHOW_COMMAND_AVAILABLE_ABILITIES); } + public void showAliases(String showName, int turnNum, PhaseStep step, TestPlayer player) { + show(showName, turnNum, step, player, SHOW_COMMAND_ALIASES); + } + /** * Removes all cards from player's library from the game. Usually this * should be used once before initialization to form the library in certain @@ -399,6 +416,19 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement @Override public void addCard(Zone gameZone, TestPlayer player, String cardName, int count, boolean tapped) { + // aliases for mage objects + String aliasName = ""; + boolean useAliasMultiNames = (count != 1); + if (cardName.contains("@")) { + aliasName = cardName.substring(cardName.indexOf("@") + 1); + cardName = cardName.substring(0, cardName.indexOf("@")); + } + + // one card = one aliase, massive adds can use auto-name + if (!useAliasMultiNames && !aliasName.isEmpty() && player.getAliasByName(aliasName) != null) { + Assert.fail("Can't add card " + cardName + " - alias " + aliasName + " already exists for " + player.getName()); + } + if (gameZone == Zone.BATTLEFIELD) { for (int i = 0; i < count; i++) { CardInfo cardInfo = CardRepository.instance.findCard(cardName); @@ -409,6 +439,10 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement PermanentCard p = new PermanentCard(card.copy(), player.getId(), currentGame); p.setTapped(tapped); getBattlefieldCards(player).add(p); + + if (!aliasName.isEmpty()) { + player.addAlias(player.generateAliasName(aliasName, useAliasMultiNames, i + 1), p.getId()); + } } } else { if (tapped) { @@ -422,6 +456,10 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement throw new AssertionError("Couldn't find a card: " + cardName); } cards.add(card); + + if (!aliasName.isEmpty()) { + player.addAlias(player.generateAliasName(aliasName, useAliasMultiNames, i + 1), card.getId()); + } } } } @@ -1246,7 +1284,6 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement } public enum StackClause { - WHILE_ON_STACK, WHILE_COPY_ON_STACK, WHILE_NOT_ON_STACK diff --git a/Mage.Tests/src/test/java/org/mage/test/testapi/TestAliases.java b/Mage.Tests/src/test/java/org/mage/test/testapi/TestAliases.java new file mode 100644 index 0000000000..0085722167 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/testapi/TestAliases.java @@ -0,0 +1,92 @@ +package org.mage.test.testapi; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author JayDi85 + */ + +public class TestAliases extends CardTestPlayerBase { + + @Test + public void test_DifferentZones() { + addCard(Zone.LIBRARY, playerA, "Swamp@lib", 1); + addCard(Zone.HAND, playerA, "Swamp@hand", 1); + addCard(Zone.BATTLEFIELD, playerA, "Swamp@battle", 1); + addCard(Zone.GRAVEYARD, playerA, "Swamp@grave", 1); + + showAliases("A aliases", 1, PhaseStep.UPKEEP, playerA); + checkAliasZone("lib", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "lib", Zone.LIBRARY); + checkAliasZone("hand", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "hand", Zone.HAND); + checkAliasZone("battle", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "battle", Zone.BATTLEFIELD); + checkAliasZone("grave", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "grave", Zone.GRAVEYARD); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + } + + @Test + public void test_MultipleNames() { + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 5); + addCard(Zone.BATTLEFIELD, playerA, "Island@isl", 5); + addCard(Zone.BATTLEFIELD, playerB, "Plains", 5); + addCard(Zone.BATTLEFIELD, playerB, "Mountain@mnt", 5); + + checkPermanentCount("Swamp must exists", 1, PhaseStep.UPKEEP, playerA, "Swamp", 5); + checkPermanentCount("Island must exists", 1, PhaseStep.UPKEEP, playerA, "Island", 5); + checkPermanentCount("Plains must exists", 1, PhaseStep.UPKEEP, playerB, "Plains", 5); + checkPermanentCount("Mountain must exists", 1, PhaseStep.UPKEEP, playerB, "Mountain", 5); + // + showAliases("A aliases", 1, PhaseStep.UPKEEP, playerA); + showAliases("B aliases", 1, PhaseStep.UPKEEP, playerB); + // A + checkAliasZone("Swamp must not", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Swamp", Zone.BATTLEFIELD, false); + checkAliasZone("Swamp.1 must not", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Swamp.1", Zone.BATTLEFIELD, false); + checkAliasZone("Island must not", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Island", Zone.BATTLEFIELD, false); + checkAliasZone("isl must not", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "isl", Zone.BATTLEFIELD, false); + checkAliasZone("isl.1 must", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "isl.1", Zone.BATTLEFIELD, true); + checkAliasZone("isl.2 must", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "isl.2", Zone.BATTLEFIELD, true); + checkAliasZone("isl.5 must", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "isl.5", Zone.BATTLEFIELD, true); + // B + checkAliasZone("Plains must not", 1, PhaseStep.PRECOMBAT_MAIN, playerB, "Plains", Zone.BATTLEFIELD, false); + checkAliasZone("Plains.1 must not", 1, PhaseStep.PRECOMBAT_MAIN, playerB, "Plains.1", Zone.BATTLEFIELD, false); + checkAliasZone("Plains must not", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Plains", Zone.BATTLEFIELD, false); + checkAliasZone("mnt must not", 1, PhaseStep.PRECOMBAT_MAIN, playerB, "mnt", Zone.BATTLEFIELD, false); + checkAliasZone("mnt.1 must", 1, PhaseStep.PRECOMBAT_MAIN, playerB, "mnt.1", Zone.BATTLEFIELD, true); + checkAliasZone("mnt.2 must", 1, PhaseStep.PRECOMBAT_MAIN, playerB, "mnt.2", Zone.BATTLEFIELD, true); + checkAliasZone("mnt.5 must", 1, PhaseStep.PRECOMBAT_MAIN, playerB, "mnt.5", Zone.BATTLEFIELD, true); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + } + + @Test + public void test_CastTarget() { + addCard(Zone.BATTLEFIELD, playerA, "Silvercoat Lion@lion", 5); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 5); + addCard(Zone.HAND, playerA, "Lightning Bolt", 3); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", "@lion.1"); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", "@lion.3"); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", "@lion.5"); + + showAliases("A aliases", 1, PhaseStep.POSTCOMBAT_MAIN, playerA); + checkAliasZone("1", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, "lion.1", Zone.BATTLEFIELD, false); + checkAliasZone("2", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, "lion.2", Zone.BATTLEFIELD, true); + checkAliasZone("3", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, "lion.3", Zone.BATTLEFIELD, false); + checkAliasZone("4", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, "lion.4", Zone.BATTLEFIELD, true); + checkAliasZone("5", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, "lion.5", Zone.BATTLEFIELD, false); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + assertGraveyardCount(playerA, "Lightning Bolt", 3); + assertGraveyardCount(playerA, "Silvercoat Lion", 3); + } +} \ No newline at end of file From 96187ad3c00f41a89353a2d751f4f800abff38f3 Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Mon, 3 Dec 2018 06:16:34 +0400 Subject: [PATCH 37/42] Tests: fixed TidehollowScullerTest; --- .../triggers/dies/TidehollowScullerTest.java | 113 +++++++++++++----- 1 file changed, 86 insertions(+), 27 deletions(-) diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/triggers/dies/TidehollowScullerTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/dies/TidehollowScullerTest.java index 3551024a71..7661906c85 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/triggers/dies/TidehollowScullerTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/dies/TidehollowScullerTest.java @@ -1,4 +1,3 @@ - package org.mage.test.cards.triggers.dies; import mage.constants.PhaseStep; @@ -7,8 +6,7 @@ import org.junit.Test; import org.mage.test.serverside.base.CardTestPlayerBase; /** - * - * @author LevelX2 + * @author LevelX2, JayDi85 */ public class TidehollowScullerTest extends CardTestPlayerBase { @@ -17,43 +15,104 @@ public class TidehollowScullerTest extends CardTestPlayerBase { * Test if the same Tidehollow Sculler is cast multiple times, the correct * corresponding exiled cards are returned */ + @Test - public void testCardFromHandWillBeExiled() { + public void test_CastOneCardFromHandWillBeExiled() { addCard(Zone.BATTLEFIELD, playerA, "Swamp", 2); addCard(Zone.BATTLEFIELD, playerA, "Island", 2); addCard(Zone.BATTLEFIELD, playerA, "Plains", 3); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 1); // Tidehollow Sculler {W}{B} // When Tidehollow Sculler enters the battlefield, target opponent reveals their hand and you choose a nonland card from it. Exile that card. // When Tidehollow Sculler leaves the battlefield, return the exiled card to its owner's hand. - addCard(Zone.HAND, playerA, "Tidehollow Sculler", 1); - // Boomerang {U}{U} - // Return target creature card from your graveyard to your hand. - addCard(Zone.HAND, playerA, "Boomerang", 1); - // Scout's Warning {W} - // The next creature card you play this turn can be played as though it had flash. - // Draw a card. - addCard(Zone.HAND, playerA, "Scout's Warning", 1); + addCard(Zone.HAND, playerA, "Tidehollow Sculler", 1); // 2/2 + addCard(Zone.HAND, playerA, "Lightning Bolt", 1); // {R} + // + addCard(Zone.HAND, playerB, "Bloodflow Connoisseur", 1); + + // cast and exile from hand + checkHandCardCount("B hand must have blood", 1, PhaseStep.UPKEEP, playerB, "Bloodflow Connoisseur", 1); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Tidehollow Sculler"); + addTarget(playerA, playerB); // choose opponent + setChoice(playerA, "Bloodflow Connoisseur"); // card to exile + checkHandCardCount("B hand must lost blood", 1, PhaseStep.BEGIN_COMBAT, playerB, "Bloodflow Connoisseur", 0); + + // destroy and return card to hand + checkPermanentCount("A must have tide", 1, PhaseStep.END_COMBAT, playerA, "Tidehollow Sculler", 1); + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Lightning Bolt", "Tidehollow Sculler"); + checkPermanentCount("A must lost tide", 1, PhaseStep.END_TURN, playerA, "Tidehollow Sculler", 0); + checkHandCardCount("B must return blood", 1, PhaseStep.END_TURN, playerB, "Bloodflow Connoisseur", 1); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + assertHandCount(playerB, "Bloodflow Connoisseur", 1); + assertPermanentCount(playerA, "Tidehollow Sculler", 0); + } + + @Test + public void test_CastTwoCardFromHandWillBeExiled() { + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 2); + addCard(Zone.BATTLEFIELD, playerA, "Island", 2); + addCard(Zone.BATTLEFIELD, playerA, "Plains", 3); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 2); + // Tidehollow Sculler {W}{B} + // When Tidehollow Sculler enters the battlefield, target opponent reveals their hand and you choose a nonland card from it. Exile that card. + // When Tidehollow Sculler leaves the battlefield, return the exiled card to its owner's hand. + addCard(Zone.HAND, playerA, "Tidehollow Sculler@tide", 2); // 2/2 + addCard(Zone.HAND, playerA, "Lightning Bolt", 2); // {R} + // addCard(Zone.HAND, playerB, "Bloodflow Connoisseur", 1); addCard(Zone.HAND, playerB, "Silvercoat Lion", 1); + // turn 1 - A + // cast 1 and exile from hand + checkHandCardCount("B hand must have blood", 1, PhaseStep.UPKEEP, playerB, "Bloodflow Connoisseur", 1); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Tidehollow Sculler"); - addTarget(playerA, "Bloodflow Connoisseur"); - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Boomerang", "Tidehollow Sculler"); - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Scout's Warning", null, "Boomerang"); - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Tidehollow Sculler", null, "", "When {this} leaves the battlefield, return the exiled card to its owner's hand."); - addTarget(playerA, "Silvercoat Lion"); + addTarget(playerA, playerB); // choose opponent + setChoice(playerA, "Bloodflow Connoisseur"); // card to exile + checkHandCardCount("B hand must lost blood", 1, PhaseStep.BEGIN_COMBAT, playerB, "Bloodflow Connoisseur", 0); + // cast 2 and exile from hand + checkHandCardCount("B hand must have lion", 1, PhaseStep.END_COMBAT, playerB, "Silvercoat Lion", 1); + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Tidehollow Sculler"); + addTarget(playerA, playerB); // choose opponent + setChoice(playerA, "Silvercoat Lion"); // card to exile + checkHandCardCount("B hand must lost lion", 1, PhaseStep.END_TURN, playerB, "Silvercoat Lion", 0); - setStopAt(1, PhaseStep.BEGIN_COMBAT); + // turn 2 - B + // destroy 1 and return card to hand + checkPermanentCount("A must have 2 tide", 2, PhaseStep.UPKEEP, playerA, "Tidehollow Sculler", 2); + checkHandCardCount("B hand must have 0 blood", 2, PhaseStep.UPKEEP, playerB, "Bloodflow Connoisseur", 0); + castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", "@tide.1"); + showHand("B hand", 2, PhaseStep.BEGIN_COMBAT, playerB); + checkPermanentCount("A must have 1 tide", 2, PhaseStep.BEGIN_COMBAT, playerA, "Tidehollow Sculler", 1); + checkHandCardCount("B hand must have 1 blood", 2, PhaseStep.BEGIN_COMBAT, playerB, "Bloodflow Connoisseur", 1); + // destroy 2 and return card to hand + checkPermanentCount("A must have 1 tide", 2, PhaseStep.END_COMBAT, playerA, "Tidehollow Sculler", 1); + checkHandCardCount("B hand must have 0 lion", 2, PhaseStep.END_COMBAT, playerB, "Silvercoat Lion", 0); + castSpell(2, PhaseStep.POSTCOMBAT_MAIN, playerA, "Lightning Bolt", "@tide.2"); + checkPermanentCount("A must have 0 tide", 2, PhaseStep.END_TURN, playerA, "Tidehollow Sculler", 0); + checkHandCardCount("B hand must have 1 lion", 2, PhaseStep.END_TURN, playerB, "Silvercoat Lion", 1); + + setStopAt(2, PhaseStep.END_TURN); execute(); - - assertGraveyardCount(playerA,"Boomerang", 1); - assertGraveyardCount(playerA,"Scout's Warning", 1); - assertHandCount(playerB, "Bloodflow Connoisseur", 0); // never comes back because first Tidehollow Sculler left battlefield before target was exiled - assertHandCount(playerB, "Silvercoat Lion", 0); - assertExileCount("Silvercoat Lion", 1); - - assertPermanentCount(playerA, "Tidehollow Sculler", 1); - + assertAllCommandsUsed(); } + + @Test + public void test_MultipleRuns() { + // test random selection by AI (must use direct select by alias, not AI) + for (int i = 1; i <= 10; i++) { + try { + this.reset(); + System.out.println("run " + i); + test_CastTwoCardFromHandWillBeExiled(); + } catch (Exception e) { + // + } + } + } + } \ No newline at end of file From 02b7e2cf107114ab9052fa787841bceebf413548 Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Fri, 7 Dec 2018 00:26:50 +0400 Subject: [PATCH 38/42] Refactor: extract card names compare logic (is empty name, is same name) Fixed last broken tests --- Mage.Sets/src/mage/cards/b/BileBlight.java | 12 +-- Mage.Sets/src/mage/cards/b/BrainPry.java | 10 +-- Mage.Sets/src/mage/cards/c/CabalTherapy.java | 8 +- Mage.Sets/src/mage/cards/c/CandlesOfLeng.java | 8 +- .../mage/cards/c/CircuDimirLobotomist.java | 3 +- .../src/mage/cards/c/ConundrumSphinx.java | 8 +- Mage.Sets/src/mage/cards/c/CorrosiveOoze.java | 23 ++--- .../mage/cards/c/CouncilOfTheAbsolute.java | 10 ++- .../src/mage/cards/c/CrownOfEmpires.java | 11 +-- Mage.Sets/src/mage/cards/c/CursedScroll.java | 8 +- .../src/mage/cards/d/DeclarationInStone.java | 14 +-- .../src/mage/cards/d/DementiaSliver.java | 13 +-- Mage.Sets/src/mage/cards/d/Denied.java | 14 +-- .../src/mage/cards/d/DetentionSphere.java | 9 +- Mage.Sets/src/mage/cards/d/DiviningWitch.java | 8 +- .../src/mage/cards/d/DragonlordKolaghan.java | 14 ++- Mage.Sets/src/mage/cards/e/EchoingCalm.java | 11 +-- .../src/mage/cards/e/EchoingCourage.java | 12 +-- Mage.Sets/src/mage/cards/e/EchoingDecay.java | 12 +-- Mage.Sets/src/mage/cards/e/EchoingRuin.java | 16 ++-- Mage.Sets/src/mage/cards/e/EchoingTruth.java | 10 +-- Mage.Sets/src/mage/cards/e/EvilTwin.java | 8 +- Mage.Sets/src/mage/cards/f/Foreshadow.java | 16 ++-- .../src/mage/cards/h/HomingLightning.java | 10 +-- .../src/mage/cards/i/IzzetStaticaster.java | 12 +-- .../src/mage/cards/l/LammastideWeave.java | 9 +- Mage.Sets/src/mage/cards/l/LiarsPendulum.java | 8 +- .../src/mage/cards/m/MagusOfTheScroll.java | 14 ++- Mage.Sets/src/mage/cards/p/PalisadeGiant.java | 37 ++++---- Mage.Sets/src/mage/cards/p/PetraSphinx.java | 7 +- Mage.Sets/src/mage/cards/p/Predict.java | 7 +- .../src/mage/cards/p/PyromancerAscension.java | 31 ++++--- Mage.Sets/src/mage/cards/r/ReflectorMage.java | 19 ++-- Mage.Sets/src/mage/cards/s/SearchTheCity.java | 8 +- .../src/mage/cards/s/SeverTheBloodline.java | 10 +-- .../src/mage/cards/s/SpoilsOfTheVault.java | 13 ++- .../src/mage/cards/t/ThoughtHemorrhage.java | 13 +-- Mage.Sets/src/mage/cards/t/TunnelVision.java | 13 ++- Mage.Sets/src/mage/cards/v/VexingArcanix.java | 8 +- .../mage/test/clientside/base/MageBase.java | 22 ++--- .../abilities/keywords/ManifestTest.java | 64 +++++++------ .../cards/abilities/keywords/MorphTest.java | 90 ++++++++++--------- .../ExileAndReturnUnderYourControl.java | 7 +- .../mage/test/cards/copy/CopySpellTest.java | 34 ++++--- .../test/cards/dynamicvalue/SweepTest.java | 4 +- .../enchantments/StarfieldOfNyxTest.java | 16 ++-- .../facedown/GhastlyConscriptionTest.java | 14 +-- .../cards/facedown/ObscuringAetherTest.java | 8 +- .../mage/test/cards/rules/CantCastTest.java | 5 +- .../single/fut/MuragandaPetroglyphsTest.java | 11 +-- .../ths/PurphorosGodOfTheForgeTest.java | 33 ++++--- .../dies/WhisperwoodElementalTest.java | 10 ++- .../java/org/mage/test/player/TestPlayer.java | 10 ++- .../base/impl/CardTestPlayerAPIImpl.java | 57 ++++++++++-- .../org/mage/test/testapi/TestAliases.java | 36 ++++++++ .../DestroyAllNamedPermanentsEffect.java | 8 +- Mage/src/main/java/mage/cards/CardImpl.java | 4 +- .../main/java/mage/constants/EmptyNames.java | 24 +++++ .../predicate/mageobject/NamePredicate.java | 24 +++-- .../game/command/emblems/MomirEmblem.java | 14 ++- .../mage/game/permanent/PermanentImpl.java | 36 +++++--- .../mage/game/permanent/PermanentToken.java | 15 +++- Mage/src/main/java/mage/util/CardUtil.java | 57 +++++++++--- 63 files changed, 614 insertions(+), 466 deletions(-) create mode 100644 Mage/src/main/java/mage/constants/EmptyNames.java diff --git a/Mage.Sets/src/mage/cards/b/BileBlight.java b/Mage.Sets/src/mage/cards/b/BileBlight.java index 64440f8042..0b23756190 100644 --- a/Mage.Sets/src/mage/cards/b/BileBlight.java +++ b/Mage.Sets/src/mage/cards/b/BileBlight.java @@ -1,7 +1,5 @@ - package mage.cards.b; -import java.util.UUID; import mage.MageObjectReference; import mage.abilities.Ability; import mage.abilities.effects.common.continuous.BoostAllEffect; @@ -12,15 +10,17 @@ import mage.constants.Duration; import mage.game.Game; import mage.game.permanent.Permanent; import mage.target.common.TargetCreaturePermanent; +import mage.util.CardUtil; + +import java.util.UUID; /** - * * @author Quercitron */ public final class BileBlight extends CardImpl { public BileBlight(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.INSTANT},"{B}{B}"); + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{B}{B}"); // Target creature and all creatures with the same name as that creature get -3/-3 until end of turn. @@ -56,12 +56,12 @@ class BileBlightEffect extends BoostAllEffect { if (this.affectedObjectsSet) { Permanent target = game.getPermanent(getTargetPointer().getFirst(game, source)); if (target != null) { - if (target.getName().isEmpty()) { // face down creature + if (CardUtil.haveEmptyName(target)) { // face down creature affectedObjectList.add(new MageObjectReference(target, game)); } else { String name = target.getName(); for (Permanent perm : game.getBattlefield().getActivePermanents(source.getControllerId(), game)) { - if (perm.getName().equals(name)) { + if (CardUtil.haveSameNames(perm.getName(), name)) { affectedObjectList.add(new MageObjectReference(perm, game)); } } diff --git a/Mage.Sets/src/mage/cards/b/BrainPry.java b/Mage.Sets/src/mage/cards/b/BrainPry.java index 2bebd65d8d..4628af7111 100644 --- a/Mage.Sets/src/mage/cards/b/BrainPry.java +++ b/Mage.Sets/src/mage/cards/b/BrainPry.java @@ -1,7 +1,5 @@ - package mage.cards.b; -import java.util.UUID; import mage.MageObject; import mage.abilities.Ability; import mage.abilities.effects.OneShotEffect; @@ -14,15 +12,17 @@ import mage.constants.Outcome; import mage.game.Game; import mage.players.Player; import mage.target.TargetPlayer; +import mage.util.CardUtil; + +import java.util.UUID; /** - * * @author Styxo */ public final class BrainPry extends CardImpl { public BrainPry(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.SORCERY},"{1}{B}"); + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{1}{B}"); //Name a nonland card. Target player reveals their hand. That player discards a card with that name. If he or she can't, you draw a card. this.getSpellAbility().addEffect((new ChooseACardNameEffect(ChooseACardNameEffect.TypeOfName.NON_LAND_NAME))); @@ -60,7 +60,7 @@ class BrainPryEffect extends OneShotEffect { if (targetPlayer != null && controller != null && sourceObject != null && cardName != null) { boolean hasDiscarded = false; for (Card card : targetPlayer.getHand().getCards(game)) { - if (card.getName().equals(cardName)) { + if (CardUtil.haveSameNames(card.getName(), cardName)) { targetPlayer.discard(card, source, game); hasDiscarded = true; break; diff --git a/Mage.Sets/src/mage/cards/c/CabalTherapy.java b/Mage.Sets/src/mage/cards/c/CabalTherapy.java index 70d7f7863c..ba03643b0a 100644 --- a/Mage.Sets/src/mage/cards/c/CabalTherapy.java +++ b/Mage.Sets/src/mage/cards/c/CabalTherapy.java @@ -15,11 +15,11 @@ import mage.game.Game; import mage.players.Player; import mage.target.TargetPlayer; import mage.target.common.TargetControlledCreaturePermanent; +import mage.util.CardUtil; import java.util.UUID; /** - * * @author jonubuu */ public final class CabalTherapy extends CardImpl { @@ -71,13 +71,13 @@ class CabalTherapyEffect extends OneShotEffect { for (Card card : hand.getCards(game)) { if (card.isSplitCard()) { SplitCard splitCard = (SplitCard) card; - if (splitCard.getLeftHalfCard().getName().equals(cardName)) { + if (CardUtil.haveSameNames(splitCard.getLeftHalfCard().getName(), cardName)) { targetPlayer.discard(card, source, game); - } else if (splitCard.getRightHalfCard().getName().equals(cardName)) { + } else if (CardUtil.haveSameNames(splitCard.getRightHalfCard().getName(), cardName)) { targetPlayer.discard(card, source, game); } } - if (card.getName().equals(cardName)) { + if (CardUtil.haveSameNames(card.getName(), cardName)) { targetPlayer.discard(card, source, game); } } diff --git a/Mage.Sets/src/mage/cards/c/CandlesOfLeng.java b/Mage.Sets/src/mage/cards/c/CandlesOfLeng.java index a6a8de33be..ca0dd9384f 100644 --- a/Mage.Sets/src/mage/cards/c/CandlesOfLeng.java +++ b/Mage.Sets/src/mage/cards/c/CandlesOfLeng.java @@ -1,7 +1,5 @@ - package mage.cards.c; -import java.util.UUID; import mage.MageObject; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; @@ -17,9 +15,11 @@ import mage.constants.Outcome; import mage.constants.Zone; import mage.game.Game; import mage.players.Player; +import mage.util.CardUtil; + +import java.util.UUID; /** - * * @author Styxo */ public final class CandlesOfLeng extends CardImpl { @@ -73,7 +73,7 @@ class CandlesOfLengEffect extends OneShotEffect { controller.revealCards(sourceObject.getName(), cards, game); boolean hasTheSameName = false; for (UUID uuid : controller.getGraveyard()) { - if (card.getName().equals(game.getCard(uuid).getName())) { + if (CardUtil.haveSameNames(card, game.getCard(uuid))) { hasTheSameName = true; } } diff --git a/Mage.Sets/src/mage/cards/c/CircuDimirLobotomist.java b/Mage.Sets/src/mage/cards/c/CircuDimirLobotomist.java index 84ec986fd2..8d8972b26a 100644 --- a/Mage.Sets/src/mage/cards/c/CircuDimirLobotomist.java +++ b/Mage.Sets/src/mage/cards/c/CircuDimirLobotomist.java @@ -1,4 +1,3 @@ - package mage.cards.c; import mage.MageInt; @@ -136,7 +135,7 @@ class CircuDimirLobotomistRuleModifyingEffect extends ContinuousRuleModifyingEff ExileZone exileZone = game.getExile().getExileZone(CardUtil.getCardExileZoneId(game, source)); if ((exileZone != null)) { for (Card card : exileZone.getCards(game)) { - if ((card.getName().equals(object.getName()))) { + if (CardUtil.haveSameNames(card, object)) { return true; } } diff --git a/Mage.Sets/src/mage/cards/c/ConundrumSphinx.java b/Mage.Sets/src/mage/cards/c/ConundrumSphinx.java index dc03cb3c69..ae2ce4eaaf 100644 --- a/Mage.Sets/src/mage/cards/c/ConundrumSphinx.java +++ b/Mage.Sets/src/mage/cards/c/ConundrumSphinx.java @@ -1,7 +1,5 @@ - package mage.cards.c; -import java.util.UUID; import mage.MageInt; import mage.MageObject; import mage.abilities.Ability; @@ -18,9 +16,11 @@ import mage.constants.SubType; import mage.constants.Zone; import mage.game.Game; import mage.players.Player; +import mage.util.CardUtil; + +import java.util.UUID; /** - * * @author BetaSteward_at_googlemail.com */ public final class ConundrumSphinx extends CardImpl { @@ -85,7 +85,7 @@ class ConundrumSphinxEffect extends OneShotEffect { if (card != null) { Cards cards = new CardsImpl(card); player.revealCards(source, player.getName(), cards, game); - if (card.getName().equals(cardName)) { + if (CardUtil.haveSameNames(card.getName(), cardName)) { player.moveCards(cards, Zone.HAND, source, game); } else { player.putCardsOnBottomOfLibrary(cards, game, source, false); diff --git a/Mage.Sets/src/mage/cards/c/CorrosiveOoze.java b/Mage.Sets/src/mage/cards/c/CorrosiveOoze.java index ac19501f19..b135e5b7e7 100644 --- a/Mage.Sets/src/mage/cards/c/CorrosiveOoze.java +++ b/Mage.Sets/src/mage/cards/c/CorrosiveOoze.java @@ -1,13 +1,5 @@ - package mage.cards.c; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.UUID; import mage.MageInt; import mage.MageObjectReference; import mage.abilities.Ability; @@ -18,12 +10,7 @@ import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.CreateDelayedTriggeredAbilityEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.CardType; -import mage.constants.Outcome; -import mage.constants.SubType; -import mage.constants.TurnPhase; -import mage.constants.WatcherScope; -import mage.constants.Zone; +import mage.constants.*; import mage.filter.common.FilterCreaturePermanent; import mage.filter.predicate.permanent.EquippedPredicate; import mage.game.Game; @@ -31,10 +18,12 @@ import mage.game.events.GameEvent; import mage.game.events.ZoneChangeEvent; import mage.game.permanent.Permanent; import mage.players.Player; +import mage.util.CardUtil; import mage.watchers.Watcher; +import java.util.*; + /** - * * @author rscoates */ public final class CorrosiveOoze extends CardImpl { @@ -156,7 +145,7 @@ class CorrosiveOozeCombatWatcher extends Watcher { if (event.getType() == GameEvent.EventType.BLOCKER_DECLARED) { Permanent attacker = game.getPermanent(event.getTargetId()); Permanent blocker = game.getPermanent(event.getSourceId()); - if (attacker != null && attacker.getName().equals("Corrosive Ooze")) { // To check for name is not working if Ooze is copied but name changed + if (attacker != null && CardUtil.haveSameNames(attacker.getName(), "Corrosive Ooze")) { // To check for name is not working if Ooze is copied but name changed if (blocker != null && hasAttachedEquipment(game, blocker)) { MageObjectReference oozeMor = new MageObjectReference(attacker, game); HashSet relatedCreatures = oozeBlocksOrBlocked.getOrDefault(oozeMor, new HashSet<>()); @@ -164,7 +153,7 @@ class CorrosiveOozeCombatWatcher extends Watcher { oozeBlocksOrBlocked.put(oozeMor, relatedCreatures); } } - if (blocker != null && blocker.getName().equals("Corrosive Ooze")) { + if (blocker != null && CardUtil.haveSameNames(blocker.getName(), "Corrosive Ooze")) { if (attacker != null && hasAttachedEquipment(game, attacker)) { MageObjectReference oozeMor = new MageObjectReference(blocker, game); HashSet relatedCreatures = oozeBlocksOrBlocked.getOrDefault(oozeMor, new HashSet<>()); diff --git a/Mage.Sets/src/mage/cards/c/CouncilOfTheAbsolute.java b/Mage.Sets/src/mage/cards/c/CouncilOfTheAbsolute.java index b9b2f943eb..bf52716087 100644 --- a/Mage.Sets/src/mage/cards/c/CouncilOfTheAbsolute.java +++ b/Mage.Sets/src/mage/cards/c/CouncilOfTheAbsolute.java @@ -1,6 +1,5 @@ package mage.cards.c; -import java.util.UUID; import mage.MageInt; import mage.MageObject; import mage.abilities.Ability; @@ -19,8 +18,9 @@ import mage.game.events.GameEvent; import mage.game.events.GameEvent.EventType; import mage.util.CardUtil; +import java.util.UUID; + /** - * * @author LevelX2 */ public final class CouncilOfTheAbsolute extends CardImpl { @@ -92,7 +92,8 @@ class CouncilOfTheAbsoluteReplacementEffect extends ContinuousRuleModifyingEffec public boolean applies(GameEvent event, Ability source, Game game) { if (game.getOpponents(source.getControllerId()).contains(event.getPlayerId())) { MageObject object = game.getObject(event.getSourceId()); - if (object != null && object.getName().equals(game.getState().getValue(source.getSourceId().toString() + ChooseACardNameEffect.INFO_KEY))) { + String needName = (String) game.getState().getValue(source.getSourceId().toString() + ChooseACardNameEffect.INFO_KEY); + if (object != null && CardUtil.haveSameNames(object.getName(), needName)) { return true; } } @@ -122,7 +123,8 @@ class CouncilOfTheAbsoluteCostReductionEffect extends CostModificationEffectImpl if ((abilityToModify instanceof SpellAbility) && abilityToModify.isControlledBy(source.getControllerId())) { Card card = game.getCard(abilityToModify.getSourceId()); - return card.getName().equals(game.getState().getValue(source.getSourceId().toString() + ChooseACardNameEffect.INFO_KEY)); + String needName = (String) game.getState().getValue(source.getSourceId().toString() + ChooseACardNameEffect.INFO_KEY); + return CardUtil.haveSameNames(card.getName(), needName); } return false; } diff --git a/Mage.Sets/src/mage/cards/c/CrownOfEmpires.java b/Mage.Sets/src/mage/cards/c/CrownOfEmpires.java index 91c4c727e5..f5bfddfe29 100644 --- a/Mage.Sets/src/mage/cards/c/CrownOfEmpires.java +++ b/Mage.Sets/src/mage/cards/c/CrownOfEmpires.java @@ -1,7 +1,5 @@ - package mage.cards.c; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.Mode; import mage.abilities.common.SimpleActivatedAbility; @@ -17,6 +15,9 @@ import mage.game.Game; import mage.game.permanent.Permanent; import mage.target.common.TargetCreaturePermanent; import mage.target.targetpointer.FixedTarget; +import mage.util.CardUtil; + +import java.util.UUID; /** * @author nantuko @@ -24,7 +25,7 @@ import mage.target.targetpointer.FixedTarget; public final class CrownOfEmpires extends CardImpl { public CrownOfEmpires(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.ARTIFACT},"{2}"); + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{2}"); // {3}, {tap}: Tap target creature. Gain control of that creature instead if you control artifacts named Scepter of Empires and Throne of Empires. Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new CrownOfEmpiresEffect(), new GenericManaCost(3)); @@ -60,9 +61,9 @@ class CrownOfEmpiresEffect extends OneShotEffect { boolean scepter = false; boolean throne = false; for (Permanent permanent : game.getBattlefield().getAllActivePermanents(source.getControllerId())) { - if (permanent.getName().equals("Scepter of Empires")) { + if (CardUtil.haveSameNames(permanent.getName(), "Scepter of Empires")) { scepter = true; - } else if (permanent.getName().equals("Throne of Empires")) { + } else if (CardUtil.haveSameNames(permanent.getName(), "Throne of Empires")) { throne = true; } if (scepter && throne) break; diff --git a/Mage.Sets/src/mage/cards/c/CursedScroll.java b/Mage.Sets/src/mage/cards/c/CursedScroll.java index 0278616567..e0ec2592d7 100644 --- a/Mage.Sets/src/mage/cards/c/CursedScroll.java +++ b/Mage.Sets/src/mage/cards/c/CursedScroll.java @@ -1,6 +1,5 @@ package mage.cards.c; -import java.util.UUID; import mage.MageObject; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; @@ -16,11 +15,12 @@ import mage.game.Game; import mage.game.permanent.Permanent; import mage.players.Player; import mage.target.common.TargetAnyTarget; +import mage.util.CardUtil; + +import java.util.UUID; /** - * * @author jeffwadsworth - * */ public final class CursedScroll extends CardImpl { @@ -70,7 +70,7 @@ class CursedScrollEffect extends OneShotEffect { } revealed.add(card); controller.revealCards(sourceObject.getIdName(), revealed, game); - if (card.getName().equals(cardName)) { + if (CardUtil.haveSameNames(card.getName(), cardName)) { Permanent creature = game.getPermanent(targetPointer.getFirst(game, source)); if (creature != null) { creature.damage(2, source.getSourceId(), game, false, true); diff --git a/Mage.Sets/src/mage/cards/d/DeclarationInStone.java b/Mage.Sets/src/mage/cards/d/DeclarationInStone.java index f53ddd93ae..45cfb5b117 100644 --- a/Mage.Sets/src/mage/cards/d/DeclarationInStone.java +++ b/Mage.Sets/src/mage/cards/d/DeclarationInStone.java @@ -1,9 +1,5 @@ - package mage.cards.d; -import java.util.HashSet; -import java.util.Set; -import java.util.UUID; import mage.MageObject; import mage.abilities.Ability; import mage.abilities.effects.OneShotEffect; @@ -20,9 +16,13 @@ import mage.game.permanent.PermanentToken; import mage.game.permanent.token.ClueArtifactToken; import mage.players.Player; import mage.target.common.TargetCreaturePermanent; +import mage.util.CardUtil; + +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; /** - * * @author escplan9 (Derek Monturo - dmontur1 at gmail dot com) */ public final class DeclarationInStone extends CardImpl { @@ -66,7 +66,7 @@ class DeclarationInStoneEffect extends OneShotEffect { if (targetPermanent != null) { Set cardsToExile = new HashSet<>(); int nonTokenCount = 0; - if (targetPermanent.getName().isEmpty()) { // face down creature + if (CardUtil.haveEmptyName(targetPermanent)) { // face down creature cardsToExile.add(targetPermanent); if (!(targetPermanent instanceof PermanentToken)) { nonTokenCount++; @@ -78,7 +78,7 @@ class DeclarationInStoneEffect extends OneShotEffect { } for (Permanent permanent : game.getBattlefield().getAllActivePermanents(StaticFilters.FILTER_PERMANENT_CREATURES, targetPermanent.getControllerId(), game)) { if (!permanent.getId().equals(targetPermanent.getId()) - && permanent.getName().equals(targetPermanent.getName())) { + && CardUtil.haveSameNames(permanent, targetPermanent)) { cardsToExile.add(permanent); // exiled count only matters for non-tokens if (!(permanent instanceof PermanentToken)) { diff --git a/Mage.Sets/src/mage/cards/d/DementiaSliver.java b/Mage.Sets/src/mage/cards/d/DementiaSliver.java index b6ed719123..40719b4412 100644 --- a/Mage.Sets/src/mage/cards/d/DementiaSliver.java +++ b/Mage.Sets/src/mage/cards/d/DementiaSliver.java @@ -1,6 +1,5 @@ package mage.cards.d; -import java.util.UUID; import mage.MageInt; import mage.MageObject; import mage.abilities.Ability; @@ -18,9 +17,11 @@ import mage.filter.predicate.mageobject.SubtypePredicate; import mage.game.Game; import mage.players.Player; import mage.target.common.TargetOpponent; +import mage.util.CardUtil; + +import java.util.UUID; /** - * * @author fireshoes */ public final class DementiaSliver extends CardImpl { @@ -44,9 +45,9 @@ public final class DementiaSliver extends CardImpl { this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new GainAbilityAllEffect(gainedAbility, Duration.WhileOnBattlefield, filter, "All Slivers have \"{T}: Choose a card name. " - + "Target opponent reveals a card at random from their hand." - + " If that card has the chosen name, that player discards it." - + " Activate this ability only during your turn.\"" + + "Target opponent reveals a card at random from their hand." + + " If that card has the chosen name, that player discards it." + + " Activate this ability only during your turn.\"" ) )); } @@ -84,7 +85,7 @@ class DementiaSliverEffect extends OneShotEffect { if (card != null) { revealed.add(card); opponent.revealCards(sourceObject.getName(), revealed, game); - if (card.getName().equals(cardName)) { + if (CardUtil.haveSameNames(card.getName(), cardName)) { opponent.discard(card, source, game); } } diff --git a/Mage.Sets/src/mage/cards/d/Denied.java b/Mage.Sets/src/mage/cards/d/Denied.java index 534d1a1a5f..a896f1e163 100644 --- a/Mage.Sets/src/mage/cards/d/Denied.java +++ b/Mage.Sets/src/mage/cards/d/Denied.java @@ -1,7 +1,5 @@ - package mage.cards.d; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.ChooseACardNameEffect; @@ -11,19 +9,21 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Outcome; import mage.game.Game; -import mage.players.Player; import mage.game.stack.Spell; +import mage.players.Player; import mage.target.TargetSpell; +import mage.util.CardUtil; + +import java.util.UUID; /** - * * @author L_J */ public final class Denied extends CardImpl { public Denied(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.INSTANT},"{U}"); - + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{U}"); + // Choose a card name, then target spell's controller reveals their hand. If a card with the chosen name is revealed this way, counter that spell. this.getSpellAbility().addEffect(new ChooseACardNameEffect(ChooseACardNameEffect.TypeOfName.ALL)); this.getSpellAbility().addEffect(new DeniedEffect()); @@ -63,7 +63,7 @@ class DeniedEffect extends OneShotEffect { player.revealCards("Denied!", player.getHand(), game, true); String namedCard = (String) object; for (Card card : player.getHand().getCards(game)) { - if (card != null && card.getName().equals(namedCard)) { + if (card != null && CardUtil.haveSameNames(card.getName(), namedCard)) { game.getStack().counter(targetSpell.getId(), source.getSourceId(), game); break; } diff --git a/Mage.Sets/src/mage/cards/d/DetentionSphere.java b/Mage.Sets/src/mage/cards/d/DetentionSphere.java index 5c7466c0b9..64943101ae 100644 --- a/Mage.Sets/src/mage/cards/d/DetentionSphere.java +++ b/Mage.Sets/src/mage/cards/d/DetentionSphere.java @@ -1,7 +1,5 @@ - package mage.cards.d; -import java.util.UUID; import mage.MageObject; import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldTriggeredAbility; @@ -25,8 +23,9 @@ import mage.target.TargetPermanent; import mage.util.CardUtil; import org.apache.log4j.Logger; +import java.util.UUID; + /** - * * @author LevelX2 */ public final class DetentionSphere extends CardImpl { @@ -81,12 +80,12 @@ class DetentionSphereEntersEffect extends OneShotEffect { MageObject sourceObject = game.getObject(source.getSourceId()); if (sourceObject != null && exileId != null && targetPermanent != null && controller != null) { - if (targetPermanent.getName().isEmpty()) { // face down creature + if (CardUtil.haveEmptyName(targetPermanent)) { // face down creature controller.moveCardToExileWithInfo(targetPermanent, exileId, sourceObject.getIdName(), source.getSourceId(), game, Zone.BATTLEFIELD, true); } else { String name = targetPermanent.getName(); for (Permanent permanent : game.getBattlefield().getActivePermanents(source.getControllerId(), game)) { - if (permanent != null && permanent.getName().equals(name)) { + if (permanent != null && CardUtil.haveSameNames(permanent.getName(), name)) { controller.moveCardToExileWithInfo(permanent, exileId, sourceObject.getIdName(), source.getSourceId(), game, Zone.BATTLEFIELD, true); } } diff --git a/Mage.Sets/src/mage/cards/d/DiviningWitch.java b/Mage.Sets/src/mage/cards/d/DiviningWitch.java index 1a7c10667c..39e231aa20 100644 --- a/Mage.Sets/src/mage/cards/d/DiviningWitch.java +++ b/Mage.Sets/src/mage/cards/d/DiviningWitch.java @@ -1,7 +1,5 @@ - package mage.cards.d; -import java.util.UUID; import mage.MageInt; import mage.MageObject; import mage.abilities.Ability; @@ -21,9 +19,11 @@ import mage.constants.Zone; import mage.game.Game; import mage.players.Player; import mage.target.common.TargetCardInHand; +import mage.util.CardUtil; + +import java.util.UUID; /** - * * @author maxlebedev */ public final class DiviningWitch extends CardImpl { @@ -92,7 +92,7 @@ public final class DiviningWitch extends CardImpl { if (card != null) { cardsToReaveal.add(card); // Put that card into your hand - if (card.getName().equals(name)) { + if (CardUtil.haveSameNames(card.getName(), name)) { cardToHand = card; break; } diff --git a/Mage.Sets/src/mage/cards/d/DragonlordKolaghan.java b/Mage.Sets/src/mage/cards/d/DragonlordKolaghan.java index dab3732b81..5c793c9bf7 100644 --- a/Mage.Sets/src/mage/cards/d/DragonlordKolaghan.java +++ b/Mage.Sets/src/mage/cards/d/DragonlordKolaghan.java @@ -1,7 +1,5 @@ - package mage.cards.d; -import java.util.UUID; import mage.MageInt; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.common.SimpleStaticAbility; @@ -13,20 +11,18 @@ import mage.abilities.keyword.HasteAbility; import mage.cards.Card; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.CardType; -import mage.constants.SubType; -import mage.constants.Duration; -import mage.constants.SuperType; -import mage.constants.Zone; +import mage.constants.*; import mage.filter.StaticFilters; import mage.game.Game; import mage.game.events.GameEvent; import mage.game.stack.Spell; import mage.players.Player; import mage.target.targetpointer.FixedTarget; +import mage.util.CardUtil; + +import java.util.UUID; /** - * * @author LevelX2 */ public final class DragonlordKolaghan extends CardImpl { @@ -95,7 +91,7 @@ class DragonlordKolaghanTriggeredAbility extends TriggeredAbilityImpl { Player opponent = game.getPlayer(event.getPlayerId()); boolean sameName = false; for (Card graveCard : opponent.getGraveyard().getCards(game)) { - if (graveCard.getName().equals(spell.getName())) { + if (CardUtil.haveSameNames(graveCard, spell)) { sameName = true; break; } diff --git a/Mage.Sets/src/mage/cards/e/EchoingCalm.java b/Mage.Sets/src/mage/cards/e/EchoingCalm.java index badff50bda..681ad75a60 100644 --- a/Mage.Sets/src/mage/cards/e/EchoingCalm.java +++ b/Mage.Sets/src/mage/cards/e/EchoingCalm.java @@ -1,7 +1,5 @@ - package mage.cards.e; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.effects.OneShotEffect; import mage.cards.CardImpl; @@ -12,6 +10,9 @@ import mage.game.Game; import mage.game.permanent.Permanent; import mage.players.Player; import mage.target.common.TargetEnchantmentPermanent; +import mage.util.CardUtil; + +import java.util.UUID; /** * @author Loki @@ -19,7 +20,7 @@ import mage.target.common.TargetEnchantmentPermanent; public final class EchoingCalm extends CardImpl { public EchoingCalm(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.INSTANT},"{1}{W}"); + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{W}"); // Destroy target enchantment and all other enchantments with the same name as that enchantment. @@ -58,9 +59,9 @@ class EchoingCalmEffect extends OneShotEffect { Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); if (controller != null && permanent != null) { permanent.destroy(source.getSourceId(), game, false); - if (!permanent.getName().isEmpty()) { // in case of face down enchantment creature + if (!CardUtil.haveEmptyName(permanent)) { // in case of face down enchantment creature for (Permanent perm : game.getBattlefield().getActivePermanents(source.getControllerId(), game)) { - if (!perm.getId().equals(permanent.getId()) && perm.getName().equals(permanent.getName()) && perm.isEnchantment()) { + if (!perm.getId().equals(permanent.getId()) && CardUtil.haveSameNames(perm, permanent) && perm.isEnchantment()) { perm.destroy(source.getSourceId(), game, false); } } diff --git a/Mage.Sets/src/mage/cards/e/EchoingCourage.java b/Mage.Sets/src/mage/cards/e/EchoingCourage.java index abacfc3385..b256f031a2 100644 --- a/Mage.Sets/src/mage/cards/e/EchoingCourage.java +++ b/Mage.Sets/src/mage/cards/e/EchoingCourage.java @@ -1,7 +1,5 @@ - package mage.cards.e; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.effects.ContinuousEffect; import mage.abilities.effects.OneShotEffect; @@ -17,15 +15,17 @@ import mage.filter.predicate.permanent.PermanentIdPredicate; import mage.game.Game; import mage.game.permanent.Permanent; import mage.target.common.TargetCreaturePermanent; +import mage.util.CardUtil; + +import java.util.UUID; /** - * * @author LevelX2 */ public final class EchoingCourage extends CardImpl { public EchoingCourage(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.INSTANT},"{1}{G}"); + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{G}"); // Target creature and all other creatures with the same name as that creature get +2/+2 until end of turn. @@ -64,12 +64,12 @@ class EchoingCourageEffect extends OneShotEffect { Permanent targetPermanent = game.getPermanent(targetPointer.getFirst(game, source)); if (targetPermanent != null) { FilterCreaturePermanent filter = new FilterCreaturePermanent(); - if (targetPermanent.getName().isEmpty()) { + if (CardUtil.haveEmptyName(targetPermanent)) { filter.add(new PermanentIdPredicate(targetPermanent.getId())); // if no name (face down creature) only the creature itself is selected } else { filter.add(new NamePredicate(targetPermanent.getName())); } - ContinuousEffect effect = new BoostAllEffect(2,2, Duration.EndOfTurn, filter, false); + ContinuousEffect effect = new BoostAllEffect(2, 2, Duration.EndOfTurn, filter, false); game.addEffect(effect, source); return true; } diff --git a/Mage.Sets/src/mage/cards/e/EchoingDecay.java b/Mage.Sets/src/mage/cards/e/EchoingDecay.java index 2d4523d0dc..89d14b83f8 100644 --- a/Mage.Sets/src/mage/cards/e/EchoingDecay.java +++ b/Mage.Sets/src/mage/cards/e/EchoingDecay.java @@ -1,7 +1,5 @@ - package mage.cards.e; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.effects.ContinuousEffect; import mage.abilities.effects.OneShotEffect; @@ -17,15 +15,17 @@ import mage.filter.predicate.permanent.PermanentIdPredicate; import mage.game.Game; import mage.game.permanent.Permanent; import mage.target.common.TargetCreaturePermanent; +import mage.util.CardUtil; + +import java.util.UUID; /** - * * @author fireshoes */ public final class EchoingDecay extends CardImpl { public EchoingDecay(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.INSTANT},"{1}{B}"); + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{B}"); // Target creature and all other creatures with the same name as that creature get -2/-2 until end of turn. this.getSpellAbility().addEffect(new EchoingDecayEffect()); @@ -63,12 +63,12 @@ class EchoingDecayEffect extends OneShotEffect { Permanent targetPermanent = game.getPermanent(targetPointer.getFirst(game, source)); if (targetPermanent != null) { FilterCreaturePermanent filter = new FilterCreaturePermanent(); - if (targetPermanent.getName().isEmpty()) { + if (CardUtil.haveEmptyName(targetPermanent)) { filter.add(new PermanentIdPredicate(targetPermanent.getId())); // if no name (face down creature) only the creature itself is selected } else { filter.add(new NamePredicate(targetPermanent.getName())); } - ContinuousEffect effect = new BoostAllEffect(-2,-2, Duration.EndOfTurn, filter, false); + ContinuousEffect effect = new BoostAllEffect(-2, -2, Duration.EndOfTurn, filter, false); game.addEffect(effect, source); return true; } diff --git a/Mage.Sets/src/mage/cards/e/EchoingRuin.java b/Mage.Sets/src/mage/cards/e/EchoingRuin.java index abc6a98bc0..738dba3ba8 100644 --- a/Mage.Sets/src/mage/cards/e/EchoingRuin.java +++ b/Mage.Sets/src/mage/cards/e/EchoingRuin.java @@ -1,7 +1,5 @@ - package mage.cards.e; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.effects.OneShotEffect; import mage.cards.CardImpl; @@ -14,21 +12,23 @@ import mage.game.Game; import mage.game.permanent.Permanent; import mage.players.Player; import mage.target.TargetPermanent; +import mage.util.CardUtil; + +import java.util.UUID; /** - * * @author fireshoes */ public final class EchoingRuin extends CardImpl { - private static final FilterPermanent filter = new FilterPermanent("artifact"); + private static final FilterPermanent filter = new FilterPermanent("artifact"); + - static { filter.add(new CardTypePredicate(CardType.ARTIFACT)); } public EchoingRuin(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.SORCERY},"{1}{R}"); + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{1}{R}"); // Destroy target artifact and all other artifacts with the same name as that artifact. this.getSpellAbility().addTarget(new TargetPermanent(filter)); @@ -66,9 +66,9 @@ class EchoingRuinEffect extends OneShotEffect { Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); if (controller != null && permanent != null) { permanent.destroy(source.getSourceId(), game, false); - if (!permanent.getName().isEmpty()) { // in case of face down artifact creature + if (!CardUtil.haveEmptyName(permanent)) { // in case of face down artifact creature for (Permanent perm : game.getBattlefield().getActivePermanents(source.getControllerId(), game)) { - if (!perm.getId().equals(permanent.getId()) && perm.getName().equals(permanent.getName()) && perm.isArtifact()) { + if (!perm.getId().equals(permanent.getId()) && CardUtil.haveSameNames(perm, permanent) && perm.isArtifact()) { perm.destroy(source.getSourceId(), game, false); } } diff --git a/Mage.Sets/src/mage/cards/e/EchoingTruth.java b/Mage.Sets/src/mage/cards/e/EchoingTruth.java index d48bd10ee2..d7a542dbc4 100644 --- a/Mage.Sets/src/mage/cards/e/EchoingTruth.java +++ b/Mage.Sets/src/mage/cards/e/EchoingTruth.java @@ -1,7 +1,5 @@ - package mage.cards.e; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.Mode; import mage.abilities.effects.OneShotEffect; @@ -20,15 +18,17 @@ import mage.game.permanent.Permanent; import mage.players.Player; import mage.target.Target; import mage.target.common.TargetNonlandPermanent; +import mage.util.CardUtil; + +import java.util.UUID; /** - * * @author LevelX2 */ public final class EchoingTruth extends CardImpl { public EchoingTruth(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.INSTANT},"{1}{U}"); + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{U}"); // Return target nonland permanent and all other permanents with the same name as that permanent to their owners' hands. Target target = new TargetNonlandPermanent(); @@ -67,7 +67,7 @@ class ReturnToHandAllNamedPermanentsEffect extends OneShotEffect { Permanent permanent = game.getPermanent(source.getFirstTarget()); if (controller != null && permanent != null) { FilterPermanent filter = new FilterPermanent(); - if (permanent.getName().isEmpty()) { + if (CardUtil.haveEmptyName(permanent)) { filter.add(new PermanentIdPredicate(permanent.getId())); // if no name (face down creature) only the creature itself is selected } else { filter.add(new NamePredicate(permanent.getName())); diff --git a/Mage.Sets/src/mage/cards/e/EvilTwin.java b/Mage.Sets/src/mage/cards/e/EvilTwin.java index 51a77803e5..cda27f37ff 100644 --- a/Mage.Sets/src/mage/cards/e/EvilTwin.java +++ b/Mage.Sets/src/mage/cards/e/EvilTwin.java @@ -1,7 +1,5 @@ - package mage.cards.e; -import java.util.UUID; import mage.MageInt; import mage.MageObject; import mage.abilities.Ability; @@ -24,10 +22,12 @@ import mage.filter.predicate.ObjectSourcePlayerPredicate; import mage.game.Game; import mage.game.permanent.Permanent; import mage.target.common.TargetCreaturePermanent; +import mage.util.CardUtil; import mage.util.functions.ApplyToPermanent; +import java.util.UUID; + /** - * * @author BetaSteward */ public final class EvilTwin extends CardImpl { @@ -90,7 +90,7 @@ class EvilTwinPredicate implements ObjectSourcePlayerPredicate input, Game game) { Permanent permanent = input.getObject(); Permanent twin = game.getPermanent(input.getSourceId()); - return permanent != null && twin != null && !twin.getName().isEmpty() && permanent.getName().equals(twin.getName()); + return CardUtil.haveSameNames(permanent, twin); } @Override diff --git a/Mage.Sets/src/mage/cards/f/Foreshadow.java b/Mage.Sets/src/mage/cards/f/Foreshadow.java index 6299ff05b7..e1d86d5e56 100644 --- a/Mage.Sets/src/mage/cards/f/Foreshadow.java +++ b/Mage.Sets/src/mage/cards/f/Foreshadow.java @@ -1,13 +1,11 @@ - package mage.cards.f; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.common.delayed.AtTheBeginOfNextUpkeepDelayedTriggeredAbility; import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.ChooseACardNameEffect; import mage.abilities.effects.common.CreateDelayedTriggeredAbilityEffect; import mage.abilities.effects.common.DrawCardSourceControllerEffect; -import mage.abilities.effects.common.ChooseACardNameEffect; import mage.cards.Card; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -17,21 +15,23 @@ import mage.constants.Zone; import mage.game.Game; import mage.players.Player; import mage.target.common.TargetOpponent; +import mage.util.CardUtil; + +import java.util.UUID; /** - * * @author Quercitron & L_J */ public final class Foreshadow extends CardImpl { public Foreshadow(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.INSTANT},"{1}{U}"); + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{U}"); // Choose a card name, then target opponent puts the top card of their library into their graveyard. If that card has the chosen name, you draw a card. this.getSpellAbility().addEffect(new ChooseACardNameEffect(ChooseACardNameEffect.TypeOfName.ALL)); this.getSpellAbility().addEffect(new ForeshadowEffect()); this.getSpellAbility().addTarget(new TargetOpponent()); - + // Draw a card at the beginning of the next turn's upkeep. this.getSpellAbility().addEffect(new CreateDelayedTriggeredAbilityEffect( new AtTheBeginOfNextUpkeepDelayedTriggeredAbility(new DrawCardSourceControllerEffect(1)), false)); @@ -72,8 +72,8 @@ class ForeshadowEffect extends OneShotEffect { Card card = targetPlayer.getLibrary().getFromTop(game); if (card != null) { controller.moveCards(card, Zone.GRAVEYARD, source, game); - if (card.getName().equals(cardName)) { - controller.drawCards(1, game); + if (CardUtil.haveSameNames(card.getName(), cardName)) { + controller.drawCards(1, game); } } return true; diff --git a/Mage.Sets/src/mage/cards/h/HomingLightning.java b/Mage.Sets/src/mage/cards/h/HomingLightning.java index db04b8b8ac..9e73c445f2 100644 --- a/Mage.Sets/src/mage/cards/h/HomingLightning.java +++ b/Mage.Sets/src/mage/cards/h/HomingLightning.java @@ -1,7 +1,5 @@ - package mage.cards.h; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.effects.OneShotEffect; import mage.cards.CardImpl; @@ -14,15 +12,17 @@ import mage.filter.predicate.permanent.PermanentIdPredicate; import mage.game.Game; import mage.game.permanent.Permanent; import mage.target.common.TargetCreaturePermanent; +import mage.util.CardUtil; + +import java.util.UUID; /** - * * @author jeffwadsworth */ public final class HomingLightning extends CardImpl { public HomingLightning(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.INSTANT},"{2}{R}{R}"); + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{2}{R}{R}"); // Homing Lightning deals 4 damage to target creature and each other creature with the same name as that creature. @@ -58,7 +58,7 @@ class HomingLightningEffect extends OneShotEffect { return false; } FilterCreaturePermanent filter = new FilterCreaturePermanent(); - if (targetPermanent.getName().isEmpty()) { + if (CardUtil.haveEmptyName(targetPermanent)) { filter.add(new PermanentIdPredicate(targetPermanent.getId())); // if no name (face down creature) only the creature itself is selected } else { filter.add(new NamePredicate(targetPermanent.getName())); diff --git a/Mage.Sets/src/mage/cards/i/IzzetStaticaster.java b/Mage.Sets/src/mage/cards/i/IzzetStaticaster.java index ee90f7d8f8..84f9257a7f 100644 --- a/Mage.Sets/src/mage/cards/i/IzzetStaticaster.java +++ b/Mage.Sets/src/mage/cards/i/IzzetStaticaster.java @@ -1,7 +1,5 @@ - package mage.cards.i; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; @@ -12,8 +10,8 @@ import mage.abilities.keyword.HasteAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.SubType; import mage.constants.Outcome; +import mage.constants.SubType; import mage.constants.Zone; import mage.filter.common.FilterCreaturePermanent; import mage.filter.predicate.mageobject.NamePredicate; @@ -21,15 +19,17 @@ import mage.filter.predicate.permanent.PermanentIdPredicate; import mage.game.Game; import mage.game.permanent.Permanent; import mage.target.common.TargetCreaturePermanent; +import mage.util.CardUtil; + +import java.util.UUID; /** - * * @author LevelX2 */ public final class IzzetStaticaster extends CardImpl { public IzzetStaticaster(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{1}{U}{R}"); + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{U}{R}"); this.subtype.add(SubType.HUMAN); this.subtype.add(SubType.WIZARD); @@ -79,7 +79,7 @@ class IzzetStaticasterDamageEffect extends OneShotEffect { Permanent targetPermanent = game.getPermanent(targetPointer.getFirst(game, source)); if (targetPermanent != null) { FilterCreaturePermanent filter = new FilterCreaturePermanent(); - if (targetPermanent.getName().isEmpty()) { + if (CardUtil.haveEmptyName(targetPermanent)) { filter.add(new PermanentIdPredicate(targetPermanent.getId())); // if no name (face down creature) only the creature itself is selected } else { filter.add(new NamePredicate(targetPermanent.getName())); diff --git a/Mage.Sets/src/mage/cards/l/LammastideWeave.java b/Mage.Sets/src/mage/cards/l/LammastideWeave.java index a67cac4585..9cd195c43b 100644 --- a/Mage.Sets/src/mage/cards/l/LammastideWeave.java +++ b/Mage.Sets/src/mage/cards/l/LammastideWeave.java @@ -1,10 +1,9 @@ package mage.cards.l; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.effects.OneShotEffect; -import mage.abilities.effects.common.DrawCardSourceControllerEffect; import mage.abilities.effects.common.ChooseACardNameEffect; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; import mage.cards.Card; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -14,9 +13,11 @@ import mage.constants.Zone; import mage.game.Game; import mage.players.Player; import mage.target.TargetPlayer; +import mage.util.CardUtil; + +import java.util.UUID; /** - * * @author Styxo */ public final class LammastideWeave extends CardImpl { @@ -70,7 +71,7 @@ class LammastideWeaveEffect extends OneShotEffect { Card card = targetPlayer.getLibrary().getFromTop(game); if (card != null) { controller.moveCards(card, Zone.GRAVEYARD, source, game); - if (card.getName().equals(cardName)) { + if (CardUtil.haveSameNames(card.getName(), cardName)) { controller.gainLife(card.getConvertedManaCost(), game, source); } } diff --git a/Mage.Sets/src/mage/cards/l/LiarsPendulum.java b/Mage.Sets/src/mage/cards/l/LiarsPendulum.java index 5163fdcb7f..bf86998ce4 100644 --- a/Mage.Sets/src/mage/cards/l/LiarsPendulum.java +++ b/Mage.Sets/src/mage/cards/l/LiarsPendulum.java @@ -1,7 +1,5 @@ - package mage.cards.l; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.common.TapSourceCost; @@ -20,9 +18,11 @@ import mage.constants.Zone; import mage.game.Game; import mage.players.Player; import mage.target.common.TargetOpponent; +import mage.util.CardUtil; + +import java.util.UUID; /** - * * @author L_J */ public final class LiarsPendulum extends CardImpl { @@ -93,7 +93,7 @@ class LiarsPendulumEffect extends OneShotEffect { rightGuess = opponentGuess; } } - if (card.getName().equals(cardName)) { + if (CardUtil.haveSameNames(card.getName(), cardName)) { rightGuess = opponentGuess; } } diff --git a/Mage.Sets/src/mage/cards/m/MagusOfTheScroll.java b/Mage.Sets/src/mage/cards/m/MagusOfTheScroll.java index cc23294d28..c5d0627c37 100644 --- a/Mage.Sets/src/mage/cards/m/MagusOfTheScroll.java +++ b/Mage.Sets/src/mage/cards/m/MagusOfTheScroll.java @@ -1,7 +1,5 @@ - package mage.cards.m; -import java.util.UUID; import mage.MageInt; import mage.MageObject; import mage.abilities.Ability; @@ -10,11 +8,7 @@ import mage.abilities.costs.common.TapSourceCost; import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.ChooseACardNameEffect; -import mage.cards.Card; -import mage.cards.CardImpl; -import mage.cards.CardSetInfo; -import mage.cards.Cards; -import mage.cards.CardsImpl; +import mage.cards.*; import mage.constants.CardType; import mage.constants.Outcome; import mage.constants.SubType; @@ -23,9 +17,11 @@ import mage.game.Game; import mage.game.permanent.Permanent; import mage.players.Player; import mage.target.common.TargetAnyTarget; +import mage.util.CardUtil; + +import java.util.UUID; /** - * * @author fireshoes */ public final class MagusOfTheScroll extends CardImpl { @@ -80,7 +76,7 @@ class MagusOfTheScrollEffect extends OneShotEffect { } revealed.add(card); you.revealCards(sourceObject.getName(), revealed, game); - if (card.getName().equals(cardName)) { + if (CardUtil.haveSameNames(card.getName(), cardName)) { Permanent creature = game.getPermanent(targetPointer.getFirst(game, source)); if (creature != null) { creature.damage(2, source.getSourceId(), game, false, true); diff --git a/Mage.Sets/src/mage/cards/p/PalisadeGiant.java b/Mage.Sets/src/mage/cards/p/PalisadeGiant.java index 10a8ffde34..0ceed13bfb 100644 --- a/Mage.Sets/src/mage/cards/p/PalisadeGiant.java +++ b/Mage.Sets/src/mage/cards/p/PalisadeGiant.java @@ -1,23 +1,20 @@ - package mage.cards.p; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.effects.ReplacementEffectImpl; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.CardType; -import mage.constants.SubType; -import mage.constants.Duration; -import mage.constants.Outcome; -import mage.constants.Zone; +import mage.constants.*; import mage.game.Game; import mage.game.events.DamageEvent; import mage.game.events.GameEvent; import mage.game.permanent.Permanent; import mage.players.Player; +import mage.util.CardUtil; + +import java.util.UUID; /** * @author LevelX2 @@ -43,7 +40,7 @@ import mage.players.Player; public final class PalisadeGiant extends CardImpl { public PalisadeGiant(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{4}{W}{W}"); + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{W}{W}"); this.subtype.add(SubType.GIANT); this.subtype.add(SubType.SOLDIER); @@ -51,7 +48,7 @@ public final class PalisadeGiant extends CardImpl { this.toughness = new MageInt(7); // All damage that would be dealt to you or another permanent you control is dealt to Palisade Giant instead. - this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new PalisadeGiantReplacementEffect())); + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new PalisadeGiantReplacementEffect())); } public PalisadeGiant(final PalisadeGiant card) { @@ -77,7 +74,7 @@ class PalisadeGiantReplacementEffect extends ReplacementEffectImpl { @Override public boolean checksEventType(GameEvent event, Game game) { - switch(event.getType()) { + switch (event.getType()) { case DAMAGE_CREATURE: case DAMAGE_PLAYER: case DAMAGE_PLANESWALKER: @@ -89,17 +86,15 @@ class PalisadeGiantReplacementEffect extends ReplacementEffectImpl { @Override public boolean applies(GameEvent event, Ability source, Game game) { - if (event.getType() == GameEvent.EventType.DAMAGE_PLAYER && event.getPlayerId().equals(source.getControllerId())) - { - return true; + if (event.getType() == GameEvent.EventType.DAMAGE_PLAYER && event.getPlayerId().equals(source.getControllerId())) { + return true; } - if (event.getType() == GameEvent.EventType.DAMAGE_CREATURE || event.getType() == GameEvent.EventType.DAMAGE_PLANESWALKER) - { + if (event.getType() == GameEvent.EventType.DAMAGE_CREATURE || event.getType() == GameEvent.EventType.DAMAGE_PLANESWALKER) { Permanent targetPermanent = game.getPermanent(event.getTargetId()); Permanent sourcePermanent = game.getPermanent(source.getSourceId()); - if (targetPermanent != null && + if (targetPermanent != null && targetPermanent.isControlledBy(source.getControllerId()) && - !targetPermanent.getName().equals(sourcePermanent.getName())) { // no redirection from or to other Palisade Giants + !CardUtil.haveSameNames(targetPermanent, sourcePermanent)) { // no redirection from or to other Palisade Giants return true; } } @@ -108,7 +103,7 @@ class PalisadeGiantReplacementEffect extends ReplacementEffectImpl { @Override public boolean replaceEvent(GameEvent event, Ability source, Game game) { - DamageEvent damageEvent = (DamageEvent)event; + DamageEvent damageEvent = (DamageEvent) event; Permanent sourcePermanent = game.getPermanent(source.getSourceId()); if (sourcePermanent != null) { // get name of old target @@ -118,13 +113,11 @@ class PalisadeGiantReplacementEffect extends ReplacementEffectImpl { message.append(damageEvent.getAmount()).append(" damage redirected from "); if (targetPermanent != null) { message.append(targetPermanent.getName()); - } - else { + } else { Player targetPlayer = game.getPlayer(event.getTargetId()); if (targetPlayer != null) { message.append(targetPlayer.getLogName()); - } - else { + } else { message.append("unknown"); } diff --git a/Mage.Sets/src/mage/cards/p/PetraSphinx.java b/Mage.Sets/src/mage/cards/p/PetraSphinx.java index cb6197c333..253d4e77f0 100644 --- a/Mage.Sets/src/mage/cards/p/PetraSphinx.java +++ b/Mage.Sets/src/mage/cards/p/PetraSphinx.java @@ -1,7 +1,5 @@ - package mage.cards.p; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; @@ -20,8 +18,9 @@ import mage.players.Player; import mage.target.TargetPlayer; import mage.util.CardUtil; +import java.util.UUID; + /** - * * @author BetaSteward_at_googlemail.com & L_J */ public final class PetraSphinx extends CardImpl { @@ -79,7 +78,7 @@ class PetraSphinxEffect extends OneShotEffect { if (card != null) { Cards cards = new CardsImpl(card); player.revealCards(source, cards, game); - if (card.getName().equals(cardName)) { + if (CardUtil.haveSameNames(card.getName(), cardName)) { player.moveCards(cards, Zone.HAND, source, game); } else { player.moveCards(cards, Zone.GRAVEYARD, source, game); diff --git a/Mage.Sets/src/mage/cards/p/Predict.java b/Mage.Sets/src/mage/cards/p/Predict.java index 193ae9b8c0..c2ae143a7a 100644 --- a/Mage.Sets/src/mage/cards/p/Predict.java +++ b/Mage.Sets/src/mage/cards/p/Predict.java @@ -1,6 +1,5 @@ package mage.cards.p; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.ChooseACardNameEffect; @@ -13,9 +12,11 @@ import mage.constants.Zone; import mage.game.Game; import mage.players.Player; import mage.target.TargetPlayer; +import mage.util.CardUtil; + +import java.util.UUID; /** - * * @author Quercitron */ public final class Predict extends CardImpl { @@ -66,7 +67,7 @@ class PredictEffect extends OneShotEffect { Card card = targetPlayer.getLibrary().getFromTop(game); if (card != null) { controller.moveCards(card, Zone.GRAVEYARD, source, game); - if (card.getName().equals(cardName)) { + if (CardUtil.haveSameNames(card.getName(), cardName)) { amount = 2; } } diff --git a/Mage.Sets/src/mage/cards/p/PyromancerAscension.java b/Mage.Sets/src/mage/cards/p/PyromancerAscension.java index c4831f5dd2..2f6f176078 100644 --- a/Mage.Sets/src/mage/cards/p/PyromancerAscension.java +++ b/Mage.Sets/src/mage/cards/p/PyromancerAscension.java @@ -1,8 +1,5 @@ - - package mage.cards.p; -import java.util.UUID; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.effects.common.CopyTargetSpellEffect; import mage.abilities.effects.common.counter.AddCountersSourceEffect; @@ -17,19 +14,21 @@ import mage.game.events.GameEvent; import mage.game.permanent.Permanent; import mage.game.stack.Spell; import mage.target.targetpointer.FixedTarget; +import mage.util.CardUtil; + +import java.util.UUID; /** - * * @author nantuko */ public final class PyromancerAscension extends CardImpl { public PyromancerAscension(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.ENCHANTMENT},"{1}{R}"); + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{1}{R}"); // Whenever you cast an instant or sorcery spell that has the same name as a card in your graveyard, you may put a quest counter on Pyromancer Ascension. this.addAbility(new PyromancerAscensionQuestTriggeredAbility()); - + // Whenever you cast an instant or sorcery spell while Pyromancer Ascension has two or more quest counters on it, you may copy that spell. You may choose new targets for the copy. this.addAbility(new PyromancerAscensionCopyTriggeredAbility()); } @@ -75,21 +74,21 @@ class PyromancerAscensionQuestTriggeredAbility extends TriggeredAbilityImpl { for (UUID uuid : game.getPlayer(this.getControllerId()).getGraveyard()) { if (!uuid.equals(sourceCard.getId())) { Card card = game.getCard(uuid); - if (card != null && card.getName().equals(sourceCard.getName())) { + if (CardUtil.haveSameNames(card, sourceCard)) { return true; } } } - } + } } } return false; } private boolean isControlledInstantOrSorcery(Spell spell) { - return spell != null && - (spell.isControlledBy(this.getControllerId())) && - (spell.isInstant() || spell.isSorcery()); + return spell != null && + (spell.isControlledBy(this.getControllerId())) && + (spell.isInstant() || spell.isSorcery()); } @Override @@ -112,12 +111,12 @@ class PyromancerAscensionCopyTriggeredAbility extends TriggeredAbilityImpl { public PyromancerAscensionCopyTriggeredAbility copy() { return new PyromancerAscensionCopyTriggeredAbility(this); } - + @Override public boolean checkEventType(GameEvent event, Game game) { return event.getType() == GameEvent.EventType.SPELL_CAST; } - + @Override public boolean checkTrigger(GameEvent event, Game game) { if (event.getPlayerId().equals(this.getControllerId())) { @@ -134,9 +133,9 @@ class PyromancerAscensionCopyTriggeredAbility extends TriggeredAbilityImpl { } private boolean isControlledInstantOrSorcery(Spell spell) { - return spell != null && - (spell.isControlledBy(this.getControllerId())) && - (spell.isInstant() || spell.isSorcery()); + return spell != null && + (spell.isControlledBy(this.getControllerId())) && + (spell.isInstant() || spell.isSorcery()); } @Override diff --git a/Mage.Sets/src/mage/cards/r/ReflectorMage.java b/Mage.Sets/src/mage/cards/r/ReflectorMage.java index a2514e833e..a7a087b19e 100644 --- a/Mage.Sets/src/mage/cards/r/ReflectorMage.java +++ b/Mage.Sets/src/mage/cards/r/ReflectorMage.java @@ -1,8 +1,5 @@ - package mage.cards.r; -import java.util.Objects; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldTriggeredAbility; @@ -11,12 +8,7 @@ import mage.abilities.effects.OneShotEffect; import mage.cards.Card; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.CardType; -import mage.constants.Duration; -import mage.constants.Outcome; -import mage.constants.SubType; -import mage.constants.TargetController; -import mage.constants.Zone; +import mage.constants.*; import mage.filter.common.FilterCreaturePermanent; import mage.filter.predicate.permanent.ControllerPredicate; import mage.game.Game; @@ -25,9 +17,12 @@ import mage.game.permanent.Permanent; import mage.game.stack.Spell; import mage.players.Player; import mage.target.common.TargetCreaturePermanent; +import mage.util.CardUtil; + +import java.util.Objects; +import java.util.UUID; /** - * * @author LevelX2 */ public final class ReflectorMage extends CardImpl { @@ -84,7 +79,7 @@ class ReflectorMageEffect extends OneShotEffect { Permanent targetCreature = game.getPermanent(getTargetPointer().getFirst(game, source)); if (targetCreature != null) { controller.moveCards(targetCreature, Zone.HAND, source, game); - if (!targetCreature.getName().isEmpty()) { // if the creature had no name, no restrict effect will be created + if (!CardUtil.haveEmptyName(targetCreature)) { // if the creature had no name, no restrict effect will be created game.addEffect(new ExclusionRitualReplacementEffect(targetCreature.getName(), targetCreature.getOwnerId()), source); } } @@ -125,7 +120,7 @@ class ExclusionRitualReplacementEffect extends ContinuousRuleModifyingEffectImpl if (spell != null && spell.isFaceDown(game)) { return false; // Face Down cast spell (Morph creature) has no name } - return card.getName().equals(creatureName) && Objects.equals(ownerId, card.getOwnerId()); + return CardUtil.haveSameNames(card.getName(), creatureName) && Objects.equals(ownerId, card.getOwnerId()); } return false; } diff --git a/Mage.Sets/src/mage/cards/s/SearchTheCity.java b/Mage.Sets/src/mage/cards/s/SearchTheCity.java index e1b87380fb..57ad86e633 100644 --- a/Mage.Sets/src/mage/cards/s/SearchTheCity.java +++ b/Mage.Sets/src/mage/cards/s/SearchTheCity.java @@ -1,7 +1,5 @@ - package mage.cards.s; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.common.EntersBattlefieldTriggeredAbility; @@ -22,9 +20,11 @@ import mage.game.permanent.Permanent; import mage.game.stack.Spell; import mage.game.turn.TurnMod; import mage.players.Player; +import mage.util.CardUtil; + +import java.util.UUID; /** - * * @author LevelX2 */ public final class SearchTheCity extends CardImpl { @@ -157,7 +157,7 @@ class SearchTheCityExiledCardToHandEffect extends OneShotEffect { ExileZone searchTheCityExileZone = game.getExile().getExileZone(source.getSourceId()); if (cardName != null && searchTheCityExileZone != null) { for (Card card : searchTheCityExileZone.getCards(game)) { - if (card.getName().equals(cardName)) { + if (CardUtil.haveSameNames(card.getName(), cardName)) { if (card.moveToZone(Zone.HAND, source.getSourceId(), game, true)) { game.informPlayers("Search the City: put " + card.getName() + " into owner's hand"); } diff --git a/Mage.Sets/src/mage/cards/s/SeverTheBloodline.java b/Mage.Sets/src/mage/cards/s/SeverTheBloodline.java index 3cbbee01a8..dfde359a4f 100644 --- a/Mage.Sets/src/mage/cards/s/SeverTheBloodline.java +++ b/Mage.Sets/src/mage/cards/s/SeverTheBloodline.java @@ -1,7 +1,5 @@ - package mage.cards.s; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.effects.OneShotEffect; @@ -19,15 +17,17 @@ import mage.game.Game; import mage.game.permanent.Permanent; import mage.players.Player; import mage.target.common.TargetCreaturePermanent; +import mage.util.CardUtil; + +import java.util.UUID; /** - * * @author North */ public final class SeverTheBloodline extends CardImpl { public SeverTheBloodline(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.SORCERY},"{3}{B}"); + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{3}{B}"); // Exile target creature and all other creatures with the same name as that creature. @@ -69,7 +69,7 @@ class SeverTheBloodlineEffect extends OneShotEffect { Permanent targetPermanent = game.getPermanent(targetPointer.getFirst(game, source)); if (controller != null && targetPermanent != null) { FilterCreaturePermanent filter = new FilterCreaturePermanent(); - if (targetPermanent.getName().isEmpty()) { + if (CardUtil.haveEmptyName(targetPermanent)) { filter.add(new PermanentIdPredicate(targetPermanent.getId())); // if no name (face down creature) only the creature itself is selected } else { filter.add(new NamePredicate(targetPermanent.getName())); diff --git a/Mage.Sets/src/mage/cards/s/SpoilsOfTheVault.java b/Mage.Sets/src/mage/cards/s/SpoilsOfTheVault.java index 622bf0c7f3..153bc570f7 100644 --- a/Mage.Sets/src/mage/cards/s/SpoilsOfTheVault.java +++ b/Mage.Sets/src/mage/cards/s/SpoilsOfTheVault.java @@ -1,23 +1,20 @@ package mage.cards.s; -import java.util.UUID; import mage.MageObject; import mage.abilities.Ability; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.ChooseACardNameEffect; -import mage.cards.Card; -import mage.cards.CardImpl; -import mage.cards.CardSetInfo; -import mage.cards.Cards; -import mage.cards.CardsImpl; +import mage.cards.*; import mage.constants.CardType; import mage.constants.Outcome; import mage.constants.Zone; import mage.game.Game; import mage.players.Player; +import mage.util.CardUtil; + +import java.util.UUID; /** - * * @author Plopman */ public final class SpoilsOfTheVault extends CardImpl { @@ -71,7 +68,7 @@ class SpoilsOfTheVaultEffect extends OneShotEffect { for (Card card : controller.getLibrary().getCards(game)) { if (card != null) { cardsToReveal.add(card); - if (card.getName().equals(cardName)) { + if (CardUtil.haveSameNames(card.getName(), cardName)) { controller.moveCards(card, Zone.HAND, source, game); break; } else { diff --git a/Mage.Sets/src/mage/cards/t/ThoughtHemorrhage.java b/Mage.Sets/src/mage/cards/t/ThoughtHemorrhage.java index bec111276c..5b0da23fd5 100644 --- a/Mage.Sets/src/mage/cards/t/ThoughtHemorrhage.java +++ b/Mage.Sets/src/mage/cards/t/ThoughtHemorrhage.java @@ -1,9 +1,5 @@ package mage.cards.t; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Set; -import java.util.UUID; import mage.MageObject; import mage.abilities.Ability; import mage.abilities.effects.OneShotEffect; @@ -21,9 +17,14 @@ import mage.players.Player; import mage.target.TargetPlayer; import mage.target.common.TargetCardInHand; import mage.target.common.TargetCardInLibrary; +import mage.util.CardUtil; + +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import java.util.UUID; /** - * * @author jeffwadsworth */ public final class ThoughtHemorrhage extends CardImpl { @@ -74,7 +75,7 @@ class ThoughtHemorrhageEffect extends OneShotEffect { targetPlayer.revealCards("hand of " + targetPlayer.getName(), targetPlayer.getHand(), game); int cardsFound = 0; for (Card card : targetPlayer.getHand().getCards(game)) { - if (card.getName().equals(cardName)) { + if (CardUtil.haveSameNames(card.getName(), cardName)) { cardsFound++; } } diff --git a/Mage.Sets/src/mage/cards/t/TunnelVision.java b/Mage.Sets/src/mage/cards/t/TunnelVision.java index e7b2e0d9bc..33b58c266c 100644 --- a/Mage.Sets/src/mage/cards/t/TunnelVision.java +++ b/Mage.Sets/src/mage/cards/t/TunnelVision.java @@ -1,23 +1,20 @@ package mage.cards.t; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.ChooseACardNameEffect; -import mage.cards.Card; -import mage.cards.CardImpl; -import mage.cards.CardSetInfo; -import mage.cards.Cards; -import mage.cards.CardsImpl; +import mage.cards.*; import mage.constants.CardType; import mage.constants.Outcome; import mage.constants.Zone; import mage.game.Game; import mage.players.Player; import mage.target.TargetPlayer; +import mage.util.CardUtil; + +import java.util.UUID; /** - * * @author escplan9 (Derek Monturo - dmontur1 at gmail dot com) */ public final class TunnelVision extends CardImpl { @@ -76,7 +73,7 @@ class TunnelVisionEffect extends OneShotEffect { for (Card card : targetPlayer.getLibrary().getCards(game)) { cardsToReveal.add(card); - if (card.getName().equals(cardName)) { + if (CardUtil.haveSameNames(card.getName(), cardName)) { namedCard = card; break; } diff --git a/Mage.Sets/src/mage/cards/v/VexingArcanix.java b/Mage.Sets/src/mage/cards/v/VexingArcanix.java index 3ca378b7ed..61ec771008 100644 --- a/Mage.Sets/src/mage/cards/v/VexingArcanix.java +++ b/Mage.Sets/src/mage/cards/v/VexingArcanix.java @@ -1,7 +1,5 @@ - package mage.cards.v; -import java.util.UUID; import mage.MageObject; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; @@ -18,9 +16,11 @@ import mage.constants.Zone; import mage.game.Game; import mage.players.Player; import mage.target.TargetPlayer; +import mage.util.CardUtil; + +import java.util.UUID; /** - * * @author BetaSteward_at_googlemail.com & L_J */ public final class VexingArcanix extends CardImpl { @@ -74,7 +74,7 @@ class VexingArcanixEffect extends OneShotEffect { if (card != null) { Cards cards = new CardsImpl(card); player.revealCards(sourceObject.getIdName(), cards, game); - if (card.getName().equals(cardName)) { + if (CardUtil.haveSameNames(card.getName(), cardName)) { player.moveCards(cards, Zone.HAND, source, game); } else { player.moveCards(cards, Zone.GRAVEYARD, source, game); diff --git a/Mage.Tests/src/frozen/org/mage/test/clientside/base/MageBase.java b/Mage.Tests/src/frozen/org/mage/test/clientside/base/MageBase.java index ff7a6990ad..0a5d24454e 100644 --- a/Mage.Tests/src/frozen/org/mage/test/clientside/base/MageBase.java +++ b/Mage.Tests/src/frozen/org/mage/test/clientside/base/MageBase.java @@ -1,13 +1,5 @@ package org.mage.test.clientside.base; -import java.rmi.NotBoundException; -import java.rmi.RemoteException; -import java.rmi.registry.LocateRegistry; -import java.rmi.registry.Registry; -import java.util.Date; -import java.util.UUID; -import java.util.logging.Level; -import java.util.logging.Logger; import mage.constants.MultiplayerAttackOption; import mage.constants.RangeOfInfluence; import mage.game.match.MatchOptions; @@ -20,7 +12,15 @@ import mage.interfaces.callback.ClientCallback; import mage.server.Main; import mage.sets.Sets; import mage.util.Logging; -import mage.view.*; + +import java.rmi.NotBoundException; +import java.rmi.RemoteException; +import java.rmi.registry.LocateRegistry; +import java.rmi.registry.Registry; +import java.util.Date; +import java.util.UUID; +import java.util.logging.Level; +import java.util.logging.Logger; /** * Base for starting Mage server. Controls interactions between MageAPI and Mage @@ -205,7 +205,7 @@ public class MageBase { } gameView = server.getGameView(gameId, sessionId, playerId); for (CardView card : gameView.getHand().values()) { - if (card.getName().equals(cardName)) { + if (CardUtil.haveSameNames(card.getName(), cardName)) { return true; } } @@ -224,7 +224,7 @@ public class MageBase { CardsView cards = gameView.getHand(); CardView cardToPlay = null; for (CardView card : cards.values()) { - if (card.getName().equals(cardName)) { + if (CardUtil.haveSameNames(card.getName(), cardName)) { cardToPlay = card; } } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/ManifestTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/ManifestTest.java index 75bbdb228d..55b8347561 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/ManifestTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/ManifestTest.java @@ -1,7 +1,7 @@ - package org.mage.test.cards.abilities.keywords; import mage.cards.Card; +import mage.constants.EmptyNames; import mage.constants.PhaseStep; import mage.constants.Zone; import mage.game.permanent.Permanent; @@ -10,7 +10,6 @@ import org.junit.Test; import org.mage.test.serverside.base.CardTestPlayerBase; /** - * * @author LevelX2 */ public class ManifestTest extends CardTestPlayerBase { @@ -34,15 +33,16 @@ public class ManifestTest extends CardTestPlayerBase { setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); + assertAllCommandsUsed(); // no life gain assertLife(playerA, 20); assertLife(playerB, 20); // a facedown creature is on the battlefield - assertPermanentCount(playerA, "", 1); - assertPowerToughness(playerA, "", 2, 2); + assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 1); + assertPowerToughness(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 2, 2); // not tapped - assertTapped("", false); + assertTapped(EmptyNames.FACE_DOWN_CREATURE.toString(), false); } /** @@ -66,13 +66,14 @@ public class ManifestTest extends CardTestPlayerBase { setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); + assertAllCommandsUsed(); // no life gain assertLife(playerA, 20); assertLife(playerB, 20); // a facedown creature is on the battlefield - assertPermanentCount(playerA, "", 1); - assertPowerToughness(playerA, "", 2, 2); + assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 1); + assertPowerToughness(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 2, 2); // PlayerB's Silvercoat Lion should not have get -1/-1/ assertPermanentCount(playerB, "Silvercoat Lion", 1); assertPowerToughness(playerB, "Silvercoat Lion", 2, 2); @@ -101,6 +102,7 @@ public class ManifestTest extends CardTestPlayerBase { setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); + assertAllCommandsUsed(); // no life gain assertLife(playerA, 20); @@ -108,8 +110,8 @@ public class ManifestTest extends CardTestPlayerBase { assertGraveyardCount(playerB, "Reality Shift", 1); assertExileCount("Silvercoat Lion", 1); // a facedown creature is on the battlefield - assertPermanentCount(playerA, "", 1); - assertPowerToughness(playerA, "", 2, 2); + assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 1); + assertPowerToughness(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 2, 2); // PlayerA's Pillarfield Ox should not have get -1/-1/ assertPermanentCount(playerB, "Pillarfield Ox", 1); assertPowerToughness(playerB, "Pillarfield Ox", 2, 4); @@ -137,6 +139,7 @@ public class ManifestTest extends CardTestPlayerBase { setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); + assertAllCommandsUsed(); // no life gain assertLife(playerA, 20); @@ -144,8 +147,8 @@ public class ManifestTest extends CardTestPlayerBase { assertGraveyardCount(playerB, "Reality Shift", 1); assertExileCount("Silvercoat Lion", 1); // a facedown creature is on the battlefield - assertPermanentCount(playerA, "", 1); - assertPowerToughness(playerA, "", 2, 2); + assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 1); + assertPowerToughness(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 2, 2); } @@ -173,6 +176,7 @@ public class ManifestTest extends CardTestPlayerBase { setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); + assertAllCommandsUsed(); // no life gain assertLife(playerA, 20); @@ -180,8 +184,8 @@ public class ManifestTest extends CardTestPlayerBase { assertGraveyardCount(playerB, "Reality Shift", 1); assertExileCount("Silvercoat Lion", 1); // a facedown creature is on the battlefield - assertPermanentCount(playerA, "", 1); - assertPowerToughness(playerA, "", 2, 2); + assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 1); + assertPowerToughness(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 2, 2); assertPowerToughness(playerA, "Foundry Street Denizen", 1, 1); } @@ -199,7 +203,6 @@ public class ManifestTest extends CardTestPlayerBase { // Strive — Silence the Believers costs more to cast for each target beyond the first. // Exile any number of target creatures and all Auras attached to them. addCard(Zone.HAND, playerB, "Silence the Believers"); - addTarget(playerB, ""); // Gore Swine {2}{R} // 4/1 addCard(Zone.LIBRARY, playerA, "Gore Swine"); @@ -210,10 +213,13 @@ public class ManifestTest extends CardTestPlayerBase { skipInitShuffling(); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Reality Shift", "Silvercoat Lion"); - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Silence the Believers", ""); + showBattlefield("A battle", 1, PhaseStep.POSTCOMBAT_MAIN, playerA); + showBattlefield("B battle", 1, PhaseStep.POSTCOMBAT_MAIN, playerB); + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerB, "Silence the Believers", EmptyNames.FACE_DOWN_CREATURE.toString()); - setStopAt(1, PhaseStep.BEGIN_COMBAT); + setStopAt(1, PhaseStep.END_TURN); execute(); + assertAllCommandsUsed(); // no life gain assertLife(playerA, 20); @@ -222,7 +228,7 @@ public class ManifestTest extends CardTestPlayerBase { assertExileCount("Silvercoat Lion", 1); assertExileCount("Gore Swine", 1); // no facedown creature is on the battlefield - assertPermanentCount(playerA, "", 0); + assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 0); for (Card card : currentGame.getExile().getAllCards(currentGame)) { if (card.getName().equals("Gore Swine")) { @@ -248,10 +254,11 @@ public class ManifestTest extends CardTestPlayerBase { skipInitShuffling(); activateAbility(2, PhaseStep.PRECOMBAT_MAIN, playerB, "{1}{B}, {T}, Sacrifice another creature"); - addTarget(playerB, "Silvercoat Lion"); + setChoice(playerB, "Silvercoat Lion"); setStopAt(2, PhaseStep.BEGIN_COMBAT); execute(); + assertAllCommandsUsed(); // no life gain assertLife(playerA, 20); @@ -261,7 +268,7 @@ public class ManifestTest extends CardTestPlayerBase { assertGraveyardCount(playerB, "Silvercoat Lion", 1); // a facedown creature is on the battlefield - assertPermanentCount(playerB, "", 1); + assertPermanentCount(playerB, EmptyNames.FACE_DOWN_CREATURE.toString(), 1); } @@ -284,12 +291,13 @@ public class ManifestTest extends CardTestPlayerBase { skipInitShuffling(); activateAbility(2, PhaseStep.PRECOMBAT_MAIN, playerB, "{1}{B}, {T}, Sacrifice another creature"); - addTarget(playerB, "Silvercoat Lion"); + setChoice(playerB, "Silvercoat Lion"); activateAbility(2, PhaseStep.POSTCOMBAT_MAIN, playerB, "{5}{G}: Turn"); setStopAt(2, PhaseStep.END_TURN); execute(); + assertAllCommandsUsed(); // no life gain assertLife(playerA, 20); @@ -297,7 +305,7 @@ public class ManifestTest extends CardTestPlayerBase { assertGraveyardCount(playerB, "Silvercoat Lion", 1); - assertPermanentCount(playerB, "", 0); + assertPermanentCount(playerB, EmptyNames.FACE_DOWN_CREATURE.toString(), 0); assertPermanentCount(playerB, "Aerie Bowmasters", 1); assertPowerToughness(playerB, "Aerie Bowmasters", 4, 5); // 3/4 and the +1/+1 counter from Megamorph Permanent aerie = getPermanent("Aerie Bowmasters", playerB); @@ -308,7 +316,6 @@ public class ManifestTest extends CardTestPlayerBase { /** * When a Forest came manifested into play my Courser of Kruphix gained me a * life. - * */ @Test public void testManifestForest() { @@ -328,10 +335,11 @@ public class ManifestTest extends CardTestPlayerBase { skipInitShuffling(); activateAbility(2, PhaseStep.PRECOMBAT_MAIN, playerB, "{1}{B}, {T}, Sacrifice another creature"); - addTarget(playerB, "Silvercoat Lion"); + setChoice(playerB, "Silvercoat Lion"); setStopAt(2, PhaseStep.END_TURN); execute(); + assertAllCommandsUsed(); // no life gain assertLife(playerA, 20); @@ -339,13 +347,12 @@ public class ManifestTest extends CardTestPlayerBase { assertGraveyardCount(playerB, "Silvercoat Lion", 1); - assertPermanentCount(playerB, "", 1); + assertPermanentCount(playerB, EmptyNames.FACE_DOWN_CREATURE.toString(), 1); } /** * Whisperwood Elemental - Its sacrifice ability doesn't work.. - * */ @Test public void testWhisperwoodElemental() { @@ -365,6 +372,7 @@ public class ManifestTest extends CardTestPlayerBase { setStopAt(1, PhaseStep.END_TURN); execute(); + assertAllCommandsUsed(); // no life gain assertLife(playerA, 20); @@ -374,14 +382,13 @@ public class ManifestTest extends CardTestPlayerBase { assertGraveyardCount(playerB, "Whisperwood Elemental", 1); assertGraveyardCount(playerB, "Silvercoat Lion", 2); - assertPermanentCount(playerB, "", 2); + assertPermanentCount(playerB, EmptyNames.FACE_DOWN_CREATURE.toString(), 2); } /** * I sacrificed a manifested face-down Smothering Abomination to Nantuko * Husk and it made me draw a card. - * */ @Test public void testDiesTriggeredAbilitiesOfManifestedCreatures() { @@ -409,10 +416,11 @@ public class ManifestTest extends CardTestPlayerBase { setChoice(playerB, "Silvercoat Lion"); activateAbility(2, PhaseStep.POSTCOMBAT_MAIN, playerB, "Sacrifice a creature"); - setChoice(playerB, ""); + setChoice(playerB, EmptyNames.FACE_DOWN_CREATURE.toString()); setStopAt(2, PhaseStep.END_TURN); execute(); + assertAllCommandsUsed(); // no life gain assertLife(playerA, 20); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/MorphTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/MorphTest.java index 6ecd554716..50bf73e391 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/MorphTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/MorphTest.java @@ -1,10 +1,7 @@ package org.mage.test.cards.abilities.keywords; import mage.cards.Card; -import mage.constants.CardType; -import mage.constants.PhaseStep; -import mage.constants.SubType; -import mage.constants.Zone; +import mage.constants.*; import mage.filter.Filter; import mage.game.permanent.Permanent; import org.junit.Assert; @@ -57,8 +54,8 @@ public class MorphTest extends CardTestPlayerBase { setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); - assertPermanentCount(playerA, "", 1); - assertPowerToughness(playerA, "", 2, 2); + assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 1); + assertPowerToughness(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 2, 2); } @@ -73,7 +70,7 @@ public class MorphTest extends CardTestPlayerBase { castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Pine Walker"); setChoice(playerA, "Yes"); // cast it face down as 2/2 creature - attack(3, playerA, ""); + attack(3, playerA, EmptyNames.FACE_DOWN_CREATURE.toString()); activateAbility(3, PhaseStep.POSTCOMBAT_MAIN, playerA, "{4}{G}: Turn this face-down permanent face up."); setStopAt(3, PhaseStep.END_TURN); @@ -81,7 +78,7 @@ public class MorphTest extends CardTestPlayerBase { assertLife(playerB, 18); - assertPermanentCount(playerA, "", 0); + assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 0); assertPermanentCount(playerA, "Pine Walker", 1); assertPowerToughness(playerA, "Pine Walker", 5, 5); assertTapped("Pine Walker", false); @@ -106,8 +103,8 @@ public class MorphTest extends CardTestPlayerBase { castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Icefeather Aven", NO_TARGET, "Pine Walker", StackClause.WHILE_NOT_ON_STACK); setChoice(playerA, "Yes"); // cast it face down as 2/2 creature - attack(3, playerA, ""); - attack(3, playerA, ""); + attack(3, playerA, EmptyNames.FACE_DOWN_CREATURE.toString()); + attack(3, playerA, EmptyNames.FACE_DOWN_CREATURE.toString()); activateAbility(3, PhaseStep.DECLARE_BLOCKERS, playerA, "{1}{G}{U}: Turn this face-down permanent face up."); setChoice(playerA, "No"); // Don't use return permanent to hand effect @@ -119,7 +116,7 @@ public class MorphTest extends CardTestPlayerBase { assertHandCount(playerA, "Pine Walker", 0); assertHandCount(playerA, "Icefeather Aven", 0); - assertPermanentCount(playerA, "", 1); + assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 1); assertPermanentCount(playerA, "Icefeather Aven", 1); assertTapped("Icefeather Aven", true); @@ -151,7 +148,7 @@ public class MorphTest extends CardTestPlayerBase { assertLife(playerB, 20); // and not 21 - assertPermanentCount(playerA, "", 1); + assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 1); assertPermanentCount(playerB, "Soldier of the Pantheon", 1); } @@ -176,22 +173,21 @@ public class MorphTest extends CardTestPlayerBase { castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Clever Impersonator"); setChoice(playerB, "Yes"); // use to copy a nonland permanent - addTarget(playerB, ""); // Morphed creature + addTarget(playerB, EmptyNames.FACE_DOWN_CREATURE.toString()); // Morphed creature setStopAt(2, PhaseStep.BEGIN_COMBAT); execute(); assertLife(playerB, 20); - assertPermanentCount(playerA, "", 1); - assertPowerToughness(playerA, "", 2, 2); - assertPermanentCount(playerB, "", 1); - assertPowerToughness(playerB, "", 2, 2); + assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 1); + assertPowerToughness(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 2, 2); + assertPermanentCount(playerB, EmptyNames.FACE_DOWN_CREATURE.toString(), 1); + assertPowerToughness(playerB, EmptyNames.FACE_DOWN_CREATURE.toString(), 2, 2); } /** - * * */ @Test @@ -223,7 +219,7 @@ public class MorphTest extends CardTestPlayerBase { assertHandCount(playerA, "Pine Walker", 0); assertHandCount(playerB, "Doomwake Giant", 0); - assertPermanentCount(playerA, "", 0); + assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 0); assertPermanentCount(playerB, "Doomwake Giant", 1); assertPermanentCount(playerA, "Pine Walker", 1); assertPowerToughness(playerA, "Pine Walker", 4, 4); @@ -264,7 +260,7 @@ public class MorphTest extends CardTestPlayerBase { assertHandCount(playerA, "Ponyback Brigade", 0); assertHandCount(playerB, "Doomwake Giant", 0); - assertPermanentCount(playerA, "", 0); + assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 0); assertPermanentCount(playerA, "Goblin", 3); assertPowerToughness(playerA, "Goblin", 1, 1, Filter.ComparisonScope.Any); assertPermanentCount(playerB, "Doomwake Giant", 1); @@ -337,7 +333,7 @@ public class MorphTest extends CardTestPlayerBase { assertHandCount(playerA, "Sagu Mauler", 0); assertHandCount(playerB, "Disdainful Stroke", 1); // can't be cast - assertPermanentCount(playerA, "", 1); + assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 1); } @@ -366,18 +362,23 @@ public class MorphTest extends CardTestPlayerBase { castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Sagu Mauler", NO_TARGET, "Sagu Mauler", StackClause.WHILE_NOT_ON_STACK); setChoice(playerA, "Yes"); // cast it face down as 2/2 creature - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Echoing Decay", ""); + showBattlefield("A battle", 1, PhaseStep.POSTCOMBAT_MAIN, playerA); + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerB, "Echoing Decay", EmptyNames.FACE_DOWN_CREATURE.toString()); - setStopAt(1, PhaseStep.BEGIN_COMBAT); + showBattlefield("A battle after", 1, PhaseStep.END_TURN, playerA); + setStopAt(1, PhaseStep.END_TURN); execute(); + assertAllCommandsUsed(); assertLife(playerB, 20); + assertHandCount(playerB, "Echoing Decay", 0); + assertGraveyardCount(playerB, "Echoing Decay", 1); + assertHandCount(playerA, "Sagu Mauler", 0); assertHandCount(playerB, "Echoing Decay", 0); - - assertPermanentCount(playerA, "", 1); - + assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 1); + assertGraveyardCount(playerA, "Sagu Mauler", 1); } /** @@ -462,7 +463,7 @@ public class MorphTest extends CardTestPlayerBase { castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Ashcloud Phoenix"); setChoice(playerA, "Yes"); // cast it face down as 2/2 creature - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Lightning Bolt", ""); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Lightning Bolt", EmptyNames.FACE_DOWN_CREATURE.toString()); setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); @@ -503,7 +504,7 @@ public class MorphTest extends CardTestPlayerBase { setChoice(playerA, "Yes"); // cast it face down as 2/2 creature attack(2, playerB, "Mirri, Cat Warrior"); - block(2, playerA, "", "Mirri, Cat Warrior"); + block(2, playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), "Mirri, Cat Warrior"); setStopAt(2, PhaseStep.POSTCOMBAT_MAIN); execute(); @@ -539,20 +540,27 @@ public class MorphTest extends CardTestPlayerBase { castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Akroma, Angel of Fury"); setChoice(playerA, "Yes"); // cast it face down as 2/2 creature - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Supplant Form", ""); + showBattlefield("A battle", 1, PhaseStep.POSTCOMBAT_MAIN, playerA); + showBattlefield("B battle", 1, PhaseStep.POSTCOMBAT_MAIN, playerB); - setStopAt(1, PhaseStep.BEGIN_COMBAT); + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerB, "Supplant Form"); + addTarget(playerB, EmptyNames.FACE_DOWN_CREATURE.toString()); + + showBattlefield("A battle end", 1, PhaseStep.END_TURN, playerA); + showBattlefield("B battle end", 1, PhaseStep.END_TURN, playerB); + setStopAt(1, PhaseStep.END_TURN); execute(); assertLife(playerB, 20); - assertGraveyardCount(playerB, "Supplant Form", 1); assertHandCount(playerA, "Akroma, Angel of Fury", 1); + assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 0); + assertPermanentCount(playerA, "Akroma, Angel of Fury", 0); + assertGraveyardCount(playerB, "Supplant Form", 1); assertPermanentCount(playerB, "Akroma, Angel of Fury", 0); - assertPermanentCount(playerB, "", 1); - assertPowerToughness(playerB, "", 2, 2); - + assertPermanentCount(playerB, EmptyNames.FACE_DOWN_TOKEN.toString(), 1); + assertPowerToughness(playerB, EmptyNames.FACE_DOWN_TOKEN.toString(), 2, 2); } /** @@ -578,7 +586,7 @@ public class MorphTest extends CardTestPlayerBase { assertLife(playerA, 20); - assertPermanentCount(playerA, "", 1); + assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 1); } @@ -603,7 +611,7 @@ public class MorphTest extends CardTestPlayerBase { castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Pine Walker"); setChoice(playerA, "Yes"); // cast it face down as 2/2 creature - attack(3, playerA, ""); + attack(3, playerA, EmptyNames.FACE_DOWN_CREATURE.toString()); activateAbility(3, PhaseStep.POSTCOMBAT_MAIN, playerA, "{4}{G}: Turn this face-down permanent face up."); setStopAt(3, PhaseStep.END_TURN); @@ -611,7 +619,7 @@ public class MorphTest extends CardTestPlayerBase { assertLife(playerB, 18); - assertPermanentCount(playerA, "", 0); + assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 0); assertPermanentCount(playerA, "Pine Walker", 1); assertPowerToughness(playerA, "Pine Walker", 5, 5); assertTapped("Pine Walker", false); @@ -656,7 +664,7 @@ public class MorphTest extends CardTestPlayerBase { assertPermanentCount(playerA, "Reflector Mage", 1); assertPermanentCount(playerB, "Rattleclaw Mystic", 0); assertHandCount(playerB, "Rattleclaw Mystic", 0); // should have been replayed - assertPermanentCount(playerB, "", 1); // Rattleclaw played as a morph + assertPermanentCount(playerB, EmptyNames.FACE_DOWN_CREATURE.toString(), 1); // Rattleclaw played as a morph } /** @@ -779,7 +787,7 @@ public class MorphTest extends CardTestPlayerBase { assertGraveyardCount(playerB, "Fatal Push", 1); assertGraveyardCount(playerA, "Pine Walker", 1); - assertPermanentCount(playerA, "", 0); + assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 0); } @@ -813,7 +821,7 @@ public class MorphTest extends CardTestPlayerBase { setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); - assertPermanentCount(playerA, "", 1); + assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 1); assertHandCount(playerA, 0); assertTappedCount("Island", true, 3); @@ -848,7 +856,7 @@ public class MorphTest extends CardTestPlayerBase { castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Quicksilver Dragon"); setChoice(playerA, "Yes"); // cast it face down as 2/2 creature - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Lightning Bolt", ""); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Lightning Bolt", EmptyNames.FACE_DOWN_CREATURE.toString()); setStopAt(2, PhaseStep.UPKEEP); execute(); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/control/ExileAndReturnUnderYourControl.java b/Mage.Tests/src/test/java/org/mage/test/cards/control/ExileAndReturnUnderYourControl.java index 59009ff581..8d5cbf28f5 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/control/ExileAndReturnUnderYourControl.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/control/ExileAndReturnUnderYourControl.java @@ -1,5 +1,6 @@ package org.mage.test.cards.control; +import mage.constants.EmptyNames; import mage.constants.PhaseStep; import mage.constants.Zone; import org.junit.Assert; @@ -9,7 +10,7 @@ import org.mage.test.serverside.base.CardTestPlayerBase; /** * Tests the effect: - Exile target creature you control, then return that card * to the battlefield under your control - * + *

* This effect grants you permanent control over the returned creature. So you * mail steal opponent's creature with "Act of Treason" and then use this effect * for permanent control effect. @@ -103,8 +104,8 @@ public class ExileAndReturnUnderYourControl extends CardTestPlayerBase { assertExileCount("Secret Plans", 0); assertPermanentCount(playerA, "Secret Plans", 1); - assertPermanentCount(playerA, "", 1); - assertPowerToughness(playerA, "", 2, 3); + assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 1); + assertPowerToughness(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 2, 3); } /** diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/copy/CopySpellTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/copy/CopySpellTest.java index fb3892ecbb..9c39f31353 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/copy/CopySpellTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/copy/CopySpellTest.java @@ -1,14 +1,13 @@ - package org.mage.test.cards.copy; import mage.abilities.keyword.FlyingAbility; import mage.constants.PhaseStep; import mage.constants.Zone; import org.junit.Test; +import org.mage.test.player.TestPlayer; import org.mage.test.serverside.base.CardTestPlayerBase; /** - * * @author LevelX2 */ public class CopySpellTest extends CardTestPlayerBase { @@ -17,29 +16,38 @@ public class CopySpellTest extends CardTestPlayerBase { public void copyChainOfVapor() { // Return target nonland permanent to its owner's hand. Then that permanent's controller may sacrifice a land. If the player does, he or she may copy this spell and may choose a new target for that copy. addCard(Zone.HAND, playerA, "Chain of Vapor", 1); - addCard(Zone.BATTLEFIELD, playerA, "Island", 1); + addCard(Zone.BATTLEFIELD, playerA, "Island", 10); - addCard(Zone.BATTLEFIELD, playerA, "Silvercoat Lion", 1); + addCard(Zone.BATTLEFIELD, playerA, "Silvercoat Lion", 10); - addCard(Zone.BATTLEFIELD, playerB, "Pillarfield Ox", 1); - addCard(Zone.BATTLEFIELD, playerB, "Island", 1); + addCard(Zone.BATTLEFIELD, playerB, "Pillarfield Ox", 10); + addCard(Zone.BATTLEFIELD, playerB, "Island", 10); // start chain from A - return pillar to hand castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Chain of Vapor", "Pillarfield Ox"); - //setChoice(playerB, "Yes"); // want to sacrifice + // chain 1 - B can return addTarget(playerB, "Island"); // select a land to sacrifice setChoice(playerB, "Yes"); // want to copy spell setChoice(playerB, "Yes"); // want to change target addTarget(playerB, "Silvercoat Lion"); // new target after copy - // stop the chain on 0 land - addTarget(playerB, ""); + // chain 2 - A can return + addTarget(playerA, "Island"); // select a land to sacrifice + setChoice(playerA, "Yes"); // want to copy spell + setChoice(playerA, "Yes"); // want to change target + addTarget(playerA, "Pillarfield Ox"); // new target after copy + // stop the chain by B + addTarget(playerB, TestPlayer.TARGET_SKIP); setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); + assertAllCommandsUsed(); - assertGraveyardCount(playerB, "Island", 1); - assertHandCount(playerB, "Pillarfield Ox", 1); assertHandCount(playerA, "Silvercoat Lion", 1); + assertHandCount(playerB, "Pillarfield Ox", 2); + assertPermanentCount(playerA, "Silvercoat Lion", 10 - 1); + assertPermanentCount(playerB, "Pillarfield Ox", 10 - 2); + assertGraveyardCount(playerA, "Island", 1); + assertGraveyardCount(playerB, "Island", 1); } @Test @@ -141,7 +149,7 @@ public class CopySpellTest extends CardTestPlayerBase { * before it is cast and therefore before Zada's ability triggers, e.g. * Desperate Ritual spliced onto Into the Fray should generate 3 red mana * for every creature i control. - * + *

* 702.46a Splice is a static ability that functions while a card is in your * hand. “Splice onto [subtype] [cost]” means “You may reveal this card from * your hand as you cast a [subtype] spell. If you do, copy this card's text @@ -190,7 +198,7 @@ public class CopySpellTest extends CardTestPlayerBase { * {4}{U} Enchantment (Enchant Player) Whenever enchanted player casts an * instant or sorcery spell, each other player may copy that spell and may * choose new targets for the copy he or she controls. - * + *

* Reported bug: "A player with Curse of Echoes attached to them played * Bribery and the player who controlled the curse had control of all 3 * copies. This seems to be the case for all spells." diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/dynamicvalue/SweepTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/dynamicvalue/SweepTest.java index 5be97fe2bc..61942c0313 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/dynamicvalue/SweepTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/dynamicvalue/SweepTest.java @@ -3,6 +3,7 @@ package org.mage.test.cards.dynamicvalue; import mage.constants.PhaseStep; import mage.constants.Zone; import org.junit.Test; +import org.mage.test.player.TestPlayer; import org.mage.test.serverside.base.CardTestPlayerBase; /** @@ -80,10 +81,11 @@ public class SweepTest extends CardTestPlayerBase { castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Plow Through Reito"); addTarget(playerA, "Raging Goblin"); // target to boost - addTarget(playerA, ""); // targets to sweep (zero) + addTarget(playerA, TestPlayer.TARGET_SKIP); // targets to sweep (zero) setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); + assertAllCommandsUsed(); assertPermanentCount(playerA, "Raging Goblin", 1); assertPermanentCount(playerA, "Plains", 5); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/enchantments/StarfieldOfNyxTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/enchantments/StarfieldOfNyxTest.java index ec167a6315..882c8e1efb 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/enchantments/StarfieldOfNyxTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/enchantments/StarfieldOfNyxTest.java @@ -1,7 +1,7 @@ - package org.mage.test.cards.enchantments; import mage.abilities.keyword.FlyingAbility; +import mage.constants.EmptyNames; import mage.constants.PhaseStep; import mage.constants.Zone; import mage.filter.Filter; @@ -11,7 +11,6 @@ import org.junit.Test; import org.mage.test.serverside.base.CardTestPlayerBase; /** - * * @author LevelX2 */ public class StarfieldOfNyxTest extends CardTestPlayerBase { @@ -22,7 +21,6 @@ public class StarfieldOfNyxTest extends CardTestPlayerBase { * Starfield of Nyx not only turned both of them into creatures (it * shouldn't, because they're auras), but it also destroyed them. The * manifests stayed on the battlefield without Flying or Hexproof. - * */ @Test public void testCloudform() { @@ -49,7 +47,7 @@ public class StarfieldOfNyxTest extends CardTestPlayerBase { execute(); assertGraveyardCount(playerA, "Thopter Spy Network", 0); - assertPowerToughness(playerA, "", 2, 2, Filter.ComparisonScope.All); // the manifested cards + assertPowerToughness(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 2, 2, Filter.ComparisonScope.All); // the manifested cards assertPermanentCount(playerA, "Starfield of Nyx", 1); assertPowerToughness(playerA, "Thopter Spy Network", 4, 4, Filter.ComparisonScope.All); assertPermanentCount(playerA, "Cloudform", 2); @@ -97,19 +95,19 @@ public class StarfieldOfNyxTest extends CardTestPlayerBase { Assert.assertEquals("Singing Bell Strike not on the battlefield", false, true); } } - + @Test public void testStarfieldOfNyxLayers() { - + addCard(Zone.BATTLEFIELD, playerA, "Starfield of Nyx"); // enchantments you control become creatures addCard(Zone.BATTLEFIELD, playerA, "Humility"); // creatures lose all abilities and are 1/1 addCard(Zone.BATTLEFIELD, playerA, "Pharika, God of Affliction"); // enchantment addCard(Zone.BATTLEFIELD, playerA, "Emrakul, the Aeons Torn"); //15/15 creature addCard(Zone.BATTLEFIELD, playerA, "Crusade", 4); // enchantments to fulfill requirement of Starfield of Nyx - + setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); execute(); - + assertPowerToughness(playerA, "Pharika, God of Affliction", 3, 3, Filter.ComparisonScope.All); assertPowerToughness(playerA, "Humility", 4, 4, Filter.ComparisonScope.All); // Humility loses its ability in layer 6. Layer 7 never gets Humility's effect @@ -117,6 +115,6 @@ public class StarfieldOfNyxTest extends CardTestPlayerBase { Permanent emrakul = getPermanent("Emrakul, the Aeons Torn", playerA.getId()); Assert.assertNotNull(emrakul); Assert.assertFalse(emrakul.getAbilities().contains(FlyingAbility.getInstance())); // loses flying though - + } } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/facedown/GhastlyConscriptionTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/facedown/GhastlyConscriptionTest.java index e4a92ee726..b516159d9b 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/facedown/GhastlyConscriptionTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/facedown/GhastlyConscriptionTest.java @@ -1,5 +1,6 @@ package org.mage.test.cards.facedown; +import mage.constants.EmptyNames; import mage.constants.PhaseStep; import mage.constants.Zone; import org.junit.Test; @@ -13,13 +14,12 @@ public class GhastlyConscriptionTest extends CardTestPlayerBase { /** * Ghastly Conscription * Sorcery, 5BB (7) - * Exile all creature cards from target player's graveyard in a face-down pile, - * shuffle that pile, then manifest those cards. (To manifest a card, put it - * onto the battlefield face down as a 2/2 creature. Turn it face up any time + * Exile all creature cards from target player's graveyard in a face-down pile, + * shuffle that pile, then manifest those cards. (To manifest a card, put it + * onto the battlefield face down as a 2/2 creature. Turn it face up any time * for its mana cost if it's a creature card.) - * */ - + // test that cards exiled using Ghastly Conscription return face down @Test public void testGhastlyConscription() { @@ -36,9 +36,9 @@ public class GhastlyConscriptionTest extends CardTestPlayerBase { assertLife(playerA, 20); assertLife(playerB, 20); - + assertGraveyardCount(playerA, 2); - assertPermanentCount(playerA, "", 2); + assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 2); } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/facedown/ObscuringAetherTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/facedown/ObscuringAetherTest.java index b0755813af..77ec01543d 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/facedown/ObscuringAetherTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/facedown/ObscuringAetherTest.java @@ -1,13 +1,12 @@ - package org.mage.test.cards.facedown; +import mage.constants.EmptyNames; import mage.constants.PhaseStep; import mage.constants.Zone; import org.junit.Test; import org.mage.test.serverside.base.CardTestPlayerBase; /** - * * @author LevelX2 */ public class ObscuringAetherTest extends CardTestPlayerBase { @@ -15,7 +14,6 @@ public class ObscuringAetherTest extends CardTestPlayerBase { /** * Obscuring Aether cannot turn into a face down 2/2 like it should. When * activating the ability to turn it over it, it dies immediately. - * */ // test that cards exiled using Ghastly Conscription return face down @Test @@ -35,8 +33,8 @@ public class ObscuringAetherTest extends CardTestPlayerBase { assertHandCount(playerA, "Obscuring Aether", 0); assertGraveyardCount(playerA, "Obscuring Aether", 0); - assertPermanentCount(playerA, "", 1); - assertPowerToughness(playerA, "", 2, 2); + assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 1); + assertPowerToughness(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 2, 2); } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/rules/CantCastTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/rules/CantCastTest.java index 7fb74ea1ad..4a8ac07bbe 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/rules/CantCastTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/rules/CantCastTest.java @@ -1,6 +1,6 @@ - package org.mage.test.cards.rules; +import mage.constants.EmptyNames; import mage.constants.PhaseStep; import mage.constants.Zone; import mage.counters.CounterType; @@ -8,7 +8,6 @@ import org.junit.Test; import org.mage.test.serverside.base.CardTestPlayerBase; /** - * * @author LevelX2 */ public class CantCastTest extends CardTestPlayerBase { @@ -113,7 +112,7 @@ public class CantCastTest extends CardTestPlayerBase { setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); - assertPermanentCount(playerA, "", 0); + assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 0); assertHandCount(playerA, "Pine Walker", 1); } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/fut/MuragandaPetroglyphsTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/fut/MuragandaPetroglyphsTest.java index 713ea046c0..0b86e47c8b 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/fut/MuragandaPetroglyphsTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/fut/MuragandaPetroglyphsTest.java @@ -1,5 +1,6 @@ package org.mage.test.cards.single.fut; +import mage.constants.EmptyNames; import mage.constants.PhaseStep; import mage.constants.Zone; import mage.filter.Filter; @@ -50,8 +51,8 @@ public class MuragandaPetroglyphsTest extends CardTestPlayerBase { setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); - assertPermanentCount(playerA, "", 1); - assertPowerToughness(playerA, "", 4, 4); + assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 1); + assertPowerToughness(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 4, 4); } @Test @@ -69,8 +70,8 @@ public class MuragandaPetroglyphsTest extends CardTestPlayerBase { setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); - assertPermanentCount(playerA, "", 1); - assertPowerToughness(playerA, "", 2, 2); + assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 1); + assertPowerToughness(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 2, 2); } @Test @@ -155,7 +156,7 @@ public class MuragandaPetroglyphsTest extends CardTestPlayerBase { // Enchanted creature doesn't untap during itscontroller's untap step. addCard(Zone.HAND, playerA, "Dehydration"); - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA,"Rancor", "Grizzly Bears"); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Rancor", "Grizzly Bears"); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Dehydration", "Runeclaw Bear"); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/ths/PurphorosGodOfTheForgeTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/ths/PurphorosGodOfTheForgeTest.java index 18e0ded9a0..d124f0882a 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/ths/PurphorosGodOfTheForgeTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/ths/PurphorosGodOfTheForgeTest.java @@ -1,7 +1,7 @@ - package org.mage.test.cards.single.ths; import mage.constants.CardType; +import mage.constants.EmptyNames; import mage.constants.PhaseStep; import mage.constants.Zone; import mage.game.permanent.Permanent; @@ -10,26 +10,25 @@ import org.junit.Test; import org.mage.test.serverside.base.CardTestPlayerBase; /** - * * @author LevelX2 */ public class PurphorosGodOfTheForgeTest extends CardTestPlayerBase { /** * I had a situation come up today where I had a Purphoros on the field - * and 5 devotion with an Eidolon and Phoenix. My opponent killed the + * and 5 devotion with an Eidolon and Phoenix. My opponent killed the * Phoenix, but Purphoros still was "turned on". */ @Test public void testFacedownNotCountedForDevotion1() { addCard(Zone.BATTLEFIELD, playerB, "Swamp", 5); addCard(Zone.HAND, playerB, "Reach of Shadows"); - + // Indestructible // As long as your devotion to red is less than five, Purphoros isn't a creature. // Whenever another creature enters the battlefield under your control, Purphoros deals 2 damage to each opponent. // {2}{R}: Creatures you control get +1/+0 until end of turn. addCard(Zone.BATTLEFIELD, playerA, "Purphoros, God of the Forge"); - + // Whenever a player casts a spell with converted mana cost 3 or less, // Eidolon of the Great Revel deals 2 damage to that player. addCard(Zone.BATTLEFIELD, playerA, "Eidolon of the Great Revel"); @@ -46,24 +45,24 @@ public class PurphorosGodOfTheForgeTest extends CardTestPlayerBase { assertLife(playerA, 20); assertLife(playerB, 18); // 2 damage from the returning Phoenix - + assertGraveyardCount(playerB, "Reach of Shadows", 1); assertPermanentCount(playerA, "Ashcloud Phoenix", 0); - assertPermanentCount(playerA, "", 1); + assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 1); Permanent purphorosGodOfTheForge = getPermanent("Purphoros, God of the Forge", playerA); Assert.assertFalse("Purphoros may not be a creature but it is", purphorosGodOfTheForge.getCardType().contains(CardType.CREATURE)); } - + @Test - public void testFacedownNotCountedForDevotion2() { + public void testFacedownNotCountedForDevotion2() { addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3); // Indestructible // As long as your devotion to red is less than five, Purphoros isn't a creature. // Whenever another creature enters the battlefield under your control, Purphoros deals 2 damage to each opponent. // {2}{R}: Creatures you control get +1/+0 until end of turn. addCard(Zone.BATTLEFIELD, playerA, "Purphoros, God of the Forge"); - + // Whenever a player casts a spell with converted mana cost 3 or less, // Eidolon of the Great Revel deals 2 damage to that player. addCard(Zone.BATTLEFIELD, playerA, "Eidolon of the Great Revel"); @@ -81,18 +80,18 @@ public class PurphorosGodOfTheForgeTest extends CardTestPlayerBase { execute(); assertPermanentCount(playerA, "Ashcloud Phoenix", 0); - assertPermanentCount(playerA, "", 1); + assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 1); assertLife(playerA, 18); // 2 damage from Eidolon of the Great Revel assertLife(playerB, 18); // 2 damage from Purphoros for the morphed Phoenix - + Permanent purphorosGodOfTheForge = getPermanent("Purphoros, God of the Forge", playerA); Assert.assertFalse("Purphoros may not be a creature but it is", purphorosGodOfTheForge.getCardType().contains(CardType.CREATURE)); - } - + } + @Test public void testHybridManaCostsForDevotion() { - + // Indestructible // As long as your devotion to red is less than five, Purphoros isn't a creature. // Whenever another creature enters the battlefield under your control, Purphoros deals 2 damage to each opponent. @@ -101,11 +100,11 @@ public class PurphorosGodOfTheForgeTest extends CardTestPlayerBase { addCard(Zone.BATTLEFIELD, playerA, "Goblin Guide", 1); // {R} addCard(Zone.HAND, playerA, "Boros Reckoner", 1); // {R/W}{R/W}{R/W} addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3); - + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Boros Reckoner"); setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); - + assertLife(playerB, 18); Permanent purphorosGodOfTheForge = getPermanent("Purphoros, God of the Forge", playerA); Assert.assertTrue("Purphoros should be a creature now but is not", purphorosGodOfTheForge.getCardType().contains(CardType.CREATURE)); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/triggers/dies/WhisperwoodElementalTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/dies/WhisperwoodElementalTest.java index e2a1849c84..e6a48d117a 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/triggers/dies/WhisperwoodElementalTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/dies/WhisperwoodElementalTest.java @@ -1,6 +1,6 @@ - package org.mage.test.cards.triggers.dies; +import mage.constants.EmptyNames; import mage.constants.PhaseStep; import mage.constants.Zone; import org.junit.Test; @@ -18,7 +18,6 @@ public class WhisperwoodElementalTest extends CardTestPlayerBase { /** * Tests that the dies triggered ability of silvercoat lion (gained by sacrificed Whisperwood Elemental) * triggers as he dies from Ligning Bolt - * */ @Test public void testDiesTriggeredAbility() { @@ -30,17 +29,20 @@ public class WhisperwoodElementalTest extends CardTestPlayerBase { activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Sacrifice {this}: Until end of turn, face-up, nontoken creatures you control gain \"When this creature dies, manifest the top card of your library."); castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Lightning Bolt", "Silvercoat Lion"); + showBattlefield("A battle", 1, PhaseStep.END_TURN, playerA); + showGraveyard("A grave", 1, PhaseStep.END_TURN, playerA); setStopAt(1, PhaseStep.END_TURN); execute(); + assertAllCommandsUsed(); assertLife(playerA, 20); assertLife(playerB, 20); - + assertGraveyardCount(playerA, "Whisperwood Elemental", 1); assertGraveyardCount(playerA, "Silvercoat Lion", 1); // Manifested creature from dying Silvercoat Lion - assertPermanentCount(playerA, "", 1); + assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 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 6573e02f11..861dea547b 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 @@ -47,6 +47,7 @@ import mage.players.Player; import mage.players.net.UserData; import mage.target.*; import mage.target.common.*; +import mage.util.CardUtil; import org.apache.log4j.Logger; import org.junit.Assert; import org.junit.Ignore; @@ -69,6 +70,8 @@ public class TestPlayer implements Player { private static final Logger logger = Logger.getLogger(TestPlayer.class); + public static final String TARGET_SKIP = "[skip]"; + private int maxCallsWithoutAction = 100; private int foundNoAction = 0; private boolean AIPlayer; @@ -218,7 +221,7 @@ public class TestPlayer implements Player { filteredName = indexedMatcher.group(1); index = Integer.valueOf(indexedMatcher.group(2)); } - filter.add(new NamePredicate(filteredName)); + filter.add(new NamePredicate(filteredName, true)); // must find any cards even without names List allPermanents = game.getBattlefield().getAllActivePermanents(filter, controllerID, game); if (allPermanents.isEmpty()) { if (failOnNotFound) { @@ -350,7 +353,8 @@ public class TestPlayer implements Player { return true; } - if (nameOrAliase.isEmpty() && object.getName().isEmpty()) { + // must search any names, even empty + if (CardUtil.haveSameNames(nameOrAliase, object.getName(), true)) { return true; } @@ -1519,7 +1523,7 @@ public class TestPlayer implements Player { } // do not select - if (targets.get(0).equals("")) { + if (targets.get(0).equals(TARGET_SKIP)) { Assert.assertEquals("found empty choice, but target is not support 0 choice", 0, target.getMinNumberOfTargets()); targets.remove(0); return true; 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 03f6a38ce1..4c18ad006f 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 @@ -24,6 +24,7 @@ import mage.game.permanent.Permanent; import mage.game.permanent.PermanentCard; import mage.players.ManaPool; import mage.players.Player; +import mage.util.CardUtil; import org.junit.Assert; import org.junit.Before; import org.mage.test.player.PlayerAction; @@ -267,6 +268,7 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement } public void checkPT(String checkName, int turnNum, PhaseStep step, TestPlayer player, String permanentName, Integer power, Integer toughness) { + //Assert.assertNotEquals("", permanentName); check(checkName, turnNum, step, player, CHECK_COMMAND_PT, permanentName, power.toString(), toughness.toString()); } @@ -275,14 +277,17 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement } public void checkAbility(String checkName, int turnNum, PhaseStep step, TestPlayer player, String permanentName, Class abilityClass, Boolean mustHave) { + //Assert.assertNotEquals("", permanentName); check(checkName, turnNum, step, player, CHECK_COMMAND_ABILITY, permanentName, abilityClass.getName(), mustHave.toString()); } public void checkPermanentCount(String checkName, int turnNum, PhaseStep step, TestPlayer player, String permanentName, Integer count) { + //Assert.assertNotEquals("", permanentName); check(checkName, turnNum, step, player, CHECK_COMMAND_PERMANENT_COUNT, permanentName, count.toString()); } public void checkExileCount(String checkName, int turnNum, PhaseStep step, TestPlayer player, String permanentName, Integer count) { + //Assert.assertNotEquals("", permanentName); check(checkName, turnNum, step, player, CHECK_COMMAND_EXILE_COUNT, permanentName, count.toString()); } @@ -291,14 +296,17 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement } public void checkHandCardCount(String checkName, int turnNum, PhaseStep step, TestPlayer player, String cardName, Integer count) { + //Assert.assertNotEquals("", cardName); check(checkName, turnNum, step, player, CHECK_COMMAND_HAND_CARD_COUNT, cardName, count.toString()); } public void checkColor(String checkName, int turnNum, PhaseStep step, TestPlayer player, String permanentName, String colors, Boolean mustHave) { + //Assert.assertNotEquals("", permanentName); check(checkName, turnNum, step, player, CHECK_COMMAND_COLOR, permanentName, colors, mustHave.toString()); } public void checkSubType(String checkName, int turnNum, PhaseStep step, TestPlayer player, String permanentName, SubType subType, Boolean mustHave) { + //Assert.assertNotEquals("", permanentName); check(checkName, turnNum, step, player, CHECK_COMMAND_SUBTYPE, permanentName, subType.toString(), mustHave.toString()); } @@ -494,7 +502,7 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement */ @Override public void setLife(TestPlayer player, int life) { - getCommands(player).put(Zone.OUTSIDE, "life:" + String.valueOf(life)); + getCommands(player).put(Zone.OUTSIDE, "life:" + life); } /** @@ -596,6 +604,7 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement @Override public void assertPowerToughness(Player player, String cardName, int power, int toughness, Filter.ComparisonScope scope) throws AssertionError { + //Assert.assertNotEquals("", cardName); int count = 0; int fit = 0; int foundPower = 0; @@ -650,6 +659,7 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement @Override public void assertAbilities(Player player, String cardName, List abilities) throws AssertionError { + //Assert.assertNotEquals("", cardName); int count = 0; Permanent found = null; for (Permanent permanent : currentGame.getBattlefield().getAllActivePermanents(player.getId())) { @@ -685,6 +695,7 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement * @throws AssertionError */ public void assertAbility(Player player, String cardName, Ability ability, boolean mustHave, int count) throws AssertionError { + //Assert.assertNotEquals("", cardName); int foundCount = 0; Permanent found = null; for (Permanent permanent : currentGame.getBattlefield().getAllActivePermanents(player.getId())) { @@ -735,6 +746,7 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement */ @Override public void assertPermanentCount(Player player, String cardName, int count) throws AssertionError { + //Assert.assertNotEquals("", cardName); int actualCount = 0; for (Permanent permanent : currentGame.getBattlefield().getAllActivePermanents()) { if (permanent.getControllerId().equals(player.getId())) { @@ -748,6 +760,7 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement @Override public void assertCommandZoneCount(Player player, String commandZoneObjectName, int count) throws AssertionError { + //Assert.assertNotEquals("", commandZoneObjectName); int actualCount = 0; for (CommandObject commandObject : currentGame.getState().getCommand()) { if (commandObject.getControllerId().equals(player.getId()) && commandObject.getName().equals(commandZoneObjectName)) { @@ -787,6 +800,7 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement } public void assertCounterCount(Player player, String cardName, CounterType type, int count) throws AssertionError { + //Assert.assertNotEquals("", cardName); Permanent found = null; for (Permanent permanent : currentGame.getBattlefield().getAllActivePermanents()) { if (permanent.getName().equals(cardName) && (player == null || permanent.getControllerId().equals(player.getId()))) { @@ -806,11 +820,12 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement * @param count Expected count. */ public void assertCounterOnExiledCardCount(String cardName, CounterType type, int count) throws AssertionError { + //Assert.assertNotEquals("", cardName); Card found = null; if (found == null) { for (Card card : currentGame.getExile().getAllCards(currentGame)) { - if (card.getName().equals(cardName)) { + if (CardUtil.haveSameNames(card.getName(), cardName, true)) { found = card; break; } @@ -840,6 +855,7 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement * @param mustHave true if creature should have type, false if it should not */ public void assertType(String cardName, CardType type, boolean mustHave) throws AssertionError { + //Assert.assertNotEquals("", cardName); Permanent found = null; for (Permanent permanent : currentGame.getBattlefield().getAllActivePermanents()) { if (permanent.getName().equals(cardName)) { @@ -862,6 +878,7 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement * @param subType a subtype to test for */ public void assertType(String cardName, CardType type, SubType subType) throws AssertionError { + //Assert.assertNotEquals("", cardName); Permanent found = getPermanent(cardName); Assert.assertTrue("(Battlefield) card type not found (" + cardName + ':' + type + ')', found.getCardType().contains(type)); if (subType != null) { @@ -876,6 +893,7 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement * @param type A type to test for */ public void assertNotType(String cardName, CardType type) throws AssertionError { + //Assert.assertNotEquals("", cardName); Permanent found = getPermanent(cardName); Assert.assertFalse("(Battlefield) card type found (" + cardName + ':' + type + ')', found.getCardType().contains(type)); } @@ -887,6 +905,7 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement * @param subType a subtype to test for */ public void assertNotSubtype(String cardName, SubType subType) throws AssertionError { + //Assert.assertNotEquals("", cardName); Permanent found = getPermanent(cardName); if (subType != null) { Assert.assertFalse("(Battlefield) card sub-type equal (" + cardName + ':' + subType.getDescription() + ')', found.getSubtype(currentGame).contains(subType)); @@ -902,6 +921,7 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement * @param mustHave must or not must have that colors */ public void assertColor(Player player, String cardName, ObjectColor searchColors, boolean mustHave) { + //Assert.assertNotEquals("", cardName); Assert.assertNotEquals("must setup colors to search", 0, searchColors.getColorCount()); Permanent card = getPermanent(cardName, player); @@ -936,6 +956,7 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement * @param tapped Whether the permanent is tapped or not */ public void assertTapped(String cardName, boolean tapped) throws AssertionError { + //Assert.assertNotEquals("", cardName); Permanent found = null; for (Permanent permanent : currentGame.getBattlefield().getAllActivePermanents()) { if (permanent.getName().equals(cardName)) { @@ -961,6 +982,7 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement * @param count The amount of this permanents that should be tapped */ public void assertTappedCount(String cardName, boolean tapped, int count) throws AssertionError { + //Assert.assertNotEquals("", cardName); int tappedAmount = 0; Permanent found = null; for (Permanent permanent : currentGame.getBattlefield().getAllActivePermanents()) { @@ -982,6 +1004,7 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement * @param attacking Whether the permanent is attacking or not */ public void assertAttacking(String cardName, boolean attacking) throws AssertionError { + //Assert.assertNotEquals("", cardName); Permanent found = null; for (Permanent permanent : currentGame.getBattlefield().getAllActivePermanents()) { if (permanent.getName().equals(cardName)) { @@ -1013,17 +1036,18 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement * @param count Expected count. */ public void assertHandCount(Player player, String cardName, int count) throws AssertionError { + //Assert.assertNotEquals("", cardName); int actual; if (cardName.contains("//")) { // special logic for cheched split cards, because in game logic of card name filtering is different than for test actual = 0; for (Card card : currentGame.getPlayer(player.getId()).getHand().getCards(currentGame)) { - if (card.getName().equals(cardName)) { + if (CardUtil.haveSameNames(card.getName(), cardName, true)) { actual++; } } } else { FilterCard filter = new FilterCard(); - filter.add(new NamePredicate(cardName)); + filter.add(new NamePredicate(cardName, true)); // must find any cards even without names actual = currentGame.getPlayer(player.getId()).getHand().count(filter, player.getId(), currentGame); } Assert.assertEquals("(Hand) Card counts for card " + cardName + " for " + player.getName() + " are not equal ", count, actual); @@ -1072,10 +1096,11 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement * @param count Expected count. */ public void assertExileCount(String cardName, int count) throws AssertionError { + //Assert.assertNotEquals("", cardName); int actualCount = 0; for (ExileZone exile : currentGame.getExile().getExileZones()) { for (Card card : exile.getCards(currentGame)) { - if (card.getName().equals(cardName)) { + if (CardUtil.haveSameNames(card.getName(), cardName, true)) { actualCount++; } } @@ -1111,6 +1136,7 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement * @param count Expected count. */ public void assertExileCount(Player owner, String cardName, int count) throws AssertionError { + //Assert.assertNotEquals("", cardName); int actualCount = 0; for (ExileZone exile : currentGame.getExile().getExileZones()) { for (Card card : exile.getCards(currentGame)) { @@ -1130,9 +1156,10 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement * @param count Expected count. */ public void assertGraveyardCount(Player player, String cardName, int count) throws AssertionError { + //Assert.assertNotEquals("", cardName); int actualCount = 0; for (Card card : player.getGraveyard().getCards(currentGame)) { - if (card.getName().equals(cardName)) { + if (CardUtil.haveSameNames(card.getName(), cardName, true)) { actualCount++; } } @@ -1147,7 +1174,6 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement * @param count Expected count. */ public void assertLibraryCount(Player player, int count) throws AssertionError { - List libraryList = player.getLibrary().getCards(currentGame); int actualCount = libraryList != null && !libraryList.isEmpty() ? libraryList.size() : 0; Assert.assertEquals("(Library " + player.getName() + ") counts are not equal", count, actualCount); @@ -1161,9 +1187,10 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement * @param count Expected count. */ public void assertLibraryCount(Player player, String cardName, int count) throws AssertionError { + //Assert.assertNotEquals("", cardName); int actualCount = 0; for (Card card : player.getLibrary().getCards(currentGame)) { - if (card.getName().equals(cardName)) { + if (CardUtil.haveSameNames(card.getName(), cardName, true)) { actualCount++; } } @@ -1232,18 +1259,22 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement } public void playLand(int turnNum, PhaseStep step, TestPlayer player, String cardName) { + //Assert.assertNotEquals("", cardName); player.addAction(turnNum, step, "activate:Play " + cardName); } public void castSpell(int turnNum, PhaseStep step, TestPlayer player, String cardName) { + //Assert.assertNotEquals("", cardName); player.addAction(turnNum, step, "activate:Cast " + cardName); } public void castSpell(int turnNum, PhaseStep step, TestPlayer player, String cardName, Player target) { + //Assert.assertNotEquals("", cardName); player.addAction(turnNum, step, "activate:Cast " + cardName + "$targetPlayer=" + target.getName()); } public void castSpell(int turnNum, PhaseStep step, TestPlayer player, String cardName, Player target, int manaInPool) { + //Assert.assertNotEquals("", cardName); player.addAction(turnNum, step, "activate:Cast " + cardName + "$targetPlayer=" + target.getName() + "$manaInPool=" + manaInPool); } @@ -1280,6 +1311,7 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement * multiple targets can be seperated by ^ */ public void castSpell(int turnNum, PhaseStep step, TestPlayer player, String cardName, String targetName) { + //Assert.assertNotEquals("", cardName); player.addAction(turnNum, step, "activate:Cast " + cardName + "$target=" + targetName); } @@ -1318,6 +1350,7 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement * @param clause */ public void castSpell(int turnNum, PhaseStep step, TestPlayer player, String cardName, String targetName, String spellOnStack, StackClause clause) { + //Assert.assertNotEquals("", cardName); if (StackClause.WHILE_ON_STACK == clause) { player.addAction(turnNum, step, "activate:Cast " + cardName + '$' + (targetName != null && targetName.startsWith("target") ? targetName : "target=" + targetName) @@ -1330,6 +1363,7 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement } public void castSpell(int turnNum, PhaseStep step, TestPlayer player, String cardName, String targetName, String spellOnStack, String spellOnTopOfStack) { + //Assert.assertNotEquals("", cardName); String action = "activate:Cast " + cardName + "$target=" + targetName; if (spellOnStack != null && !spellOnStack.isEmpty()) { action += "$spellOnStack=" + spellOnStack; @@ -1398,22 +1432,28 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement } public void addCounters(int turnNum, PhaseStep step, TestPlayer player, String cardName, CounterType type, int count) { + //Assert.assertNotEquals("", cardName); player.addAction(turnNum, step, "addCounters:" + cardName + '$' + type.getName() + '$' + count); } public void attack(int turnNum, TestPlayer player, String attacker) { + //Assert.assertNotEquals("", attacker); player.addAction(turnNum, PhaseStep.DECLARE_ATTACKERS, "attack:" + attacker); } public void attack(int turnNum, TestPlayer player, String attacker, TestPlayer defendingPlayer) { + //Assert.assertNotEquals("", attacker); player.addAction(turnNum, PhaseStep.DECLARE_ATTACKERS, "attack:" + attacker + "$defendingPlayer=" + defendingPlayer.getName()); } public void attack(int turnNum, TestPlayer player, String attacker, String planeswalker) { + //Assert.assertNotEquals("", attacker); player.addAction(turnNum, PhaseStep.DECLARE_ATTACKERS, new StringBuilder("attack:").append(attacker).append("$planeswalker=").append(planeswalker).toString()); } public void block(int turnNum, TestPlayer player, String blocker, String attacker) { + //Assert.assertNotEquals("", blocker); + //Assert.assertNotEquals("", attacker); player.addAction(turnNum, PhaseStep.DECLARE_BLOCKERS, "block:" + blocker + '$' + attacker); } @@ -1507,6 +1547,7 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement } public void assertDamageReceived(Player player, String cardName, int expected) { + //Assert.assertNotEquals("", cardName); Permanent p = getPermanent(cardName, player.getId()); if (p != null) { Assert.assertEquals("Wrong damage received: ", expected, p.getDamage()); diff --git a/Mage.Tests/src/test/java/org/mage/test/testapi/TestAliases.java b/Mage.Tests/src/test/java/org/mage/test/testapi/TestAliases.java index 0085722167..13d6915f92 100644 --- a/Mage.Tests/src/test/java/org/mage/test/testapi/TestAliases.java +++ b/Mage.Tests/src/test/java/org/mage/test/testapi/TestAliases.java @@ -1,7 +1,10 @@ package org.mage.test.testapi; +import mage.constants.EmptyNames; import mage.constants.PhaseStep; import mage.constants.Zone; +import mage.util.CardUtil; +import org.junit.Assert; import org.junit.Test; import org.mage.test.serverside.base.CardTestPlayerBase; @@ -11,6 +14,39 @@ import org.mage.test.serverside.base.CardTestPlayerBase; public class TestAliases extends CardTestPlayerBase { + @Test + public void test_NamesEquals() { + // empty names for face down cards + Assert.assertTrue(CardUtil.haveEmptyName("")); + Assert.assertTrue(CardUtil.haveEmptyName(EmptyNames.FACE_DOWN_CREATURE.toString())); + Assert.assertFalse(CardUtil.haveEmptyName(" ")); + Assert.assertFalse(CardUtil.haveEmptyName("123")); + Assert.assertFalse(CardUtil.haveEmptyName("Sample Name")); + + // same names (empty names can't be same) + Assert.assertFalse(CardUtil.haveSameNames("", "")); + Assert.assertFalse(CardUtil.haveSameNames(EmptyNames.FACE_DOWN_CREATURE.toString(), "")); + Assert.assertFalse(CardUtil.haveSameNames(EmptyNames.FACE_DOWN_CREATURE.toString(), EmptyNames.FACE_DOWN_CREATURE.toString())); + Assert.assertFalse(CardUtil.haveSameNames(EmptyNames.FACE_DOWN_TOKEN.toString(), "")); + Assert.assertFalse(CardUtil.haveSameNames(EmptyNames.FACE_DOWN_TOKEN.toString(), EmptyNames.FACE_DOWN_CREATURE.toString())); + Assert.assertTrue(CardUtil.haveSameNames("Name", "Name")); + Assert.assertFalse(CardUtil.haveSameNames("Name", "")); + Assert.assertFalse(CardUtil.haveSameNames("Name", " ")); + Assert.assertFalse(CardUtil.haveSameNames("Name", "123")); + Assert.assertFalse(CardUtil.haveSameNames("Name", EmptyNames.FACE_DOWN_CREATURE.toString())); + Assert.assertFalse(CardUtil.haveSameNames("Name1", "Name2")); + + // ignore mtg rules (empty names must be same) + Assert.assertTrue(CardUtil.haveSameNames("", "", true)); + Assert.assertTrue(CardUtil.haveSameNames(EmptyNames.FACE_DOWN_CREATURE.toString(), EmptyNames.FACE_DOWN_CREATURE.toString(), true)); + Assert.assertTrue(CardUtil.haveSameNames("Name", "Name", true)); + Assert.assertFalse(CardUtil.haveSameNames("Name", "", true)); + Assert.assertFalse(CardUtil.haveSameNames("Name", " ", true)); + Assert.assertFalse(CardUtil.haveSameNames("Name", "123", true)); + Assert.assertFalse(CardUtil.haveSameNames("Name", EmptyNames.FACE_DOWN_CREATURE.toString(), true)); + Assert.assertFalse(CardUtil.haveSameNames("Name1", "Name2", true)); + } + @Test public void test_DifferentZones() { addCard(Zone.LIBRARY, playerA, "Swamp@lib", 1); diff --git a/Mage/src/main/java/mage/abilities/effects/common/DestroyAllNamedPermanentsEffect.java b/Mage/src/main/java/mage/abilities/effects/common/DestroyAllNamedPermanentsEffect.java index a2ef730ccd..cc767a1a60 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/DestroyAllNamedPermanentsEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/DestroyAllNamedPermanentsEffect.java @@ -1,5 +1,3 @@ - - package mage.abilities.effects.common; import mage.abilities.Ability; @@ -11,9 +9,9 @@ import mage.filter.predicate.mageobject.NamePredicate; import mage.filter.predicate.permanent.PermanentIdPredicate; import mage.game.Game; import mage.game.permanent.Permanent; +import mage.util.CardUtil; /** - * * @author BetaSteward_at_googlemail.com */ public class DestroyAllNamedPermanentsEffect extends OneShotEffect { @@ -38,12 +36,12 @@ public class DestroyAllNamedPermanentsEffect extends OneShotEffect { return false; } FilterPermanent filter = new FilterPermanent(); - if (targetPermanent.getName().isEmpty()) { + if (CardUtil.haveEmptyName(targetPermanent)) { filter.add(new PermanentIdPredicate(targetPermanent.getId())); // if no name (face down creature) only the creature itself is selected } else { filter.add(new NamePredicate(targetPermanent.getName())); } - for (Permanent perm: game.getBattlefield().getActivePermanents(filter, source.getControllerId(), game)) { + for (Permanent perm : game.getBattlefield().getActivePermanents(filter, source.getControllerId(), game)) { perm.destroy(source.getSourceId(), game, false); } return true; diff --git a/Mage/src/main/java/mage/cards/CardImpl.java b/Mage/src/main/java/mage/cards/CardImpl.java index 8b19cf5d3f..56cad1816c 100644 --- a/Mage/src/main/java/mage/cards/CardImpl.java +++ b/Mage/src/main/java/mage/cards/CardImpl.java @@ -327,7 +327,7 @@ public abstract class CardImpl extends MageObjectImpl implements Card { return spellAbility; } -// @Override + // @Override // public void adjustCosts(Ability ability, Game game) { // } @Override @@ -720,7 +720,7 @@ public abstract class CardImpl extends MageObjectImpl implements Card { @Override public String getLogName() { if (name.isEmpty()) { - return GameLog.getNeutralColoredText("face down card"); + return GameLog.getNeutralColoredText(EmptyNames.FACE_DOWN_CREATURE.toString()); } else { return GameLog.getColoredObjectIdName(this); } diff --git a/Mage/src/main/java/mage/constants/EmptyNames.java b/Mage/src/main/java/mage/constants/EmptyNames.java new file mode 100644 index 0000000000..998e75714a --- /dev/null +++ b/Mage/src/main/java/mage/constants/EmptyNames.java @@ -0,0 +1,24 @@ +package mage.constants; + +/** + * + * @author JayDi85 + */ +public enum EmptyNames { + + // TODO: make names for that cards and enable Assert.assertNotEquals("", permanentName); for assertXXX tests + // TODO: replace all getName().equals to haveSameNames and haveEmptyName + FACE_DOWN_CREATURE(""), // "Face down creature" + FACE_DOWN_TOKEN(""); // "Face down token" + + private final String cardName; + + EmptyNames(String cardName) { + this.cardName = cardName; + } + + @Override + public String toString() { + return cardName; + } +} diff --git a/Mage/src/main/java/mage/filter/predicate/mageobject/NamePredicate.java b/Mage/src/main/java/mage/filter/predicate/mageobject/NamePredicate.java index 65ac53f8d4..b80f7766a7 100644 --- a/Mage/src/main/java/mage/filter/predicate/mageobject/NamePredicate.java +++ b/Mage/src/main/java/mage/filter/predicate/mageobject/NamePredicate.java @@ -1,4 +1,3 @@ - package mage.filter.predicate.mageobject; import mage.MageObject; @@ -7,17 +6,23 @@ import mage.constants.SpellAbilityType; import mage.filter.predicate.Predicate; import mage.game.Game; import mage.game.stack.Spell; +import mage.util.CardUtil; /** - * * @author North */ public class NamePredicate implements Predicate { private final String name; + private final Boolean ignoreMtgRuleForEmptyNames; // NamePredicate uses at test and checks, it's must ignore that rules (empty names is not equals in mtg) public NamePredicate(String name) { + this(name, false); + } + + public NamePredicate(String name, Boolean ignoreMtgRuleForEmptyNames) { this.name = name; + this.ignoreMtgRuleForEmptyNames = ignoreMtgRuleForEmptyNames; } @Override @@ -25,17 +30,20 @@ public class NamePredicate implements Predicate { // If a player names a card, the player may name either half of a split card, but not both. // A split card has the chosen name if one of its two names matches the chosen name. if (input instanceof SplitCard) { - return name.equals(((SplitCard)input).getLeftHalfCard().getName()) || name.equals(((SplitCard)input).getRightHalfCard().getName()); - } else if (input instanceof Spell && ((Spell) input).getSpellAbility().getSpellAbilityType() == SpellAbilityType.SPLIT_FUSED){ - SplitCard card = (SplitCard) ((Spell)input).getCard(); - return name.equals(card.getLeftHalfCard().getName()) || name.equals(card.getRightHalfCard().getName()); + return CardUtil.haveSameNames(name, ((SplitCard) input).getLeftHalfCard().getName(), this.ignoreMtgRuleForEmptyNames) || + CardUtil.haveSameNames(name, ((SplitCard) input).getRightHalfCard().getName(), this.ignoreMtgRuleForEmptyNames); + } else if (input instanceof Spell && ((Spell) input).getSpellAbility().getSpellAbilityType() == SpellAbilityType.SPLIT_FUSED) { + SplitCard card = (SplitCard) ((Spell) input).getCard(); + return CardUtil.haveSameNames(name, card.getLeftHalfCard().getName(), this.ignoreMtgRuleForEmptyNames) || + CardUtil.haveSameNames(name, card.getRightHalfCard().getName(), this.ignoreMtgRuleForEmptyNames); } else { if (name.contains(" // ")) { String leftName = name.substring(0, name.indexOf(" // ")); String rightName = name.substring(name.indexOf(" // ") + 4, name.length()); - return leftName.equals(input.getName()) || rightName.equals(input.getName()); + return CardUtil.haveSameNames(leftName, input.getName(), this.ignoreMtgRuleForEmptyNames) || + CardUtil.haveSameNames(rightName, input.getName(), this.ignoreMtgRuleForEmptyNames); } else { - return name.equals(input.getName()); + return CardUtil.haveSameNames(name, input.getName(), this.ignoreMtgRuleForEmptyNames); } } } diff --git a/Mage/src/main/java/mage/game/command/emblems/MomirEmblem.java b/Mage/src/main/java/mage/game/command/emblems/MomirEmblem.java index 272a96ba41..ae721d6d07 100644 --- a/Mage/src/main/java/mage/game/command/emblems/MomirEmblem.java +++ b/Mage/src/main/java/mage/game/command/emblems/MomirEmblem.java @@ -1,7 +1,5 @@ - package mage.game.command.emblems; -import java.util.List; import mage.abilities.Ability; import mage.abilities.common.LimitedTimesPerTurnActivatedAbility; import mage.abilities.costs.common.DiscardCardCost; @@ -13,19 +11,16 @@ import mage.cards.Sets; import mage.cards.repository.CardCriteria; import mage.cards.repository.CardInfo; import mage.cards.repository.CardRepository; -import mage.constants.CardType; -import mage.constants.Outcome; -import mage.constants.SetType; -import mage.constants.TimingRule; -import mage.constants.Zone; +import mage.constants.*; import mage.game.Game; import mage.game.command.Emblem; import mage.game.permanent.token.EmptyToken; import mage.util.CardUtil; import mage.util.RandomUtil; +import java.util.List; + /** - * * @author spjspj */ public final class MomirEmblem extends Emblem { @@ -70,7 +65,7 @@ class MomirEffect extends OneShotEffect { return false; } EmptyToken token = new EmptyToken(); // search for a non custom set creature - while (token.getName().isEmpty() && !options.isEmpty()) { + while (!options.isEmpty()) { int index = RandomUtil.nextInt(options.size()); ExpansionSet expansionSet = Sets.findSet(options.get(index).getSetCode()); if (expansionSet == null || expansionSet.getSetType() == SetType.CUSTOM_SET) { @@ -79,6 +74,7 @@ class MomirEffect extends OneShotEffect { Card card = options.get(index).getCard(); if (card != null) { CardUtil.copyTo(token).from(card); + break; } else { options.remove(index); } diff --git a/Mage/src/main/java/mage/game/permanent/PermanentImpl.java b/Mage/src/main/java/mage/game/permanent/PermanentImpl.java index 776f0b775d..800ccba21d 100644 --- a/Mage/src/main/java/mage/game/permanent/PermanentImpl.java +++ b/Mage/src/main/java/mage/game/permanent/PermanentImpl.java @@ -1,4 +1,3 @@ - package mage.game.permanent; import mage.MageObject; @@ -50,6 +49,7 @@ public abstract class PermanentImpl extends CardImpl implements Permanent { this.counter = counter; this.sourceObject = sourceObject; } + Counter counter; MageObject sourceObject; } @@ -164,7 +164,7 @@ public abstract class PermanentImpl extends CardImpl implements Permanent { @Override public String toString() { StringBuilder sb = threadLocalBuilder.get(); - sb.append(this.name).append('-').append(this.expansionSetCode); + sb.append(this.getName()).append('-').append(this.expansionSetCode); if (copy) { sb.append(" [Copy]"); } @@ -195,10 +195,23 @@ public abstract class PermanentImpl extends CardImpl implements Permanent { } } + @Override + public String getName() { + if (name.isEmpty()) { + if (faceDown) { + return EmptyNames.FACE_DOWN_CREATURE.toString(); + } else { + return ""; + } + } else { + return name; + } + } + @Override public String getValue(GameState state) { StringBuilder sb = threadLocalBuilder.get(); - sb.append(controllerId).append(name).append(tapped).append(damage); + sb.append(controllerId).append(getName()).append(tapped).append(damage); sb.append(subtype).append(supertype).append(power.getValue()).append(toughness.getValue()); sb.append(abilities.getValue()); for (Counter counter : getCounters(state).values()) { @@ -245,7 +258,6 @@ public abstract class PermanentImpl extends CardImpl implements Permanent { } /** - * * @param ability * @param game */ @@ -665,7 +677,7 @@ public abstract class PermanentImpl extends CardImpl implements Permanent { this.attachedTo = attachToObjectId; this.attachedToZoneChangeCounter = game.getState().getZoneChangeCounter(attachToObjectId); for (Ability ability : this.getAbilities()) { - for (Iterator ite = ability.getEffects(game, EffectType.CONTINUOUS).iterator(); ite.hasNext();) { + for (Iterator ite = ability.getEffects(game, EffectType.CONTINUOUS).iterator(); ite.hasNext(); ) { ContinuousEffect effect = (ContinuousEffect) ite.next(); game.getContinuousEffects().setOrder(effect); // It's important to update the timestamp of the copied effect in ContinuousEffects because it does the action @@ -715,8 +727,8 @@ public abstract class PermanentImpl extends CardImpl implements Permanent { * @param game * @param preventable * @param combat - * @param markDamage If true, damage will be dealt later in applyDamage - * method + * @param markDamage If true, damage will be dealt later in applyDamage + * method * @return */ private int damage(int damageAmount, UUID sourceId, Game game, boolean preventable, boolean combat, boolean markDamage, List appliedEffects) { @@ -1441,20 +1453,20 @@ public abstract class PermanentImpl extends CardImpl implements Permanent { //If an object leaves the zone it's in, all attached permanents become unattached //note that this code doesn't actually detach anything, and is a bit of a bandaid public void detachAllAttachments(Game game) { - for(UUID attachmentId : getAttachments()) { + for (UUID attachmentId : getAttachments()) { Permanent attachment = game.getPermanent(attachmentId); Card attachmentCard = game.getCard(attachmentId); - if(attachment != null && attachmentCard != null) { + if (attachment != null && attachmentCard != null) { //make bestow cards and licids into creatures //aura test to stop bludgeon brawl shenanigans from using this code //consider adding code to handle that case? - if(attachment.hasSubtype(SubType.AURA, game) && attachmentCard.isCreature()) { + if (attachment.hasSubtype(SubType.AURA, game) && attachmentCard.isCreature()) { BestowAbility.becomeCreature(attachment, game); } } } } - + @Override public boolean moveToZone(Zone toZone, UUID sourceId, Game game, boolean flag, List appliedEffects) { Zone fromZone = game.getState().getZone(objectId); @@ -1480,7 +1492,7 @@ public abstract class PermanentImpl extends CardImpl implements Permanent { Zone fromZone = game.getState().getZone(objectId); ZoneChangeEvent event = new ZoneChangeEvent(this, sourceId, ownerId, fromZone, Zone.EXILED, appliedEffects); ZoneChangeInfo.Exile info = new ZoneChangeInfo.Exile(event, exileId, name); - + boolean successfullyMoved = ZonesHandler.moveCard(info, game); //20180810 - 701.3d detachAllAttachments(game); diff --git a/Mage/src/main/java/mage/game/permanent/PermanentToken.java b/Mage/src/main/java/mage/game/permanent/PermanentToken.java index bb04a3916d..ba809c2056 100644 --- a/Mage/src/main/java/mage/game/permanent/PermanentToken.java +++ b/Mage/src/main/java/mage/game/permanent/PermanentToken.java @@ -1,15 +1,15 @@ - package mage.game.permanent; -import java.util.UUID; import mage.MageObject; import mage.abilities.Ability; import mage.abilities.costs.mana.ManaCost; +import mage.constants.EmptyNames; import mage.game.Game; import mage.game.permanent.token.Token; +import java.util.UUID; + /** - * * @author BetaSteward_at_googlemail.com */ public class PermanentToken extends PermanentImpl { @@ -42,6 +42,15 @@ public class PermanentToken extends PermanentImpl { this.toughness.resetToBaseValue(); } + @Override + public String getName() { + if (name.isEmpty()) { + return EmptyNames.FACE_DOWN_TOKEN.toString(); + } else { + return name; + } + } + private void copyFromToken(Token token, Game game, boolean reset) { this.name = token.getName(); this.abilities.clear(); diff --git a/Mage/src/main/java/mage/util/CardUtil.java b/Mage/src/main/java/mage/util/CardUtil.java index 53da40ceef..9b3cfe75ca 100644 --- a/Mage/src/main/java/mage/util/CardUtil.java +++ b/Mage/src/main/java/mage/util/CardUtil.java @@ -1,9 +1,5 @@ - package mage.util; -import java.util.UUID; -import java.util.stream.Stream; - import mage.MageObject; import mage.Mana; import mage.abilities.Ability; @@ -12,11 +8,14 @@ import mage.abilities.SpellAbility; import mage.abilities.costs.VariableCost; import mage.abilities.costs.mana.*; import mage.cards.Card; +import mage.constants.EmptyNames; import mage.game.Game; import mage.game.permanent.Permanent; import mage.game.permanent.token.Token; import mage.util.functions.CopyTokenFunction; +import java.util.UUID; + /** * @author nantuko */ @@ -25,10 +24,10 @@ public final class CardUtil { private static final String SOURCE_EXILE_ZONE_TEXT = "SourceExileZone"; static final String[] numberStrings = {"zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", - "ten", "eleven", "twelve", "thirteen", "fourteen", "fifteen", "sixteen", "seventeen", "eighteen", "nineteen", "twenty"}; + "ten", "eleven", "twelve", "thirteen", "fourteen", "fifteen", "sixteen", "seventeen", "eighteen", "nineteen", "twenty"}; static final String[] ordinalStrings = {"first", "second", "third", "fourth", "fifth", "sixth", "seventh", "eightth", "ninth", - "tenth", "eleventh", "twelfth", "thirteenth", "fourteenth", "fifteenth", "sixteenth", "seventeenth", "eighteenth", "nineteenth", "twentieth"}; + "tenth", "eleventh", "twelfth", "thirteenth", "fourteenth", "fifteenth", "sixteenth", "seventeenth", "eighteenth", "nineteenth", "twentieth"}; /** * Increase spell or ability cost to be paid. @@ -129,8 +128,8 @@ public final class CardUtil { * * @param spellAbility * @param manaCostsToReduce costs to reduce - * @param convertToGeneric colored mana does reduce generic mana if no - * appropriate colored mana is in the costs included + * @param convertToGeneric colored mana does reduce generic mana if no + * appropriate colored mana is in the costs included */ public static void adjustCost(SpellAbility spellAbility, ManaCosts manaCostsToReduce, boolean convertToGeneric) { ManaCosts previousCost = spellAbility.getManaCostsToPay(); @@ -315,7 +314,7 @@ public final class CardUtil { * * @param number number to convert to text * @param forOne if the number is 1, this string will be returnedinstead of - * "one". + * "one". * @return */ public static String numberToText(int number, String forOne) { @@ -346,7 +345,7 @@ public final class CardUtil { if (number >= 1 && number < 21) { return ordinalStrings[number - 1]; } - return Integer.toString(number) + "th"; + return number + "th"; } public static String replaceSourceName(String message, String sourceName) { @@ -388,7 +387,7 @@ public final class CardUtil { /** * Creates and saves a (card + zoneChangeCounter) specific exileId. * - * @param game the current game + * @param game the current game * @param source source ability * @return the specific UUID */ @@ -423,9 +422,9 @@ public final class CardUtil { * be specific to a permanent instance. So they won't match, if a permanent * was e.g. exiled and came back immediately. * - * @param text short value to describe the value + * @param text short value to describe the value * @param cardId id of the card - * @param game the game + * @param game the game * @return */ public static String getCardZoneString(String text, UUID cardId, Game game) { @@ -525,9 +524,39 @@ public final class CardUtil { title = textSuffix == null ? "" : textSuffix; } } else { - title = textSuffix == null ? "" : textSuffix;; + title = textSuffix == null ? "" : textSuffix; + ; } return title; } + + /** + * Face down cards and their copy tokens don't have names and that's "empty" names is not equals + */ + public static boolean haveSameNames(String name1, String name2, Boolean ignoreMtgRuleForEmptyNames) { + if (ignoreMtgRuleForEmptyNames) { + // simple compare for tests and engine + return name1 != null && name2 != null && name1.equals(name2); + } else { + // mtg logic compare for game (empty names can't be same) + return !haveEmptyName(name1) && !haveEmptyName(name2) && name1.equals(name2); + } + } + + public static boolean haveSameNames(String name1, String name2) { + return haveSameNames(name1, name2, false); + } + + public static boolean haveSameNames(MageObject object1, MageObject object2) { + return object1 != null && object2 != null && haveSameNames(object1.getName(), object2.getName()); + } + + public static boolean haveEmptyName(String name) { + return name == null || name.isEmpty() || name.equals(EmptyNames.FACE_DOWN_CREATURE.toString()) || name.equals(EmptyNames.FACE_DOWN_TOKEN.toString()); + } + + public static boolean haveEmptyName(MageObject object) { + return object == null || haveEmptyName(object.getName()); + } } From a922cb4d078a5a76ecaa66f022e42698ecd6736e Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Fri, 7 Dec 2018 02:51:11 +0400 Subject: [PATCH 39/42] Fixed Stronghold card numbers --- Mage.Sets/src/mage/sets/Stronghold.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Mage.Sets/src/mage/sets/Stronghold.java b/Mage.Sets/src/mage/sets/Stronghold.java index b575ec9416..173d3fcf77 100644 --- a/Mage.Sets/src/mage/sets/Stronghold.java +++ b/Mage.Sets/src/mage/sets/Stronghold.java @@ -1,4 +1,3 @@ - package mage.sets; import mage.cards.ExpansionSet; @@ -27,6 +26,7 @@ public final class Stronghold extends ExpansionSet { this.numBoosterUncommon = 3; this.numBoosterRare = 1; this.ratioBoosterMythic = 0; + cards.add(new SetCardInfo("Acidic Sliver", 126, Rarity.UNCOMMON, mage.cards.a.AcidicSliver.class)); cards.add(new SetCardInfo("Amok", 76, Rarity.RARE, mage.cards.a.Amok.class)); cards.add(new SetCardInfo("Awakening", 101, Rarity.RARE, mage.cards.a.Awakening.class)); @@ -77,7 +77,7 @@ public final class Stronghold extends ExpansionSet { cards.add(new SetCardInfo("Hermit Druid", 108, Rarity.RARE, mage.cards.h.HermitDruid.class)); cards.add(new SetCardInfo("Hesitation", 33, Rarity.UNCOMMON, mage.cards.h.Hesitation.class)); cards.add(new SetCardInfo("Hibernation Sliver", 128, Rarity.UNCOMMON, mage.cards.h.HibernationSliver.class)); - cards.add(new SetCardInfo("Hidden Retreat", 106, Rarity.RARE, mage.cards.h.HiddenRetreat.class)); + cards.add(new SetCardInfo("Hidden Retreat", 6, Rarity.RARE, mage.cards.h.HiddenRetreat.class)); cards.add(new SetCardInfo("Honor Guard", 7, Rarity.COMMON, mage.cards.h.HonorGuard.class)); cards.add(new SetCardInfo("Horn of Greed", 135, Rarity.RARE, mage.cards.h.HornOfGreed.class)); cards.add(new SetCardInfo("Hornet Cannon", 136, Rarity.UNCOMMON, mage.cards.h.HornetCannon.class)); @@ -117,7 +117,7 @@ public final class Stronghold extends ExpansionSet { cards.add(new SetCardInfo("Rolling Stones", 11, Rarity.RARE, mage.cards.r.RollingStones.class)); cards.add(new SetCardInfo("Ruination", 95, Rarity.RARE, mage.cards.r.Ruination.class)); cards.add(new SetCardInfo("Sacred Ground", 12, Rarity.RARE, mage.cards.s.SacredGround.class)); - cards.add(new SetCardInfo("Samite Blessing", 113, Rarity.COMMON, mage.cards.s.SamiteBlessing.class)); + cards.add(new SetCardInfo("Samite Blessing", 13, Rarity.COMMON, mage.cards.s.SamiteBlessing.class)); cards.add(new SetCardInfo("Scapegoat", 14, Rarity.UNCOMMON, mage.cards.s.Scapegoat.class)); cards.add(new SetCardInfo("Seething Anger", 96, Rarity.COMMON, mage.cards.s.SeethingAnger.class)); cards.add(new SetCardInfo("Serpent Warrior", 69, Rarity.COMMON, mage.cards.s.SerpentWarrior.class)); @@ -127,7 +127,7 @@ public final class Stronghold extends ExpansionSet { cards.add(new SetCardInfo("Shock", 98, Rarity.COMMON, mage.cards.s.Shock.class)); cards.add(new SetCardInfo("Sift", 42, Rarity.COMMON, mage.cards.s.Sift.class)); cards.add(new SetCardInfo("Silver Wyvern", 43, Rarity.RARE, mage.cards.s.SilverWyvern.class)); - cards.add(new SetCardInfo("Skeleton Scavengers", 20, Rarity.RARE, mage.cards.s.SkeletonScavengers.class)); + cards.add(new SetCardInfo("Skeleton Scavengers", 70, Rarity.RARE, mage.cards.s.SkeletonScavengers.class)); cards.add(new SetCardInfo("Skyshroud Archer", 114, Rarity.COMMON, mage.cards.s.SkyshroudArcher.class)); cards.add(new SetCardInfo("Skyshroud Falcon", 16, Rarity.COMMON, mage.cards.s.SkyshroudFalcon.class)); cards.add(new SetCardInfo("Skyshroud Troopers", 115, Rarity.COMMON, mage.cards.s.SkyshroudTroopers.class)); From 41a2573319315275c391c287b8645ca89ce39acc Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Fri, 7 Dec 2018 02:55:27 +0400 Subject: [PATCH 40/42] Fixed wrong MtgJsonV4 data format (#5415); --- .../src/main/java/mage/verify/Booster.java | 8 -- .../src/main/java/mage/verify/JsonCard.java | 82 ++++++++----------- ...{ForeignData.java => JsonForeignData.java} | 14 ++-- .../{Legality.java => JsonLegalities.java} | 14 ++-- .../mage/verify/{Meta.java => JsonMeta.java} | 2 +- .../verify/{Ruling.java => JsonRuling.java} | 4 +- .../src/main/java/mage/verify/JsonSet.java | 40 +++------ .../verify/{Token.java => JsonToken.java} | 7 +- 8 files changed, 69 insertions(+), 102 deletions(-) delete mode 100644 Mage.Verify/src/main/java/mage/verify/Booster.java rename Mage.Verify/src/main/java/mage/verify/{ForeignData.java => JsonForeignData.java} (71%) rename Mage.Verify/src/main/java/mage/verify/{Legality.java => JsonLegalities.java} (58%) rename Mage.Verify/src/main/java/mage/verify/{Meta.java => JsonMeta.java} (75%) rename Mage.Verify/src/main/java/mage/verify/{Ruling.java => JsonRuling.java} (73%) rename Mage.Verify/src/main/java/mage/verify/{Token.java => JsonToken.java} (78%) diff --git a/Mage.Verify/src/main/java/mage/verify/Booster.java b/Mage.Verify/src/main/java/mage/verify/Booster.java deleted file mode 100644 index 161dcfff1d..0000000000 --- a/Mage.Verify/src/main/java/mage/verify/Booster.java +++ /dev/null @@ -1,8 +0,0 @@ -package mage.verify; - -public class Booster { - - public Booster(String mythic){ - - } -} diff --git a/Mage.Verify/src/main/java/mage/verify/JsonCard.java b/Mage.Verify/src/main/java/mage/verify/JsonCard.java index ef857d8d75..8a7d1b6916 100644 --- a/Mage.Verify/src/main/java/mage/verify/JsonCard.java +++ b/Mage.Verify/src/main/java/mage/verify/JsonCard.java @@ -3,59 +3,49 @@ package mage.verify; import java.util.List; class JsonCard { + // docs: https://mtgjson.com/v4/docs.html - public String uuid; - public String convertedManaCost; - public List foreignData; - public boolean isReserved; - public String side; - public Legality legalities; - public List printings; - public List rulings; + public String artist; + public String borderColor; + public List colorIdentity; public List colorIndicator; - public String layout; - public String name; - public List names; // flip cards - public String manaCost; + public List colors; + public float convertedManaCost; + public float faceConvertedManaCost; + public String flavorText; + public List foreignData; + public String frameVersion; public boolean hasFoil; public boolean hasNonFoil; - public String multiverseId; - public String frameVersion; - public int cmc; - public List colors; - public List colorIdentity; - public String type; - public List supertypes; - public List types; - public List subtypes; - public String text; - public String power; - public String toughness; + public boolean isOnlineOnly; + public boolean isOversized; + public boolean isReserved; + public boolean isTimeshifted; + public String layout; + public JsonLegalities legalities; public String loyalty; - public String imageName; - public boolean starter; // only available in boxed sets and not in boosters - public int hand; // vanguard - public int life; // vanguard + public String manaCost; + public int multiverseId; + public String name; + public List names; + public String number; public String originalText; public String originalType; - public String flavorText; - public boolean isOnlineOnly; - - // only available in AllSets.json - public String artist; - public String flavor; - public String id; - public int multiverseid; + public List printings; + public String power; public String rarity; - public boolean reserved; - public int[] variations; - public String number; - public String mciNumber; - public String releaseDate; // promos - public String border; + public List rulings; + public List subtypes; + public List supertypes; + public String text; + public String toughness; + public String type; + public List types; + public String uuid; + public List variations; public String watermark; - public boolean timeshifted; - public String borderColor; - public boolean isOversized; - public String faceConvertedManaCost; + + // unknown + public boolean starter; + public String side; } diff --git a/Mage.Verify/src/main/java/mage/verify/ForeignData.java b/Mage.Verify/src/main/java/mage/verify/JsonForeignData.java similarity index 71% rename from Mage.Verify/src/main/java/mage/verify/ForeignData.java rename to Mage.Verify/src/main/java/mage/verify/JsonForeignData.java index e410fb24d7..c6ca9353e4 100644 --- a/Mage.Verify/src/main/java/mage/verify/ForeignData.java +++ b/Mage.Verify/src/main/java/mage/verify/JsonForeignData.java @@ -1,12 +1,10 @@ package mage.verify; -public class ForeignData { - - - public String language; - public String name; - public String type; - public String text; +public class JsonForeignData { public String flavorText; - public String multiverseId; + public String language; + public int multiverseId; + public String name; + public String text; + public String type; } diff --git a/Mage.Verify/src/main/java/mage/verify/Legality.java b/Mage.Verify/src/main/java/mage/verify/JsonLegalities.java similarity index 58% rename from Mage.Verify/src/main/java/mage/verify/Legality.java rename to Mage.Verify/src/main/java/mage/verify/JsonLegalities.java index 925bc2189e..064838754e 100644 --- a/Mage.Verify/src/main/java/mage/verify/Legality.java +++ b/Mage.Verify/src/main/java/mage/verify/JsonLegalities.java @@ -2,18 +2,18 @@ package mage.verify; import com.fasterxml.jackson.annotation.JsonProperty; -public class Legality { +public class JsonLegalities { @JsonProperty("1v1") public String oneVersusOne; + public String brawl; public String commander; public String duel; + public String frontier; + public String future; public String legacy; + public String modern; public String penny; + public String pauper; + public String standard; public String vintage; -public String frontier; -public String modern; -public String pauper; -public String brawl; -public String future; -public String standard; } diff --git a/Mage.Verify/src/main/java/mage/verify/Meta.java b/Mage.Verify/src/main/java/mage/verify/JsonMeta.java similarity index 75% rename from Mage.Verify/src/main/java/mage/verify/Meta.java rename to Mage.Verify/src/main/java/mage/verify/JsonMeta.java index 4a93171da0..4d68703479 100644 --- a/Mage.Verify/src/main/java/mage/verify/Meta.java +++ b/Mage.Verify/src/main/java/mage/verify/JsonMeta.java @@ -1,6 +1,6 @@ package mage.verify; -public class Meta { +public class JsonMeta { public String date; public String version; } diff --git a/Mage.Verify/src/main/java/mage/verify/Ruling.java b/Mage.Verify/src/main/java/mage/verify/JsonRuling.java similarity index 73% rename from Mage.Verify/src/main/java/mage/verify/Ruling.java rename to Mage.Verify/src/main/java/mage/verify/JsonRuling.java index 5bcb036329..1576700d58 100644 --- a/Mage.Verify/src/main/java/mage/verify/Ruling.java +++ b/Mage.Verify/src/main/java/mage/verify/JsonRuling.java @@ -1,6 +1,6 @@ package mage.verify; -public class Ruling { - public String text; +public class JsonRuling { public String date; + public String text; } diff --git a/Mage.Verify/src/main/java/mage/verify/JsonSet.java b/Mage.Verify/src/main/java/mage/verify/JsonSet.java index 466ebbfb63..95bbbfdd5e 100644 --- a/Mage.Verify/src/main/java/mage/verify/JsonSet.java +++ b/Mage.Verify/src/main/java/mage/verify/JsonSet.java @@ -1,36 +1,20 @@ package mage.verify; -import com.fasterxml.jackson.annotation.JsonIgnore; - import java.util.List; -import java.util.Map; class JsonSet { - public String name; - public String code; - public String oldCode; - public String gathererCode; - public String magicCardsInfoCode; - public String[] magicRaritiesCodes; - public String[] alternativeNames; - public String releaseDate; - public String border; - public String type; - public List booster; // [String|[String]] - public List cards; - public String block; - public boolean onlineOnly; - public String mkm_id; - public String mkm_name; - public Map translations; public int baseSetSize; - @JsonIgnore - public List boosterV3; - public String borderColor; - public Meta meta; - public String mtgoCode; - public List tokens; - public int totalSetSize; - public boolean isOnlineOnly; + public String block; + public List boosterV3; // [["rare", "mythic rare"], "uncommon", "uncommon", "uncommon", "common"] + public List cards; + public String code; public boolean isFoilOnly; + public boolean isOnlineOnly; + public JsonMeta meta; + public String mtgoCode; + public String name; + public String releaseDate; + public List tokens; + public int totalSetSize; + public String type; } diff --git a/Mage.Verify/src/main/java/mage/verify/Token.java b/Mage.Verify/src/main/java/mage/verify/JsonToken.java similarity index 78% rename from Mage.Verify/src/main/java/mage/verify/Token.java rename to Mage.Verify/src/main/java/mage/verify/JsonToken.java index c99ed0abc2..98f3881635 100644 --- a/Mage.Verify/src/main/java/mage/verify/Token.java +++ b/Mage.Verify/src/main/java/mage/verify/JsonToken.java @@ -2,17 +2,20 @@ package mage.verify; import java.util.List; -public class Token { +public class JsonToken { public String artist; public String borderColor; public List colorIdentity; + public List colorIndicator; public List colors; + public String loyalty; public String name; public String number; public String power; - public String toughness; public List reverseRelated; + public String side; public String text; + public String toughness; public String type; public String uuid; public String watermark; From 1986b01bf6d4617db78d3401c913c21c9d57f990 Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Fri, 7 Dec 2018 07:32:49 +0400 Subject: [PATCH 41/42] Fixed card name in GRN; --- Mage.Sets/src/mage/sets/GuildsOfRavnica.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mage.Sets/src/mage/sets/GuildsOfRavnica.java b/Mage.Sets/src/mage/sets/GuildsOfRavnica.java index 0c9ee0fd14..6cf884943b 100644 --- a/Mage.Sets/src/mage/sets/GuildsOfRavnica.java +++ b/Mage.Sets/src/mage/sets/GuildsOfRavnica.java @@ -86,7 +86,7 @@ public final class GuildsOfRavnica extends ExpansionSet { cards.add(new SetCardInfo("Deadly Visit", 68, Rarity.COMMON, mage.cards.d.DeadlyVisit.class)); cards.add(new SetCardInfo("Deafening Clarion", 165, Rarity.RARE, mage.cards.d.DeafeningClarion.class)); cards.add(new SetCardInfo("Demotion", 9, Rarity.UNCOMMON, mage.cards.d.Demotion.class)); - cards.add(new SetCardInfo("Devious Cover-up", 35, Rarity.COMMON, mage.cards.d.DeviousCoverUp.class)); + cards.add(new SetCardInfo("Devious Cover-Up", 35, Rarity.COMMON, mage.cards.d.DeviousCoverUp.class)); cards.add(new SetCardInfo("Devkarin Dissident", 127, Rarity.COMMON, mage.cards.d.DevkarinDissident.class)); cards.add(new SetCardInfo("Dimir Guildgate", 245, Rarity.COMMON, mage.cards.d.DimirGuildgate.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Dimir Guildgate", 246, Rarity.COMMON, mage.cards.d.DimirGuildgate.class, NON_FULL_USE_VARIOUS)); From a4d797e473d3ab30a00b299b1f76e23052e7e8a9 Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Fri, 7 Dec 2018 07:34:38 +0400 Subject: [PATCH 42/42] Fixed mtgjson data; --- .../src/main/java/mage/verify/JsonCard.java | 6 ++-- .../src/main/java/mage/verify/MtgJson.java | 24 ++++++++++--- .../java/mage/verify/VerifyCardDataTest.java | 34 ++++++++++++------- 3 files changed, 43 insertions(+), 21 deletions(-) diff --git a/Mage.Verify/src/main/java/mage/verify/JsonCard.java b/Mage.Verify/src/main/java/mage/verify/JsonCard.java index 8a7d1b6916..0196ee128a 100644 --- a/Mage.Verify/src/main/java/mage/verify/JsonCard.java +++ b/Mage.Verify/src/main/java/mage/verify/JsonCard.java @@ -34,6 +34,8 @@ class JsonCard { public List printings; public String power; public String rarity; + public boolean starter; + public String side; public List rulings; public List subtypes; public List supertypes; @@ -44,8 +46,4 @@ class JsonCard { public String uuid; public List variations; public String watermark; - - // unknown - public boolean starter; - public String side; } diff --git a/Mage.Verify/src/main/java/mage/verify/MtgJson.java b/Mage.Verify/src/main/java/mage/verify/MtgJson.java index 4301ac0b6c..355eafc930 100644 --- a/Mage.Verify/src/main/java/mage/verify/MtgJson.java +++ b/Mage.Verify/src/main/java/mage/verify/MtgJson.java @@ -12,7 +12,10 @@ import java.net.URL; import java.nio.file.Files; import java.nio.file.StandardCopyOption; import java.text.Normalizer; -import java.util.*; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import java.util.zip.ZipInputStream; public final class MtgJson { @@ -60,16 +63,29 @@ public final class MtgJson { static { try { cards = loadAllCards(); - List oldKeys = new ArrayList<>(); + + List keysToDelete = new ArrayList<>(); + + // fix names Map newKeys = new HashMap<>(); for (String key : cards.keySet()) { if (key.contains("(")) { newKeys.put(key.replaceAll("\\(.*\\)", "").trim(), cards.get(key)); - oldKeys.add(key); + keysToDelete.add(key); } } cards.putAll(newKeys); - cards.keySet().removeAll(oldKeys); + cards.keySet().removeAll(keysToDelete); + + // remove wrong data (tokens) + keysToDelete.clear(); + for (Map.Entry record : cards.entrySet()) { + if (record.getValue().layout.equals("token") || record.getValue().layout.equals("double_faced_token")) { + keysToDelete.add(record.getKey()); + } + } + cards.keySet().removeAll(keysToDelete); + addAliases(cards); } catch (IOException e) { throw new RuntimeException(e); diff --git a/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java b/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java index 0ebbe0f3e4..0d11f3230a 100644 --- a/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java +++ b/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java @@ -1,17 +1,5 @@ package mage.verify; -import java.io.IOException; -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Modifier; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.NoSuchFileException; -import java.nio.file.Paths; -import java.util.*; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import java.util.stream.Collectors; import mage.ObjectColor; import mage.abilities.keyword.MultikickerAbility; import mage.cards.*; @@ -29,6 +17,19 @@ import org.mage.plugins.card.images.CardDownloadData; import org.mage.plugins.card.images.DownloadPictures; import org.reflections.Reflections; +import java.io.IOException; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Modifier; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.NoSuchFileException; +import java.nio.file.Paths; +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + /** * @author JayDi85 */ @@ -58,18 +59,25 @@ public class VerifyCardDataTest { skipListCreate("PT"); skipListAddName("PT", "UST", "Garbage Elemental"); skipListAddName("PT", "UST", "Infinity Elemental"); + skipListAddName("PT", "UNH", "Old Fogey"); // color skipListCreate("COLOR"); // cost skipListCreate("COST"); + skipListAddName("COST", "KTK", "Erase"); + skipListAddName("COST", "M13", "Erase"); + skipListAddName("COST", "ULG", "Erase"); + skipListAddName("COST", "H17", "Grimlock, Dinobot Leader"); // supertype skipListCreate("SUPERTYPE"); // type skipListCreate("TYPE"); + skipListAddName("TYPE", "UNH", "Old Fogey"); + skipListAddName("TYPE", "UST", "capital offense"); // subtype skipListCreate("SUBTYPE"); @@ -594,7 +602,7 @@ public class VerifyCardDataTest { // fix names (e.g. Urza’s to Urza's) if (expected != null && expected.contains("Urza’s")) { expected = new ArrayList<>(expected); - for (ListIterator it = ((List) expected).listIterator(); it.hasNext();) { + for (ListIterator it = ((List) expected).listIterator(); it.hasNext(); ) { if (it.next().equals("Urza’s")) { it.set("Urza's"); }