From 1f1d1088a103337c30a7ce772e55868dc724a6f3 Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Sat, 11 Jan 2025 22:21:58 +0400 Subject: [PATCH 01/17] Turn under control reworked: - game: fixed game freezes when computer try to take control over another computer or human (added game logs, related to #12878); - cheats: improved take and give control commands, now you can give control under yourself to another player; - cheats: improved take and give control commands, now you can return control to computer in the same priority; - cheats: deleted useless and unused command to activate opponent's ability; --- .../src/main/java/mage/utils/SystemUtil.java | 84 +++++-------------- .../src/mage/player/human/HumanPlayer.java | 9 +- .../java/org/mage/test/player/TestPlayer.java | 4 +- Mage/src/main/java/mage/players/Player.java | 3 +- .../main/java/mage/players/PlayerImpl.java | 17 +++- Mage/src/main/java/mage/util/CardUtil.java | 6 +- 6 files changed, 50 insertions(+), 73 deletions(-) diff --git a/Mage.Common/src/main/java/mage/utils/SystemUtil.java b/Mage.Common/src/main/java/mage/utils/SystemUtil.java index 4f71a78bca2..aff66539187 100644 --- a/Mage.Common/src/main/java/mage/utils/SystemUtil.java +++ b/Mage.Common/src/main/java/mage/utils/SystemUtil.java @@ -1,8 +1,6 @@ package mage.utils; -import mage.MageObject; import mage.abilities.Ability; -import mage.abilities.ActivatedAbility; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.effects.ContinuousEffect; import mage.abilities.effects.Effect; @@ -26,6 +24,7 @@ import mage.game.permanent.token.Token; import mage.players.Player; import mage.target.Target; import mage.target.TargetPlayer; +import mage.target.common.TargetOpponent; import mage.util.CardUtil; import mage.util.MultiAmountMessage; import mage.util.RandomUtil; @@ -68,15 +67,14 @@ public final class SystemUtil { // [@mana add] -> MANA ADD private static final String COMMAND_CARDS_ADD_TO_HAND = "@card add to hand"; private static final String COMMAND_LANDS_ADD_TO_BATTLEFIELD = "@lands add"; - private static final String COMMAND_OPPONENT_UNDER_CONTROL_START = "@opponent under control start"; - private static final String COMMAND_OPPONENT_UNDER_CONTROL_END = "@opponent under control end"; + private static final String COMMAND_UNDER_CONTROL_TAKE = "@under control take"; + private static final String COMMAND_UNDER_CONTROL_GIVE = "@under control give"; private static final String COMMAND_MANA_ADD = "@mana add"; // TODO: not implemented private static final String COMMAND_RUN_CUSTOM_CODE = "@run custom code"; // TODO: not implemented private static final String COMMAND_SHOW_OPPONENT_HAND = "@show opponent hand"; private static final String COMMAND_SHOW_OPPONENT_LIBRARY = "@show opponent library"; private static final String COMMAND_SHOW_MY_HAND = "@show my hand"; private static final String COMMAND_SHOW_MY_LIBRARY = "@show my library"; - private static final String COMMAND_ACTIVATE_OPPONENT_ABILITY = "@activate opponent ability"; private static final Map supportedCommands = new HashMap<>(); static { @@ -84,14 +82,13 @@ public final class SystemUtil { supportedCommands.put(COMMAND_CARDS_ADD_TO_HAND, "CARDS: ADD TO HAND"); supportedCommands.put(COMMAND_MANA_ADD, "MANA ADD"); supportedCommands.put(COMMAND_LANDS_ADD_TO_BATTLEFIELD, "LANDS: ADD TO BATTLEFIELD"); - supportedCommands.put(COMMAND_OPPONENT_UNDER_CONTROL_START, "OPPONENT CONTROL: ENABLE"); - supportedCommands.put(COMMAND_OPPONENT_UNDER_CONTROL_END, "OPPONENT CONTROL: DISABLE"); + supportedCommands.put(COMMAND_UNDER_CONTROL_TAKE, "UNDER CONTROL: TAKE"); + supportedCommands.put(COMMAND_UNDER_CONTROL_GIVE, "UNDER CONTROL: GIVE"); supportedCommands.put(COMMAND_RUN_CUSTOM_CODE, "RUN CUSTOM CODE"); supportedCommands.put(COMMAND_SHOW_OPPONENT_HAND, "SHOW OPPONENT HAND"); supportedCommands.put(COMMAND_SHOW_OPPONENT_LIBRARY, "SHOW OPPONENT LIBRARY"); supportedCommands.put(COMMAND_SHOW_MY_HAND, "SHOW MY HAND"); supportedCommands.put(COMMAND_SHOW_MY_LIBRARY, "SHOW MY LIBRARY"); - supportedCommands.put(COMMAND_ACTIVATE_OPPONENT_ABILITY, "ACTIVATE OPPONENT ABILITY"); } private static final Pattern patternGroup = Pattern.compile("\\[(.+)\\]"); // [test new card] @@ -307,8 +304,8 @@ public final class SystemUtil { // add default commands initLines.add(0, String.format("[%s]", COMMAND_LANDS_ADD_TO_BATTLEFIELD)); initLines.add(1, String.format("[%s]", COMMAND_CARDS_ADD_TO_HAND)); - initLines.add(2, String.format("[%s]", COMMAND_OPPONENT_UNDER_CONTROL_START)); - initLines.add(3, String.format("[%s]", COMMAND_OPPONENT_UNDER_CONTROL_END)); + initLines.add(2, String.format("[%s]", COMMAND_UNDER_CONTROL_TAKE)); + initLines.add(3, String.format("[%s]", COMMAND_UNDER_CONTROL_GIVE)); // collect all commands CommandGroup currentGroup = null; @@ -427,53 +424,6 @@ public final class SystemUtil { break; } - case COMMAND_ACTIVATE_OPPONENT_ABILITY: { - // WARNING, maybe very bugged if called in wrong priority - // uses choose triggered ability dialog to select it - UUID savedPriorityPlayer = null; - if (game.getActivePlayerId() != opponent.getId()) { - savedPriorityPlayer = game.getActivePlayerId(); - } - - // change active player to find and play selected abilities (it's danger and buggy code) - if (savedPriorityPlayer != null) { - game.getState().setPriorityPlayerId(opponent.getId()); - game.firePriorityEvent(opponent.getId()); - } - - List abilities = opponent.getPlayable(game, true); - Map choices = new HashMap<>(); - abilities.forEach(ability -> { - MageObject object = ability.getSourceObject(game); - choices.put(ability.getId().toString(), object.getName() + ": " + ability.toString()); - }); - // TODO: set priority for us? - Choice choice = new ChoiceImpl(false); - choice.setMessage("Choose playable ability to activate by opponent " + opponent.getName()); - choice.setKeyChoices(choices); - if (feedbackPlayer.choose(Outcome.Detriment, choice, game) && choice.getChoiceKey() != null) { - String needId = choice.getChoiceKey(); - Optional ability = abilities.stream().filter(a -> a.getId().toString().equals(needId)).findFirst(); - if (ability.isPresent()) { - // TODO: set priority for player? - ActivatedAbility activatedAbility = ability.get(); - game.informPlayers(feedbackPlayer.getLogName() + " as another player " + opponent.getLogName() - + " trying to force an activate ability: " + activatedAbility.getGameLogMessage(game)); - if (opponent.activateAbility(activatedAbility, game)) { - game.informPlayers("Force to activate ability: DONE"); - } else { - game.informPlayers("Force to activate ability: FAIL"); - } - } - } - // restore original priority player - if (savedPriorityPlayer != null) { - game.getState().setPriorityPlayerId(savedPriorityPlayer); - game.firePriorityEvent(savedPriorityPlayer); - } - break; - } - case COMMAND_CARDS_ADD_TO_HAND: { // card @@ -552,12 +502,12 @@ public final class SystemUtil { break; } - case COMMAND_OPPONENT_UNDER_CONTROL_START: { - Target target = new TargetPlayer().withNotTarget(true).withChooseHint("to take under your control"); + case COMMAND_UNDER_CONTROL_TAKE: { + Target target = new TargetOpponent().withNotTarget(true).withChooseHint("to take under your control"); Ability fakeSourceAbility = fakeSourceAbilityTemplate.copy(); if (feedbackPlayer.chooseTarget(Outcome.GainControl, target, fakeSourceAbility, game)) { Player targetPlayer = game.getPlayer(target.getFirstTarget()); - if (targetPlayer != null && targetPlayer != feedbackPlayer) { + if (!targetPlayer.getId().equals(feedbackPlayer.getId())) { CardUtil.takeControlUnderPlayerStart(game, fakeSourceAbility, feedbackPlayer, targetPlayer, false); // allow priority play again in same step (for better cheat UX) targetPlayer.resetPassed(); @@ -568,13 +518,19 @@ public final class SystemUtil { break; } - case COMMAND_OPPONENT_UNDER_CONTROL_END: { - Target target = new TargetPlayer().withNotTarget(true).withChooseHint("to free from your control"); + case COMMAND_UNDER_CONTROL_GIVE: { + Target target = new TargetPlayer().withNotTarget(true).withChooseHint("to give control of your player"); Ability fakeSourceAbility = fakeSourceAbilityTemplate.copy(); if (feedbackPlayer.chooseTarget(Outcome.GainControl, target, fakeSourceAbility, game)) { Player targetPlayer = game.getPlayer(target.getFirstTarget()); - if (targetPlayer != null && targetPlayer != feedbackPlayer && !targetPlayer.isGameUnderControl()) { - CardUtil.takeControlUnderPlayerEnd(game, fakeSourceAbility, feedbackPlayer, targetPlayer); + if (targetPlayer != null) { + if (!targetPlayer.getId().equals(feedbackPlayer.getId())) { + // give control to another player + CardUtil.takeControlUnderPlayerStart(game, fakeSourceAbility, targetPlayer, feedbackPlayer, false); + } else { + // return control to itself + CardUtil.takeControlUnderPlayerEnd(game, fakeSourceAbility, feedbackPlayer, targetPlayer); + } } // workaround for refresh priority dialog like avatar click (cheats called from priority in 99%) game.firePriorityEvent(feedbackPlayer.getId()); diff --git a/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/HumanPlayer.java b/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/HumanPlayer.java index 705f2479182..f6618532f77 100644 --- a/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/HumanPlayer.java +++ b/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/HumanPlayer.java @@ -360,9 +360,14 @@ public class HumanPlayer extends PlayerImpl { // async command: cheat by current player if (response.getAsyncWantCheat()) { - // execute cheats and continue + // run cheats SystemUtil.executeCheatCommands(game, null, this); - game.fireUpdatePlayersEvent(); // need force to game update for new possible data + // force to game update for new possible data + game.fireUpdatePlayersEvent(); + // must stop current dialog on changed control, so game can give priority to actual player + if (this.isGameUnderControl() != game.getPlayer(this.getId()).isGameUnderControl()) { + return; + } // wait another answer if (canRespond()) { loop = true; 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 b2270995726..2d086bc2b0f 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 @@ -3080,8 +3080,8 @@ public class TestPlayer implements Player { } @Override - public void controlPlayersTurn(Game game, UUID playerUnderControlId, String info) { - computerPlayer.controlPlayersTurn(game, playerUnderControlId, info); + public boolean controlPlayersTurn(Game game, UUID playerUnderControlId, String info) { + return computerPlayer.controlPlayersTurn(game, playerUnderControlId, info); } @Override diff --git a/Mage/src/main/java/mage/players/Player.java b/Mage/src/main/java/mage/players/Player.java index df84549735d..9566348bc7c 100644 --- a/Mage/src/main/java/mage/players/Player.java +++ b/Mage/src/main/java/mage/players/Player.java @@ -341,8 +341,9 @@ public interface Player extends MageItem, Copyable { * @param game * @param playerUnderControlId * @param info additional info to show in game logs like source + * @return false on failed taken control, e.g. on unsupported player type */ - void controlPlayersTurn(Game game, UUID playerUnderControlId, String info); + boolean controlPlayersTurn(Game game, UUID playerUnderControlId, String info); /** * Sets player {@link UUID} who controls this player's turn. diff --git a/Mage/src/main/java/mage/players/PlayerImpl.java b/Mage/src/main/java/mage/players/PlayerImpl.java index 4eaea5fff07..fc1a1ad0f51 100644 --- a/Mage/src/main/java/mage/players/PlayerImpl.java +++ b/Mage/src/main/java/mage/players/PlayerImpl.java @@ -598,8 +598,17 @@ public abstract class PlayerImpl implements Player, Serializable { } @Override - public void controlPlayersTurn(Game game, UUID playerUnderControlId, String info) { + public boolean controlPlayersTurn(Game game, UUID playerUnderControlId, String info) { Player playerUnderControl = game.getPlayer(playerUnderControlId); + + // TODO: add support computer over computer + // TODO: add support computer over human + if (this.isComputer()) { + // not supported yet + game.informPlayers(getLogName() + " is AI and can't take control over " + playerUnderControl.getLogName() + info); + return false; + } + playerUnderControl.setTurnControlledBy(this.getId()); game.informPlayers(getLogName() + " taken turn control of " + playerUnderControl.getLogName() + info); if (!playerUnderControlId.equals(this.getId())) { @@ -609,6 +618,8 @@ public abstract class PlayerImpl implements Player, Serializable { } // control will reset on start of the turn } + + return true; } @Override @@ -777,10 +788,10 @@ public abstract class PlayerImpl implements Player, Serializable { } // if this method was called from a replacement event, pass the number of cards back through // (uncomment conditions if correct ruling is to only count cards drawn by the same player) - if (event instanceof DrawCardEvent /* && event.getPlayerId().equals(getId()) */ ) { + if (event instanceof DrawCardEvent /* && event.getPlayerId().equals(getId()) */) { ((DrawCardEvent) event).incrementCardsDrawn(numDrawn); } - if (event instanceof DrawTwoOrMoreCardsEvent /* && event.getPlayerId().equals(getId()) */ ) { + if (event instanceof DrawTwoOrMoreCardsEvent /* && event.getPlayerId().equals(getId()) */) { ((DrawTwoOrMoreCardsEvent) event).incrementCardsDrawn(numDrawn); } return numDrawn; diff --git a/Mage/src/main/java/mage/util/CardUtil.java b/Mage/src/main/java/mage/util/CardUtil.java index 3f85a6fa591..dcabf68a6db 100644 --- a/Mage/src/main/java/mage/util/CardUtil.java +++ b/Mage/src/main/java/mage/util/CardUtil.java @@ -1358,7 +1358,11 @@ public final class CardUtil { */ public static void takeControlUnderPlayerStart(Game game, Ability source, Player controller, Player playerUnderControl, boolean givePauseForResponse) { // game logs added in child's call - controller.controlPlayersTurn(game, playerUnderControl.getId(), CardUtil.getSourceLogName(game, source)); + if (!controller.controlPlayersTurn(game, playerUnderControl.getId(), CardUtil.getSourceLogName(game, source))) { + return; + } + + // give pause, so new controller can look around battlefield and hands before finish controlling choose dialog if (givePauseForResponse) { while (controller.canRespond()) { if (controller.chooseUse(Outcome.Benefit, "You got control of " + playerUnderControl.getLogName() -- 2.47.2 From 39872e26255db47228f2aad8a2025f68cf37c79e Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Sun, 12 Jan 2025 14:46:16 +0400 Subject: [PATCH 02/17] AI: fixed that computer can cheat with target selection and choose 1 creature instead multiple required (example: sacrifice, close #13219) --- .../java/mage/player/ai/ComputerPlayer.java | 4 +- .../cards/single/rix/VonasHungerTest.java | 96 +++++++++++++++++++ 2 files changed, 99 insertions(+), 1 deletion(-) create mode 100644 Mage.Tests/src/test/java/org/mage/test/cards/single/rix/VonasHungerTest.java diff --git a/Mage.Server.Plugins/Mage.Player.AI/src/main/java/mage/player/ai/ComputerPlayer.java b/Mage.Server.Plugins/Mage.Player.AI/src/main/java/mage/player/ai/ComputerPlayer.java index 002a3f9d493..562af6d24e3 100644 --- a/Mage.Server.Plugins/Mage.Player.AI/src/main/java/mage/player/ai/ComputerPlayer.java +++ b/Mage.Server.Plugins/Mage.Player.AI/src/main/java/mage/player/ai/ComputerPlayer.java @@ -204,7 +204,9 @@ public class ComputerPlayer extends PlayerImpl { for (Permanent permanent : targets) { if (origTarget.canTarget(abilityControllerId, permanent.getId(), source, game, false) && !target.getTargets().contains(permanent.getId())) { target.add(permanent.getId(), game); - return true; + if (target.isChosen(game)) { + return true; + } } } return false; diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/rix/VonasHungerTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/rix/VonasHungerTest.java new file mode 100644 index 00000000000..89089f52a22 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/rix/VonasHungerTest.java @@ -0,0 +1,96 @@ +package org.mage.test.cards.single.rix; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBaseWithAIHelps; + +/** + * @author JayDi85 + */ +public class VonasHungerTest extends CardTestPlayerBaseWithAIHelps { + + @Test + public void test_NoAscend_Normal() { + // Ascend (If you control ten or more permanents, you get the city's blessing for the rest of the game.) + // Each opponent sacrifices a creature. + // If you have the city's blessing, instead each opponent sacrifices half the creatures they control rounded up. + addCard(Zone.HAND, playerA, "Vona's Hunger"); // {2}{B} + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 3); + // + addCard(Zone.BATTLEFIELD, playerB, "Grizzly Bears", 5); + + // opponent must sacrifice 1 + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Vona's Hunger"); + setChoice(playerB, "Grizzly Bears"); // to sacrifice + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertPermanentCount(playerB, 5 - 1); + } + + @Test + public void test_NoAscend_AI() { + // Ascend (If you control ten or more permanents, you get the city's blessing for the rest of the game.) + // Each opponent sacrifices a creature. + // If you have the city's blessing, instead each opponent sacrifices half the creatures they control rounded up. + addCard(Zone.HAND, playerA, "Vona's Hunger"); // {2}{B} + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 3); + // + addCard(Zone.BATTLEFIELD, playerB, "Grizzly Bears", 5); + + // AI must sacrifice 1 + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Vona's Hunger"); + aiPlayStep(1, PhaseStep.PRECOMBAT_MAIN, playerB); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertPermanentCount(playerB, 5 - 1); + } + + @Test + public void test_Ascend_Normal() { + // Ascend (If you control ten or more permanents, you get the city's blessing for the rest of the game.) + // Each opponent sacrifices a creature. + // If you have the city's blessing, instead each opponent sacrifices half the creatures they control rounded up. + addCard(Zone.HAND, playerA, "Vona's Hunger"); // {2}{B} + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 10); + // + addCard(Zone.BATTLEFIELD, playerB, "Grizzly Bears", 5); + + // opponent must sacrifice 3 + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Vona's Hunger"); + setChoice(playerB, "Grizzly Bears^Grizzly Bears^Grizzly Bears"); // to sacrifice + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertPermanentCount(playerB, 5 - 3); + } + + @Test + public void test_Ascend_AI() { + // Ascend (If you control ten or more permanents, you get the city's blessing for the rest of the game.) + // Each opponent sacrifices a creature. + // If you have the city's blessing, instead each opponent sacrifices half the creatures they control rounded up. + addCard(Zone.HAND, playerA, "Vona's Hunger"); // {2}{B} + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 10); + // + addCard(Zone.BATTLEFIELD, playerB, "Grizzly Bears", 5); + + // AI must sacrifice 3 + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Vona's Hunger"); + aiPlayStep(1, PhaseStep.PRECOMBAT_MAIN, playerB); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertPermanentCount(playerB, 5 - 3); + } +} -- 2.47.2 From 3d147552d1da47783e9fd35279cb9130d249860e Mon Sep 17 00:00:00 2001 From: Cameron Merkel <44722506+Cguy7777@users.noreply.github.com> Date: Mon, 13 Jan 2025 01:56:36 -0600 Subject: [PATCH 03/17] Leyline of Resonance - fixed that it triggering when targeting opponents' creatures (#13223) --- Mage.Sets/src/mage/cards/l/LeylineOfResonance.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mage.Sets/src/mage/cards/l/LeylineOfResonance.java b/Mage.Sets/src/mage/cards/l/LeylineOfResonance.java index 8c8eac7290f..e1ddbc7d1e5 100644 --- a/Mage.Sets/src/mage/cards/l/LeylineOfResonance.java +++ b/Mage.Sets/src/mage/cards/l/LeylineOfResonance.java @@ -24,7 +24,7 @@ public final class LeylineOfResonance extends CardImpl { ); static { - filter.add(new HasOnlySingleTargetPermanentPredicate(StaticFilters.FILTER_PERMANENT_CREATURE)); + filter.add(new HasOnlySingleTargetPermanentPredicate(StaticFilters.FILTER_CONTROLLED_CREATURE)); } public LeylineOfResonance(UUID ownerId, CardSetInfo setInfo) { -- 2.47.2 From b58fbbdd84727fc10c03cd603acb4659a56e47a9 Mon Sep 17 00:00:00 2001 From: Marco Romano Date: Wed, 15 Jan 2025 15:28:30 +0100 Subject: [PATCH 04/17] [DSK] Implement Acrobatic Cheerleader and per-game trigger limits (#13232) --- .../mage/cards/a/AcrobaticCheerleader.java | 43 +++++++ .../src/mage/sets/DuskmournHouseOfHorror.java | 1 + .../TriggerAbilityOnlyLimitedTimesTest.java | 108 ++++++++++++++++++ .../mage/abilities/TriggeredAbilities.java | 3 +- .../java/mage/abilities/TriggeredAbility.java | 12 ++ .../mage/abilities/TriggeredAbilityImpl.java | 59 +++++++++- 6 files changed, 220 insertions(+), 6 deletions(-) create mode 100644 Mage.Sets/src/mage/cards/a/AcrobaticCheerleader.java create mode 100644 Mage.Tests/src/test/java/org/mage/test/cards/rules/TriggerAbilityOnlyLimitedTimesTest.java diff --git a/Mage.Sets/src/mage/cards/a/AcrobaticCheerleader.java b/Mage.Sets/src/mage/cards/a/AcrobaticCheerleader.java new file mode 100644 index 00000000000..11b89648186 --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/AcrobaticCheerleader.java @@ -0,0 +1,43 @@ +package mage.cards.a; + +import mage.MageInt; +import mage.abilities.TriggeredAbility; +import mage.abilities.abilityword.SurvivalAbility; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.counters.CounterType; + +import java.util.UUID; + +/** + * @author markort147 + */ +public final class AcrobaticCheerleader extends CardImpl { + + public AcrobaticCheerleader(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{W}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.SURVIVOR); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // Survival -- At the beginning of your second main phase, if Acrobatic Cheerleader is tapped, put a flying counter on it. This ability triggers only once. + TriggeredAbility ability = new SurvivalAbility(new AddCountersSourceEffect(CounterType.FLYING.createInstance())) + .setTriggersLimitEachGame(1) + .withRuleTextReplacement(true); + this.addAbility(ability); + } + + private AcrobaticCheerleader(final AcrobaticCheerleader card) { + super(card); + } + + @Override + public AcrobaticCheerleader copy() { + return new AcrobaticCheerleader(this); + } +} diff --git a/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java b/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java index 069107adced..4ddb9287005 100644 --- a/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java +++ b/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java @@ -23,6 +23,7 @@ public final class DuskmournHouseOfHorror extends ExpansionSet { cards.add(new SetCardInfo("Abandoned Campground", 255, Rarity.COMMON, mage.cards.a.AbandonedCampground.class)); cards.add(new SetCardInfo("Abhorrent Oculus", 42, Rarity.MYTHIC, mage.cards.a.AbhorrentOculus.class)); + cards.add(new SetCardInfo("Acrobatic Cheerleader", 1, Rarity.COMMON, mage.cards.a.AcrobaticCheerleader.class)); cards.add(new SetCardInfo("Altanak, the Thrice-Called", 166, Rarity.UNCOMMON, mage.cards.a.AltanakTheThriceCalled.class)); cards.add(new SetCardInfo("Anthropede", 167, Rarity.COMMON, mage.cards.a.Anthropede.class)); cards.add(new SetCardInfo("Appendage Amalgam", 83, Rarity.COMMON, mage.cards.a.AppendageAmalgam.class)); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/rules/TriggerAbilityOnlyLimitedTimesTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/rules/TriggerAbilityOnlyLimitedTimesTest.java new file mode 100644 index 00000000000..6a382c3830c --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/rules/TriggerAbilityOnlyLimitedTimesTest.java @@ -0,0 +1,108 @@ + + +package org.mage.test.cards.rules; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import mage.counters.CounterType; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author markort147 + */ + +public class TriggerAbilityOnlyLimitedTimesTest extends CardTestPlayerBase { + + /** + * Enduring Innocence {1}{W}{W} + * Enchantment Creature - Sheep Glimmer + * 2/1 + * Lifelink + * Whenever one or more other creatures you control with power 2 or less enter, draw a card. This ability triggers only once each turn. + * When Enduring Innocence dies, if it was a creature, return it to the battlefield under its owner's control. It's an enchantment. (It's not a creature.) + */ + @Test + public void testTriggerOnceEachTurn() { + addCard(Zone.HAND, playerA, "Llanowar Elves", 2); + addCard(Zone.BATTLEFIELD, playerA, "Enduring Innocence", 1); + addCard(Zone.BATTLEFIELD, playerA, "Forest", 2); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Llanowar Elves", true); // Draw a card + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Llanowar Elves", true); // Do not draw a card + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertHandCount(playerA, 1); + } + + /** + * Momentary Blink {1}{W} + * Instant + * Exile target creature you control, then return it to the battlefield under its owner's control. + * Flashback (You may cast this card from your graveyard for its flashback cost. Then exile it.) + */ + @Test + public void testTriggerTwiceSameTurnIfBlinked() { + + addCard(Zone.HAND, playerA, "Llanowar Elves", 2); + addCard(Zone.HAND, playerA, "Momentary Blink"); + addCard(Zone.BATTLEFIELD, playerA, "Enduring Innocence", 1); + addCard(Zone.BATTLEFIELD, playerA, "Forest", 3); + addCard(Zone.BATTLEFIELD, playerA, "Plains", 1); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Llanowar Elves", true); // Draw a card + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Momentary Blink", "Enduring Innocence", true); // Blink + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Llanowar Elves", true); // Draw a card again + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertHandCount(playerA, 2); + assertPermanentCount(playerA, "Enduring Innocence", 1); + } + + /** + * Acrobatic Cheerleader {1}{W} + * Creature - Human Survivor + * 2/2 + * Survival — At the beginning of your second main phase, if Acrobatic Cheerleader is tapped, put a flying counter on it. This ability triggers only once. + */ + @Test + public void testTriggerOnceEachGame() { + addCard(Zone.BATTLEFIELD, playerA, "Acrobatic Cheerleader", 1); + + attack(3, playerA, "Acrobatic Cheerleader", playerB); // Put a flying counter + attack(5, playerA, "Acrobatic Cheerleader", playerB); // Do not put another flying counter + + setStopAt(5, PhaseStep.END_TURN); + execute(); + + assertCounterCount(playerA, "Acrobatic Cheerleader", CounterType.FLYING, 1); + } + + /** + * Momentary Blink {1}{W} + * Instant + * Exile target creature you control, then return it to the battlefield under its owner's control. + * Flashback (You may cast this card from your graveyard for its flashback cost. Then exile it.) + */ + @Test + public void testTriggerTwiceSameGameIfBlinked() { + addCard(Zone.BATTLEFIELD, playerA, "Acrobatic Cheerleader", 1); + addCard(Zone.HAND, playerA, "Momentary Blink"); + addCard(Zone.BATTLEFIELD, playerA, "Plains", 2); + + attack(3, playerA, "Acrobatic Cheerleader", playerB); // Put a flying counter + castSpell(3, PhaseStep.END_TURN, playerA, "Momentary Blink", "Acrobatic Cheerleader"); // Blinks and loses the counter + attack(5, playerA, "Acrobatic Cheerleader", playerB); // Put a flying counter + + setStopAt(5, PhaseStep.END_TURN); + execute(); + + assertCounterCount(playerA, "Acrobatic Cheerleader", CounterType.FLYING, 1); + } +} \ No newline at end of file diff --git a/Mage/src/main/java/mage/abilities/TriggeredAbilities.java b/Mage/src/main/java/mage/abilities/TriggeredAbilities.java index 3670dc461f6..3d9a78b5acb 100644 --- a/Mage/src/main/java/mage/abilities/TriggeredAbilities.java +++ b/Mage/src/main/java/mage/abilities/TriggeredAbilities.java @@ -244,7 +244,8 @@ public class TriggeredAbilities extends LinkedHashMap NumberOfTriggersEvent numberOfTriggersEvent = new NumberOfTriggersEvent(ability, event); // event == null - state based triggers like StateTriggeredAbility, must be ignored for number event if (event == null || !game.replaceEvent(numberOfTriggersEvent, ability)) { - int numTriggers = Integer.min(ability.getRemainingTriggersLimitEachTurn(game), numberOfTriggersEvent.getAmount()); + int limit = Integer.min(ability.getRemainingTriggersLimitEachGame(game), ability.getRemainingTriggersLimitEachTurn(game)); + int numTriggers = Integer.min(limit, numberOfTriggersEvent.getAmount()); for (int i = 0; i < numTriggers; i++) { if (this.enableIntegrityLogs) { logger.info("trigger will be USED: " + ability); diff --git a/Mage/src/main/java/mage/abilities/TriggeredAbility.java b/Mage/src/main/java/mage/abilities/TriggeredAbility.java index f47a10f76c6..f948f9ffe4b 100644 --- a/Mage/src/main/java/mage/abilities/TriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/TriggeredAbility.java @@ -40,6 +40,11 @@ public interface TriggeredAbility extends Ability { */ TriggeredAbility setTriggersLimitEachTurn(int limit); + /** + * limit the number of triggers each game + */ + TriggeredAbility setTriggersLimitEachGame(int limit); + /** * Get the number of times the trigger may trigger this turn. * e.g. 0, 1 or 2 for a trigger that is limited to trigger twice each turn. @@ -47,6 +52,13 @@ public interface TriggeredAbility extends Ability { */ int getRemainingTriggersLimitEachTurn(Game game); + /** + * Get the number of times the trigger may trigger this game. + * e.g. 0, 1 or 2 for a trigger that is limited to trigger twice each game. + * Integer.MAX_VALUE when no limit. + */ + int getRemainingTriggersLimitEachGame(Game game); + TriggeredAbility setDoOnlyOnceEachTurn(boolean doOnlyOnce); /** diff --git a/Mage/src/main/java/mage/abilities/TriggeredAbilityImpl.java b/Mage/src/main/java/mage/abilities/TriggeredAbilityImpl.java index 212300758ed..cb87938acdc 100644 --- a/Mage/src/main/java/mage/abilities/TriggeredAbilityImpl.java +++ b/Mage/src/main/java/mage/abilities/TriggeredAbilityImpl.java @@ -10,9 +10,7 @@ import mage.constants.Zone; import mage.game.Game; import mage.game.events.BatchEvent; import mage.game.events.GameEvent; -import mage.game.events.ZoneChangeBatchEvent; import mage.game.events.ZoneChangeEvent; -import mage.game.permanent.Permanent; import mage.game.permanent.PermanentToken; import mage.players.Player; import mage.util.CardUtil; @@ -31,6 +29,7 @@ public abstract class TriggeredAbilityImpl extends AbilityImpl implements Trigge private Condition interveningIfCondition; private boolean leavesTheBattlefieldTrigger; private int triggerLimitEachTurn = Integer.MAX_VALUE; // for "triggers only once|twice each turn" + private int triggerLimitEachGame = Integer.MAX_VALUE; // for "triggers only once|twice" private boolean doOnlyOnceEachTurn = false; private boolean replaceRuleText = false; // if true, replace "{this}" with "it" in effect text private GameEvent triggerEvent = null; @@ -60,6 +59,7 @@ public abstract class TriggeredAbilityImpl extends AbilityImpl implements Trigge this.interveningIfCondition = ability.interveningIfCondition; this.leavesTheBattlefieldTrigger = ability.leavesTheBattlefieldTrigger; this.triggerLimitEachTurn = ability.triggerLimitEachTurn; + this.triggerLimitEachGame = ability.triggerLimitEachGame; this.doOnlyOnceEachTurn = ability.doOnlyOnceEachTurn; this.replaceRuleText = ability.replaceRuleText; this.triggerEvent = ability.triggerEvent; @@ -70,7 +70,8 @@ public abstract class TriggeredAbilityImpl extends AbilityImpl implements Trigge public void trigger(Game game, UUID controllerId, GameEvent triggeringEvent) { //20091005 - 603.4 if (checkInterveningIfClause(game)) { - setLastTrigger(game); + updateTurnCount(game); + updateGameCount(game); game.addTriggeredAbility(this, triggeringEvent); } } @@ -89,7 +90,14 @@ public abstract class TriggeredAbilityImpl extends AbilityImpl implements Trigge ); } - private void setLastTrigger(Game game) { + // Used for triggers with a per-game limit. + private String getKeyGameTriggeredCount(Game game) { + return CardUtil.getCardZoneString( + "gameTriggeredCount|" + getOriginalId(), getSourceId(), game + ); + } + + private void updateTurnCount(Game game) { if (triggerLimitEachTurn == Integer.MAX_VALUE) { return; } @@ -108,6 +116,16 @@ public abstract class TriggeredAbilityImpl extends AbilityImpl implements Trigge } } + private void updateGameCount(Game game) { + if (triggerLimitEachGame == Integer.MAX_VALUE) { + return; + } + String keyGameTriggeredCount = getKeyGameTriggeredCount(game); + int lastCount = Optional.ofNullable((Integer) game.getState().getValue(keyGameTriggeredCount)).orElse(0); + // Incrementing the count. + game.getState().setValue(keyGameTriggeredCount, lastCount + 1); + } + @Override public TriggeredAbilityImpl setTriggerPhrase(String triggerPhrase) { this.triggerPhrase = triggerPhrase; @@ -126,7 +144,7 @@ public abstract class TriggeredAbilityImpl extends AbilityImpl implements Trigge @Override public boolean checkTriggeredLimit(Game game) { - return getRemainingTriggersLimitEachTurn(game) > 0; + return getRemainingTriggersLimitEachGame(game) > 0 && getRemainingTriggersLimitEachTurn(game) > 0; } @Override @@ -146,6 +164,12 @@ public abstract class TriggeredAbilityImpl extends AbilityImpl implements Trigge return this; } + @Override + public TriggeredAbility setTriggersLimitEachGame(int limit) { + this.triggerLimitEachGame = limit; + return this; + } + @Override public int getRemainingTriggersLimitEachTurn(Game game) { if (triggerLimitEachTurn == Integer.MAX_VALUE) { @@ -165,6 +189,16 @@ public abstract class TriggeredAbilityImpl extends AbilityImpl implements Trigge } } + @Override + public int getRemainingTriggersLimitEachGame(Game game) { + if (triggerLimitEachGame == Integer.MAX_VALUE) { + return Integer.MAX_VALUE; + } + String keyGameTriggeredCount = getKeyGameTriggeredCount(game); + int count = Optional.ofNullable((Integer) game.getState().getValue(keyGameTriggeredCount)).orElse(0); + return Math.max(0, triggerLimitEachGame - count); + } + @Override public TriggeredAbility setDoOnlyOnceEachTurn(boolean doOnlyOnce) { this.doOnlyOnceEachTurn = doOnlyOnce; @@ -300,6 +334,21 @@ public abstract class TriggeredAbilityImpl extends AbilityImpl implements Trigge } sb.append(" each turn."); } + if (triggerLimitEachGame != Integer.MAX_VALUE) { + sb.append(" This ability triggers only "); + switch (triggerLimitEachGame) { + case 1: + sb.append("once."); + break; + case 2: + // No card with that behavior yet, so feel free to change the text once one exist + sb.append("twice."); + break; + default: + // No card with that behavior yet, so feel free to change the text once one exist + sb.append(CardUtil.numberToText(triggerLimitEachGame)).append(" times."); + } + } if (doOnlyOnceEachTurn) { sb.append(" Do this only once each turn."); } -- 2.47.2 From 5d4112c45d62d02c86cc0facdf3abfa1e80e285a Mon Sep 17 00:00:00 2001 From: Cameron Merkel <44722506+Cguy7777@users.noreply.github.com> Date: Wed, 15 Jan 2025 08:32:00 -0600 Subject: [PATCH 05/17] =?UTF-8?q?Awakened=20Skyclave=20&=20Grond,=20The=20?= =?UTF-8?q?Gatebreaker=20-=20fixed=20that=20it=20doesn=E2=80=99t=20give=20?= =?UTF-8?q?a=20land=20type=20(#13229)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Mage.Sets/src/mage/cards/a/AwakenedSkyclave.java | 2 +- .../common/continuous/AddCardTypeSourceEffect.java | 10 +++++++++- .../common/continuous/AddCardTypeTargetEffect.java | 5 +++++ 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/Mage.Sets/src/mage/cards/a/AwakenedSkyclave.java b/Mage.Sets/src/mage/cards/a/AwakenedSkyclave.java index b104abbe8bd..48e9a24dee8 100644 --- a/Mage.Sets/src/mage/cards/a/AwakenedSkyclave.java +++ b/Mage.Sets/src/mage/cards/a/AwakenedSkyclave.java @@ -35,7 +35,7 @@ public final class AwakenedSkyclave extends CardImpl { this.addAbility(HasteAbility.getInstance()); // As long as Awakened Skyclave is on the battlefield, it's a land in addition to its other types. - this.addAbility(new SimpleStaticAbility(new AddCardTypeSourceEffect(Duration.WhileOnBattlefield) + this.addAbility(new SimpleStaticAbility(new AddCardTypeSourceEffect(Duration.WhileOnBattlefield, CardType.LAND) .setText("as long as {this} is on the battlefield, it's a land in addition to its other types"))); // {T}: Add one mana of any color. diff --git a/Mage/src/main/java/mage/abilities/effects/common/continuous/AddCardTypeSourceEffect.java b/Mage/src/main/java/mage/abilities/effects/common/continuous/AddCardTypeSourceEffect.java index cde16f3fa3c..5d3f0b1bea0 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/continuous/AddCardTypeSourceEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/continuous/AddCardTypeSourceEffect.java @@ -21,12 +21,17 @@ public class AddCardTypeSourceEffect extends ContinuousEffectImpl { public AddCardTypeSourceEffect(Duration duration, CardType... addedCardType) { super(duration, Layer.TypeChangingEffects_4, SubLayer.NA, Outcome.Benefit); + if (addedCardType.length == 0) { + throw new IllegalArgumentException("AddCardTypeSourceEffect should be called with at least one card type."); + } for (CardType cardType : addedCardType) { this.addedCardTypes.add(cardType); if (cardType == CardType.ENCHANTMENT) { dependencyTypes.add(DependencyType.EnchantmentAddingRemoving); } else if (cardType == CardType.ARTIFACT) { dependencyTypes.add(DependencyType.ArtifactAddingRemoving); + } else if (cardType == CardType.LAND) { + dependencyTypes.add(DependencyType.BecomeNonbasicLand); } } } @@ -45,7 +50,10 @@ public class AddCardTypeSourceEffect extends ContinuousEffectImpl { @Override public boolean apply(Game game, Ability source) { Permanent permanent = game.getPermanent(source.getSourceId()); - if (permanent != null && affectedObjectList.contains(new MageObjectReference(permanent, game))) { + if (permanent != null + && (affectedObjectList.contains(new MageObjectReference(permanent, game)) + // Workaround to support abilities like "As long as __, this permanent is a __ in addition to its other types." + || !duration.isOnlyValidIfNoZoneChange())) { for (CardType cardType : addedCardTypes) { permanent.addCardType(game, cardType); } diff --git a/Mage/src/main/java/mage/abilities/effects/common/continuous/AddCardTypeTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/continuous/AddCardTypeTargetEffect.java index 7cda13f3c0e..188465afe27 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/continuous/AddCardTypeTargetEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/continuous/AddCardTypeTargetEffect.java @@ -21,12 +21,17 @@ public class AddCardTypeTargetEffect extends ContinuousEffectImpl { public AddCardTypeTargetEffect(Duration duration, CardType... addedCardType) { super(duration, Layer.TypeChangingEffects_4, SubLayer.NA, Outcome.Benefit); + if (addedCardType.length == 0) { + throw new IllegalArgumentException("AddCardTypeTargetEffect should be called with at least one card type."); + } for (CardType cardType : addedCardType) { this.addedCardTypes.add(cardType); if (cardType == CardType.ENCHANTMENT) { dependencyTypes.add(DependencyType.EnchantmentAddingRemoving); } else if (cardType == CardType.ARTIFACT) { dependencyTypes.add(DependencyType.ArtifactAddingRemoving); + } else if (cardType == CardType.LAND) { + dependencyTypes.add(DependencyType.BecomeNonbasicLand); } } -- 2.47.2 From c82511f96f0eb9005c34c84ec1347ab46d546e66 Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Thu, 16 Jan 2025 08:13:36 +0400 Subject: [PATCH 06/17] tests: added more strictly checks for choice commands order, fixed wrong commands in old tests (related to #12044); --- Mage.Sets/src/mage/cards/m/MimicVat.java | 2 +- .../abilities/keywords/ConspireTest.java | 26 +-- .../mage/test/cards/copy/MimicVatTest.java | 25 ++- .../test/cards/copy/PhantasmalImageTest.java | 170 +++++++++++++----- .../additional/RemoveCounterCostTest.java | 15 +- .../cost/modification/HeartstoneTest.java | 11 +- .../cost/sacrifice/SacrificeLandTest.java | 27 ++- .../entersBattlefield/PrimalClayTest.java | 4 +- .../single/dmc/CadricSoulKindlerTest.java | 13 +- .../java/org/mage/test/player/TestPlayer.java | 17 +- .../costs/common/RemoveCounterCost.java | 9 +- 11 files changed, 219 insertions(+), 100 deletions(-) diff --git a/Mage.Sets/src/mage/cards/m/MimicVat.java b/Mage.Sets/src/mage/cards/m/MimicVat.java index 8d7a377ec93..a09b547b86b 100644 --- a/Mage.Sets/src/mage/cards/m/MimicVat.java +++ b/Mage.Sets/src/mage/cards/m/MimicVat.java @@ -42,7 +42,7 @@ public final class MimicVat extends CardImpl { // Imprint - Whenever a nontoken creature dies, you may exile that card. If you do, return each other card exiled with Mimic Vat to its owner's graveyard. this.addAbility(new MimicVatTriggeredAbility()); - // {3}, {tap}: Create a token that's a copy of the exiled card. It gains haste. Exile it at the beginning of the next end step. + // {3}, {T}: Create a token that's a copy of the exiled card. It gains haste. Exile it at the beginning of the next end step. Ability ability = new SimpleActivatedAbility(new MimicVatCreateTokenEffect(), new GenericManaCost(3)); ability.addCost(new TapSourceCost()); this.addAbility(ability); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/ConspireTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/ConspireTest.java index 138c9f6ac1c..7d4c6d447a7 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/ConspireTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/ConspireTest.java @@ -47,6 +47,7 @@ public class ConspireTest extends CardTestPlayerBase { setChoice(playerA, "Goblin Roughrider^Raging Goblin"); // tap for conspire setChoice(playerA, false); // don't change target + setStrictChooseMode(true); setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); @@ -54,7 +55,6 @@ public class ConspireTest extends CardTestPlayerBase { assertGraveyardCount(playerA, "Burn Trail", 1); assertTapped("Goblin Roughrider", true); assertTapped("Raging Goblin", true); - } @Test @@ -67,6 +67,7 @@ public class ConspireTest extends CardTestPlayerBase { castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Burn Trail", playerB); setChoice(playerA, false); + setStrictChooseMode(true); setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); @@ -103,7 +104,6 @@ public class ConspireTest extends CardTestPlayerBase { assertPermanentCount(playerA, "Wort, the Raidmother", 1); assertGraveyardCount(playerA, "Lightning Bolt", 1); assertLife(playerB, 20 - 3 - 3); - } @Test @@ -137,7 +137,6 @@ public class ConspireTest extends CardTestPlayerBase { assertGraveyardCount(playerA, "Lightning Bolt", 1); assertTapped("Raging Goblin", false); assertLife(playerB, 20 - 3 - 3 - 2); - } @Test @@ -158,8 +157,10 @@ public class ConspireTest extends CardTestPlayerBase { setChoice(playerA, "Goblin Warrior Token", 2); // tap for conspire setChoice(playerA, false); // keep targets + setStrictChooseMode(true); setStopAt(1, PhaseStep.END_TURN); execute(); + assertPermanentCount(playerA, "Wort, the Raidmother", 1); assertLife(playerB, 20 - 3 - 3); assertLife(playerA, 20); @@ -181,22 +182,20 @@ public class ConspireTest extends CardTestPlayerBase { castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Burn Trail", playerB); setChoice(playerA, true); // use Conspire from Burn Trail itself setChoice(playerA, true); // use Conspire gained from Wort, the Raidmother - setChoice(playerA, "When you pay the conspire"); // order triggers - setChoice(playerA, "Goblin Warrior Token", 2); // tap for conspire - setChoice(playerA, false); // keep targets - setChoice(playerA, "Raging Goblin", 2); // tap for conspire + setChoice(playerA, "When you pay the conspire"); // order triggers + setChoice(playerA, false); // keep targets setChoice(playerA, false); // keep targets - + setStrictChooseMode(true); setStopAt(1, PhaseStep.END_TURN); execute(); + assertPermanentCount(playerA, "Wort, the Raidmother", 1); assertLife(playerB, 20 - 3 - 3 - 3); assertLife(playerA, 20); assertGraveyardCount(playerA, "Burn Trail", 1); - } @Test @@ -221,8 +220,10 @@ public class ConspireTest extends CardTestPlayerBase { setChoice(playerA, "Goblin Warrior Token", 2); // tap for conspire setChoice(playerA, false); // keep targets + setStrictChooseMode(true); setStopAt(1, PhaseStep.END_TURN); execute(); + assertPermanentCount(playerA, "Wort, the Raidmother", 1); assertPermanentCount(playerA, "Sakashima the Impostor", 1); assertLife(playerB, 20 - 3 - 3); @@ -248,15 +249,16 @@ public class ConspireTest extends CardTestPlayerBase { castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Lightning Bolt", playerB); setChoice(playerA, true); // use Conspire gained from Wort, the Raidmother setChoice(playerA, true); // use Conspire gained from Sakashima the Imposter + setChoice(playerA, "Goblin Warrior Token", 2); // tap for conspire + setChoice(playerA, "Goblin Warrior Token", 2); // tap for conspire setChoice(playerA, "When you pay the conspire"); // order triggers - - setChoice(playerA, "Goblin Warrior Token", 2); // tap for conspire setChoice(playerA, false); // keep targets - setChoice(playerA, "Goblin Warrior Token", 2); // tap for conspire setChoice(playerA, false); // keep targets + setStrictChooseMode(true); setStopAt(1, PhaseStep.END_TURN); execute(); + assertPermanentCount(playerA, "Wort, the Raidmother", 1); assertPermanentCount(playerA, "Sakashima the Impostor", 1); assertLife(playerB, 20 - 3 - 3 - 3); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/copy/MimicVatTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/copy/MimicVatTest.java index feb34603a76..ae18716ec97 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/copy/MimicVatTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/copy/MimicVatTest.java @@ -10,10 +10,10 @@ import org.mage.test.serverside.base.CardTestPlayerBase; * {3} * Artifact * Imprint — Whenever a nontoken creature dies, you may exile that card. - * If you do, return each other card exiled with Mimic Vat to its owner’s graveyard. + * If you do, return each other card exiled with Mimic Vat to its owner’s graveyard. * {3}, {T}: Create a token that’s a copy of a card exiled with Mimic Vat. - * It gains haste. - * Exile it at the beginning of the next end step. + * It gains haste. + * Exile it at the beginning of the next end step. * * @author LevelX2 */ @@ -77,17 +77,23 @@ public class MimicVatTest extends CardTestPlayerBase { addCard(Zone.BATTLEFIELD, playerB, "Silvercoat Lion", 1); + // prepare copy castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Phyrexian Metamorph", true); - setChoice(playerA, true); - setChoice(playerA, "Silvercoat Lion"); + setChoice(playerA, true); // pay 2 life + setChoice(playerA, true); // copy on etb + setChoice(playerA, "Silvercoat Lion"); // copy + // sacrifice and exile a copy activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{2}, {T}, Sacrifice a creature"); - setChoice(playerA, true); - - activateAbility(3, PhaseStep.PRECOMBAT_MAIN, playerA, "{3}, {T}: Create a token that's a copy of a card exiled with "); - setChoice(playerA, true); setChoice(playerA, "Silvercoat Lion"); + setChoice(playerA, true); + // create copy of exiled Metamorph + activateAbility(3, PhaseStep.PRECOMBAT_MAIN, playerA, "{3}, {T}: Create a token that's a copy of a card exiled with "); + setChoice(playerA, true); // copy on etb + setChoice(playerA, "Silvercoat Lion"); // copy + + setStrictChooseMode(true); setStopAt(3, PhaseStep.BEGIN_COMBAT); execute(); @@ -125,6 +131,7 @@ public class MimicVatTest extends CardTestPlayerBase { activateAbility(3, PhaseStep.PRECOMBAT_MAIN, playerA, "{3}, {T}: Create a token that's a copy of a card exiled with "); + setStrictChooseMode(true); setStopAt(3, PhaseStep.BEGIN_COMBAT); execute(); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/copy/PhantasmalImageTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/copy/PhantasmalImageTest.java index 75ef87401b0..e1f47e65aa0 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/copy/PhantasmalImageTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/copy/PhantasmalImageTest.java @@ -9,12 +9,13 @@ import mage.constants.Zone; import mage.game.permanent.Permanent; import org.junit.Assert; import org.junit.Test; +import org.mage.test.player.TestPlayer; import org.mage.test.serverside.base.CardTestPlayerBase; import static org.junit.Assert.*; /** - * @author noxx + * @author noxx, JayDi85 *

* Card: You may have {this} enter the battlefield as a copy of any creature on * the battlefield, except it's an Illusion in addition to its other types and @@ -30,7 +31,10 @@ public class PhantasmalImageTest extends CardTestPlayerBase { addCard(Zone.BATTLEFIELD, playerB, "Craw Wurm"); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Phantasmal Image"); + setChoice(playerA, true); // use copy on etb + setChoice(playerA, "Craw Wurm"); // copy + setStrictChooseMode(true); setStopAt(2, PhaseStep.END_TURN); execute(); @@ -51,7 +55,10 @@ public class PhantasmalImageTest extends CardTestPlayerBase { addCard(Zone.BATTLEFIELD, playerB, "Howling Banshee"); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Phantasmal Image"); + setChoice(playerA, true); // use copy on etb + setChoice(playerA, "Howling Banshee"); // copy + setStrictChooseMode(true); setStopAt(2, PhaseStep.END_TURN); execute(); @@ -79,7 +86,10 @@ public class PhantasmalImageTest extends CardTestPlayerBase { } castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Phantasmal Image"); + setChoice(playerB, true); // use copy on etb + setChoice(playerB, "Transcendent Master"); // copy + setStrictChooseMode(true); setStopAt(2, PhaseStep.END_TURN); execute(); @@ -112,10 +122,14 @@ public class PhantasmalImageTest extends CardTestPlayerBase { addCard(Zone.BATTLEFIELD, playerA, "Illusionary Servant"); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Phantasmal Image", true); + setChoice(playerA, true); // use copy on etb setChoice(playerA, "Illusionary Servant"); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Phantasmal Image"); + setChoice(playerA, true); // use copy on etb setChoice(playerA, "Illusionary Servant-M10"); + setStrictChooseMode(true); setStopAt(1, PhaseStep.END_TURN); execute(); @@ -147,16 +161,28 @@ public class PhantasmalImageTest extends CardTestPlayerBase { // Enchantment - Creatures you control have hexproof. addCard(Zone.HAND, playerA, "Asceticism"); + // Huntmaster of the Fells: // Whenever this creature enters the battlefield or transforms into // Huntmaster of the Fells, put a 2/2 green Wolf creature token onto // the battlefield and you gain 2 life. // At the beginning of each upkeep, if no spells were cast last turn, transform Huntmaster of the Fells. ==> Ravager of the Fells + // Ravager of the Fells: + // Whenever this creature transforms into Ravager of the Fells, it deals 2 damage to target opponent and 2 damage to up to one target creature that player controls. addCard(Zone.BATTLEFIELD, playerA, "Huntmaster of the Fells"); + // transform on upkeep to Ravager of the Fells + addTarget(playerA, playerB); // x2 damage on transform (player) + addTarget(playerA, TestPlayer.TARGET_SKIP); // x2 damage on transform (creature) + + // copy transformed castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Phantasmal Image"); // copy target: Ravergers of the Fells + setChoice(playerB, true); // use copy on etb + setChoice(playerB, "Ravager of the Fells"); // copy + castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, "Asceticism"); castSpell(3, PhaseStep.POSTCOMBAT_MAIN, playerB, "Titanic Growth", "Ravager of the Fells"); + setStrictChooseMode(true); setStopAt(3, PhaseStep.END_TURN); execute(); @@ -167,7 +193,6 @@ public class PhantasmalImageTest extends CardTestPlayerBase { // check playerB's creature was sacrificed assertGraveyardCount(playerB, "Phantasmal Image", 1); assertPermanentCount(playerB, "Ravager of the Fells", 0); - } /** @@ -175,7 +200,7 @@ public class PhantasmalImageTest extends CardTestPlayerBase { * Messenger: Geralf's Messenger enters the battlefield tapped */ @Test - public void testCopyEntersTapped() { + public void testCopyEntersTappedAndEtb() { addCard(Zone.BATTLEFIELD, playerA, "Island", 2); // You may have Phantasmal Image enter the battlefield as a copy of any creature // on the battlefield, except it's an Illusion in addition to its other types and @@ -184,7 +209,11 @@ public class PhantasmalImageTest extends CardTestPlayerBase { addCard(Zone.BATTLEFIELD, playerB, "Geralf's Messenger"); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Phantasmal Image"); + setChoice(playerA, true); // use copy on etb + setChoice(playerA, "Geralf's Messenger"); // copy + addTarget(playerA, playerB); // x2 damage from etb of copied + setStrictChooseMode(true); setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); @@ -211,11 +240,14 @@ public class PhantasmalImageTest extends CardTestPlayerBase { // When you control no permanents of the chosen color, sacrifice Lurebound Scarecrow. addCard(Zone.HAND, playerA, "Lurebound Scarecrow"); - setChoice(playerA, "Green"); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lurebound Scarecrow"); - setChoice(playerA, "Red"); + setChoice(playerA, "Green"); castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Phantasmal Image"); + setChoice(playerA, true); // use copy on etb + setChoice(playerA, "Lurebound Scarecrow"); // copy + setChoice(playerA, "Red"); + setStrictChooseMode(true); setStopAt(1, PhaseStep.END_TURN); execute(); @@ -235,11 +267,14 @@ public class PhantasmalImageTest extends CardTestPlayerBase { addCard(Zone.HAND, playerA, "Phantasmal Image"); addCard(Zone.HAND, playerA, "Lurebound Scarecrow"); - setChoice(playerA, "Green"); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lurebound Scarecrow"); - setChoice(playerA, "Red"); + setChoice(playerA, "Green"); castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Phantasmal Image"); + setChoice(playerA, true); // use copy on etb + setChoice(playerA, "Lurebound Scarecrow"); // copy + setChoice(playerA, "Red"); + setStrictChooseMode(true); setStopAt(1, PhaseStep.END_TURN); execute(); @@ -257,12 +292,16 @@ public class PhantasmalImageTest extends CardTestPlayerBase { addCard(Zone.BATTLEFIELD, playerB, "Llanowar Elves"); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Phantasmal Image"); + setChoice(playerA, true); // use copy on etb + setChoice(playerA, "Azure Drake"); // copy + attack(1, playerA, "Azure Drake"); block(1, playerB, "Llanowar Elves", "Azure Drake"); attack(2, playerB, "Azure Drake"); block(2, playerA, "Elite Vanguard", "Azure Drake"); + setStrictChooseMode(true); setStopAt(2, PhaseStep.END_TURN); execute(); @@ -288,13 +327,15 @@ public class PhantasmalImageTest extends CardTestPlayerBase { setChoice(playerA, "X=0"); castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Phantasmal Image"); - setChoice(playerB, "Steel Hellkite"); + setChoice(playerB, true); // use copy on etb + setChoice(playerB, "Steel Hellkite"); // copy attack(4, playerB, "Steel Hellkite"); activateAbility(4, PhaseStep.POSTCOMBAT_MAIN, playerB, "{X}:"); setChoice(playerB, "X=0"); + setStrictChooseMode(true); setStopAt(4, PhaseStep.END_TURN); execute(); @@ -306,7 +347,6 @@ public class PhantasmalImageTest extends CardTestPlayerBase { assertPermanentCount(playerA, "Chalice of the Void", 0); assertGraveyardCount(playerA, "Chalice of the Void", 1); - } /** @@ -317,38 +357,59 @@ public class PhantasmalImageTest extends CardTestPlayerBase { */ @Test public void testCopiedFrostTitan() { - // Whenever Frost Titan becomes the target of a spell or ability an opponent controls, counter that spell or ability unless its controller pays {2}. - // Whenever Frost Titan enters the battlefield or attacks, tap target permanent. It doesn't untap during its controller's next untap step. + // Whenever Frost Titan becomes the target of a spell or ability an opponent controls, + // counter that spell or ability unless its controller pays 2. + // Whenever Frost Titan enters the battlefield or attacks, tap target permanent. It doesn't + // untap during its controller's next untap step. addCard(Zone.BATTLEFIELD, playerA, "Frost Titan"); - addCard(Zone.HAND, playerA, "Terror"); + addCard(Zone.BATTLEFIELD, playerA, "Island", 2); // for counter pay + addCard(Zone.BATTLEFIELD, playerB, "Island", 2); // for counter pay + // // {1}{U} - Target creature gains shroud until end of turn and can't be blocked this turn. - addCard(Zone.HAND, playerA, "Veil of Secrecy"); - addCard(Zone.BATTLEFIELD, playerA, "Swamp", 3); + addCard(Zone.HAND, playerA, "Veil of Secrecy", 1); addCard(Zone.BATTLEFIELD, playerA, "Island", 2); - + addCard(Zone.HAND, playerB, "Veil of Secrecy", 1); + addCard(Zone.BATTLEFIELD, playerB, "Island", 2); + // + addCard(Zone.HAND, playerB, "Phantasmal Image"); // {1}{U} addCard(Zone.BATTLEFIELD, playerB, "Island", 2); - addCard(Zone.HAND, playerB, "Phantasmal Image"); - castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Phantasmal Image"); // not targeted - castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerA, "Veil of Secrecy", "Frost Titan"); // so it's no longer targetable - setChoice(playerB, "Frost Titan"); + // prepare copy + castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Phantasmal Image"); + setChoice(playerB, true); // use copy on etb + setChoice(playerB, "Frost Titan"); // copy + addTarget(playerB, "Island"); // tap on etb + waitStackResolved(2, PhaseStep.PRECOMBAT_MAIN); - castSpell(2, PhaseStep.POSTCOMBAT_MAIN, playerA, "Terror", "Frost Titan"); // of player Bs Phantasmal Image copying Frost Titan - // should be countered if not paying {2} + // make sure both has titan permanent + checkPermanentCount("after prepare", 2, PhaseStep.PRECOMBAT_MAIN, playerB, playerA, "Frost Titan", 1); + checkPermanentCount("after prepare", 2, PhaseStep.PRECOMBAT_MAIN, playerB, playerB, "Frost Titan", 1); + // try original target trigger (B cast into A's titan and ask to anti-counter pay) + castSpell(2, PhaseStep.POSTCOMBAT_MAIN, playerB, "Veil of Secrecy"); + addTarget(playerB, "Frost Titan[no copy]"); + setChoice(playerB, true); // pay to stop counter + waitStackResolved(2, PhaseStep.POSTCOMBAT_MAIN); + + // try copied target trigger (A cast into B's titan and ask to anti-counter pay) + castSpell(2, PhaseStep.POSTCOMBAT_MAIN, playerA, "Veil of Secrecy"); + addTarget(playerA, "Frost Titan[only copy]"); + setChoice(playerB, "When {this} becomes the target of a spell or ability, sacrifice it."); // x2 triggers (make sure illusion's will go first) + setChoice(playerA, true); // pay to stop counter (it's useless here, but will sure trigger works fine) + + setStrictChooseMode(true); setStopAt(2, PhaseStep.END_TURN); execute(); - assertGraveyardCount(playerA, "Veil of Secrecy", 1); - assertGraveyardCount(playerA, "Terror", 1); - assertLife(playerB, 20); assertLife(playerA, 20); assertPermanentCount(playerA, "Frost Titan", 1); + assertPermanentCount(playerB, "Frost Titan", 0); + assertGraveyardCount(playerB, "Phantasmal Image", 1); - assertGraveyardCount(playerB, "Phantasmal Image", 1); // if triggered ability did not work, the Titan would be in the graveyard instaed - + // 2 from cast Phantasmal + 4 from cast Veil + 4 from counter pay + assertTappedCount("Island", true, 2 + 2 * 2 + 2 * 2); } // I've casted a Phantasmal Image targeting opponent's Wurmcoil Engine @@ -365,12 +426,14 @@ public class PhantasmalImageTest extends CardTestPlayerBase { addCard(Zone.BATTLEFIELD, playerB, "Island", 2); addCard(Zone.HAND, playerB, "Phantasmal Image"); - castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Phantasmal Image"); // not targeted - setChoice(playerB, "Wurmcoil Engine"); + castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Phantasmal Image"); + setChoice(playerB, true); // use copy on etb + setChoice(playerB, "Wurmcoil Engine"); // copy - castSpell(2, PhaseStep.POSTCOMBAT_MAIN, playerA, "Public Execution", "Wurmcoil Engine"); // of player Bs Phantasmal Image copying Frost Titan - // should be countered if not paying {2} + castSpell(2, PhaseStep.POSTCOMBAT_MAIN, playerA, "Public Execution"); + addTarget(playerA, "Wurmcoil Engine[only copy]"); + setStrictChooseMode(true); setStopAt(2, PhaseStep.END_TURN); execute(); @@ -403,11 +466,14 @@ public class PhantasmalImageTest extends CardTestPlayerBase { addCard(Zone.BATTLEFIELD, playerB, "Island", 2); addCard(Zone.HAND, playerB, "Phantasmal Image"); - castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Phantasmal Image"); // not targeted - setChoice(playerB, "Thalakos Seer"); + castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Phantasmal Image"); + setChoice(playerB, true); // use copy on etb + setChoice(playerB, "Thalakos Seer"); // copy - castSpell(2, PhaseStep.POSTCOMBAT_MAIN, playerA, "Public Execution", "Thalakos Seer"); + castSpell(2, PhaseStep.POSTCOMBAT_MAIN, playerA, "Public Execution"); + addTarget(playerA, "Thalakos Seer[only copy]"); + setStrictChooseMode(true); setStopAt(2, PhaseStep.END_TURN); execute(); @@ -474,11 +540,16 @@ public class PhantasmalImageTest extends CardTestPlayerBase { castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Kitchen Finks"); castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Phantasmal Image"); // not targeted + setChoice(playerB, true); // use copy on etb + setChoice(playerB, "Kitchen Finks"); // copy + + // destroy and return to battlefield by persist + castSpell(2, PhaseStep.POSTCOMBAT_MAIN, playerA, "Public Execution"); + addTarget(playerA, "Kitchen Finks[only copy]"); + setChoice(playerB, true); // use copy on etb setChoice(playerB, "Kitchen Finks"); - castSpell(2, PhaseStep.POSTCOMBAT_MAIN, playerA, "Public Execution", "Kitchen Finks"); - setChoice(playerB, "Kitchen Finks"); - + setStrictChooseMode(true); setStopAt(2, PhaseStep.END_TURN); execute(); @@ -515,11 +586,16 @@ public class PhantasmalImageTest extends CardTestPlayerBase { castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Butcher Ghoul"); castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Phantasmal Image"); // not targeted - setChoice(playerB, "Butcher Ghoul"); + setChoice(playerB, true); // use copy on etb + setChoice(playerB, "Butcher Ghoul"); // copy - castSpell(2, PhaseStep.POSTCOMBAT_MAIN, playerA, "Public Execution", "Butcher Ghoul"); - setChoice(playerB, "Butcher Ghoul"); + // destroy and return to battlefield by undying + castSpell(2, PhaseStep.POSTCOMBAT_MAIN, playerA, "Public Execution"); + addTarget(playerA, "Butcher Ghoul[only copy]"); + setChoice(playerB, true); // use copy on etb + setChoice(playerB, "Butcher Ghoul"); // copy + setStrictChooseMode(true); setStopAt(2, PhaseStep.END_TURN); execute(); @@ -559,10 +635,13 @@ public class PhantasmalImageTest extends CardTestPlayerBase { addCard(Zone.HAND, playerA, "Phantasmal Image"); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Phantasmal Image"); // not targeted + setChoice(playerA, true); // use copy on etb setChoice(playerA, "Wurmcoil Engine"); attack(2, playerB, "Wurmcoil Engine"); block(2, playerA, "Wurmcoil Engine", "Wurmcoil Engine"); + + setStrictChooseMode(true); setStopAt(2, PhaseStep.POSTCOMBAT_MAIN); execute(); @@ -591,10 +670,13 @@ public class PhantasmalImageTest extends CardTestPlayerBase { addCard(Zone.HAND, playerA, "Phantasmal Image"); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Phantasmal Image"); // not targeted + setChoice(playerA, true); // use copy on etb setChoice(playerA, "Voice of Resurgence"); attack(2, playerB, "Voice of Resurgence"); block(2, playerA, "Voice of Resurgence", "Voice of Resurgence"); + + setStrictChooseMode(true); setStopAt(2, PhaseStep.POSTCOMBAT_MAIN); execute(); @@ -615,12 +697,14 @@ public class PhantasmalImageTest extends CardTestPlayerBase { addCard(Zone.BATTLEFIELD, playerA, "Island", 2); addCard(Zone.HAND, playerA, "Phantasmal Image"); - setChoice(playerB, "X=1"); activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerB, "{X}"); + setChoice(playerB, "X=1"); - setChoice(playerA, "Chimeric Staff"); castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Phantasmal Image"); + setChoice(playerA, true); // use copy on etb + setChoice(playerA, "Chimeric Staff"); // copy + setStrictChooseMode(true); setStopAt(1, PhaseStep.END_TURN); execute(); @@ -646,9 +730,11 @@ public class PhantasmalImageTest extends CardTestPlayerBase { castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Karn's Touch", "Cloak and Dagger"); - setChoice(playerA, "Cloak and Dagger"); castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Phantasmal Image"); + setChoice(playerA, true); // use copy on etb + setChoice(playerA, "Cloak and Dagger"); // copy + setStrictChooseMode(true); setStopAt(1, PhaseStep.END_TURN); execute(); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/cost/additional/RemoveCounterCostTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/cost/additional/RemoveCounterCostTest.java index da992b438a4..41dd7262dbc 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/cost/additional/RemoveCounterCostTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/cost/additional/RemoveCounterCostTest.java @@ -1,4 +1,3 @@ - package org.mage.test.cards.cost.additional; import mage.constants.PhaseStep; @@ -7,7 +6,6 @@ import org.junit.Test; import org.mage.test.serverside.base.CardTestPlayerBase; /** - * * @author LevelX2 */ @@ -15,16 +13,18 @@ public class RemoveCounterCostTest extends CardTestPlayerBase { @Test public void testNovijenSages() { - addCard(Zone.BATTLEFIELD, playerA, "Island", 7); // Graft 4 // {1}, Remove two +1/+1 counters from among creatures you control: Draw a card. addCard(Zone.HAND, playerA, "Novijen Sages"); + addCard(Zone.BATTLEFIELD, playerA, "Island", 7); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Novijen Sages", true); - - activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{1}, Remove two +1/+1 counters"); - setChoice(playerA, "X=2"); + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{1}, Remove two +1/+1 counters"); + setChoice(playerA, "Novijen Sages"); // counters to remove + setChoice(playerA, "X=2"); // counters to remove + + setStrictChooseMode(true); setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); @@ -32,9 +32,8 @@ public class RemoveCounterCostTest extends CardTestPlayerBase { assertPowerToughness(playerA, "Novijen Sages", 2, 2); assertHandCount(playerA, 1); - + assertLife(playerA, 20); assertLife(playerB, 20); } - } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/cost/modification/HeartstoneTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/cost/modification/HeartstoneTest.java index 1ec742aa810..13ecc8352a1 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/cost/modification/HeartstoneTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/cost/modification/HeartstoneTest.java @@ -1,4 +1,3 @@ - package org.mage.test.cards.cost.modification; import mage.constants.PhaseStep; @@ -7,7 +6,6 @@ import org.junit.Test; import org.mage.test.serverside.base.CardTestPlayerBase; /** - * * @author LevelX2 */ public class HeartstoneTest extends CardTestPlayerBase { @@ -29,8 +27,12 @@ public class HeartstoneTest extends CardTestPlayerBase { castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Lightning Bolt", playerA); activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{1}{U}", "Lightning Bolt", "Lightning Bolt"); - setChoice(playerA, true); - addTarget(playerA, playerB); + setChoice(playerA, "Fugitive Wizard"); // tap cost 1 of 2 + setChoice(playerA, "Sigil Tracer"); // tap cost 2 of 2 + setChoice(playerA, true); // change target + addTarget(playerA, playerB); // new target + + setStrictChooseMode(true); setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); @@ -41,7 +43,6 @@ public class HeartstoneTest extends CardTestPlayerBase { assertLife(playerB, 17); assertTappedCount("Island", true, 1); - } } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/cost/sacrifice/SacrificeLandTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/cost/sacrifice/SacrificeLandTest.java index b895bf7b1f0..c95d87b64b7 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/cost/sacrifice/SacrificeLandTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/cost/sacrifice/SacrificeLandTest.java @@ -4,16 +4,14 @@ import mage.constants.PhaseStep; import mage.constants.Zone; import mage.util.RandomUtil; import org.junit.Test; -import org.mage.test.sba.PlaneswalkerRuleTest; import org.mage.test.serverside.base.CardTestPlayerBase; -import java.util.Random; - /** * Reported bugs: https://github.com/magefree/mage/issues/8980 - * Cards like Soldevi Excavations, Heart of Yavimaya, and Lake of the Dead - * will sometimes immediately go to the graveyard without asking the controller if they wish to sacrifice a land - * even though they had one available + * Cards like Soldevi Excavations, Heart of Yavimaya, and Lake of the Dead + * will sometimes immediately go to the graveyard without asking the controller if they wish to sacrifice a land + * even though they had one available + * * @author Alex-Vasile */ public class SacrificeLandTest extends CardTestPlayerBase { @@ -35,14 +33,21 @@ public class SacrificeLandTest extends CardTestPlayerBase { playLand(1, PhaseStep.PRECOMBAT_MAIN, playerA, soldeviExcavations); setChoice(playerA, sacFirstLand); + if (sacFirstLand) { + setChoice(playerA, "Island"); + } rollbackTurns(1, PhaseStep.PRECOMBAT_MAIN, playerA, 0); rollbackAfterActionsStart(); playLand(1, PhaseStep.PRECOMBAT_MAIN, playerA, soldeviExcavations); setChoice(playerA, sacSecondLand); + if (sacSecondLand) { + setChoice(playerA, "Island"); + } rollbackAfterActionsEnd(); + setStrictChooseMode(true); setStopAt(1, PhaseStep.PRECOMBAT_MAIN); execute(); @@ -73,14 +78,18 @@ public class SacrificeLandTest extends CardTestPlayerBase { addCard(Zone.BATTLEFIELD, playerA, "Swamp"); playLand(1, PhaseStep.PRECOMBAT_MAIN, playerA, soldeviExcavations); - setChoice(playerA, "Yes"); + setChoice(playerA, "Yes"); // use sacrifice to keep soldevi + setChoice(playerA, "Island"); // sacrifice playLand(3, PhaseStep.PRECOMBAT_MAIN, playerA, heartofYavimaya); - setChoice(playerA, "Yes"); + setChoice(playerA, "Yes"); // use sacrifice to keep heart + setChoice(playerA, "Forest"); // sacrifice playLand(5, PhaseStep.PRECOMBAT_MAIN, playerA, lakeOfTheDead); - setChoice(playerA, "Yes"); + setChoice(playerA, "Yes"); // use sacrifice to keep lake + setChoice(playerA, "Swamp"); // sacrifice + setStrictChooseMode(true); setStopAt(5, PhaseStep.PRECOMBAT_MAIN); execute(); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/replacement/entersBattlefield/PrimalClayTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/replacement/entersBattlefield/PrimalClayTest.java index 6177edc5bec..3fba06e5618 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/replacement/entersBattlefield/PrimalClayTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/replacement/entersBattlefield/PrimalClayTest.java @@ -181,8 +181,8 @@ public class PrimalClayTest extends CardTestPlayerBase { castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, clone); setChoice(playerB, true); // whether to copy - setFlipCoinResult(playerB, false); setChoice(playerB, sentry); // what to copy + setFlipCoinResult(playerB, false); setStrictChooseMode(true); setStopAt(2, PhaseStep.BEGIN_COMBAT); @@ -218,7 +218,7 @@ public class PrimalClayTest extends CardTestPlayerBase { // Target creature you control gets +2/+2 until end of turn if its power is 2. Then it fights target creature you don’t control. addCard(Zone.BATTLEFIELD, playerB, "Siege Mastodon", 1); // 3/5 creature for fighting - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, aquamorph+" using Morph"); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, aquamorph + " using Morph"); waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Savage Swipe"); addTarget(playerA, EmptyNames.FACE_DOWN_CREATURE.getTestCommand()); // morph diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/dmc/CadricSoulKindlerTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/dmc/CadricSoulKindlerTest.java index 1c93a61452e..19070a56425 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/dmc/CadricSoulKindlerTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/dmc/CadricSoulKindlerTest.java @@ -10,6 +10,9 @@ import org.mage.test.serverside.base.CardTestPlayerBase; */ public class CadricSoulKindlerTest extends CardTestPlayerBase { + // The "legend rule" doesn't apply to tokens you control. + // Whenever another nontoken legendary permanent you control enters, you may pay {1}. If you do, create a token + // that's a copy of it. That token gains haste. Sacrifice it at the beginning of the next end step. private static final String cadric = "Cadric, Soul Kindler"; private static final String isamaru = "Isamaru, Hound of Konda"; @@ -20,7 +23,7 @@ public class CadricSoulKindlerTest extends CardTestPlayerBase { addCard(Zone.HAND, playerA, isamaru); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, isamaru); - setChoice(playerA, true); + setChoice(playerA, true); // create copy setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); setStrictChooseMode(true); @@ -36,12 +39,14 @@ public class CadricSoulKindlerTest extends CardTestPlayerBase { addCard(Zone.BATTLEFIELD, playerA, "Plains", 4); addCard(Zone.HAND, playerA, isamaru, 2); + // first isamaru and copy of it castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, isamaru); - setChoice(playerA, true); + setChoice(playerA, true); // create copy + // second isamaru and copy of it castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, isamaru); - setChoice(playerA, true); - setChoice(playerA, isamaru); + setChoice(playerA, isamaru); // keep 1 perm due legendary rule + setChoice(playerA, true); // create copy setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); setStrictChooseMode(true); 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 2d086bc2b0f..e09423b00c4 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 @@ -2293,7 +2293,8 @@ public class TestPlayer implements Player { } else { filterPermanent = ((TargetPermanent) target.getOriginalTarget()).getFilter(); } - for (String choiceRecord : choices) { + while (!choices.isEmpty()) { + String choiceRecord = choices.get(0); String[] targetList = choiceRecord.split("\\^"); boolean targetFound = false; for (String targetName : targetList) { @@ -2332,9 +2333,17 @@ public class TestPlayer implements Player { } } } - if (targetFound) { - choices.remove(choiceRecord); - return true; + + try { + if (target.isChosen(game)) { + return true; + } else { + if (!targetFound) { + Assert.fail(String.format("Found wrong choice command:\n%s\n%s\n%s", choiceRecord, getInfo(target, game), getInfo(source, game))); + } + } + } finally { + choices.remove(0); } } } diff --git a/Mage/src/main/java/mage/abilities/costs/common/RemoveCounterCost.java b/Mage/src/main/java/mage/abilities/costs/common/RemoveCounterCost.java index f952023890d..15f52b824ad 100644 --- a/Mage/src/main/java/mage/abilities/costs/common/RemoveCounterCost.java +++ b/Mage/src/main/java/mage/abilities/costs/common/RemoveCounterCost.java @@ -78,8 +78,9 @@ public class RemoveCounterCost extends CostImpl { Outcome outcome; if (target instanceof TargetPermanent) { - outcome = Outcome.UnboostCreature; - } else if (target instanceof TargetCard) { // For Mari, the Killing Quill + outcome = Outcome.AIDontUseIt; + } else if (target instanceof TargetCard) { + // Mari, the Killing Quill - AI can safely use it all the time outcome = Outcome.Neutral; } else { throw new IllegalArgumentException( @@ -123,7 +124,7 @@ public class RemoveCounterCost extends CostImpl { } choice.setChoices(choices); choice.setMessage("Choose a counter to remove from " + targetObject.getLogName()); - if (!controller.choose(Outcome.UnboostCreature, choice, game)) { + if (!controller.choose(outcome, choice, game)) { return false; } counterName = choice.getChoice(); @@ -135,7 +136,7 @@ public class RemoveCounterCost extends CostImpl { int numberOfCountersSelected = 1; if (countersLeft > 1 && countersOnPermanent > 1) { numberOfCountersSelected = controller.getAmount(1, Math.min(countersLeft, countersOnPermanent), - "Remove how many counters from " + targetObject.getIdName(), game); + "Choose how many counters (" + counterName + ") to remove from " + targetObject.getLogName() + " as payment", game); } targetObject.removeCounters(counterName, numberOfCountersSelected, source, game); countersRemoved += numberOfCountersSelected; -- 2.47.2 From 7045eaea6469fb61e5e0854c0455aacaaaac828a Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Thu, 16 Jan 2025 08:29:34 +0400 Subject: [PATCH 07/17] tests: fixed verify tests --- Mage.Sets/src/mage/sets/DuskmournHouseOfHorrorCommander.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mage.Sets/src/mage/sets/DuskmournHouseOfHorrorCommander.java b/Mage.Sets/src/mage/sets/DuskmournHouseOfHorrorCommander.java index 5ad516da099..3f71ee7f462 100644 --- a/Mage.Sets/src/mage/sets/DuskmournHouseOfHorrorCommander.java +++ b/Mage.Sets/src/mage/sets/DuskmournHouseOfHorrorCommander.java @@ -105,7 +105,7 @@ public final class DuskmournHouseOfHorrorCommander extends ExpansionSet { cards.add(new SetCardInfo("Explosive Vegetation", 177, Rarity.UNCOMMON, mage.cards.e.ExplosiveVegetation.class)); cards.add(new SetCardInfo("Extravagant Replication", 117, Rarity.RARE, mage.cards.e.ExtravagantReplication.class)); cards.add(new SetCardInfo("Ezuri's Predation", 178, Rarity.RARE, mage.cards.e.EzurisPredation.class)); - cards.add(new SetCardInfo("Falkenrath Noble", 140, Rarity.UNCOMMON, mage.cards.f.FalkenrathNoble.class)); + cards.add(new SetCardInfo("Falkenrath Noble", 140, Rarity.COMMON, mage.cards.f.FalkenrathNoble.class)); cards.add(new SetCardInfo("Fate Unraveler", 141, Rarity.RARE, mage.cards.f.FateUnraveler.class)); cards.add(new SetCardInfo("Fear of Sleep Paralysis", 12, Rarity.RARE, mage.cards.f.FearOfSleepParalysis.class)); cards.add(new SetCardInfo("Feed the Swarm", 78, Rarity.COMMON, mage.cards.f.FeedTheSwarm.class)); -- 2.47.2 From 34af53879a50943881f7c08058e4b59820c3c766 Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Thu, 16 Jan 2025 14:14:16 +0400 Subject: [PATCH 08/17] Do if cost pay improved: * added additional hint to the optional pay dialog, so user can split it and remember for auto-answer (see withChooseHint); * Mana Vault - improved UX, now user can hide an untap cost dialog for already untapped permanent (use right click on buttons, #2656); --- Mage.Sets/src/mage/cards/m/ManaVault.java | 6 +- .../cards/conditional/DoIfCostPaidTest.java | 75 ++++++++++++++++++- .../effects/common/DoIfCostPaid.java | 55 ++++++++++---- 3 files changed, 119 insertions(+), 17 deletions(-) diff --git a/Mage.Sets/src/mage/cards/m/ManaVault.java b/Mage.Sets/src/mage/cards/m/ManaVault.java index 007f4fbbf10..a895a164276 100644 --- a/Mage.Sets/src/mage/cards/m/ManaVault.java +++ b/Mage.Sets/src/mage/cards/m/ManaVault.java @@ -1,6 +1,9 @@ package mage.cards.m; import mage.Mana; +import mage.abilities.hint.ConditionHint; +import mage.abilities.hint.ConditionTrueHint; +import mage.abilities.hint.ValueConditionHint; import mage.abilities.triggers.BeginningOfDrawTriggeredAbility; import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility; import mage.abilities.common.SimpleStaticAbility; @@ -33,13 +36,14 @@ public final class ManaVault extends CardImpl { // At the beginning of your upkeep, you may pay {4}. If you do, untap Mana Vault. this.addAbility(new BeginningOfUpkeepTriggeredAbility( new DoIfCostPaid(new UntapSourceEffect(), new GenericManaCost(4), "Pay {4} to untap {this}?") + .withChooseHint(new ConditionHint(SourceTappedCondition.TAPPED)) )); // At the beginning of your draw step, if Mana Vault is tapped, it deals 1 damage to you. this.addAbility(new BeginningOfDrawTriggeredAbility(new DamageControllerEffect(1, "it"), false).withInterveningIf(SourceTappedCondition.TAPPED)); - // {tap}: Add {C}{C}{C}. + // {T}: Add {C}{C}{C}. this.addAbility(new SimpleManaAbility(Zone.BATTLEFIELD, Mana.ColorlessMana(3), new TapSourceCost())); } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/conditional/DoIfCostPaidTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/conditional/DoIfCostPaidTest.java index 0705a4722a2..b24e05559eb 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/conditional/DoIfCostPaidTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/conditional/DoIfCostPaidTest.java @@ -6,13 +6,12 @@ import org.junit.Test; import org.mage.test.serverside.base.CardTestPlayerBase; /** - * - * @author Quercitron + * @author Quercitron, JayDi85 */ public class DoIfCostPaidTest extends CardTestPlayerBase { @Test - public void testPayIsNotOptional() { + public void test_NonOptional() { addCard(Zone.BATTLEFIELD, playerA, "Mountain", 1); // Shock deals 2 damage to any target. addCard(Zone.HAND, playerA, "Shock", 1); @@ -22,6 +21,8 @@ public class DoIfCostPaidTest extends CardTestPlayerBase { addCard(Zone.BATTLEFIELD, playerB, "Awaken the Sky Tyrant"); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Shock", playerB); + + setStrictChooseMode(true); setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); @@ -29,4 +30,72 @@ public class DoIfCostPaidTest extends CardTestPlayerBase { assertPermanentCount(playerB, "Dragon Token", 1); } + @Test + public void test_Optional_ManaVault_1() { + // Mana Vault doesn't untap during your untap step. + // At the beginning of your upkeep, you may pay {4}. If you do, untap Mana Vault. + // At the beginning of your draw step, if Mana Vault is tapped, it deals 1 damage to you. + // {T}: Add {C}{C}{C}. + addCard(Zone.BATTLEFIELD, playerA, "Mana Vault", 1); + addCard(Zone.BATTLEFIELD, playerA, "Island", 4); + + // turn 1 - untapped and ask about untap + setChoice(playerA, false); // do not pay + checkPermanentTapped("must be untapped on start", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Mana Vault", false, 1); + checkLife("no damage on untapped", 1, PhaseStep.PRECOMBAT_MAIN, playerA, 20); + + // turn 2 - tap + activateManaAbility(2, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {C}"); + checkPermanentTapped("must be tapped after usage", 2, PhaseStep.PRECOMBAT_MAIN, playerA, "Mana Vault", true, 1); + + // turn 3 - tapped and ask about untap (do not pay) + setChoice(playerA, false); + checkPermanentTapped("must be tapped after dialog", 3, PhaseStep.PRECOMBAT_MAIN, playerA, "Mana Vault", true, 1); + checkLife("must do damage on tapped", 3, PhaseStep.PRECOMBAT_MAIN, playerA, 20 - 1); + + // turn 4 - nothing + + // turn 5 - tapped and ask about untap (do pay) + setChoice(playerA, true); + checkPermanentTapped("must be untapped after dialog", 5, PhaseStep.PRECOMBAT_MAIN, playerA, "Mana Vault", false, 1); + checkLife("no damage on untapped", 5, PhaseStep.PRECOMBAT_MAIN, playerA, 20 - 1); // -1 from old damage + + setStrictChooseMode(true); + setStopAt(5, PhaseStep.END_TURN); + execute(); + } + + @Test + public void test_Optional_ManaVault_2() { + // Make sure it allow to pay untap cost anyway, so some combos can be used, see https://github.com/magefree/mage/issues/2656 + // Example: + // When Mana Vault is untapped you can respond to the trigger pay four to untap is to tap it and then pay 4 + // to untap it. That would not be possible if the trigger is skipped. + // That's legal according to the rules and would net mana if Mana Reflection was in play, would allow + // self milling with Mesmeric Orb in play, and I'm sure many other interactions that change the game state. + + // Mana Vault doesn't untap during your untap step. + // At the beginning of your upkeep, you may pay {4}. If you do, untap Mana Vault. + // At the beginning of your draw step, if Mana Vault is tapped, it deals 1 damage to you. + // {T}: Add {C}{C}{C}. + addCard(Zone.BATTLEFIELD, playerA, "Mana Vault", 1); + // + // If you tap a permanent for mana, it produces twice as much of that mana instead. + addCard(Zone.BATTLEFIELD, playerA, "Mana Reflection", 1); + // + // Whenever a permanent becomes untapped, that permanent's controller puts the top card of + // their library into their graveyard. + addCard(Zone.BATTLEFIELD, playerA, "Mesmeric Orb", 1); + + setChoice(playerA, true); // pay by itself + checkPermanentTapped("must be untapped", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Mana Vault", false, 1); + checkLife("no damage on untapped", 1, PhaseStep.PRECOMBAT_MAIN, playerA, 20); + + checkGraveyardCount("must mill", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Mountain", 1); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + } + } 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 c8c408787d3..e577f5d4ea0 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/DoIfCostPaid.java +++ b/Mage/src/main/java/mage/abilities/effects/common/DoIfCostPaid.java @@ -8,6 +8,7 @@ import mage.abilities.effects.ContinuousEffect; import mage.abilities.effects.Effect; import mage.abilities.effects.Effects; import mage.abilities.effects.OneShotEffect; +import mage.abilities.hint.Hint; import mage.constants.Outcome; import mage.game.Game; import mage.players.Player; @@ -20,6 +21,7 @@ public class DoIfCostPaid extends OneShotEffect { protected final Cost cost; private final String chooseUseText; private final boolean optional; + private Hint chooseHint = null; public DoIfCostPaid(Effect effectOnPaid, Cost cost) { this(effectOnPaid, cost, null); @@ -63,6 +65,7 @@ public class DoIfCostPaid extends OneShotEffect { this.cost = effect.cost.copy(); this.chooseUseText = effect.chooseUseText; this.optional = effect.optional; + this.chooseHint = effect.chooseHint; } public DoIfCostPaid addEffect(Effect effect) { @@ -75,6 +78,19 @@ public class DoIfCostPaid extends OneShotEffect { return this; } + /** + * Allow to add additional info in pay dialog, so user can split it in diff use cases to remember by right click + * Example: ignore untap payment for already untapped permanent like Mana Vault + */ + public DoIfCostPaid withChooseHint(Hint chooseHint) { + if (!this.optional) { + throw new IllegalArgumentException("Wrong code usage: chooseHint can be used for optional dialogs only"); + } + + this.chooseHint = chooseHint; + return this; + } + @Override public boolean apply(Game game, Ability source) { Player player = getPayingPlayer(game, source); @@ -82,11 +98,16 @@ public class DoIfCostPaid extends OneShotEffect { if (player == null || mageObject == null) { return false; } - String message = CardUtil.replaceSourceName(makeChooseText(source), mageObject.getName()); + + // nothing to pay (do not support mana cost - it's true all the time) + if (!this.cost.canPay(source, source, player.getId(), game)) { + return false; + } + + String message = CardUtil.replaceSourceName(makeChooseText(game, source), mageObject.getName()); Outcome payOutcome = executingEffects.getOutcome(source, this.outcome); - boolean canPay = cost.canPay(source, source, player.getId(), game); boolean didPay = false; - if (canPay && (!optional || player.chooseUse(payOutcome, message, source, game))) { + if (!optional || player.chooseUse(payOutcome, message, source, game)) { cost.clearPaid(); int bookmark = game.bookmarkState(); if (cost.pay(source, game, source, player.getId(), false)) { @@ -121,18 +142,26 @@ public class DoIfCostPaid extends OneShotEffect { } } - private String makeChooseText(Ability source) { - if (chooseUseText != null && !chooseUseText.isEmpty()) { - return chooseUseText; + private String makeChooseText(Game game, Ability source) { + // static + String res = chooseUseText; + + // dynamic + if (res == null || res.isEmpty()) { + String effectText = executingEffects.getText(source.getModes().getMode()); + if (!effectText.isEmpty() && effectText.charAt(effectText.length() - 1) == '.') { + effectText = effectText.substring(0, effectText.length() - 1); + } + res = CardUtil.addCostVerb(cost.getText()) + (effectText.isEmpty() ? "" : " and " + effectText) + "?"; + res = Character.toUpperCase(res.charAt(0)) + res.substring(1); } - String message; - String effectText = executingEffects.getText(source.getModes().getMode()); - if (!effectText.isEmpty() && effectText.charAt(effectText.length() - 1) == '.') { - effectText = effectText.substring(0, effectText.length() - 1); + + // additional hint, so user can remember it + if (this.chooseHint != null) { + res += String.format(" (%s)", this.chooseHint.getText(game, source)); } - message = CardUtil.addCostVerb(cost.getText()) + (effectText.isEmpty() ? "" : " and " + effectText) + "?"; - message = Character.toUpperCase(message.charAt(0)) + message.substring(1); - return message; + + return res; } protected Player getPayingPlayer(Game game, Ability source) { -- 2.47.2 From d3d4e2c908d72b22568bec1115405c1cb23e1a2d Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Thu, 16 Jan 2025 14:25:28 +0400 Subject: [PATCH 09/17] merge fix --- Mage.Sets/src/mage/cards/m/ManaVault.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mage.Sets/src/mage/cards/m/ManaVault.java b/Mage.Sets/src/mage/cards/m/ManaVault.java index a895a164276..9ea837babb2 100644 --- a/Mage.Sets/src/mage/cards/m/ManaVault.java +++ b/Mage.Sets/src/mage/cards/m/ManaVault.java @@ -36,7 +36,7 @@ public final class ManaVault extends CardImpl { // At the beginning of your upkeep, you may pay {4}. If you do, untap Mana Vault. this.addAbility(new BeginningOfUpkeepTriggeredAbility( new DoIfCostPaid(new UntapSourceEffect(), new GenericManaCost(4), "Pay {4} to untap {this}?") - .withChooseHint(new ConditionHint(SourceTappedCondition.TAPPED)) + .withChooseHint(new ConditionHint(SourceTappedCondition.UNTAPPED)) )); // At the beginning of your draw step, if Mana Vault is tapped, it deals 1 damage to you. -- 2.47.2 From ccb8ff3715c648c83e08af69308a6b0de04dfad2 Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Thu, 16 Jan 2025 14:33:54 +0400 Subject: [PATCH 10/17] If you do, untap - added auto-answer support to skip yes/no dialog for already untapped permanent (right click on yes/no buttons, example: Mana Vault, #2656) --- Mage.Sets/src/mage/cards/b/BrassGnat.java | 3 +++ Mage.Sets/src/mage/cards/b/BrassMan.java | 3 +++ Mage.Sets/src/mage/cards/g/GoblinDirigible.java | 6 +++++- Mage.Sets/src/mage/cards/g/GoblinWarWagon.java | 5 ++++- Mage.Sets/src/mage/cards/i/IslandFishJasconius.java | 3 +++ 5 files changed, 18 insertions(+), 2 deletions(-) diff --git a/Mage.Sets/src/mage/cards/b/BrassGnat.java b/Mage.Sets/src/mage/cards/b/BrassGnat.java index 3e61dccac2f..2d490f3001e 100644 --- a/Mage.Sets/src/mage/cards/b/BrassGnat.java +++ b/Mage.Sets/src/mage/cards/b/BrassGnat.java @@ -3,6 +3,8 @@ package mage.cards.b; import java.util.UUID; import mage.MageInt; +import mage.abilities.condition.common.SourceTappedCondition; +import mage.abilities.hint.ConditionHint; import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.costs.mana.GenericManaCost; @@ -35,6 +37,7 @@ public final class BrassGnat extends CardImpl { // At the beginning of your upkeep, you may pay {1}. If you do, untap Brass Gnat. this.addAbility(new BeginningOfUpkeepTriggeredAbility( new DoIfCostPaid(new UntapSourceEffect(), new GenericManaCost(1)) + .withChooseHint(new ConditionHint(SourceTappedCondition.UNTAPPED)) )); } diff --git a/Mage.Sets/src/mage/cards/b/BrassMan.java b/Mage.Sets/src/mage/cards/b/BrassMan.java index 5e56a966663..b268ffdcef0 100644 --- a/Mage.Sets/src/mage/cards/b/BrassMan.java +++ b/Mage.Sets/src/mage/cards/b/BrassMan.java @@ -3,6 +3,8 @@ package mage.cards.b; import java.util.UUID; import mage.MageInt; +import mage.abilities.condition.common.SourceTappedCondition; +import mage.abilities.hint.ConditionHint; import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.costs.mana.GenericManaCost; @@ -33,6 +35,7 @@ public final class BrassMan extends CardImpl { // At the beginning of your upkeep, you may pay {1}. If you do, untap Brass Man. this.addAbility(new BeginningOfUpkeepTriggeredAbility( new DoIfCostPaid(new UntapSourceEffect(), new GenericManaCost(1)) + .withChooseHint(new ConditionHint(SourceTappedCondition.UNTAPPED)) )); } diff --git a/Mage.Sets/src/mage/cards/g/GoblinDirigible.java b/Mage.Sets/src/mage/cards/g/GoblinDirigible.java index 07235b2eb4c..e8502c803e8 100644 --- a/Mage.Sets/src/mage/cards/g/GoblinDirigible.java +++ b/Mage.Sets/src/mage/cards/g/GoblinDirigible.java @@ -3,6 +3,8 @@ package mage.cards.g; import java.util.UUID; import mage.MageInt; +import mage.abilities.condition.common.SourceTappedCondition; +import mage.abilities.hint.ConditionHint; import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.costs.mana.ManaCostsImpl; @@ -34,7 +36,9 @@ public final class GoblinDirigible extends CardImpl { this.addAbility(new SimpleStaticAbility(new DontUntapInControllersUntapStepSourceEffect())); // At the beginning of your upkeep, you may pay {4}. If you do, untap Goblin Dirigible. this.addAbility(new BeginningOfUpkeepTriggeredAbility(new DoIfCostPaid( - new UntapSourceEffect(), new ManaCostsImpl<>("{4}")))); + new UntapSourceEffect(), new ManaCostsImpl<>("{4}")) + .withChooseHint(new ConditionHint(SourceTappedCondition.UNTAPPED)) + )); } private GoblinDirigible(final GoblinDirigible card) { diff --git a/Mage.Sets/src/mage/cards/g/GoblinWarWagon.java b/Mage.Sets/src/mage/cards/g/GoblinWarWagon.java index cacd4bd1d4a..0eaf8a07f99 100644 --- a/Mage.Sets/src/mage/cards/g/GoblinWarWagon.java +++ b/Mage.Sets/src/mage/cards/g/GoblinWarWagon.java @@ -3,6 +3,8 @@ package mage.cards.g; import java.util.UUID; import mage.MageInt; +import mage.abilities.condition.common.SourceTappedCondition; +import mage.abilities.hint.ConditionHint; import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.costs.mana.ManaCostsImpl; @@ -31,7 +33,8 @@ public final class GoblinWarWagon extends CardImpl { this.addAbility(new SimpleStaticAbility(new DontUntapInControllersUntapStepSourceEffect())); // At the beginning of your upkeep, you may pay {2}. If you do, untap Goblin War Wagon. this.addAbility(new BeginningOfUpkeepTriggeredAbility(new DoIfCostPaid( - new UntapSourceEffect(), new ManaCostsImpl<>("{2}")))); + new UntapSourceEffect(), new ManaCostsImpl<>("{2}")) + .withChooseHint(new ConditionHint(SourceTappedCondition.UNTAPPED)))); } private GoblinWarWagon(final GoblinWarWagon card) { diff --git a/Mage.Sets/src/mage/cards/i/IslandFishJasconius.java b/Mage.Sets/src/mage/cards/i/IslandFishJasconius.java index bd0914f927b..17db2f7954f 100644 --- a/Mage.Sets/src/mage/cards/i/IslandFishJasconius.java +++ b/Mage.Sets/src/mage/cards/i/IslandFishJasconius.java @@ -3,6 +3,8 @@ package mage.cards.i; import java.util.UUID; import mage.MageInt; +import mage.abilities.condition.common.SourceTappedCondition; +import mage.abilities.hint.ConditionHint; import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility; import mage.abilities.common.ControlsPermanentsControllerTriggeredAbility; import mage.abilities.common.SimpleStaticAbility; @@ -35,6 +37,7 @@ public final class IslandFishJasconius extends CardImpl { // At the beginning of your upkeep, you may pay {U}{U}{U}. If you do, untap Island Fish Jasconius. this.addAbility(new BeginningOfUpkeepTriggeredAbility( new DoIfCostPaid(new UntapSourceEffect(), new ManaCostsImpl<>("{U}{U}{U}")) + .withChooseHint(new ConditionHint(SourceTappedCondition.UNTAPPED)) )); // Island Fish Jasconius can't attack unless defending player controls an Island. -- 2.47.2 From 1f7af53dbbdfb2f1a7b7b05315190a197c9c9b53 Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Thu, 16 Jan 2025 17:26:44 +0400 Subject: [PATCH 11/17] tests: added more strictly checks for choice commands order (related to #12044); --- .../java/org/mage/test/player/TestPlayer.java | 35 +++++++++++++++---- 1 file changed, 29 insertions(+), 6 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 e09423b00c4..cef50ca4a8e 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 @@ -2339,7 +2339,7 @@ public class TestPlayer implements Player { return true; } else { if (!targetFound) { - Assert.fail(String.format("Found wrong choice command:\n%s\n%s\n%s", choiceRecord, getInfo(target, game), getInfo(source, game))); + failOnLastBadChoice(game, source, target, choiceRecord, "unknown or can't target"); } } } finally { @@ -2349,16 +2349,30 @@ public class TestPlayer implements Player { } if (target instanceof TargetPlayer) { - for (Player player : game.getPlayers().values()) { - for (String choose2 : choices) { - if (player.getName().equals(choose2)) { + while (!choices.isEmpty()) { + String choiceRecord = choices.get(0); + boolean targetFound = false; + for (Player player : game.getPlayers().values()) { + if (player.getName().equals(choiceRecord)) { if (target.canTarget(abilityControllerId, player.getId(), null, game) && !target.getTargets().contains(player.getId())) { target.add(player.getId(), game); - choices.remove(choose2); - return true; + targetFound = true; + } else { + failOnLastBadChoice(game, source, target, choiceRecord, "can't target"); } } } + + try { + if (target.isChosen(game)) { + return true; + } + if (!targetFound) { + failOnLastBadChoice(game, source, target, choiceRecord, "unknown target"); + } + } finally { + choices.remove(0); + } } } @@ -4693,6 +4707,15 @@ public class TestPlayer implements Player { return !this.strictChooseMode; } + private void failOnLastBadChoice(Game game, Ability source, Target target, String lastChoice, String reason) { + Assert.fail(String.format("Found wrong choice command (%s):\n%s\n%s\n%s", + reason, + lastChoice, + getInfo(target, game), + getInfo(source, game) + )); + } + private void assertWrongChoiceUsage(String choice) { // TODO: enable fail checks and fix tests, it's a part of setStrictChooseMode's implementation to all tests //Assert.fail("Wrong choice command: " + choice); -- 2.47.2 From ad26810281b83ca6b8574ea8e5eb22e34baac1e9 Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Fri, 17 Jan 2025 20:34:19 +0400 Subject: [PATCH 12/17] typo fix --- Mage.Sets/src/mage/cards/m/MysticSubdual.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Mage.Sets/src/mage/cards/m/MysticSubdual.java b/Mage.Sets/src/mage/cards/m/MysticSubdual.java index 5313ffb6169..43b0fce4634 100644 --- a/Mage.Sets/src/mage/cards/m/MysticSubdual.java +++ b/Mage.Sets/src/mage/cards/m/MysticSubdual.java @@ -73,11 +73,11 @@ class MysticSubdualEffect extends ContinuousEffectImpl { public boolean apply(Game game, Ability source) { Permanent permanent = game.getPermanent(source.getSourceId()); if (permanent == null) { - return true; + return false; } Permanent creature = game.getPermanent(permanent.getAttachedTo()); if (creature == null) { - return true; + return false; } creature.removeAllAbilities(source.getSourceId(), game); return true; -- 2.47.2 From 1e9c1714415d2c44f6a8f8a569b7413f8ed6c5c5 Mon Sep 17 00:00:00 2001 From: PurpleCrowbar <26198472+PurpleCrowbar@users.noreply.github.com> Date: Fri, 17 Jan 2025 18:57:10 +0000 Subject: [PATCH 13/17] [BLC] Implement Fisher's Talent --- Mage.Sets/src/mage/cards/f/FishersTalent.java | 211 ++++++++++++++++++ .../src/mage/sets/BloomburrowCommander.java | 1 + .../game/permanent/token/Shark33Token.java | 29 +++ 3 files changed, 241 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/f/FishersTalent.java create mode 100644 Mage/src/main/java/mage/game/permanent/token/Shark33Token.java diff --git a/Mage.Sets/src/mage/cards/f/FishersTalent.java b/Mage.Sets/src/mage/cards/f/FishersTalent.java new file mode 100644 index 00000000000..324f785e20a --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/FishersTalent.java @@ -0,0 +1,211 @@ +package mage.cards.f; + +import mage.abilities.Ability; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.ReplacementEffectImpl; +import mage.abilities.effects.common.continuous.GainClassAbilitySourceEffect; +import mage.abilities.keyword.ClassLevelAbility; +import mage.abilities.keyword.ClassReminderAbility; +import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.cards.CardsImpl; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.game.Game; +import mage.game.events.CreateTokenEvent; +import mage.game.events.GameEvent; +import mage.game.permanent.token.FishNoAbilityToken; +import mage.game.permanent.token.OctopusToken; +import mage.game.permanent.token.Shark33Token; +import mage.game.permanent.token.Token; +import mage.players.Player; + +import java.util.Iterator; +import java.util.Map; +import java.util.UUID; + +/** + * Note to future reader: this card's replacement effects do NOT affect tokens with Changeling such as those of Birthing Boughs. This is not a bug, see 701.6b + * @author PurpleCrowbar + */ +public final class FishersTalent extends CardImpl { + + public FishersTalent(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{G}{U}"); + this.subtype.add(SubType.CLASS); + + // (Gain the next level as a sorcery to add its ability.) + this.addAbility(new ClassReminderAbility()); + + // At the beginning of your upkeep, look at the top card of your library. You may reveal it if it's a land card. Create a 1/1 blue Fish creature token if you revealed it this way. Then draw a card. + this.addAbility(new BeginningOfUpkeepTriggeredAbility(new FishersTalentLevel1Effect())); + + // {G}{U}: Level 2 + this.addAbility(new ClassLevelAbility(2, "{G}{U}")); + + // If you would create a Fish token, create a 3/3 blue Shark creature token instead. + this.addAbility(new SimpleStaticAbility(new GainClassAbilitySourceEffect(new SimpleStaticAbility(new FishersTalentLevel2Effect()), 2))); + + // {2}{G}{U}: Level 3 + this.addAbility(new ClassLevelAbility(3, "{2}{G}{U}")); + + // If you would create a Shark token, create an 8/8 blue Octopus creature token instead. + this.addAbility(new SimpleStaticAbility(new GainClassAbilitySourceEffect(new SimpleStaticAbility(new FishersTalentLevel3Effect()), 3))); + } + + private FishersTalent(final FishersTalent card) { + super(card); + } + + @Override + public FishersTalent copy() { + return new FishersTalent(this); + } +} + +class FishersTalentLevel1Effect extends OneShotEffect { + + FishersTalentLevel1Effect() { + super(Outcome.Benefit); + this.staticText = "look at the top card of your library. You may reveal it if it's a land card. " + + "Create a 1/1 blue Fish creature token if you revealed it this way. Then draw a card."; + } + + private FishersTalentLevel1Effect(final FishersTalentLevel1Effect effect) { + super(effect); + } + + @Override + public FishersTalentLevel1Effect copy() { + return new FishersTalentLevel1Effect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller == null) { + return false; + } + Card topCard = controller.getLibrary().getFromTop(game); + if (topCard != null) { + controller.lookAtCards("Top card of library", topCard, game); + if (topCard.isLand(game)) { + if (controller.chooseUse(Outcome.PutCreatureInPlay, "Reveal " + topCard.getLogName() + " to create a 1/1 blue Fish creature token?", source, game)) { + controller.revealCards(source, new CardsImpl(topCard), game); + new FishNoAbilityToken().putOntoBattlefield(1, game, source); + } + } + } + controller.drawCards(1, source, game); + return true; + } +} + +class FishersTalentLevel2Effect extends ReplacementEffectImpl { + + FishersTalentLevel2Effect() { + super(Duration.WhileOnBattlefield, Outcome.Benefit); + this.staticText = "If you would create a Fish token, create a 3/3 blue Shark creature token instead"; + } + + private FishersTalentLevel2Effect(final FishersTalentLevel2Effect effect) { + super(effect); + } + + @Override + public FishersTalentLevel2Effect copy() { + return new FishersTalentLevel2Effect(this); + } + + @Override + public boolean checksEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.CREATE_TOKEN; + } + + @Override + public boolean applies(GameEvent event, Ability source, Game game) { + if (!(event instanceof CreateTokenEvent) || !event.getPlayerId().equals(source.getControllerId())) { + return false; + } + for (Token token : ((CreateTokenEvent) event).getTokens().keySet()) { + if (token.hasSubtype(SubType.FISH, game)) { + return true; + } + } + return false; + } + + @Override + public boolean replaceEvent(GameEvent event, Ability source, Game game) { + int amount = 0; + Map tokens = ((CreateTokenEvent) event).getTokens(); + for (Iterator> iter = tokens.entrySet().iterator(); iter.hasNext(); ) { + Map.Entry entry = iter.next(); + Token token = entry.getKey(); + if (token.hasSubtype(SubType.FISH, game)) { + amount += entry.getValue(); + iter.remove(); + } + } + + tokens.put(new Shark33Token(), amount); + return false; + } +} + +class FishersTalentLevel3Effect extends ReplacementEffectImpl { + + FishersTalentLevel3Effect() { + super(Duration.WhileOnBattlefield, Outcome.Benefit); + this.staticText = "If you would create a Shark token, create an 8/8 blue Octopus creature token instead"; + } + + private FishersTalentLevel3Effect(final FishersTalentLevel3Effect effect) { + super(effect); + } + + @Override + public FishersTalentLevel3Effect copy() { + return new FishersTalentLevel3Effect(this); + } + + @Override + public boolean checksEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.CREATE_TOKEN; + } + + @Override + public boolean applies(GameEvent event, Ability source, Game game) { + if (!(event instanceof CreateTokenEvent) || !event.getPlayerId().equals(source.getControllerId())) { + return false; + } + for (Token token : ((CreateTokenEvent) event).getTokens().keySet()) { + if (token.hasSubtype(SubType.SHARK, game)) { + return true; + } + } + return false; + } + + @Override + public boolean replaceEvent(GameEvent event, Ability source, Game game) { + int amount = 0; + Map tokens = ((CreateTokenEvent) event).getTokens(); + for (Iterator> iter = tokens.entrySet().iterator(); iter.hasNext(); ) { + Map.Entry entry = iter.next(); + Token token = entry.getKey(); + if (token.hasSubtype(SubType.SHARK, game)) { + amount += entry.getValue(); + iter.remove(); + } + } + + tokens.put(new OctopusToken(), amount); + return false; + } +} diff --git a/Mage.Sets/src/mage/sets/BloomburrowCommander.java b/Mage.Sets/src/mage/sets/BloomburrowCommander.java index a18386e107e..daf45fc4390 100644 --- a/Mage.Sets/src/mage/sets/BloomburrowCommander.java +++ b/Mage.Sets/src/mage/sets/BloomburrowCommander.java @@ -104,6 +104,7 @@ public final class BloomburrowCommander extends ExpansionSet { cards.add(new SetCardInfo("Farseek", 119, Rarity.UNCOMMON, mage.cards.f.Farseek.class)); cards.add(new SetCardInfo("Fellwar Stone", 269, Rarity.UNCOMMON, mage.cards.f.FellwarStone.class)); cards.add(new SetCardInfo("Ferrous Lake", 303, Rarity.RARE, mage.cards.f.FerrousLake.class)); + cards.add(new SetCardInfo("Fisher's Talent", 36, Rarity.RARE, mage.cards.f.FishersTalent.class)); cards.add(new SetCardInfo("Flooded Grove", 304, Rarity.RARE, mage.cards.f.FloodedGrove.class)); cards.add(new SetCardInfo("Flubs, the Fool", 356, Rarity.MYTHIC, mage.cards.f.FlubsTheFool.class)); cards.add(new SetCardInfo("Forgotten Ancient", 217, Rarity.RARE, mage.cards.f.ForgottenAncient.class)); diff --git a/Mage/src/main/java/mage/game/permanent/token/Shark33Token.java b/Mage/src/main/java/mage/game/permanent/token/Shark33Token.java new file mode 100644 index 00000000000..42c5c0b548b --- /dev/null +++ b/Mage/src/main/java/mage/game/permanent/token/Shark33Token.java @@ -0,0 +1,29 @@ +package mage.game.permanent.token; + +import mage.MageInt; +import mage.constants.CardType; +import mage.constants.SubType; + +/** + * @author PurpleCrowbar + */ +public final class Shark33Token extends TokenImpl { + + public Shark33Token() { + super("Shark Token", "3/3 blue Shark creature token"); + + cardType.add(CardType.CREATURE); + color.setBlue(true); + subtype.add(SubType.SHARK); + power = new MageInt(3); + toughness = new MageInt(3); + } + + private Shark33Token(final Shark33Token token) { + super(token); + } + + public Shark33Token copy() { + return new Shark33Token(this); + } +} -- 2.47.2 From 67f4562c7bc4c0bab751e2d6ad9d66c09aacb54b Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Sat, 18 Jan 2025 00:39:36 +0400 Subject: [PATCH 14/17] added set Innistrad Remastered (INR) --- .../dl/sources/ScryfallImageSupportCards.java | 1 + .../src/mage/sets/InnistradRemastered.java | 604 ++++++++++++++++++ 2 files changed, 605 insertions(+) create mode 100644 Mage.Sets/src/mage/sets/InnistradRemastered.java diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/ScryfallImageSupportCards.java b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/ScryfallImageSupportCards.java index 7c39f355e76..81c95a5b3d0 100644 --- a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/ScryfallImageSupportCards.java +++ b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/ScryfallImageSupportCards.java @@ -565,6 +565,7 @@ public class ScryfallImageSupportCards { add("DSC"); // Duskmourn: House of Horror Commander add("FDN"); // Foundations add("J25"); // Foundations Jumpstart + add("INR"); // Innistrad Remastered add("DFT"); // Aetherdrift // Custom sets using Scryfall images - must provide a direct link for each card in directDownloadLinks diff --git a/Mage.Sets/src/mage/sets/InnistradRemastered.java b/Mage.Sets/src/mage/sets/InnistradRemastered.java new file mode 100644 index 00000000000..f16bc7bef7a --- /dev/null +++ b/Mage.Sets/src/mage/sets/InnistradRemastered.java @@ -0,0 +1,604 @@ +package mage.sets; + +import mage.cards.CardGraphicInfo; +import mage.cards.ExpansionSet; +import mage.cards.FrameStyle; +import mage.constants.Rarity; +import mage.constants.SetType; + +/** + * https://scryfall.com/sets/inr + * + * @author JayDi85 + */ +public class InnistradRemastered extends ExpansionSet { + + private static final InnistradRemastered instance = new InnistradRemastered(); + + public static InnistradRemastered getInstance() { + return instance; + } + + private InnistradRemastered() { + super("Innistrad Remastered", "INR", ExpansionSet.buildDate(2025, 1, 24), SetType.SUPPLEMENTAL); + this.hasBoosters = true; // TODO: after set release - improve rarity distribution or implement collation + this.hasBasicLands = true; + this.numBoosterLands = 1; + this.numBoosterCommon = 7 + 1; // +1 instead the list + this.numBoosterUncommon = 3 + 1; // +1 instead 1 of 2 wildcards + this.numBoosterRare = 1 + 1; // +1 instead 2 of 2 wildcards + this.ratioBoosterMythic = 8; + this.numBoosterDoubleFaced = -1; + this.maxCardNumberInBooster = 480; // play boosters #1–480, collector boosters #1–491 + + final CardGraphicInfo FULL_ART = new CardGraphicInfo(FrameStyle.MPOP_FULL_ART_BASIC, true); + + cards.add(new SetCardInfo("Aberrant Researcher", 454, Rarity.UNCOMMON, mage.cards.a.AberrantResearcher.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Aberrant Researcher", 52, Rarity.UNCOMMON, mage.cards.a.AberrantResearcher.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Abrade", 139, Rarity.COMMON, mage.cards.a.Abrade.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Abrade", 311, Rarity.COMMON, mage.cards.a.Abrade.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Abundant Growth", 184, Rarity.COMMON, mage.cards.a.AbundantGrowth.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Abundant Growth", 315, Rarity.COMMON, mage.cards.a.AbundantGrowth.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Abundant Growth", 406, Rarity.COMMON, mage.cards.a.AbundantGrowth.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Abundant Maw", 1, Rarity.COMMON, mage.cards.a.AbundantMaw.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Abundant Maw", 329, Rarity.COMMON, mage.cards.a.AbundantMaw.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Aim High", 185, Rarity.COMMON, mage.cards.a.AimHigh.class)); + cards.add(new SetCardInfo("Alchemist's Greeting", 140, Rarity.COMMON, mage.cards.a.AlchemistsGreeting.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Alchemist's Greeting", 393, Rarity.COMMON, mage.cards.a.AlchemistsGreeting.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Altered Ego", 228, Rarity.RARE, mage.cards.a.AlteredEgo.class)); + cards.add(new SetCardInfo("Ambitious Farmhand", 448, Rarity.UNCOMMON, mage.cards.a.AmbitiousFarmhand.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Ambitious Farmhand", 8, Rarity.UNCOMMON, mage.cards.a.AmbitiousFarmhand.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Ambush Viper", 186, Rarity.COMMON, mage.cards.a.AmbushViper.class)); + cards.add(new SetCardInfo("Ancestral Anger", 141, Rarity.COMMON, mage.cards.a.AncestralAnger.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Ancestral Anger", 394, Rarity.COMMON, mage.cards.a.AncestralAnger.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Angel's Tomb", 253, Rarity.UNCOMMON, mage.cards.a.AngelsTomb.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Angel's Tomb", 438, Rarity.UNCOMMON, mage.cards.a.AngelsTomb.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Angelfire Ignition", 229, Rarity.RARE, mage.cards.a.AngelfireIgnition.class)); + cards.add(new SetCardInfo("Angelic Purge", 333, Rarity.COMMON, mage.cards.a.AngelicPurge.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Angelic Purge", 9, Rarity.COMMON, mage.cards.a.AngelicPurge.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Apothecary Geist", 10, Rarity.COMMON, mage.cards.a.ApothecaryGeist.class)); + cards.add(new SetCardInfo("Archangel Avacyn", 11, Rarity.MYTHIC, mage.cards.a.ArchangelAvacyn.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Archangel Avacyn", 449, Rarity.MYTHIC, mage.cards.a.ArchangelAvacyn.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Archghoul of Thraben", 370, Rarity.UNCOMMON, mage.cards.a.ArchghoulOfThraben.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Archghoul of Thraben", 95, Rarity.UNCOMMON, mage.cards.a.ArchghoulOfThraben.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Arlinn Kord", 230, Rarity.MYTHIC, mage.cards.a.ArlinnKord.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Arlinn Kord", 324, Rarity.MYTHIC, mage.cards.a.ArlinnKord.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Arlinn, Embraced by the Moon", 230, Rarity.MYTHIC, mage.cards.a.ArlinnEmbracedByTheMoon.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Arlinn, Embraced by the Moon", 324, Rarity.MYTHIC, mage.cards.a.ArlinnEmbracedByTheMoon.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Ashmouth Blade", 269, Rarity.UNCOMMON, mage.cards.a.AshmouthBlade.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Ashmouth Blade", 473, Rarity.UNCOMMON, mage.cards.a.AshmouthBlade.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Asylum Visitor", 371, Rarity.UNCOMMON, mage.cards.a.AsylumVisitor.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Asylum Visitor", 96, Rarity.UNCOMMON, mage.cards.a.AsylumVisitor.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Aurora of Emrakul", 260, Rarity.UNCOMMON, mage.cards.a.AuroraOfEmrakul.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Aurora of Emrakul", 472, Rarity.UNCOMMON, mage.cards.a.AuroraOfEmrakul.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Avacyn, Angel of Hope", 477, Rarity.MYTHIC, mage.cards.a.AvacynAngelOfHope.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Avacyn, Angel of Hope", 482, Rarity.MYTHIC, mage.cards.a.AvacynAngelOfHope.class, FULL_ART)); + cards.add(new SetCardInfo("Avacyn, the Purifier", 11, Rarity.MYTHIC, mage.cards.a.AvacynThePurifier.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Avacyn, the Purifier", 449, Rarity.MYTHIC, mage.cards.a.AvacynThePurifier.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Avacynian Priest", 12, Rarity.COMMON, mage.cards.a.AvacynianPriest.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Avacynian Priest", 334, Rarity.COMMON, mage.cards.a.AvacynianPriest.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Awoken Demon", 107, Rarity.COMMON, mage.cards.a.AwokenDemon.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Awoken Demon", 462, Rarity.COMMON, mage.cards.a.AwokenDemon.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Awoken Horror", 460, Rarity.RARE, mage.cards.a.AwokenHorror.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Awoken Horror", 91, Rarity.RARE, mage.cards.a.AwokenHorror.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Balefire Dragon", 479, Rarity.MYTHIC, mage.cards.b.BalefireDragon.class)); + cards.add(new SetCardInfo("Bane of Hanweir", 158, Rarity.COMMON, mage.cards.b.BaneOfHanweir.class)); + cards.add(new SetCardInfo("Battleground Geist", 53, Rarity.COMMON, mage.cards.b.BattlegroundGeist.class)); + cards.add(new SetCardInfo("Bedlam Reveler", 142, Rarity.RARE, mage.cards.b.BedlamReveler.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Bedlam Reveler", 312, Rarity.RARE, mage.cards.b.BedlamReveler.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Biolume Egg", 455, Rarity.UNCOMMON, mage.cards.b.BiolumeEgg.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Biolume Egg", 54, Rarity.UNCOMMON, mage.cards.b.BiolumeEgg.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Biolume Serpent", 455, Rarity.UNCOMMON, mage.cards.b.BiolumeSerpent.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Biolume Serpent", 54, Rarity.UNCOMMON, mage.cards.b.BiolumeSerpent.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Bladestitched Skaab", 231, Rarity.UNCOMMON, mage.cards.b.BladestitchedSkaab.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Bladestitched Skaab", 426, Rarity.UNCOMMON, mage.cards.b.BladestitchedSkaab.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Blazing Torch", 254, Rarity.COMMON, mage.cards.b.BlazingTorch.class)); + cards.add(new SetCardInfo("Blood Artist", 326, Rarity.UNCOMMON, mage.cards.b.BloodArtist.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Blood Artist", 372, Rarity.UNCOMMON, mage.cards.b.BloodArtist.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Blood Artist", 97, Rarity.UNCOMMON, mage.cards.b.BloodArtist.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Blood Mist", 143, Rarity.UNCOMMON, mage.cards.b.BloodMist.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Blood Mist", 395, Rarity.UNCOMMON, mage.cards.b.BloodMist.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Blood Petal Celebrant", 144, Rarity.COMMON, mage.cards.b.BloodPetalCelebrant.class)); + cards.add(new SetCardInfo("Bloodbat Summoner", 138, Rarity.RARE, mage.cards.b.BloodbatSummoner.class)); + cards.add(new SetCardInfo("Bloodhall Priest", 232, Rarity.RARE, mage.cards.b.BloodhallPriest.class)); + cards.add(new SetCardInfo("Bloodline Keeper", 327, Rarity.MYTHIC, mage.cards.b.BloodlineKeeper.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Bloodline Keeper", 461, Rarity.MYTHIC, mage.cards.b.BloodlineKeeper.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Bloodline Keeper", 98, Rarity.MYTHIC, mage.cards.b.BloodlineKeeper.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Bloodmad Vampire", 145, Rarity.COMMON, mage.cards.b.BloodmadVampire.class)); + cards.add(new SetCardInfo("Bloodsoaked Reveler", 128, Rarity.UNCOMMON, mage.cards.b.BloodsoakedReveler.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Bloodsoaked Reveler", 463, Rarity.UNCOMMON, mage.cards.b.BloodsoakedReveler.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Bloodtithe Harvester", 233, Rarity.UNCOMMON, mage.cards.b.BloodtitheHarvester.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Bloodtithe Harvester", 427, Rarity.UNCOMMON, mage.cards.b.BloodtitheHarvester.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Boarded Window", 255, Rarity.UNCOMMON, mage.cards.b.BoardedWindow.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Boarded Window", 439, Rarity.UNCOMMON, mage.cards.b.BoardedWindow.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Borrowed Hostility", 146, Rarity.COMMON, mage.cards.b.BorrowedHostility.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Borrowed Hostility", 396, Rarity.COMMON, mage.cards.b.BorrowedHostility.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Bound by Moonsilver", 13, Rarity.COMMON, mage.cards.b.BoundByMoonsilver.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Bound by Moonsilver", 335, Rarity.COMMON, mage.cards.b.BoundByMoonsilver.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Bramble Wurm", 187, Rarity.COMMON, mage.cards.b.BrambleWurm.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Bramble Wurm", 407, Rarity.COMMON, mage.cards.b.BrambleWurm.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Brisela, Voice of Nightmares", "14b", Rarity.MYTHIC, mage.cards.b.BriselaVoiceOfNightmares.class)); + cards.add(new SetCardInfo("Bruna, the Fading Light", "14a", Rarity.RARE, mage.cards.b.BrunaTheFadingLight.class)); + cards.add(new SetCardInfo("Burning Vengeance", 147, Rarity.UNCOMMON, mage.cards.b.BurningVengeance.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Burning Vengeance", 397, Rarity.UNCOMMON, mage.cards.b.BurningVengeance.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Butcher Ghoul", 373, Rarity.COMMON, mage.cards.b.ButcherGhoul.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Butcher Ghoul", 99, Rarity.COMMON, mage.cards.b.ButcherGhoul.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Butcher's Cleaver", 256, Rarity.UNCOMMON, mage.cards.b.ButchersCleaver.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Butcher's Cleaver", 440, Rarity.UNCOMMON, mage.cards.b.ButchersCleaver.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Cackling Counterpart", 353, Rarity.UNCOMMON, mage.cards.c.CacklingCounterpart.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Cackling Counterpart", 55, Rarity.UNCOMMON, mage.cards.c.CacklingCounterpart.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Captivating Vampire", 100, Rarity.RARE, mage.cards.c.CaptivatingVampire.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Captivating Vampire", 374, Rarity.RARE, mage.cards.c.CaptivatingVampire.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Captivating Vampire", 484, Rarity.RARE, mage.cards.c.CaptivatingVampire.class, FULL_ART)); + cards.add(new SetCardInfo("Cathar Commando", 15, Rarity.COMMON, mage.cards.c.CatharCommando.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Cathar Commando", 336, Rarity.COMMON, mage.cards.c.CatharCommando.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Cathar's Call", 16, Rarity.UNCOMMON, mage.cards.c.CatharsCall.class)); + cards.add(new SetCardInfo("Cathars' Crusade", 17, Rarity.RARE, mage.cards.c.CatharsCrusade.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Cathars' Crusade", 337, Rarity.RARE, mage.cards.c.CatharsCrusade.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Cathars' Crusade", 483, Rarity.RARE, mage.cards.c.CatharsCrusade.class, FULL_ART)); + cards.add(new SetCardInfo("Chalice of Death", 257, Rarity.UNCOMMON, mage.cards.c.ChaliceOfDeath.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Chalice of Death", 471, Rarity.UNCOMMON, mage.cards.c.ChaliceOfDeath.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Chalice of Life", 257, Rarity.UNCOMMON, mage.cards.c.ChaliceOfLife.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Chalice of Life", 471, Rarity.UNCOMMON, mage.cards.c.ChaliceOfLife.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Chandra, Dressed to Kill", 148, Rarity.MYTHIC, mage.cards.c.ChandraDressedToKill.class)); + cards.add(new SetCardInfo("Chittering Host", "123b", Rarity.COMMON, mage.cards.c.ChitteringHost.class)); + cards.add(new SetCardInfo("Cipherbound Spirit", 459, Rarity.UNCOMMON, mage.cards.c.CipherboundSpirit.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Cipherbound Spirit", 85, Rarity.UNCOMMON, mage.cards.c.CipherboundSpirit.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Clear Shot", 188, Rarity.UNCOMMON, mage.cards.c.ClearShot.class)); + cards.add(new SetCardInfo("Cobbled Lancer", 56, Rarity.UNCOMMON, mage.cards.c.CobbledLancer.class)); + cards.add(new SetCardInfo("Cobbled Wings", 258, Rarity.COMMON, mage.cards.c.CobbledWings.class)); + cards.add(new SetCardInfo("Collective Brutality", 101, Rarity.RARE, mage.cards.c.CollectiveBrutality.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Collective Brutality", 308, Rarity.RARE, mage.cards.c.CollectiveBrutality.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Collective Brutality", 375, Rarity.RARE, mage.cards.c.CollectiveBrutality.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Collective Defiance", 149, Rarity.RARE, mage.cards.c.CollectiveDefiance.class)); + cards.add(new SetCardInfo("Compelling Deterrence", 57, Rarity.UNCOMMON, mage.cards.c.CompellingDeterrence.class)); + cards.add(new SetCardInfo("Conduit of Emrakul", 150, Rarity.COMMON, mage.cards.c.ConduitOfEmrakul.class)); + cards.add(new SetCardInfo("Conduit of Storms", 150, Rarity.COMMON, mage.cards.c.ConduitOfStorms.class)); + cards.add(new SetCardInfo("Conjurer's Closet", 259, Rarity.RARE, mage.cards.c.ConjurersCloset.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Conjurer's Closet", 321, Rarity.RARE, mage.cards.c.ConjurersCloset.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Conjurer's Closet", 441, Rarity.RARE, mage.cards.c.ConjurersCloset.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Covetous Castaway", 456, Rarity.UNCOMMON, mage.cards.c.CovetousCastaway.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Covetous Castaway", 58, Rarity.UNCOMMON, mage.cards.c.CovetousCastaway.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Craterhoof Behemoth", 480, Rarity.MYTHIC, mage.cards.c.CraterhoofBehemoth.class)); + cards.add(new SetCardInfo("Crawl from the Cellar", 102, Rarity.COMMON, mage.cards.c.CrawlFromTheCellar.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Crawl from the Cellar", 376, Rarity.COMMON, mage.cards.c.CrawlFromTheCellar.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Crusader of Odric", 18, Rarity.COMMON, mage.cards.c.CrusaderOfOdric.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Crusader of Odric", 338, Rarity.COMMON, mage.cards.c.CrusaderOfOdric.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Cryptolith Fragment", 260, Rarity.UNCOMMON, mage.cards.c.CryptolithFragment.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Cryptolith Fragment", 472, Rarity.UNCOMMON, mage.cards.c.CryptolithFragment.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Cryptolith Rite", 189, Rarity.RARE, mage.cards.c.CryptolithRite.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Cryptolith Rite", 316, Rarity.RARE, mage.cards.c.CryptolithRite.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Cryptolith Rite", 408, Rarity.RARE, mage.cards.c.CryptolithRite.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Cultivator Colossus", 190, Rarity.MYTHIC, mage.cards.c.CultivatorColossus.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Cultivator Colossus", 317, Rarity.MYTHIC, mage.cards.c.CultivatorColossus.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Cultivator Colossus", 409, Rarity.MYTHIC, mage.cards.c.CultivatorColossus.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Dauntless Cathar", 19, Rarity.COMMON, mage.cards.d.DauntlessCathar.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Dauntless Cathar", 339, Rarity.COMMON, mage.cards.d.DauntlessCathar.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Dawnhart Disciple", 191, Rarity.COMMON, mage.cards.d.DawnhartDisciple.class)); + cards.add(new SetCardInfo("Deadeye Navigator", 492, Rarity.RARE, mage.cards.d.DeadeyeNavigator.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Deadeye Navigator", 59, Rarity.RARE, mage.cards.d.DeadeyeNavigator.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Deadly Allure", 103, Rarity.UNCOMMON, mage.cards.d.DeadlyAllure.class)); + cards.add(new SetCardInfo("Deathcap Glade", 275, Rarity.RARE, mage.cards.d.DeathcapGlade.class)); + cards.add(new SetCardInfo("Decimator of the Provinces", 2, Rarity.RARE, mage.cards.d.DecimatorOfTheProvinces.class)); + cards.add(new SetCardInfo("Deluge of the Dead", 120, Rarity.RARE, mage.cards.d.DelugeOfTheDead.class)); + cards.add(new SetCardInfo("Delver of Secrets", 457, Rarity.COMMON, mage.cards.d.DelverOfSecrets.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Delver of Secrets", 60, Rarity.COMMON, mage.cards.d.DelverOfSecrets.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Demonic Taskmaster", 104, Rarity.UNCOMMON, mage.cards.d.DemonicTaskmaster.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Demonic Taskmaster", 377, Rarity.UNCOMMON, mage.cards.d.DemonicTaskmaster.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Demonmail Hauberk", 261, Rarity.UNCOMMON, mage.cards.d.DemonmailHauberk.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Demonmail Hauberk", 442, Rarity.UNCOMMON, mage.cards.d.DemonmailHauberk.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Depraved Harvester", 105, Rarity.COMMON, mage.cards.d.DepravedHarvester.class)); + cards.add(new SetCardInfo("Deranged Assistant", 61, Rarity.COMMON, mage.cards.d.DerangedAssistant.class)); + cards.add(new SetCardInfo("Deserted Beach", 276, Rarity.RARE, mage.cards.d.DesertedBeach.class)); + cards.add(new SetCardInfo("Desperate Farmer", 105, Rarity.COMMON, mage.cards.d.DesperateFarmer.class)); + cards.add(new SetCardInfo("Distended Mindbender", 3, Rarity.RARE, mage.cards.d.DistendedMindbender.class)); + cards.add(new SetCardInfo("Docent of Perfection", 62, Rarity.RARE, mage.cards.d.DocentOfPerfection.class)); + cards.add(new SetCardInfo("Dreamroot Cascade", 277, Rarity.RARE, mage.cards.d.DreamrootCascade.class)); + cards.add(new SetCardInfo("Drogskol Shieldmate", 20, Rarity.COMMON, mage.cards.d.DrogskolShieldmate.class)); + cards.add(new SetCardInfo("Drunau Corpse Trawler", 63, Rarity.UNCOMMON, mage.cards.d.DrunauCorpseTrawler.class)); + cards.add(new SetCardInfo("Duel for Dominance", 192, Rarity.COMMON, mage.cards.d.DuelForDominance.class)); + cards.add(new SetCardInfo("Duskwatch Recruiter", 193, Rarity.UNCOMMON, mage.cards.d.DuskwatchRecruiter.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Duskwatch Recruiter", 323, Rarity.UNCOMMON, mage.cards.d.DuskwatchRecruiter.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Duskwatch Recruiter", 467, Rarity.UNCOMMON, mage.cards.d.DuskwatchRecruiter.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Eaten Alive", 106, Rarity.COMMON, mage.cards.e.EatenAlive.class)); + cards.add(new SetCardInfo("Eccentric Farmer", 194, Rarity.COMMON, mage.cards.e.EccentricFarmer.class)); + cards.add(new SetCardInfo("Ecstatic Awakener", 107, Rarity.COMMON, mage.cards.e.EcstaticAwakener.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Ecstatic Awakener", 462, Rarity.COMMON, mage.cards.e.EcstaticAwakener.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Edgar Markov", 234, Rarity.MYTHIC, mage.cards.e.EdgarMarkov.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Edgar Markov", 328, Rarity.MYTHIC, mage.cards.e.EdgarMarkov.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Edgar Markov", 428, Rarity.MYTHIC, mage.cards.e.EdgarMarkov.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Edgar Markov", 491, Rarity.MYTHIC, mage.cards.e.EdgarMarkov.class, FULL_ART)); + cards.add(new SetCardInfo("Edgar's Awakening", 108, Rarity.UNCOMMON, mage.cards.e.EdgarsAwakening.class)); + cards.add(new SetCardInfo("Elder Deep-Fiend", 4, Rarity.RARE, mage.cards.e.ElderDeepFiend.class)); + cards.add(new SetCardInfo("Eldritch Evolution", 195, Rarity.RARE, mage.cards.e.EldritchEvolution.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Eldritch Evolution", 410, Rarity.RARE, mage.cards.e.EldritchEvolution.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Emrakul, the Promised End", 330, Rarity.MYTHIC, mage.cards.e.EmrakulThePromisedEnd.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Emrakul, the Promised End", 481, Rarity.MYTHIC, mage.cards.e.EmrakulThePromisedEnd.class, FULL_ART)); + cards.add(new SetCardInfo("Emrakul, the Promised End", 5, Rarity.MYTHIC, mage.cards.e.EmrakulThePromisedEnd.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Epitaph Golem", 262, Rarity.COMMON, mage.cards.e.EpitaphGolem.class)); + cards.add(new SetCardInfo("Erupting Dreadwolf", 171, Rarity.UNCOMMON, mage.cards.e.EruptingDreadwolf.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Erupting Dreadwolf", 465, Rarity.UNCOMMON, mage.cards.e.EruptingDreadwolf.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Essence Flux", 354, Rarity.COMMON, mage.cards.e.EssenceFlux.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Essence Flux", 64, Rarity.COMMON, mage.cards.e.EssenceFlux.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Evolving Wilds", 278, Rarity.COMMON, mage.cards.e.EvolvingWilds.class)); + cards.add(new SetCardInfo("Faith Unbroken", 21, Rarity.UNCOMMON, mage.cards.f.FaithUnbroken.class)); + cards.add(new SetCardInfo("Faithless Looting", 151, Rarity.COMMON, mage.cards.f.FaithlessLooting.class)); + cards.add(new SetCardInfo("Falkenrath Gorger", 152, Rarity.RARE, mage.cards.f.FalkenrathGorger.class)); + cards.add(new SetCardInfo("Falkenrath Torturer", 109, Rarity.COMMON, mage.cards.f.FalkenrathTorturer.class)); + cards.add(new SetCardInfo("Festerhide Boar", 196, Rarity.COMMON, mage.cards.f.FesterhideBoar.class)); + cards.add(new SetCardInfo("Festival Crasher", 153, Rarity.COMMON, mage.cards.f.FestivalCrasher.class)); + cards.add(new SetCardInfo("Fiend Hunter", 22, Rarity.UNCOMMON, mage.cards.f.FiendHunter.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Fiend Hunter", 340, Rarity.UNCOMMON, mage.cards.f.FiendHunter.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Fiery Temper", 154, Rarity.UNCOMMON, mage.cards.f.FieryTemper.class)); + cards.add(new SetCardInfo("Final Iteration", 62, Rarity.RARE, mage.cards.f.FinalIteration.class)); + cards.add(new SetCardInfo("Fleshtaker", 235, Rarity.UNCOMMON, mage.cards.f.Fleshtaker.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Fleshtaker", 429, Rarity.UNCOMMON, mage.cards.f.Fleshtaker.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Forbidden Alchemy", 355, Rarity.UNCOMMON, mage.cards.f.ForbiddenAlchemy.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Forbidden Alchemy", 65, Rarity.UNCOMMON, mage.cards.f.ForbiddenAlchemy.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Forest", 296, Rarity.LAND, mage.cards.basiclands.Forest.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Forest", 297, Rarity.LAND, mage.cards.basiclands.Forest.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Furyblade Vampire", 155, Rarity.UNCOMMON, mage.cards.f.FurybladeVampire.class)); + cards.add(new SetCardInfo("Galvanic Iteration", 236, Rarity.RARE, mage.cards.g.GalvanicIteration.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Galvanic Iteration", 430, Rarity.RARE, mage.cards.g.GalvanicIteration.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Galvanic Juggernaut", 263, Rarity.UNCOMMON, mage.cards.g.GalvanicJuggernaut.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Galvanic Juggernaut", 443, Rarity.UNCOMMON, mage.cards.g.GalvanicJuggernaut.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Garruk Relentless", 197, Rarity.MYTHIC, mage.cards.g.GarrukRelentless.class)); + cards.add(new SetCardInfo("Garruk, the Veil-Cursed", 197, Rarity.MYTHIC, mage.cards.g.GarrukTheVeilCursed.class)); + cards.add(new SetCardInfo("Gather the Townsfolk", 23, Rarity.COMMON, mage.cards.g.GatherTheTownsfolk.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Gather the Townsfolk", 341, Rarity.COMMON, mage.cards.g.GatherTheTownsfolk.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Geier Reach Bandit", 156, Rarity.UNCOMMON, mage.cards.g.GeierReachBandit.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Geier Reach Bandit", 464, Rarity.UNCOMMON, mage.cards.g.GeierReachBandit.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Geistcatcher's Rig", 264, Rarity.UNCOMMON, mage.cards.g.GeistcatchersRig.class)); + cards.add(new SetCardInfo("Geistlight Snare", 356, Rarity.UNCOMMON, mage.cards.g.GeistlightSnare.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Geistlight Snare", 66, Rarity.UNCOMMON, mage.cards.g.GeistlightSnare.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Ghostly Castigator", 456, Rarity.UNCOMMON, mage.cards.g.GhostlyCastigator.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Ghostly Castigator", 58, Rarity.UNCOMMON, mage.cards.g.GhostlyCastigator.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Ghoulish Procession", 110, Rarity.UNCOMMON, mage.cards.g.GhoulishProcession.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Ghoulish Procession", 378, Rarity.UNCOMMON, mage.cards.g.GhoulishProcession.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Ghoultree", 198, Rarity.UNCOMMON, mage.cards.g.Ghoultree.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Ghoultree", 411, Rarity.UNCOMMON, mage.cards.g.Ghoultree.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Gisa and Geralf", 237, Rarity.RARE, mage.cards.g.GisaAndGeralf.class)); + cards.add(new SetCardInfo("Gisa's Bidding", 111, Rarity.COMMON, mage.cards.g.GisasBidding.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Gisa's Bidding", 379, Rarity.COMMON, mage.cards.g.GisasBidding.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Gisela, the Broken Blade", 24, Rarity.MYTHIC, mage.cards.g.GiselaTheBrokenBlade.class)); + cards.add(new SetCardInfo("Gluttonous Guest", 112, Rarity.COMMON, mage.cards.g.GluttonousGuest.class)); + cards.add(new SetCardInfo("Graf Rats", 113, Rarity.UNCOMMON, mage.cards.g.GrafRats.class)); + cards.add(new SetCardInfo("Grapple with the Past", 199, Rarity.COMMON, mage.cards.g.GrappleWithThePast.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Grapple with the Past", 412, Rarity.COMMON, mage.cards.g.GrappleWithThePast.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Gravecrawler", 114, Rarity.RARE, mage.cards.g.Gravecrawler.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Gravecrawler", 380, Rarity.RARE, mage.cards.g.Gravecrawler.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Grimgrin, Corpse-Born", 239, Rarity.MYTHIC, mage.cards.g.GrimgrinCorpseBorn.class)); + cards.add(new SetCardInfo("Griselbrand", 115, Rarity.MYTHIC, mage.cards.g.Griselbrand.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Griselbrand", 381, Rarity.MYTHIC, mage.cards.g.Griselbrand.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Griselbrand", 485, Rarity.MYTHIC, mage.cards.g.Griselbrand.class, FULL_ART)); + cards.add(new SetCardInfo("Grisly Anglerfish", 458, Rarity.UNCOMMON, mage.cards.g.GrislyAnglerfish.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Grisly Anglerfish", 67, Rarity.UNCOMMON, mage.cards.g.GrislyAnglerfish.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Grizzled Angler", 458, Rarity.UNCOMMON, mage.cards.g.GrizzledAngler.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Grizzled Angler", 67, Rarity.UNCOMMON, mage.cards.g.GrizzledAngler.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Grizzly Ghoul", 240, Rarity.UNCOMMON, mage.cards.g.GrizzlyGhoul.class)); + cards.add(new SetCardInfo("Groundskeeper", 200, Rarity.UNCOMMON, mage.cards.g.Groundskeeper.class)); + cards.add(new SetCardInfo("Gryff's Boon", 25, Rarity.UNCOMMON, mage.cards.g.GryffsBoon.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Gryff's Boon", 342, Rarity.UNCOMMON, mage.cards.g.GryffsBoon.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Guardian of Pilgrims", 26, Rarity.COMMON, mage.cards.g.GuardianOfPilgrims.class)); + cards.add(new SetCardInfo("Hamlet Captain", 201, Rarity.UNCOMMON, mage.cards.h.HamletCaptain.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Hamlet Captain", 413, Rarity.UNCOMMON, mage.cards.h.HamletCaptain.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Hanweir Battlements", 279, Rarity.RARE, mage.cards.h.HanweirBattlements.class)); + cards.add(new SetCardInfo("Hanweir Garrison", "157a", Rarity.RARE, mage.cards.h.HanweirGarrison.class)); + cards.add(new SetCardInfo("Hanweir Watchkeep", 158, Rarity.COMMON, mage.cards.h.HanweirWatchkeep.class)); + cards.add(new SetCardInfo("Hanweir, the Writhing Township", "157b", Rarity.RARE, mage.cards.h.HanweirTheWrithingTownship.class)); + cards.add(new SetCardInfo("Harvest Hand", 265, Rarity.COMMON, mage.cards.h.HarvestHand.class)); + cards.add(new SetCardInfo("Haunted Dead", 116, Rarity.UNCOMMON, mage.cards.h.HauntedDead.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Haunted Dead", 382, Rarity.UNCOMMON, mage.cards.h.HauntedDead.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Haunted Ridge", 280, Rarity.RARE, mage.cards.h.HauntedRidge.class)); + cards.add(new SetCardInfo("Heartless Summoning", 117, Rarity.RARE, mage.cards.h.HeartlessSummoning.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Heartless Summoning", 309, Rarity.RARE, mage.cards.h.HeartlessSummoning.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Heartless Summoning", 383, Rarity.RARE, mage.cards.h.HeartlessSummoning.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Helvault", 266, Rarity.RARE, mage.cards.h.Helvault.class)); + cards.add(new SetCardInfo("Hermit Druid", 202, Rarity.RARE, mage.cards.h.HermitDruid.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Hermit Druid", 488, Rarity.RARE, mage.cards.h.HermitDruid.class, FULL_ART)); + cards.add(new SetCardInfo("Hinterland Logger", 203, Rarity.COMMON, mage.cards.h.HinterlandLogger.class)); + cards.add(new SetCardInfo("Honeymoon Hearse", 159, Rarity.UNCOMMON, mage.cards.h.HoneymoonHearse.class)); + cards.add(new SetCardInfo("Hopeful Initiate", 27, Rarity.RARE, mage.cards.h.HopefulInitiate.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Hopeful Initiate", 343, Rarity.RARE, mage.cards.h.HopefulInitiate.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Howling Chorus", 214, Rarity.UNCOMMON, mage.cards.h.HowlingChorus.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Howling Chorus", 469, Rarity.UNCOMMON, mage.cards.h.HowlingChorus.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Howlpack Alpha", 207, Rarity.RARE, mage.cards.h.HowlpackAlpha.class)); + cards.add(new SetCardInfo("Howlpack of Estwald", 224, Rarity.COMMON, mage.cards.h.HowlpackOfEstwald.class)); + cards.add(new SetCardInfo("Howlpack Resurgence", 204, Rarity.UNCOMMON, mage.cards.h.HowlpackResurgence.class)); + cards.add(new SetCardInfo("Hullbreaker Horror", 303, Rarity.RARE, mage.cards.h.HullbreakerHorror.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Hullbreaker Horror", 357, Rarity.RARE, mage.cards.h.HullbreakerHorror.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Hullbreaker Horror", 68, Rarity.RARE, mage.cards.h.HullbreakerHorror.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Hungry Ridgewolf", 160, Rarity.COMMON, mage.cards.h.HungryRidgewolf.class)); + cards.add(new SetCardInfo("Huntmaster of the Fells", 241, Rarity.RARE, mage.cards.h.HuntmasterOfTheFells.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Huntmaster of the Fells", 325, Rarity.RARE, mage.cards.h.HuntmasterOfTheFells.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Huntmaster of the Fells", 470, Rarity.RARE, mage.cards.h.HuntmasterOfTheFells.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Imprisoned in the Moon", 358, Rarity.COMMON, mage.cards.i.ImprisonedInTheMoon.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Imprisoned in the Moon", 69, Rarity.COMMON, mage.cards.i.ImprisonedInTheMoon.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Incited Rabble", 451, Rarity.UNCOMMON, mage.cards.i.IncitedRabble.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Incited Rabble", 46, Rarity.UNCOMMON, mage.cards.i.IncitedRabble.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Indulgent Aristocrat", 118, Rarity.UNCOMMON, mage.cards.i.IndulgentAristocrat.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Indulgent Aristocrat", 384, Rarity.UNCOMMON, mage.cards.i.IndulgentAristocrat.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Infernal Grasp", 119, Rarity.UNCOMMON, mage.cards.i.InfernalGrasp.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Infernal Grasp", 310, Rarity.UNCOMMON, mage.cards.i.InfernalGrasp.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Infernal Grasp", 385, Rarity.UNCOMMON, mage.cards.i.InfernalGrasp.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Insectile Aberration", 457, Rarity.COMMON, mage.cards.i.InsectileAberration.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Insectile Aberration", 60, Rarity.COMMON, mage.cards.i.InsectileAberration.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Inspiring Captain", 28, Rarity.COMMON, mage.cards.i.InspiringCaptain.class)); + cards.add(new SetCardInfo("Intangible Virtue", 29, Rarity.UNCOMMON, mage.cards.i.IntangibleVirtue.class)); + cards.add(new SetCardInfo("Intrepid Provisioner", 205, Rarity.COMMON, mage.cards.i.IntrepidProvisioner.class)); + cards.add(new SetCardInfo("Invasion of Innistrad", 120, Rarity.RARE, mage.cards.i.InvasionOfInnistrad.class)); + cards.add(new SetCardInfo("Island", 290, Rarity.LAND, mage.cards.basiclands.Island.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Island", 291, Rarity.LAND, mage.cards.basiclands.Island.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("It of the Horrid Swarm", 331, Rarity.COMMON, mage.cards.i.ItOfTheHorridSwarm.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("It of the Horrid Swarm", 6, Rarity.COMMON, mage.cards.i.ItOfTheHorridSwarm.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Jace, Unraveler of Secrets", 70, Rarity.MYTHIC, mage.cards.j.JaceUnravelerOfSecrets.class)); + cards.add(new SetCardInfo("Join the Dance", 242, Rarity.UNCOMMON, mage.cards.j.JoinTheDance.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Join the Dance", 432, Rarity.UNCOMMON, mage.cards.j.JoinTheDance.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Killing Wave", 121, Rarity.UNCOMMON, mage.cards.k.KillingWave.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Killing Wave", 386, Rarity.UNCOMMON, mage.cards.k.KillingWave.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Krallenhorde Howler", 193, Rarity.UNCOMMON, mage.cards.k.KrallenhordeHowler.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Krallenhorde Howler", 323, Rarity.UNCOMMON, mage.cards.k.KrallenhordeHowler.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Krallenhorde Howler", 467, Rarity.UNCOMMON, mage.cards.k.KrallenhordeHowler.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Kruin Outlaw", 161, Rarity.RARE, mage.cards.k.KruinOutlaw.class)); + cards.add(new SetCardInfo("Laboratory Maniac", 304, Rarity.UNCOMMON, mage.cards.l.LaboratoryManiac.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Laboratory Maniac", 359, Rarity.UNCOMMON, mage.cards.l.LaboratoryManiac.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Laboratory Maniac", 71, Rarity.UNCOMMON, mage.cards.l.LaboratoryManiac.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Lantern Bearer", 72, Rarity.COMMON, mage.cards.l.LanternBearer.class)); + cards.add(new SetCardInfo("Lanterns' Lift", 72, Rarity.COMMON, mage.cards.l.LanternsLift.class)); + cards.add(new SetCardInfo("Liesa, Forgotten Archangel", 243, Rarity.RARE, mage.cards.l.LiesaForgottenArchangel.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Liesa, Forgotten Archangel", 433, Rarity.RARE, mage.cards.l.LiesaForgottenArchangel.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Lightning Axe", 162, Rarity.UNCOMMON, mage.cards.l.LightningAxe.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Lightning Axe", 398, Rarity.UNCOMMON, mage.cards.l.LightningAxe.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Lightning Mauler", 163, Rarity.UNCOMMON, mage.cards.l.LightningMauler.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Lightning Mauler", 399, Rarity.UNCOMMON, mage.cards.l.LightningMauler.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Liliana of the Veil", 475, Rarity.MYTHIC, mage.cards.l.LilianaOfTheVeil.class)); + cards.add(new SetCardInfo("Lingering Souls", 30, Rarity.UNCOMMON, mage.cards.l.LingeringSouls.class)); + cards.add(new SetCardInfo("Lord of Lineage", 327, Rarity.MYTHIC, mage.cards.l.LordOfLineage.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Lord of Lineage", 461, Rarity.MYTHIC, mage.cards.l.LordOfLineage.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Lord of Lineage", 98, Rarity.MYTHIC, mage.cards.l.LordOfLineage.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Lumberknot", 206, Rarity.UNCOMMON, mage.cards.l.Lumberknot.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Lumberknot", 414, Rarity.UNCOMMON, mage.cards.l.Lumberknot.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Luminous Phantom", 32, Rarity.COMMON, mage.cards.l.LuminousPhantom.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Luminous Phantom", 450, Rarity.COMMON, mage.cards.l.LuminousPhantom.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Lunarch Mantle", 31, Rarity.COMMON, mage.cards.l.LunarchMantle.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Lunarch Mantle", 344, Rarity.COMMON, mage.cards.l.LunarchMantle.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Lunarch Veteran", 32, Rarity.COMMON, mage.cards.l.LunarchVeteran.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Lunarch Veteran", 450, Rarity.COMMON, mage.cards.l.LunarchVeteran.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Lupine Prototype", 267, Rarity.UNCOMMON, mage.cards.l.LupinePrototype.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Lupine Prototype", 444, Rarity.UNCOMMON, mage.cards.l.LupinePrototype.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Maelstrom Pulse", 244, Rarity.RARE, mage.cards.m.MaelstromPulse.class)); + cards.add(new SetCardInfo("Makeshift Mauler", 73, Rarity.COMMON, mage.cards.m.MakeshiftMauler.class)); + cards.add(new SetCardInfo("Markov Waltzer", 245, Rarity.UNCOMMON, mage.cards.m.MarkovWaltzer.class)); + cards.add(new SetCardInfo("Mass Hysteria", 164, Rarity.RARE, mage.cards.m.MassHysteria.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mass Hysteria", 400, Rarity.RARE, mage.cards.m.MassHysteria.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mausoleum Guard", 33, Rarity.UNCOMMON, mage.cards.m.MausoleumGuard.class)); + cards.add(new SetCardInfo("Mausoleum Wanderer", 305, Rarity.RARE, mage.cards.m.MausoleumWanderer.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mausoleum Wanderer", 360, Rarity.RARE, mage.cards.m.MausoleumWanderer.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mausoleum Wanderer", 74, Rarity.RARE, mage.cards.m.MausoleumWanderer.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mayor of Avabruck", 207, Rarity.RARE, mage.cards.m.MayorOfAvabruck.class)); + cards.add(new SetCardInfo("Memory Deluge", 361, Rarity.RARE, mage.cards.m.MemoryDeluge.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Memory Deluge", 75, Rarity.RARE, mage.cards.m.MemoryDeluge.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mentor of the Meek", 34, Rarity.UNCOMMON, mage.cards.m.MentorOfTheMeek.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mentor of the Meek", 345, Rarity.UNCOMMON, mage.cards.m.MentorOfTheMeek.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Metallic Mimic", 268, Rarity.RARE, mage.cards.m.MetallicMimic.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Metallic Mimic", 445, Rarity.RARE, mage.cards.m.MetallicMimic.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Midnight Scavengers", 123, Rarity.COMMON, mage.cards.m.MidnightScavengers.class)); + cards.add(new SetCardInfo("Mirrorwing Dragon", 165, Rarity.MYTHIC, mage.cards.m.MirrorwingDragon.class)); + cards.add(new SetCardInfo("Mist Raven", 362, Rarity.UNCOMMON, mage.cards.m.MistRaven.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mist Raven", 76, Rarity.UNCOMMON, mage.cards.m.MistRaven.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Moldgraf Millipede", 208, Rarity.COMMON, mage.cards.m.MoldgrafMillipede.class)); + cards.add(new SetCardInfo("Moonlight Hunt", 209, Rarity.UNCOMMON, mage.cards.m.MoonlightHunt.class)); + cards.add(new SetCardInfo("Moonrise Intruder", 179, Rarity.COMMON, mage.cards.m.MoonriseIntruder.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Moonrise Intruder", 466, Rarity.COMMON, mage.cards.m.MoonriseIntruder.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Moonscarred Werewolf", 212, Rarity.COMMON, mage.cards.m.MoonscarredWerewolf.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Moonscarred Werewolf", 468, Rarity.COMMON, mage.cards.m.MoonscarredWerewolf.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Morbid Opportunist", 124, Rarity.UNCOMMON, mage.cards.m.MorbidOpportunist.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Morbid Opportunist", 388, Rarity.UNCOMMON, mage.cards.m.MorbidOpportunist.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Morkrut Banshee", 125, Rarity.UNCOMMON, mage.cards.m.MorkrutBanshee.class)); + cards.add(new SetCardInfo("Mountain", 294, Rarity.LAND, mage.cards.basiclands.Mountain.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mountain", 295, Rarity.LAND, mage.cards.basiclands.Mountain.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Murderous Compulsion", 126, Rarity.COMMON, mage.cards.m.MurderousCompulsion.class)); + cards.add(new SetCardInfo("Mystic Retrieval", 363, Rarity.UNCOMMON, mage.cards.m.MysticRetrieval.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mystic Retrieval", 77, Rarity.UNCOMMON, mage.cards.m.MysticRetrieval.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Nebelgast Herald", 364, Rarity.UNCOMMON, mage.cards.n.NebelgastHerald.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Nebelgast Herald", 78, Rarity.UNCOMMON, mage.cards.n.NebelgastHerald.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Necroduality", 365, Rarity.MYTHIC, mage.cards.n.Necroduality.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Necroduality", 79, Rarity.MYTHIC, mage.cards.n.Necroduality.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Neglected Heirloom", 269, Rarity.UNCOMMON, mage.cards.n.NeglectedHeirloom.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Neglected Heirloom", 473, Rarity.UNCOMMON, mage.cards.n.NeglectedHeirloom.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Neonate's Rush", 166, Rarity.COMMON, mage.cards.n.NeonatesRush.class)); + cards.add(new SetCardInfo("Niblis of the Urn", 346, Rarity.UNCOMMON, mage.cards.n.NiblisOfTheUrn.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Niblis of the Urn", 35, Rarity.UNCOMMON, mage.cards.n.NiblisOfTheUrn.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Noose Constrictor", 210, Rarity.UNCOMMON, mage.cards.n.NooseConstrictor.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Noose Constrictor", 415, Rarity.UNCOMMON, mage.cards.n.NooseConstrictor.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Odric, Lunarch Marshal", 298, Rarity.RARE, mage.cards.o.OdricLunarchMarshal.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Odric, Lunarch Marshal", 36, Rarity.RARE, mage.cards.o.OdricLunarchMarshal.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Olivia Voldaren", 246, Rarity.MYTHIC, mage.cards.o.OliviaVoldaren.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Olivia Voldaren", 490, Rarity.MYTHIC, mage.cards.o.OliviaVoldaren.class, FULL_ART)); + cards.add(new SetCardInfo("Olivia's Dragoon", 127, Rarity.COMMON, mage.cards.o.OliviasDragoon.class)); + cards.add(new SetCardInfo("Ormendahl, Profane Prince", 287, Rarity.RARE, mage.cards.o.OrmendahlProfanePrince.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Ormendahl, Profane Prince", 474, Rarity.RARE, mage.cards.o.OrmendahlProfanePrince.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Overcharged Amalgam", 80, Rarity.RARE, mage.cards.o.OverchargedAmalgam.class)); + cards.add(new SetCardInfo("Overgrown Farmland", 281, Rarity.RARE, mage.cards.o.OvergrownFarmland.class)); + cards.add(new SetCardInfo("Pack Guardian", 211, Rarity.UNCOMMON, mage.cards.p.PackGuardian.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Pack Guardian", 416, Rarity.UNCOMMON, mage.cards.p.PackGuardian.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Perfected Form", 454, Rarity.UNCOMMON, mage.cards.p.PerfectedForm.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Perfected Form", 52, Rarity.UNCOMMON, mage.cards.p.PerfectedForm.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Plains", 288, Rarity.LAND, mage.cards.basiclands.Plains.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Plains", 289, Rarity.LAND, mage.cards.basiclands.Plains.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Rally the Peasants", 347, Rarity.UNCOMMON, mage.cards.r.RallyThePeasants.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Rally the Peasants", 37, Rarity.UNCOMMON, mage.cards.r.RallyThePeasants.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Ravager of the Fells", 241, Rarity.RARE, mage.cards.r.RavagerOfTheFells.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Ravager of the Fells", 325, Rarity.RARE, mage.cards.r.RavagerOfTheFells.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Ravager of the Fells", 470, Rarity.RARE, mage.cards.r.RavagerOfTheFells.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Reckless Scholar", 81, Rarity.COMMON, mage.cards.r.RecklessScholar.class)); + cards.add(new SetCardInfo("Reforge the Soul", 167, Rarity.RARE, mage.cards.r.ReforgeTheSoul.class)); + cards.add(new SetCardInfo("Restless Bloodseeker", 128, Rarity.UNCOMMON, mage.cards.r.RestlessBloodseeker.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Restless Bloodseeker", 463, Rarity.UNCOMMON, mage.cards.r.RestlessBloodseeker.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Restoration Angel", 299, Rarity.RARE, mage.cards.r.RestorationAngel.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Restoration Angel", 38, Rarity.RARE, mage.cards.r.RestorationAngel.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Rise from the Tides", 366, Rarity.UNCOMMON, mage.cards.r.RiseFromTheTides.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Rise from the Tides", 82, Rarity.UNCOMMON, mage.cards.r.RiseFromTheTides.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Rockfall Vale", 282, Rarity.RARE, mage.cards.r.RockfallVale.class)); + cards.add(new SetCardInfo("Rooftop Storm", 306, Rarity.RARE, mage.cards.r.RooftopStorm.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Rooftop Storm", 83, Rarity.RARE, mage.cards.r.RooftopStorm.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Runebound Wolf", 168, Rarity.UNCOMMON, mage.cards.r.RuneboundWolf.class)); + cards.add(new SetCardInfo("Sanitarium Skeleton", 129, Rarity.COMMON, mage.cards.s.SanitariumSkeleton.class)); + cards.add(new SetCardInfo("Savage Alliance", 169, Rarity.UNCOMMON, mage.cards.s.SavageAlliance.class)); + cards.add(new SetCardInfo("Scorned Villager", 212, Rarity.COMMON, mage.cards.s.ScornedVillager.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Scorned Villager", 468, Rarity.COMMON, mage.cards.s.ScornedVillager.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Scrounged Scythe", 265, Rarity.COMMON, mage.cards.s.ScroungedScythe.class)); + cards.add(new SetCardInfo("Seasoned Cathar", 448, Rarity.UNCOMMON, mage.cards.s.SeasonedCathar.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Seasoned Cathar", 8, Rarity.UNCOMMON, mage.cards.s.SeasonedCathar.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Second Harvest", 213, Rarity.RARE, mage.cards.s.SecondHarvest.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Second Harvest", 417, Rarity.RARE, mage.cards.s.SecondHarvest.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Seize the Storm", 170, Rarity.COMMON, mage.cards.s.SeizeTheStorm.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Seize the Storm", 401, Rarity.COMMON, mage.cards.s.SeizeTheStorm.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Sever the Bloodline", 130, Rarity.UNCOMMON, mage.cards.s.SeverTheBloodline.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Sever the Bloodline", 389, Rarity.UNCOMMON, mage.cards.s.SeverTheBloodline.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Shattered Sanctum", 283, Rarity.RARE, mage.cards.s.ShatteredSanctum.class)); + cards.add(new SetCardInfo("Shipwreck Marsh", 284, Rarity.RARE, mage.cards.s.ShipwreckMarsh.class)); + cards.add(new SetCardInfo("Shrill Howler", 214, Rarity.UNCOMMON, mage.cards.s.ShrillHowler.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Shrill Howler", 469, Rarity.UNCOMMON, mage.cards.s.ShrillHowler.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Siege Zombie", 131, Rarity.COMMON, mage.cards.s.SiegeZombie.class)); + cards.add(new SetCardInfo("Sigarda, Host of Herons", 247, Rarity.MYTHIC, mage.cards.s.SigardaHostOfHerons.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Sigarda, Host of Herons", 434, Rarity.MYTHIC, mage.cards.s.SigardaHostOfHerons.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Silent Departure", 367, Rarity.COMMON, mage.cards.s.SilentDeparture.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Silent Departure", 84, Rarity.COMMON, mage.cards.s.SilentDeparture.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Skirsdag High Priest", 132, Rarity.RARE, mage.cards.s.SkirsdagHighPriest.class)); + cards.add(new SetCardInfo("Slayer of the Wicked", 348, Rarity.UNCOMMON, mage.cards.s.SlayerOfTheWicked.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Slayer of the Wicked", 39, Rarity.UNCOMMON, mage.cards.s.SlayerOfTheWicked.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Smoldering Werewolf", 171, Rarity.UNCOMMON, mage.cards.s.SmolderingWerewolf.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Smoldering Werewolf", 465, Rarity.UNCOMMON, mage.cards.s.SmolderingWerewolf.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Snapcaster Mage", 478, Rarity.MYTHIC, mage.cards.s.SnapcasterMage.class)); + cards.add(new SetCardInfo("Somberwald Sage", 215, Rarity.UNCOMMON, mage.cards.s.SomberwaldSage.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Somberwald Sage", 418, Rarity.UNCOMMON, mage.cards.s.SomberwaldSage.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Sorin, Imperious Bloodlord", 133, Rarity.MYTHIC, mage.cards.s.SorinImperiousBloodlord.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Sorin, Imperious Bloodlord", 322, Rarity.MYTHIC, mage.cards.s.SorinImperiousBloodlord.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Sorin, Imperious Bloodlord", 476, Rarity.MYTHIC, mage.cards.s.SorinImperiousBloodlord.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Soul Separator", 270, Rarity.UNCOMMON, mage.cards.s.SoulSeparator.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Soul Separator", 446, Rarity.UNCOMMON, mage.cards.s.SoulSeparator.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Soul-Guide Gryff", 40, Rarity.COMMON, mage.cards.s.SoulGuideGryff.class)); + cards.add(new SetCardInfo("Soulcipher Board", 459, Rarity.UNCOMMON, mage.cards.s.SoulcipherBoard.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Soulcipher Board", 85, Rarity.UNCOMMON, mage.cards.s.SoulcipherBoard.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Spectral Shepherd", 349, Rarity.UNCOMMON, mage.cards.s.SpectralShepherd.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Spectral Shepherd", 41, Rarity.UNCOMMON, mage.cards.s.SpectralShepherd.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Spell Queller", 248, Rarity.RARE, mage.cards.s.SpellQueller.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Spell Queller", 320, Rarity.RARE, mage.cards.s.SpellQueller.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Spell Queller", 435, Rarity.RARE, mage.cards.s.SpellQueller.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Spider Spawning", 216, Rarity.UNCOMMON, mage.cards.s.SpiderSpawning.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Spider Spawning", 419, Rarity.UNCOMMON, mage.cards.s.SpiderSpawning.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Splinterfright", 217, Rarity.UNCOMMON, mage.cards.s.Splinterfright.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Splinterfright", 420, Rarity.UNCOMMON, mage.cards.s.Splinterfright.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Spontaneous Mutation", 86, Rarity.COMMON, mage.cards.s.SpontaneousMutation.class)); + cards.add(new SetCardInfo("Spore Crawler", 218, Rarity.COMMON, mage.cards.s.SporeCrawler.class)); + cards.add(new SetCardInfo("Stensia Masquerade", 172, Rarity.UNCOMMON, mage.cards.s.StensiaMasquerade.class)); + cards.add(new SetCardInfo("Stitched Mangler", 87, Rarity.COMMON, mage.cards.s.StitchedMangler.class)); + cards.add(new SetCardInfo("Stitcher's Graft", 271, Rarity.RARE, mage.cards.s.StitchersGraft.class)); + cards.add(new SetCardInfo("Stormcarved Coast", 285, Rarity.RARE, mage.cards.s.StormcarvedCoast.class)); + cards.add(new SetCardInfo("Strength of Arms", 42, Rarity.COMMON, mage.cards.s.StrengthOfArms.class)); + cards.add(new SetCardInfo("Stromkirk Occultist", 173, Rarity.UNCOMMON, mage.cards.s.StromkirkOccultist.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Stromkirk Occultist", 402, Rarity.UNCOMMON, mage.cards.s.StromkirkOccultist.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Subjugator Angel", 350, Rarity.UNCOMMON, mage.cards.s.SubjugatorAngel.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Subjugator Angel", 43, Rarity.UNCOMMON, mage.cards.s.SubjugatorAngel.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Summary Dismissal", 368, Rarity.UNCOMMON, mage.cards.s.SummaryDismissal.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Summary Dismissal", 88, Rarity.UNCOMMON, mage.cards.s.SummaryDismissal.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Sundown Pass", 286, Rarity.RARE, mage.cards.s.SundownPass.class)); + cards.add(new SetCardInfo("Swamp", 292, Rarity.LAND, mage.cards.basiclands.Swamp.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Swamp", 293, Rarity.LAND, mage.cards.basiclands.Swamp.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Syncopate", 89, Rarity.COMMON, mage.cards.s.Syncopate.class)); + cards.add(new SetCardInfo("Tamiyo's Journal", 272, Rarity.RARE, mage.cards.t.TamiyosJournal.class)); + cards.add(new SetCardInfo("Tamiyo, Field Researcher", 249, Rarity.MYTHIC, mage.cards.t.TamiyoFieldResearcher.class)); + cards.add(new SetCardInfo("Temporal Mastery", 307, Rarity.MYTHIC, mage.cards.t.TemporalMastery.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Temporal Mastery", 90, Rarity.MYTHIC, mage.cards.t.TemporalMastery.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Terror of Kruin Pass", 161, Rarity.RARE, mage.cards.t.TerrorOfKruinPass.class)); + cards.add(new SetCardInfo("Thalia, Heretic Cathar", 300, Rarity.RARE, mage.cards.t.ThaliaHereticCathar.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Thalia, Heretic Cathar", 351, Rarity.RARE, mage.cards.t.ThaliaHereticCathar.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Thalia, Heretic Cathar", 44, Rarity.RARE, mage.cards.t.ThaliaHereticCathar.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("The Gitrog Monster", 238, Rarity.MYTHIC, mage.cards.t.TheGitrogMonster.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("The Gitrog Monster", 431, Rarity.MYTHIC, mage.cards.t.TheGitrogMonster.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("The Gitrog Monster", 489, Rarity.MYTHIC, mage.cards.t.TheGitrogMonster.class, FULL_ART)); + cards.add(new SetCardInfo("The Meathook Massacre", 122, Rarity.MYTHIC, mage.cards.t.TheMeathookMassacre.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("The Meathook Massacre", 387, Rarity.MYTHIC, mage.cards.t.TheMeathookMassacre.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("The Meathook Massacre", 486, Rarity.MYTHIC, mage.cards.t.TheMeathookMassacre.class, FULL_ART)); + cards.add(new SetCardInfo("Thermo-Alchemist", 174, Rarity.UNCOMMON, mage.cards.t.ThermoAlchemist.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Thermo-Alchemist", 403, Rarity.UNCOMMON, mage.cards.t.ThermoAlchemist.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Thing in the Ice", 460, Rarity.RARE, mage.cards.t.ThingInTheIce.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Thing in the Ice", 91, Rarity.RARE, mage.cards.t.ThingInTheIce.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Think Twice", 369, Rarity.COMMON, mage.cards.t.ThinkTwice.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Think Twice", 92, Rarity.COMMON, mage.cards.t.ThinkTwice.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Thraben Inspector", 301, Rarity.COMMON, mage.cards.t.ThrabenInspector.class, FULL_ART)); + cards.add(new SetCardInfo("Thraben Inspector", 45, Rarity.COMMON, mage.cards.t.ThrabenInspector.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Through the Breach", 175, Rarity.MYTHIC, mage.cards.t.ThroughTheBreach.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Through the Breach", 404, Rarity.MYTHIC, mage.cards.t.ThroughTheBreach.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Through the Breach", 487, Rarity.MYTHIC, mage.cards.t.ThroughTheBreach.class, FULL_ART)); + cards.add(new SetCardInfo("Timber Shredder", 203, Rarity.COMMON, mage.cards.t.TimberShredder.class)); + cards.add(new SetCardInfo("Tireless Tracker", 219, Rarity.RARE, mage.cards.t.TirelessTracker.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Tireless Tracker", 318, Rarity.RARE, mage.cards.t.TirelessTracker.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Torens, Fist of the Angels", 250, Rarity.RARE, mage.cards.t.TorensFistOfTheAngels.class)); + cards.add(new SetCardInfo("Tower Geist", 93, Rarity.COMMON, mage.cards.t.TowerGeist.class)); + cards.add(new SetCardInfo("Town Gossipmonger", 451, Rarity.UNCOMMON, mage.cards.t.TownGossipmonger.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Town Gossipmonger", 46, Rarity.UNCOMMON, mage.cards.t.TownGossipmonger.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Tragic Slip", 134, Rarity.COMMON, mage.cards.t.TragicSlip.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Tragic Slip", 390, Rarity.COMMON, mage.cards.t.TragicSlip.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Travel Preparations", 220, Rarity.UNCOMMON, mage.cards.t.TravelPreparations.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Travel Preparations", 421, Rarity.UNCOMMON, mage.cards.t.TravelPreparations.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Traveler's Amulet", 273, Rarity.COMMON, mage.cards.t.TravelersAmulet.class)); + cards.add(new SetCardInfo("Traverse the Ulvenwald", 221, Rarity.RARE, mage.cards.t.TraverseTheUlvenwald.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Traverse the Ulvenwald", 422, Rarity.RARE, mage.cards.t.TraverseTheUlvenwald.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Tree of Perdition", 135, Rarity.RARE, mage.cards.t.TreeOfPerdition.class)); + cards.add(new SetCardInfo("Triskaidekaphobia", 136, Rarity.UNCOMMON, mage.cards.t.Triskaidekaphobia.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Triskaidekaphobia", 391, Rarity.UNCOMMON, mage.cards.t.Triskaidekaphobia.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Twinblade Geist", 452, Rarity.UNCOMMON, mage.cards.t.TwinbladeGeist.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Twinblade Geist", 47, Rarity.UNCOMMON, mage.cards.t.TwinbladeGeist.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Twinblade Invocation", 452, Rarity.UNCOMMON, mage.cards.t.TwinbladeInvocation.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Twinblade Invocation", 47, Rarity.UNCOMMON, mage.cards.t.TwinbladeInvocation.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Ulrich's Kindred", 176, Rarity.UNCOMMON, mage.cards.u.UlrichsKindred.class)); + cards.add(new SetCardInfo("Ulvenwald Mysteries", 222, Rarity.UNCOMMON, mage.cards.u.UlvenwaldMysteries.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Ulvenwald Mysteries", 423, Rarity.UNCOMMON, mage.cards.u.UlvenwaldMysteries.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Uncaged Fury", 177, Rarity.UNCOMMON, mage.cards.u.UncagedFury.class)); + cards.add(new SetCardInfo("Unnatural Growth", 223, Rarity.RARE, mage.cards.u.UnnaturalGrowth.class)); + cards.add(new SetCardInfo("Valorous Stance", 352, Rarity.UNCOMMON, mage.cards.v.ValorousStance.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Valorous Stance", 48, Rarity.UNCOMMON, mage.cards.v.ValorousStance.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Vanquish the Horde", 302, Rarity.RARE, mage.cards.v.VanquishTheHorde.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Vanquish the Horde", 49, Rarity.RARE, mage.cards.v.VanquishTheHorde.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Vexing Devil", 178, Rarity.RARE, mage.cards.v.VexingDevil.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Vexing Devil", 313, Rarity.RARE, mage.cards.v.VexingDevil.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Vildin-Pack Alpha", 156, Rarity.UNCOMMON, mage.cards.v.VildinPackAlpha.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Vildin-Pack Alpha", 464, Rarity.UNCOMMON, mage.cards.v.VildinPackAlpha.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Vilespawn Spider", 251, Rarity.UNCOMMON, mage.cards.v.VilespawnSpider.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Vilespawn Spider", 436, Rarity.UNCOMMON, mage.cards.v.VilespawnSpider.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Village Messenger", 179, Rarity.COMMON, mage.cards.v.VillageMessenger.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Village Messenger", 466, Rarity.COMMON, mage.cards.v.VillageMessenger.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Village Rites", 137, Rarity.COMMON, mage.cards.v.VillageRites.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Village Rites", 392, Rarity.COMMON, mage.cards.v.VillageRites.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Villagers of Estwald", 224, Rarity.COMMON, mage.cards.v.VillagersOfEstwald.class)); + cards.add(new SetCardInfo("Voice of the Blessed", 50, Rarity.RARE, mage.cards.v.VoiceOfTheBlessed.class)); + cards.add(new SetCardInfo("Voldaren Ambusher", 180, Rarity.UNCOMMON, mage.cards.v.VoldarenAmbusher.class)); + cards.add(new SetCardInfo("Voldaren Bloodcaster", 138, Rarity.RARE, mage.cards.v.VoldarenBloodcaster.class)); + cards.add(new SetCardInfo("Voldaren Duelist", 181, Rarity.COMMON, mage.cards.v.VoldarenDuelist.class)); + cards.add(new SetCardInfo("Voldaren Epicure", 182, Rarity.COMMON, mage.cards.v.VoldarenEpicure.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Voldaren Epicure", 405, Rarity.COMMON, mage.cards.v.VoldarenEpicure.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Wandering Mind", 252, Rarity.UNCOMMON, mage.cards.w.WanderingMind.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Wandering Mind", 437, Rarity.UNCOMMON, mage.cards.w.WanderingMind.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Wedding Announcement", 453, Rarity.RARE, mage.cards.w.WeddingAnnouncement.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Wedding Announcement", 51, Rarity.RARE, mage.cards.w.WeddingAnnouncement.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Wedding Festivity", 453, Rarity.RARE, mage.cards.w.WeddingFestivity.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Wedding Festivity", 51, Rarity.RARE, mage.cards.w.WeddingFestivity.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Westvale Abbey", 287, Rarity.RARE, mage.cards.w.WestvaleAbbey.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Westvale Abbey", 474, Rarity.RARE, mage.cards.w.WestvaleAbbey.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Wild Hunger", 225, Rarity.UNCOMMON, mage.cards.w.WildHunger.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Wild Hunger", 424, Rarity.UNCOMMON, mage.cards.w.WildHunger.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Wild-Field Scarecrow", 274, Rarity.COMMON, mage.cards.w.WildFieldScarecrow.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Wild-Field Scarecrow", 447, Rarity.COMMON, mage.cards.w.WildFieldScarecrow.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Wrenn and Seven", 226, Rarity.MYTHIC, mage.cards.w.WrennAndSeven.class)); + cards.add(new SetCardInfo("Wretched Gryff", 332, Rarity.COMMON, mage.cards.w.WretchedGryff.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Wretched Gryff", 7, Rarity.COMMON, mage.cards.w.WretchedGryff.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Wretched Throng", 94, Rarity.COMMON, mage.cards.w.WretchedThrong.class)); + cards.add(new SetCardInfo("Young Wolf", 227, Rarity.COMMON, mage.cards.y.YoungWolf.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Young Wolf", 319, Rarity.COMMON, mage.cards.y.YoungWolf.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Young Wolf", 425, Rarity.COMMON, mage.cards.y.YoungWolf.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Zealous Conscripts", 183, Rarity.RARE, mage.cards.z.ZealousConscripts.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Zealous Conscripts", 314, Rarity.RARE, mage.cards.z.ZealousConscripts.class, NON_FULL_USE_VARIOUS)); + } +} \ No newline at end of file -- 2.47.2 From 10a25e3f213a9210fc96c1b654212e07fd9fda03 Mon Sep 17 00:00:00 2001 From: PurpleCrowbar <26198472+PurpleCrowbar@users.noreply.github.com> Date: Fri, 17 Jan 2025 21:17:16 +0000 Subject: [PATCH 15/17] Add BLC token images --- .../sources/ScryfallImageSupportTokens.java | 34 ++++++++++++++++++ Mage/src/main/resources/tokens-database.txt | 36 ++++++++++++++++++- 2 files changed, 69 insertions(+), 1 deletion(-) diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/ScryfallImageSupportTokens.java b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/ScryfallImageSupportTokens.java index 573b54094e2..7679f7b895a 100644 --- a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/ScryfallImageSupportTokens.java +++ b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/ScryfallImageSupportTokens.java @@ -2455,7 +2455,41 @@ public class ScryfallImageSupportTokens { put("BLB/Wall", "https://api.scryfall.com/cards/tblb/4/en?format=image"); // BLC + put("BLC/Beast/1", "https://api.scryfall.com/cards/tblc/24/en?format=image"); + put("BLC/Beast/2", "https://api.scryfall.com/cards/tblc/25/en?format=image"); + put("BLC/Bird/1", "https://api.scryfall.com/cards/tblc/11/en?format=image"); + put("BLC/Bird/2", "https://api.scryfall.com/cards/tblc/3/en?format=image"); + put("BLC/Blood", "https://api.scryfall.com/cards/tblc/36/en?format=image"); + put("BLC/Cat", "https://api.scryfall.com/cards/tblc/26/en?format=image"); + put("BLC/Citizen", "https://api.scryfall.com/cards/tblc/33/en?format=image"); + put("BLC/Clue", "https://api.scryfall.com/cards/tblc/37/en?format=image"); + put("BLC/Eldrazi", "https://api.scryfall.com/cards/tblc/2/en?format=image"); + put("BLC/Elemental", "https://api.scryfall.com/cards/tblc/4/en?format=image"); + put("BLC/Elephant", "https://api.scryfall.com/cards/tblc/27/en?format=image"); + put("BLC/Faerie", "https://api.scryfall.com/cards/tblc/12/en?format=image"); + put("BLC/Frog Lizard", "https://api.scryfall.com/cards/tblc/28/en?format=image"); + put("BLC/Goat", "https://api.scryfall.com/cards/tblc/5/en?format=image"); + put("BLC/Goblin", "https://api.scryfall.com/cards/tblc/21/en?format=image"); + put("BLC/Hamster", "https://api.scryfall.com/cards/tblc/22/en?format=image"); + put("BLC/Human", "https://api.scryfall.com/cards/tblc/6/en?format=image"); + put("BLC/Human Soldier", "https://api.scryfall.com/cards/tblc/7/en?format=image"); + put("BLC/Illusion", "https://api.scryfall.com/cards/tblc/13/en?format=image"); + put("BLC/Octopus", "https://api.scryfall.com/cards/tblc/14/en?format=image"); + put("BLC/Pest", "https://api.scryfall.com/cards/tblc/34/en?format=image"); + put("BLC/Phyrexian Golem", "https://api.scryfall.com/cards/tblc/38/en?format=image"); put("BLC/Raccoon", "https://api.scryfall.com/cards/tblc/29/en?format=image"); + put("BLC/Rat", "https://api.scryfall.com/cards/tblc/19/en?format=image"); + put("BLC/Saproling", "https://api.scryfall.com/cards/tblc/30/en?format=image"); + put("BLC/Shapeshifter", "https://api.scryfall.com/cards/tblc/15/en?format=image"); + put("BLC/Shark", "https://api.scryfall.com/cards/tblc/16/en?format=image"); + put("BLC/Soldier", "https://api.scryfall.com/cards/tblc/8/en?format=image"); + put("BLC/Spider", "https://api.scryfall.com/cards/tblc/31/en?format=image"); + put("BLC/Spirit", "https://api.scryfall.com/cards/tblc/9/en?format=image"); + put("BLC/Squid", "https://api.scryfall.com/cards/tblc/17/en?format=image"); + put("BLC/Storm Crow", "https://api.scryfall.com/cards/tblc/18/en?format=image"); + put("BLC/Thopter", "https://api.scryfall.com/cards/tblc/39/en?format=image"); + put("BLC/Wolf/1", "https://api.scryfall.com/cards/tblc/35/en?format=image"); + put("BLC/Wolf/2", "https://api.scryfall.com/cards/tblc/32/en?format=image"); // DSK put("DSK/Emblem Kaito", "https://api.scryfall.com/cards/tdsk/17/en?format=image"); diff --git a/Mage/src/main/resources/tokens-database.txt b/Mage/src/main/resources/tokens-database.txt index a842d1c5600..d4f567f4b12 100644 --- a/Mage/src/main/resources/tokens-database.txt +++ b/Mage/src/main/resources/tokens-database.txt @@ -2330,7 +2330,41 @@ |Generate|TOK:BLB|Wall|||WallWhiteToken| # BLC -|Generate|TOK:BLC|Racoon|||RaccoonToken| +|Generate|TOK:BLC|Beast|1||BeastToken| +|Generate|TOK:BLC|Beast|2||BeastToken2| +|Generate|TOK:BLC|Bird|1||SwanSongBirdToken| +|Generate|TOK:BLC|Bird|2||BirdToken| +|Generate|TOK:BLC|Blood|||BloodToken| +|Generate|TOK:BLC|Cat|||GreenCat2Token| +|Generate|TOK:BLC|Citizen|||CitizenGreenWhiteToken| +|Generate|TOK:BLC|Clue|||ClueArtifactToken| +|Generate|TOK:BLC|Eldrazi|||EldraziToken| +|Generate|TOK:BLC|Elemental|||WhiteElementalToken| +|Generate|TOK:BLC|Elephant|||ElephantToken| +|Generate|TOK:BLC|Faerie|||FaerieToken| +|Generate|TOK:BLC|Frog Lizard|||FrogLizardToken| +|Generate|TOK:BLC|Goat|||GoatToken| +|Generate|TOK:BLC|Goblin|||GoblinToken| +|Generate|TOK:BLC|Hamster|||HamsterToken| +|Generate|TOK:BLC|Human|||HumanToken| +|Generate|TOK:BLC|Human Soldier|||HumanSoldierToken| +|Generate|TOK:BLC|Illusion|||CustomIllusionToken| +|Generate|TOK:BLC|Octopus|||OctopusToken| +|Generate|TOK:BLC|Pest|||Pest11GainLifeToken| +|Generate|TOK:BLC|Phyrexian Golem|||PhyrexianGolemToken| +|Generate|TOK:BLC|Raccoon|||RaccoonToken| +|Generate|TOK:BLC|Rat|||RatToken| +|Generate|TOK:BLC|Saproling|||SaprolingToken| +|Generate|TOK:BLC|Shapeshifter|||ShapeshifterBlueToken| +|Generate|TOK:BLC|Shark|||Shark33Token| +|Generate|TOK:BLC|Soldier|||SoldierToken| +|Generate|TOK:BLC|Spider|||SpiderToken| +|Generate|TOK:BLC|Spirit|||SpiritWhiteToken| +|Generate|TOK:BLC|Squid|||SquidToken| +|Generate|TOK:BLC|Storm Crow|||StormCrowToken| +|Generate|TOK:BLC|Thopter|||ThopterColorlessToken| +|Generate|TOK:BLC|Wolf|1||GarrukCursedHuntsmanToken| +|Generate|TOK:BLC|Wolf|2||WolfToken| # FDN |Generate|TOK:FDN|Beast|1||BeastToken| -- 2.47.2 From e399e233d7cf17c85fbc076133138b97574d0e1c Mon Sep 17 00:00:00 2001 From: PurpleCrowbar <26198472+PurpleCrowbar@users.noreply.github.com> Date: Sat, 18 Jan 2025 01:30:37 +0000 Subject: [PATCH 16/17] [DSC] Implement Rendmaw, Creaking Nest --- .../src/mage/cards/r/RendmawCreakingNest.java | 132 ++++++++++++++++++ .../sets/DuskmournHouseOfHorrorCommander.java | 1 + .../permanent/token/Black22BirdToken.java | 32 +++++ 3 files changed, 165 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/r/RendmawCreakingNest.java create mode 100644 Mage/src/main/java/mage/game/permanent/token/Black22BirdToken.java diff --git a/Mage.Sets/src/mage/cards/r/RendmawCreakingNest.java b/Mage.Sets/src/mage/cards/r/RendmawCreakingNest.java new file mode 100644 index 00000000000..fcf91f09500 --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/RendmawCreakingNest.java @@ -0,0 +1,132 @@ +package mage.cards.r; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.combat.GoadTargetEffect; +import mage.abilities.keyword.MenaceAbility; +import mage.abilities.keyword.ReachAbility; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.permanent.token.Black22BirdToken; +import mage.game.permanent.token.Token; +import mage.game.stack.Spell; +import mage.target.targetpointer.FixedTarget; + +import java.util.UUID; + +/** + * @author PurpleCrowbar + */ +public final class RendmawCreakingNest extends CardImpl { + + public RendmawCreakingNest(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{3}{B}{G}"); + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.SCARECROW); + this.power = new MageInt(5); + this.toughness = new MageInt(5); + + // Menace + this.addAbility(new MenaceAbility(false)); + + // Reach + this.addAbility(ReachAbility.getInstance()); + + // When Rendmaw, Creaking Nest enters and whenever you play a card with two or more card types, each player creates a tapped 2/2 black Bird creature token with flying. The tokens are goaded for the rest of the game. + this.addAbility(new RendmawCreakingNestTriggeredAbility()); + } + + private RendmawCreakingNest(final RendmawCreakingNest card) { + super(card); + } + + @Override + public RendmawCreakingNest copy() { + return new RendmawCreakingNest(this); + } +} + +class RendmawCreakingNestTriggeredAbility extends TriggeredAbilityImpl { + + RendmawCreakingNestTriggeredAbility() { + super(Zone.BATTLEFIELD, new RendmawCreakingNestEffect()); + setTriggerPhrase("When {this} enters and whenever you play a card with two or more card types, "); + } + + private RendmawCreakingNestTriggeredAbility(final RendmawCreakingNestTriggeredAbility ability) { + super(ability); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.ENTERS_THE_BATTLEFIELD + || event.getType() == GameEvent.EventType.SPELL_CAST + || event.getType() == GameEvent.EventType.LAND_PLAYED; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + switch (event.getType()) { + case ENTERS_THE_BATTLEFIELD: + return event.getTargetId().equals(getSourceId()); + case SPELL_CAST: + if (!event.getPlayerId().equals(this.getControllerId())) { + return false; + } + Spell spell = game.getSpellOrLKIStack(event.getTargetId()); + return spell != null && spell.getCardType(game).size() >= 2; + case LAND_PLAYED: + if (!event.getPlayerId().equals(this.getControllerId())) { + return false; + } + Card card = game.getCard(event.getTargetId()); + return card != null && card.getCardType(game).size() >= 2; + default: + return false; + } + } + + @Override + public RendmawCreakingNestTriggeredAbility copy() { + return new RendmawCreakingNestTriggeredAbility(this); + } +} + +class RendmawCreakingNestEffect extends OneShotEffect { + + RendmawCreakingNestEffect() { + super(Outcome.Benefit); + staticText = "each player creates a tapped 2/2 black Bird creature token with flying. " + + "The tokens are goaded for the rest of the game"; + } + + private RendmawCreakingNestEffect(final RendmawCreakingNestEffect effect) { + super(effect); + } + + @Override + public RendmawCreakingNestEffect copy() { + return new RendmawCreakingNestEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + for (UUID playerId : game.getState().getPlayersInRange(source.getControllerId(), game)) { + if (!game.getPlayer(playerId).isInGame()) { + continue; + } + Token token = new Black22BirdToken(); + token.putOntoBattlefield(1, game, source, playerId, true, false); + token.getLastAddedTokenIds().forEach(id -> game.addEffect( + new GoadTargetEffect().setDuration(Duration.EndOfGame).setTargetPointer(new FixedTarget(id, game)), source + )); + } + return true; + } +} diff --git a/Mage.Sets/src/mage/sets/DuskmournHouseOfHorrorCommander.java b/Mage.Sets/src/mage/sets/DuskmournHouseOfHorrorCommander.java index 3f71ee7f462..29f49bf67b8 100644 --- a/Mage.Sets/src/mage/sets/DuskmournHouseOfHorrorCommander.java +++ b/Mage.Sets/src/mage/sets/DuskmournHouseOfHorrorCommander.java @@ -213,6 +213,7 @@ public final class DuskmournHouseOfHorrorCommander extends ExpansionSet { cards.add(new SetCardInfo("Reanimate", 155, Rarity.RARE, mage.cards.r.Reanimate.class)); cards.add(new SetCardInfo("Redress Fate", 9, Rarity.RARE, mage.cards.r.RedressFate.class)); cards.add(new SetCardInfo("Reliquary Tower", 295, Rarity.UNCOMMON, mage.cards.r.ReliquaryTower.class)); + cards.add(new SetCardInfo("Rendmaw, Creaking Nest", 5, Rarity.MYTHIC, mage.cards.r.RendmawCreakingNest.class)); cards.add(new SetCardInfo("Retreat to Coralhelm", 126, Rarity.UNCOMMON, mage.cards.r.RetreatToCoralhelm.class)); cards.add(new SetCardInfo("Return to Dust", 102, Rarity.UNCOMMON, mage.cards.r.ReturnToDust.class)); cards.add(new SetCardInfo("Sakura-Tribe Elder", 194, Rarity.COMMON, mage.cards.s.SakuraTribeElder.class)); diff --git a/Mage/src/main/java/mage/game/permanent/token/Black22BirdToken.java b/Mage/src/main/java/mage/game/permanent/token/Black22BirdToken.java new file mode 100644 index 00000000000..d2bcac3187c --- /dev/null +++ b/Mage/src/main/java/mage/game/permanent/token/Black22BirdToken.java @@ -0,0 +1,32 @@ +package mage.game.permanent.token; + +import mage.MageInt; +import mage.abilities.keyword.FlyingAbility; +import mage.constants.CardType; +import mage.constants.SubType; + +/** + * @author PurpleCrowbar + */ +public final class Black22BirdToken extends TokenImpl { + + public Black22BirdToken() { + super("Bird Token", "2/2 black Bird creature token with flying"); + cardType.add(CardType.CREATURE); + color.setBlack(true); + subtype.add(SubType.BIRD); + power = new MageInt(2); + toughness = new MageInt(2); + + addAbility(FlyingAbility.getInstance()); + } + + private Black22BirdToken(final Black22BirdToken token) { + super(token); + } + + @Override + public Black22BirdToken copy() { + return new Black22BirdToken(this); + } +} -- 2.47.2 From d7fa071e58261a7dfe797dc95c04610231d9c03f Mon Sep 17 00:00:00 2001 From: Grath <1895280+Grath@users.noreply.github.com> Date: Sat, 18 Jan 2025 00:30:07 -0500 Subject: [PATCH 17/17] Fix Slinza's cost reduction effect Fixes #13235 --- Mage.Sets/src/mage/cards/s/SlinzaTheSpikedStampede.java | 1 + 1 file changed, 1 insertion(+) diff --git a/Mage.Sets/src/mage/cards/s/SlinzaTheSpikedStampede.java b/Mage.Sets/src/mage/cards/s/SlinzaTheSpikedStampede.java index 871e1e1ef12..98f8d8ac359 100644 --- a/Mage.Sets/src/mage/cards/s/SlinzaTheSpikedStampede.java +++ b/Mage.Sets/src/mage/cards/s/SlinzaTheSpikedStampede.java @@ -35,6 +35,7 @@ public final class SlinzaTheSpikedStampede extends CardImpl { private static final FilterPermanent filter3 = new FilterCreaturePermanent("creature with power 4 or greater"); static { + filter.add(SubType.BEAST.getPredicate()); filter3.add(new PowerPredicate(ComparisonType.MORE_THAN, 3)); } -- 2.47.2