From 72ff6f27b3e6afb57d8f44d5d143a3e1fcf99277 Mon Sep 17 00:00:00 2001 From: betasteward Date: Mon, 23 Mar 2015 13:04:09 -0400 Subject: [PATCH] changes to the way abilities are added to cards --- .../sets/commander/ChorusOfTheConclave.java | 2 +- .../dragonsmaze/VarolzTheScarStriped.java | 2 +- .../fatereforged/SoulfireGrandMaster.java | 2 +- .../mage/sets/fifthdawn/MycosynthGolem.java | 2 +- .../mage/sets/fifthedition/PrimalClay.java | 4 +- .../src/mage/sets/futuresight/Delay.java | 2 +- .../src/mage/sets/innistrad/PastInFlames.java | 2 +- .../mage/sets/innistrad/SnapcasterMage.java | 2 +- .../mage/sets/magic2015/ChiefEngineer.java | 2 +- .../sets/mirrodinbesieged/GruesomeEncore.java | 2 +- .../sets/newphyrexia/PostmortemLunge.java | 2 +- Mage.Sets/src/mage/sets/odyssey/Recoup.java | 2 +- .../sets/planechase2012/PrimalPlasma.java | 30 ++-- .../BeastbreakerOfBalaGed.java | 5 +- .../sets/riseoftheeldrazi/BrimstoneMage.java | 5 +- .../sets/riseoftheeldrazi/CaravanEscort.java | 4 +- .../riseoftheeldrazi/CastThroughTime.java | 82 +++++------ .../riseoftheeldrazi/CoralhelmCommander.java | 5 +- .../mage/sets/riseoftheeldrazi/EchoMage.java | 8 +- .../riseoftheeldrazi/EnclaveCryptologist.java | 5 +- .../riseoftheeldrazi/GuulDrazAssassin.java | 5 +- .../sets/riseoftheeldrazi/HadaSpyPatrol.java | 5 +- .../riseoftheeldrazi/HalimarWavewatch.java | 5 +- .../riseoftheeldrazi/HedronFieldPurists.java | 8 +- .../sets/riseoftheeldrazi/IkiralOutrider.java | 5 +- .../riseoftheeldrazi/JoragaTreespeaker.java | 5 +- .../riseoftheeldrazi/KabiraVindicator.java | 5 +- .../riseoftheeldrazi/KarganDragonlord.java | 5 +- .../riseoftheeldrazi/KazanduTuskcaller.java | 5 +- .../riseoftheeldrazi/KnightOfCliffhaven.java | 5 +- .../LighthouseChronologist.java | 5 +- .../LordOfShatterskullPass.java | 8 +- .../riseoftheeldrazi/NirkanaCutthroat.java | 5 +- .../sets/riseoftheeldrazi/NullChampion.java | 5 +- .../riseoftheeldrazi/SkywatcherAdept.java | 5 +- .../riseoftheeldrazi/StudentOfWarfare.java | 5 +- .../riseoftheeldrazi/TranscendentMaster.java | 5 +- .../riseoftheeldrazi/ZulaportEnforcer.java | 5 +- .../shardsofalara/SedrisTheTraitorKing.java | 2 +- .../src/mage/sets/tenth/CrucibleOfWorlds.java | 2 +- .../src/mage/sets/theros/WhipOfErebos.java | 1 - .../mage/sets/timespiral/DralnuLichLord.java | 2 +- .../sets/timespiral/TeferiMageOfZhalfir.java | 6 +- .../src/mage/sets/visions/Necromancy.java | 6 +- .../abilities/keywords/ChampionTest.java | 103 ++++++++++++++ .../abilities/keywords/ConspireTest.java | 110 +++++++++++++++ .../cards/abilities/keywords/DashTest.java | 99 +++++++++++++ .../cards/abilities/keywords/FadingTest.java | 105 ++++++++++++++ .../cards/abilities/keywords/GraftTest.java | 133 ++++++++++++++++++ .../abilities/keywords/HideawayTest.java | 84 +++++++++++ .../cards/abilities/keywords/MadnessTest.java | 115 +++++++++++++++ .../cards/abilities/keywords/ModularTest.java | 111 +++++++++++++++ .../abilities/keywords/ReplicateTest.java | 115 +++++++++++++++ .../abilities/other/ChiefEngineerTest.java | 70 +++++++++ .../other/ChorusOfTheConclaveTest.java | 73 ++++++++++ .../abilities/other/CrucibleOfWorldsTest.java | 83 +++++++++++ .../abilities/other/MycosynthGolemTest.java | 70 +++++++++ .../cards/abilities/other/NecromancyTest.java | 127 +++++++++++++++++ .../abilities/other/PastInFlamesTest.java | 71 ++++++++++ .../other/SoulfireGrandMasterTest.java | 93 ++++++++++++ .../other/VarolzTheScarStripedTest.java | 73 ++++++++++ Mage/src/mage/abilities/Ability.java | 13 ++ Mage/src/mage/abilities/AbilityImpl.java | 35 +++++ .../continuous/GainAbilitySourceEffect.java | 3 +- .../continuous/GainAbilityTargetEffect.java | 2 +- .../mage/abilities/keyword/BestowAbility.java | 2 +- .../abilities/keyword/ChampionAbility.java | 4 +- .../abilities/keyword/ConspireAbility.java | 2 +- .../mage/abilities/keyword/DashAbility.java | 2 +- .../mage/abilities/keyword/EvokeAbility.java | 2 +- .../mage/abilities/keyword/FadingAbility.java | 2 +- .../mage/abilities/keyword/GraftAbility.java | 2 +- .../mage/abilities/keyword/HauntAbility.java | 2 +- .../abilities/keyword/HideawayAbility.java | 4 +- .../abilities/keyword/LevelerCardBuilder.java | 21 +-- .../abilities/keyword/MadnessAbility.java | 2 +- .../abilities/keyword/ModularAbility.java | 4 +- .../mage/abilities/keyword/MorphAbility.java | 2 +- .../abilities/keyword/ReboundAbility.java | 2 +- .../abilities/keyword/ReplicateAbility.java | 2 +- .../abilities/keyword/SuspendAbility.java | 10 +- Mage/src/mage/cards/Card.java | 3 +- Mage/src/mage/cards/CardImpl.java | 51 ++++++- Mage/src/mage/cards/LevelerCard.java | 2 +- Mage/src/mage/cards/SplitCard.java | 13 ++ Mage/src/mage/game/CardState.java | 38 +++++ Mage/src/mage/game/GameState.java | 46 +++--- Mage/src/mage/game/permanent/Permanent.java | 1 - .../mage/game/permanent/PermanentImpl.java | 12 ++ Mage/src/mage/game/stack/Spell.java | 6 +- Mage/src/mage/game/stack/StackAbility.java | 10 ++ 91 files changed, 2003 insertions(+), 217 deletions(-) create mode 100644 Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/ChampionTest.java create mode 100644 Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/ConspireTest.java create mode 100644 Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/DashTest.java create mode 100644 Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/FadingTest.java create mode 100644 Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/GraftTest.java create mode 100644 Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/HideawayTest.java create mode 100644 Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/MadnessTest.java create mode 100644 Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/ModularTest.java create mode 100644 Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/ReplicateTest.java create mode 100644 Mage.Tests/src/test/java/org/mage/test/cards/abilities/other/ChiefEngineerTest.java create mode 100644 Mage.Tests/src/test/java/org/mage/test/cards/abilities/other/ChorusOfTheConclaveTest.java create mode 100644 Mage.Tests/src/test/java/org/mage/test/cards/abilities/other/CrucibleOfWorldsTest.java create mode 100644 Mage.Tests/src/test/java/org/mage/test/cards/abilities/other/MycosynthGolemTest.java create mode 100644 Mage.Tests/src/test/java/org/mage/test/cards/abilities/other/NecromancyTest.java create mode 100644 Mage.Tests/src/test/java/org/mage/test/cards/abilities/other/PastInFlamesTest.java create mode 100644 Mage.Tests/src/test/java/org/mage/test/cards/abilities/other/SoulfireGrandMasterTest.java create mode 100644 Mage.Tests/src/test/java/org/mage/test/cards/abilities/other/VarolzTheScarStripedTest.java diff --git a/Mage.Sets/src/mage/sets/commander/ChorusOfTheConclave.java b/Mage.Sets/src/mage/sets/commander/ChorusOfTheConclave.java index 8d3c8c2984b..155503d66a2 100644 --- a/Mage.Sets/src/mage/sets/commander/ChorusOfTheConclave.java +++ b/Mage.Sets/src/mage/sets/commander/ChorusOfTheConclave.java @@ -119,7 +119,7 @@ class ChorusOfTheConclaveReplacementEffect extends ReplacementEffectImpl { if (xCost > 0) { Ability ability = new EntersBattlefieldAbility(new AddCountersSourceEffect(CounterType.P1P1.createInstance(xCost))); ability.setRuleVisible(false); - card.addAbility(ability); + game.getState().addOtherAbility(card, ability); ability.setControllerId(source.getControllerId()); ability.setSourceId(card.getId()); game.getState().addAbility(ability, source.getSourceId(), card); diff --git a/Mage.Sets/src/mage/sets/dragonsmaze/VarolzTheScarStriped.java b/Mage.Sets/src/mage/sets/dragonsmaze/VarolzTheScarStriped.java index b6382083ce4..36ec394a01e 100644 --- a/Mage.Sets/src/mage/sets/dragonsmaze/VarolzTheScarStriped.java +++ b/Mage.Sets/src/mage/sets/dragonsmaze/VarolzTheScarStriped.java @@ -115,7 +115,7 @@ class VarolzTheScarStripedEffect extends ContinuousEffectImpl { ScavengeAbility ability = new ScavengeAbility(new ManaCostsImpl(card.getManaCost().getText())); ability.setSourceId(cardId); ability.setControllerId(card.getOwnerId()); - game.getState().addOtherAbility(cardId, ability); + game.getState().addOtherAbility(card, ability); } } return true; diff --git a/Mage.Sets/src/mage/sets/fatereforged/SoulfireGrandMaster.java b/Mage.Sets/src/mage/sets/fatereforged/SoulfireGrandMaster.java index 92b5ccb2087..b42fce09390 100644 --- a/Mage.Sets/src/mage/sets/fatereforged/SoulfireGrandMaster.java +++ b/Mage.Sets/src/mage/sets/fatereforged/SoulfireGrandMaster.java @@ -144,7 +144,7 @@ class GainAbilitySpellsEffect extends ContinuousEffectImpl { Card card = spell.getCard(); if (card != null) { if (!card.getAbilities().contains(ability)) { - card.addAbility(ability); + game.getState().addOtherAbility(card, ability); SoulfireGrandMasterLeavesStackWatcher watcher = (SoulfireGrandMasterLeavesStackWatcher) game.getState().getWatchers().get("SoulfireGrandMasterLeavesStackWatcher"); if (watcher != null) { watcher.addCardId(card.getId()); diff --git a/Mage.Sets/src/mage/sets/fifthdawn/MycosynthGolem.java b/Mage.Sets/src/mage/sets/fifthdawn/MycosynthGolem.java index 8f746dd82e4..acd7225b6b0 100644 --- a/Mage.Sets/src/mage/sets/fifthdawn/MycosynthGolem.java +++ b/Mage.Sets/src/mage/sets/fifthdawn/MycosynthGolem.java @@ -103,7 +103,7 @@ class MycosynthGolemEffect extends ReplacementEffectImpl { if (object != null) { Card card = (Card) object; Ability ability = new AffinityForArtifactsAbility(); - card.addAbility(ability); + game.getState().addOtherAbility(card, ability); ability.setControllerId(source.getControllerId()); ability.setSourceId(card.getId()); game.getState().addAbility(ability, source.getSourceId(), card); diff --git a/Mage.Sets/src/mage/sets/fifthedition/PrimalClay.java b/Mage.Sets/src/mage/sets/fifthedition/PrimalClay.java index a1db6a6bace..5ccc37709e5 100644 --- a/Mage.Sets/src/mage/sets/fifthedition/PrimalClay.java +++ b/Mage.Sets/src/mage/sets/fifthedition/PrimalClay.java @@ -150,7 +150,7 @@ class PrimalPlasmaReplacementEffect extends ReplacementEffectImpl { mageObject.getPower().setValue(2); mageObject.getToughness().setValue(2); if (mageObject instanceof Card) { - ((Card)mageObject).addAbility(FlyingAbility.getInstance()); + game.getState().addOtherAbility((Card)mageObject, FlyingAbility.getInstance()); } else { ((Token)mageObject).addAbility(FlyingAbility.getInstance()); } @@ -159,7 +159,7 @@ class PrimalPlasmaReplacementEffect extends ReplacementEffectImpl { mageObject.getPower().setValue(1); mageObject.getToughness().setValue(6); if (mageObject instanceof Card) { - ((Card)mageObject).addAbility(DefenderAbility.getInstance()); + game.getState().addOtherAbility((Card)mageObject, DefenderAbility.getInstance()); } else { ((Token)mageObject).addAbility(DefenderAbility.getInstance()); } diff --git a/Mage.Sets/src/mage/sets/futuresight/Delay.java b/Mage.Sets/src/mage/sets/futuresight/Delay.java index 0f6974bcc37..a5956fe8d90 100644 --- a/Mage.Sets/src/mage/sets/futuresight/Delay.java +++ b/Mage.Sets/src/mage/sets/futuresight/Delay.java @@ -112,7 +112,7 @@ class DelayEffect extends OneShotEffect { // If the exiled card leaves exile by another way, the abilites won't be removed from the card Abilities oldAbilities = card.getAbilities().copy(); SuspendAbility suspendAbility = new SuspendAbility(3, null, card); - card.addAbility(suspendAbility); + game.getState().addOtherAbility(card, suspendAbility); for (Ability ability :card.getAbilities()) { if (!oldAbilities.contains(ability)) { diff --git a/Mage.Sets/src/mage/sets/innistrad/PastInFlames.java b/Mage.Sets/src/mage/sets/innistrad/PastInFlames.java index 16b9d76d901..5c7dd92f9bc 100644 --- a/Mage.Sets/src/mage/sets/innistrad/PastInFlames.java +++ b/Mage.Sets/src/mage/sets/innistrad/PastInFlames.java @@ -124,7 +124,7 @@ class PastInFlamesEffect extends ContinuousEffectImpl { if (ability != null) { ability.setSourceId(cardId); ability.setControllerId(card.getOwnerId()); - game.getState().addOtherAbility(cardId, ability); + game.getState().addOtherAbility(card, ability); } } } diff --git a/Mage.Sets/src/mage/sets/innistrad/SnapcasterMage.java b/Mage.Sets/src/mage/sets/innistrad/SnapcasterMage.java index a1dacf3e28f..292a17a97de 100644 --- a/Mage.Sets/src/mage/sets/innistrad/SnapcasterMage.java +++ b/Mage.Sets/src/mage/sets/innistrad/SnapcasterMage.java @@ -121,7 +121,7 @@ class SnapcasterMageEffect extends ContinuousEffectImpl { } ability.setSourceId(card.getId()); ability.setControllerId(card.getOwnerId()); - game.getState().addOtherAbility(card.getId(), ability); + game.getState().addOtherAbility(card, ability); return true; } return false; diff --git a/Mage.Sets/src/mage/sets/magic2015/ChiefEngineer.java b/Mage.Sets/src/mage/sets/magic2015/ChiefEngineer.java index a0b23f2619f..c33b5da89d0 100644 --- a/Mage.Sets/src/mage/sets/magic2015/ChiefEngineer.java +++ b/Mage.Sets/src/mage/sets/magic2015/ChiefEngineer.java @@ -101,7 +101,7 @@ class ChiefEngineerEffect extends ReplacementEffectImpl { if (object != null) { Card card = (Card) object; Ability ability = new ConvokeAbility(); - card.addAbility(ability); + game.getState().addOtherAbility(card, ability); ability.setControllerId(source.getControllerId()); ability.setSourceId(card.getId()); game.getState().addAbility(ability, source.getSourceId(), card); diff --git a/Mage.Sets/src/mage/sets/mirrodinbesieged/GruesomeEncore.java b/Mage.Sets/src/mage/sets/mirrodinbesieged/GruesomeEncore.java index f5c7f64aa7b..d7f56e40889 100644 --- a/Mage.Sets/src/mage/sets/mirrodinbesieged/GruesomeEncore.java +++ b/Mage.Sets/src/mage/sets/mirrodinbesieged/GruesomeEncore.java @@ -98,7 +98,7 @@ class GruesomeEncoreEffect extends OneShotEffect { public boolean apply(Game game, Ability source) { Card card = game.getCard(source.getFirstTarget()); if (card != null) { - card.addAbility(HasteAbility.getInstance()); + game.getState().addOtherAbility(card, HasteAbility.getInstance()); card.putOntoBattlefield(game, Zone.GRAVEYARD, source.getSourceId(), source.getControllerId()); ExileTargetEffect exileEffect = new ExileTargetEffect(); diff --git a/Mage.Sets/src/mage/sets/newphyrexia/PostmortemLunge.java b/Mage.Sets/src/mage/sets/newphyrexia/PostmortemLunge.java index 682fb58f6ed..c7274afd37b 100644 --- a/Mage.Sets/src/mage/sets/newphyrexia/PostmortemLunge.java +++ b/Mage.Sets/src/mage/sets/newphyrexia/PostmortemLunge.java @@ -110,7 +110,7 @@ class PostmortemLungeEffect extends OneShotEffect { if (player == null) { return false; } - card.addAbility(HasteAbility.getInstance()); + game.getState().addOtherAbility(card, HasteAbility.getInstance()); card.putOntoBattlefield(game, Zone.GRAVEYARD, source.getSourceId(), source.getControllerId()); ExileTargetEffect exileEffect = new ExileTargetEffect(); diff --git a/Mage.Sets/src/mage/sets/odyssey/Recoup.java b/Mage.Sets/src/mage/sets/odyssey/Recoup.java index 4efb89f4e4c..5e6c7e427ab 100644 --- a/Mage.Sets/src/mage/sets/odyssey/Recoup.java +++ b/Mage.Sets/src/mage/sets/odyssey/Recoup.java @@ -107,7 +107,7 @@ class RecoupEffect extends ContinuousEffectImpl { FlashbackAbility ability = new FlashbackAbility(card.getManaCost(), TimingRule.SORCERY); ability.setSourceId(card.getId()); ability.setControllerId(card.getOwnerId()); - game.getState().addOtherAbility(card.getId(), ability); + game.getState().addOtherAbility(card, ability); return true; } } diff --git a/Mage.Sets/src/mage/sets/planechase2012/PrimalPlasma.java b/Mage.Sets/src/mage/sets/planechase2012/PrimalPlasma.java index f448b9501c5..df5864a7031 100644 --- a/Mage.Sets/src/mage/sets/planechase2012/PrimalPlasma.java +++ b/Mage.Sets/src/mage/sets/planechase2012/PrimalPlasma.java @@ -136,34 +136,20 @@ class PrimalPlasmaReplacementEffect extends ReplacementEffectImpl { } } } - MageObject mageObject; - if (permanent instanceof PermanentCard) { - mageObject = ((PermanentCard) permanent).getCard(); - } else { - mageObject = ((PermanentToken) permanent).getToken(); - } switch (choice.getChoice()) { case choice33: - mageObject.getPower().setValue(3); - mageObject.getToughness().setValue(3); + permanent.getPower().setValue(3); + permanent.getToughness().setValue(3); break; case choice22: - mageObject.getPower().setValue(2); - mageObject.getToughness().setValue(2); - if (mageObject instanceof Card) { - ((Card)mageObject).addAbility(FlyingAbility.getInstance()); - } else { - ((Token)mageObject).addAbility(FlyingAbility.getInstance()); - } + permanent.getPower().setValue(2); + permanent.getToughness().setValue(2); + permanent.addAbility(FlyingAbility.getInstance(), source.getId(), game); break; case choice16: - mageObject.getPower().setValue(1); - mageObject.getToughness().setValue(6); - if (mageObject instanceof Card) { - ((Card)mageObject).addAbility(DefenderAbility.getInstance()); - } else { - ((Token)mageObject).addAbility(DefenderAbility.getInstance()); - } + permanent.getPower().setValue(1); + permanent.getToughness().setValue(6); + permanent.addAbility(DefenderAbility.getInstance(), source.getId(), game); break; } } diff --git a/Mage.Sets/src/mage/sets/riseoftheeldrazi/BeastbreakerOfBalaGed.java b/Mage.Sets/src/mage/sets/riseoftheeldrazi/BeastbreakerOfBalaGed.java index c1d5f4c21be..4b5116d14b5 100644 --- a/Mage.Sets/src/mage/sets/riseoftheeldrazi/BeastbreakerOfBalaGed.java +++ b/Mage.Sets/src/mage/sets/riseoftheeldrazi/BeastbreakerOfBalaGed.java @@ -63,7 +63,7 @@ public class BeastbreakerOfBalaGed extends LevelerCard { Abilities levelAbilities = new AbilitiesImpl(); levelAbilities.add(TrampleAbility.getInstance()); - LevelerCardBuilder.construct(this, + this.addAbilities(LevelerCardBuilder.construct( // LEVEL 1-3 // 4/4 new LevelerCardBuilder.LevelAbility(1, 3, new AbilitiesImpl(), 4, 4), @@ -71,7 +71,8 @@ public class BeastbreakerOfBalaGed extends LevelerCard { // 4/4 // Trample new LevelerCardBuilder.LevelAbility(4, -1, levelAbilities, 6, 6) - ); + )); + setMaxLevelCounters(4); } public BeastbreakerOfBalaGed(final BeastbreakerOfBalaGed card) { diff --git a/Mage.Sets/src/mage/sets/riseoftheeldrazi/BrimstoneMage.java b/Mage.Sets/src/mage/sets/riseoftheeldrazi/BrimstoneMage.java index 0210c22576d..de382d9acbc 100644 --- a/Mage.Sets/src/mage/sets/riseoftheeldrazi/BrimstoneMage.java +++ b/Mage.Sets/src/mage/sets/riseoftheeldrazi/BrimstoneMage.java @@ -72,10 +72,11 @@ public class BrimstoneMage extends LevelerCard { ability.addTarget(new TargetCreatureOrPlayer()); abilities2.add(ability); - LevelerCardBuilder.construct(this, + this.addAbilities(LevelerCardBuilder.construct( new LevelerCardBuilder.LevelAbility(1, 2, abilities1, 2, 3), new LevelerCardBuilder.LevelAbility(3, -1, abilities2, 2, 4) - ); + )); + setMaxLevelCounters(3); } public BrimstoneMage (final BrimstoneMage card) { diff --git a/Mage.Sets/src/mage/sets/riseoftheeldrazi/CaravanEscort.java b/Mage.Sets/src/mage/sets/riseoftheeldrazi/CaravanEscort.java index a84ebf0918a..211feee6546 100644 --- a/Mage.Sets/src/mage/sets/riseoftheeldrazi/CaravanEscort.java +++ b/Mage.Sets/src/mage/sets/riseoftheeldrazi/CaravanEscort.java @@ -59,10 +59,10 @@ public class CaravanEscort extends LevelerCard { this.addAbility(new LevelUpAbility(new ManaCostsImpl("{2}"))); AbilitiesImpl levelAbilities = new AbilitiesImpl(FirstStrikeAbility.getInstance()); - LevelerCardBuilder.construct(this, + this.addAbilities(LevelerCardBuilder.construct( new LevelerCardBuilder.LevelAbility(1, 4, new AbilitiesImpl(), 2, 2), new LevelerCardBuilder.LevelAbility(5, -1, levelAbilities, 5, 5) - ); + )); setMaxLevelCounters(5); } diff --git a/Mage.Sets/src/mage/sets/riseoftheeldrazi/CastThroughTime.java b/Mage.Sets/src/mage/sets/riseoftheeldrazi/CastThroughTime.java index 6de6995cc95..e03d3bf6580 100644 --- a/Mage.Sets/src/mage/sets/riseoftheeldrazi/CastThroughTime.java +++ b/Mage.Sets/src/mage/sets/riseoftheeldrazi/CastThroughTime.java @@ -68,7 +68,7 @@ public class CastThroughTime extends CardImpl { // Instant and sorcery spells you control have rebound. // (Exile the spell as it resolves if you cast it from your hand. At the beginning of your next upkeep, you may cast that card from exile without paying its mana cost.) - this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new GainReboundEffect()), new LeavesBattlefieldWatcher()); + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new GainReboundEffect())); } public CastThroughTime(final CastThroughTime card) { @@ -131,52 +131,52 @@ class GainReboundEffect extends ContinuousEffectImpl { } } if (!found) { - Ability ability = new AttachedReboundAbility(); - card.addAbility(ability); + Ability ability = new ReboundAbility(); +// card.addAbility(ability); ability.setControllerId(source.getControllerId()); ability.setSourceId(card.getId()); - game.getState().addAbility(ability, card); + game.getState().addOtherAbility(card, ability); } } } } -class AttachedReboundAbility extends ReboundAbility {} +//class AttachedReboundAbility extends ReboundAbility {} -class LeavesBattlefieldWatcher extends Watcher { - - public LeavesBattlefieldWatcher() { - super("LeavesBattlefieldWatcher", WatcherScope.CARD); - } - - public LeavesBattlefieldWatcher(final LeavesBattlefieldWatcher watcher) { - super(watcher); - } - - @Override - public void watch(GameEvent event, Game game) { - if (event.getType() == GameEvent.EventType.ZONE_CHANGE && event.getTargetId().equals(this.getSourceId())) { - ZoneChangeEvent zEvent = (ZoneChangeEvent)event; - if (zEvent.getFromZone() == Zone.BATTLEFIELD) { - Player player = game.getPlayer(this.getControllerId()); - if (player != null) { - for (Card card : player.getHand().getCards(CastThroughTime.filter, game)) { - Iterator it = card.getAbilities().iterator(); - while (it.hasNext()) { - if (it.next() instanceof AttachedReboundAbility) { - it.remove(); - } - } - } - } - } - } - } - - @Override - public LeavesBattlefieldWatcher copy() { - return new LeavesBattlefieldWatcher(this); - } - -} +//class LeavesBattlefieldWatcher extends Watcher { +// +// public LeavesBattlefieldWatcher() { +// super("LeavesBattlefieldWatcher", WatcherScope.CARD); +// } +// +// public LeavesBattlefieldWatcher(final LeavesBattlefieldWatcher watcher) { +// super(watcher); +// } +// +// @Override +// public void watch(GameEvent event, Game game) { +// if (event.getType() == GameEvent.EventType.ZONE_CHANGE && event.getTargetId().equals(this.getSourceId())) { +// ZoneChangeEvent zEvent = (ZoneChangeEvent)event; +// if (zEvent.getFromZone() == Zone.BATTLEFIELD) { +// Player player = game.getPlayer(this.getControllerId()); +// if (player != null) { +// for (Card card : player.getHand().getCards(CastThroughTime.filter, game)) { +// Iterator it = card.getAbilities().iterator(); +// while (it.hasNext()) { +// if (it.next() instanceof AttachedReboundAbility) { +// it.remove(); +// } +// } +// } +// } +// } +// } +// } +// +// @Override +// public LeavesBattlefieldWatcher copy() { +// return new LeavesBattlefieldWatcher(this); +// } +// +//} diff --git a/Mage.Sets/src/mage/sets/riseoftheeldrazi/CoralhelmCommander.java b/Mage.Sets/src/mage/sets/riseoftheeldrazi/CoralhelmCommander.java index 954632f151a..5ea77e24e67 100644 --- a/Mage.Sets/src/mage/sets/riseoftheeldrazi/CoralhelmCommander.java +++ b/Mage.Sets/src/mage/sets/riseoftheeldrazi/CoralhelmCommander.java @@ -78,10 +78,11 @@ public class CoralhelmCommander extends LevelerCard { abilities2.add(FlyingAbility.getInstance()); abilities2.add(new SimpleStaticAbility(Zone.BATTLEFIELD, new BoostControlledEffect(1, 1, Duration.WhileOnBattlefield, filter, true))); - LevelerCardBuilder.construct(this, + this.addAbilities(LevelerCardBuilder.construct( new LevelerCardBuilder.LevelAbility(2, 3, abilities1, 3, 3), new LevelerCardBuilder.LevelAbility(4, -1, abilities2, 4, 4) - ); + )); + setMaxLevelCounters(4); } public CoralhelmCommander(final CoralhelmCommander card) { diff --git a/Mage.Sets/src/mage/sets/riseoftheeldrazi/EchoMage.java b/Mage.Sets/src/mage/sets/riseoftheeldrazi/EchoMage.java index 289cc36dade..4ebf9639baf 100644 --- a/Mage.Sets/src/mage/sets/riseoftheeldrazi/EchoMage.java +++ b/Mage.Sets/src/mage/sets/riseoftheeldrazi/EchoMage.java @@ -44,6 +44,7 @@ import mage.abilities.effects.common.CopyTargetSpellEffect; import mage.abilities.keyword.LevelUpAbility; import mage.abilities.keyword.LevelerCardBuilder; import mage.cards.CardImpl; +import mage.cards.LevelerCard; import mage.filter.FilterSpell; import mage.filter.predicate.Predicates; import mage.filter.predicate.mageobject.CardTypePredicate; @@ -55,7 +56,7 @@ import mage.target.TargetSpell; * * @author North */ -public class EchoMage extends CardImpl { +public class EchoMage extends LevelerCard { private static final FilterSpell filter = new FilterSpell("instant or sorcery spell"); @@ -94,9 +95,10 @@ public class EchoMage extends CardImpl { ability.addCost(new TapSourceCost()); abilities2.add(ability); - LevelerCardBuilder.construct(this, + this.addAbilities(LevelerCardBuilder.construct( new LevelerCardBuilder.LevelAbility(2, 3, abilities1, 2, 4), - new LevelerCardBuilder.LevelAbility(4, -1, abilities2, 2, 5)); + new LevelerCardBuilder.LevelAbility(4, -1, abilities2, 2, 5))); + setMaxLevelCounters(4); } public EchoMage(final EchoMage card) { diff --git a/Mage.Sets/src/mage/sets/riseoftheeldrazi/EnclaveCryptologist.java b/Mage.Sets/src/mage/sets/riseoftheeldrazi/EnclaveCryptologist.java index 5291b1c9255..2d24d8cbb2a 100644 --- a/Mage.Sets/src/mage/sets/riseoftheeldrazi/EnclaveCryptologist.java +++ b/Mage.Sets/src/mage/sets/riseoftheeldrazi/EnclaveCryptologist.java @@ -69,10 +69,11 @@ public class EnclaveCryptologist extends LevelerCard { ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new DrawCardSourceControllerEffect(1), new TapSourceCost()); Abilities abilities2 = new AbilitiesImpl(ability); - LevelerCardBuilder.construct(this, + this.addAbilities(LevelerCardBuilder.construct( new LevelerCardBuilder.LevelAbility(1, 2, abilities1, 0, 1), new LevelerCardBuilder.LevelAbility(3, -1, abilities2, 0, 1) - ); + )); + setMaxLevelCounters(3); } public EnclaveCryptologist (final EnclaveCryptologist card) { diff --git a/Mage.Sets/src/mage/sets/riseoftheeldrazi/GuulDrazAssassin.java b/Mage.Sets/src/mage/sets/riseoftheeldrazi/GuulDrazAssassin.java index e4e726b9586..324aaba3350 100644 --- a/Mage.Sets/src/mage/sets/riseoftheeldrazi/GuulDrazAssassin.java +++ b/Mage.Sets/src/mage/sets/riseoftheeldrazi/GuulDrazAssassin.java @@ -76,10 +76,11 @@ public class GuulDrazAssassin extends LevelerCard { ability2.addCost(new TapSourceCost()); abilities2.add(ability2); - LevelerCardBuilder.construct(this, + this.addAbilities(LevelerCardBuilder.construct( new LevelerCardBuilder.LevelAbility(2, 3, abilities1, 2, 2), new LevelerCardBuilder.LevelAbility(4, -1, abilities2, 4, 4) - ); + )); + setMaxLevelCounters(4); } public GuulDrazAssassin (final GuulDrazAssassin card) { diff --git a/Mage.Sets/src/mage/sets/riseoftheeldrazi/HadaSpyPatrol.java b/Mage.Sets/src/mage/sets/riseoftheeldrazi/HadaSpyPatrol.java index 2b0d50741d4..223ac64da1b 100644 --- a/Mage.Sets/src/mage/sets/riseoftheeldrazi/HadaSpyPatrol.java +++ b/Mage.Sets/src/mage/sets/riseoftheeldrazi/HadaSpyPatrol.java @@ -74,10 +74,11 @@ public class HadaSpyPatrol extends LevelerCard { abilities2.add(ShroudAbility.getInstance()); abilities2.add(new CantBeBlockedSourceAbility()); - LevelerCardBuilder.construct(this, + this.addAbilities(LevelerCardBuilder.construct( new LevelerCardBuilder.LevelAbility(1, 2, abilities1, 2, 2), new LevelerCardBuilder.LevelAbility(3, -1, abilities2, 3, 3) - ); + )); + setMaxLevelCounters(3); } public HadaSpyPatrol(final HadaSpyPatrol card) { diff --git a/Mage.Sets/src/mage/sets/riseoftheeldrazi/HalimarWavewatch.java b/Mage.Sets/src/mage/sets/riseoftheeldrazi/HalimarWavewatch.java index fc731c941eb..d7ce21a1e57 100644 --- a/Mage.Sets/src/mage/sets/riseoftheeldrazi/HalimarWavewatch.java +++ b/Mage.Sets/src/mage/sets/riseoftheeldrazi/HalimarWavewatch.java @@ -62,10 +62,11 @@ public class HalimarWavewatch extends LevelerCard { Abilities levelAbilities = new AbilitiesImpl(); levelAbilities.add(new IslandwalkAbility()); - LevelerCardBuilder.construct(this, + this.addAbilities(LevelerCardBuilder.construct( new LevelerCardBuilder.LevelAbility(1, 4, new AbilitiesImpl(), 0, 6), new LevelerCardBuilder.LevelAbility(5, -1, levelAbilities, 6, 6) - ); + )); + setMaxLevelCounters(5); } public HalimarWavewatch(final HalimarWavewatch card) { diff --git a/Mage.Sets/src/mage/sets/riseoftheeldrazi/HedronFieldPurists.java b/Mage.Sets/src/mage/sets/riseoftheeldrazi/HedronFieldPurists.java index 53376b51953..cc68adf68cf 100644 --- a/Mage.Sets/src/mage/sets/riseoftheeldrazi/HedronFieldPurists.java +++ b/Mage.Sets/src/mage/sets/riseoftheeldrazi/HedronFieldPurists.java @@ -42,6 +42,7 @@ import mage.abilities.effects.PreventionEffectImpl; import mage.abilities.keyword.LevelUpAbility; import mage.abilities.keyword.LevelerCardBuilder; import mage.cards.CardImpl; +import mage.cards.LevelerCard; import mage.game.Game; import mage.game.events.GameEvent; import mage.game.permanent.Permanent; @@ -50,7 +51,7 @@ import mage.game.permanent.Permanent; * * @author North */ -public class HedronFieldPurists extends CardImpl { +public class HedronFieldPurists extends LevelerCard { public HedronFieldPurists(UUID ownerId) { super(ownerId, 25, "Hedron-Field Purists", Rarity.RARE, new CardType[]{CardType.CREATURE}, "{2}{W}"); @@ -75,9 +76,10 @@ public class HedronFieldPurists extends CardImpl { Abilities abilities2 = new AbilitiesImpl<>(); abilities2.add(new SimpleStaticAbility(Zone.BATTLEFIELD, new HedronFieldPuristsEffect(2))); - LevelerCardBuilder.construct(this, + this.addAbilities(LevelerCardBuilder.construct( new LevelerCardBuilder.LevelAbility(1, 4, abilities1, 1, 4), - new LevelerCardBuilder.LevelAbility(5, -1, abilities2, 2, 5)); + new LevelerCardBuilder.LevelAbility(5, -1, abilities2, 2, 5))); + setMaxLevelCounters(5); } public HedronFieldPurists(final HedronFieldPurists card) { diff --git a/Mage.Sets/src/mage/sets/riseoftheeldrazi/IkiralOutrider.java b/Mage.Sets/src/mage/sets/riseoftheeldrazi/IkiralOutrider.java index 61d018e051b..44426cf1139 100644 --- a/Mage.Sets/src/mage/sets/riseoftheeldrazi/IkiralOutrider.java +++ b/Mage.Sets/src/mage/sets/riseoftheeldrazi/IkiralOutrider.java @@ -65,10 +65,11 @@ public class IkiralOutrider extends LevelerCard { Abilities abilities2 = new AbilitiesImpl(); abilities2.add(VigilanceAbility.getInstance()); - LevelerCardBuilder.construct(this, + this.addAbilities(LevelerCardBuilder.construct( new LevelerCardBuilder.LevelAbility(1, 3, abilities1, 2, 6), new LevelerCardBuilder.LevelAbility(4, -1, abilities2, 3, 10) - ); + )); + setMaxLevelCounters(4); } public IkiralOutrider (final IkiralOutrider card) { diff --git a/Mage.Sets/src/mage/sets/riseoftheeldrazi/JoragaTreespeaker.java b/Mage.Sets/src/mage/sets/riseoftheeldrazi/JoragaTreespeaker.java index 98a8e66a998..9d248cc48c1 100644 --- a/Mage.Sets/src/mage/sets/riseoftheeldrazi/JoragaTreespeaker.java +++ b/Mage.Sets/src/mage/sets/riseoftheeldrazi/JoragaTreespeaker.java @@ -92,10 +92,11 @@ public class JoragaTreespeaker extends LevelerCard { new TapSourceCost()), Duration.WhileOnBattlefield, filter))); - LevelerCardBuilder.construct(this, + this.addAbilities(LevelerCardBuilder.construct( new LevelerCardBuilder.LevelAbility(1, 4, abilities1, 1, 2), new LevelerCardBuilder.LevelAbility(5, -1, abilities2, 1, 4) - ); + )); + setMaxLevelCounters(5); } public JoragaTreespeaker(final JoragaTreespeaker card) { diff --git a/Mage.Sets/src/mage/sets/riseoftheeldrazi/KabiraVindicator.java b/Mage.Sets/src/mage/sets/riseoftheeldrazi/KabiraVindicator.java index 8bfdb412ce3..2668570c480 100644 --- a/Mage.Sets/src/mage/sets/riseoftheeldrazi/KabiraVindicator.java +++ b/Mage.Sets/src/mage/sets/riseoftheeldrazi/KabiraVindicator.java @@ -69,10 +69,11 @@ public class KabiraVindicator extends LevelerCard { Abilities abilities2 = new AbilitiesImpl(); abilities2.add(new SimpleStaticAbility(Zone.BATTLEFIELD, new BoostControlledEffect(2, 2, Duration.WhileOnBattlefield, new FilterCreaturePermanent(), true))); - LevelerCardBuilder.construct(this, + this.addAbilities(LevelerCardBuilder.construct( new LevelerCardBuilder.LevelAbility(2, 4, abilities1, 3, 6), new LevelerCardBuilder.LevelAbility(5, -1, abilities2, 4, 8) - ); + )); + setMaxLevelCounters(5); } public KabiraVindicator(final KabiraVindicator card) { diff --git a/Mage.Sets/src/mage/sets/riseoftheeldrazi/KarganDragonlord.java b/Mage.Sets/src/mage/sets/riseoftheeldrazi/KarganDragonlord.java index 52dd033fda1..e6fd9c429fa 100644 --- a/Mage.Sets/src/mage/sets/riseoftheeldrazi/KarganDragonlord.java +++ b/Mage.Sets/src/mage/sets/riseoftheeldrazi/KarganDragonlord.java @@ -72,10 +72,11 @@ public class KarganDragonlord extends LevelerCard { abilities2.add(TrampleAbility.getInstance()); abilities2.add(new SimpleActivatedAbility(Zone.BATTLEFIELD, new BoostSourceEffect(1, 0, Duration.EndOfTurn), new ManaCostsImpl("{R}"))); - LevelerCardBuilder.construct(this, + this.addAbilities(LevelerCardBuilder.construct( new LevelerCardBuilder.LevelAbility(4, 7, abilities1, 4, 4), new LevelerCardBuilder.LevelAbility(8, -1, abilities2, 8, 8) - ); + )); + setMaxLevelCounters(8); } public KarganDragonlord(final KarganDragonlord card) { diff --git a/Mage.Sets/src/mage/sets/riseoftheeldrazi/KazanduTuskcaller.java b/Mage.Sets/src/mage/sets/riseoftheeldrazi/KazanduTuskcaller.java index 83039ad0cff..5341a7c33d4 100644 --- a/Mage.Sets/src/mage/sets/riseoftheeldrazi/KazanduTuskcaller.java +++ b/Mage.Sets/src/mage/sets/riseoftheeldrazi/KazanduTuskcaller.java @@ -72,10 +72,11 @@ public class KazanduTuskcaller extends LevelerCard { new CreateTokenEffect(new ElephantToken(), 2), new TapSourceCost())); - LevelerCardBuilder.construct(this, + this.addAbilities(LevelerCardBuilder.construct( new LevelerCardBuilder.LevelAbility(2, 5, abilities1, 1, 1), new LevelerCardBuilder.LevelAbility(6, -1, abilities2, 1, 1) - ); + )); + setMaxLevelCounters(6); } public KazanduTuskcaller(final KazanduTuskcaller card) { diff --git a/Mage.Sets/src/mage/sets/riseoftheeldrazi/KnightOfCliffhaven.java b/Mage.Sets/src/mage/sets/riseoftheeldrazi/KnightOfCliffhaven.java index 9cbc544c133..4b9a95de21c 100644 --- a/Mage.Sets/src/mage/sets/riseoftheeldrazi/KnightOfCliffhaven.java +++ b/Mage.Sets/src/mage/sets/riseoftheeldrazi/KnightOfCliffhaven.java @@ -67,10 +67,11 @@ public class KnightOfCliffhaven extends LevelerCard { abilities2.add(FlyingAbility.getInstance()); abilities2.add(VigilanceAbility.getInstance()); - LevelerCardBuilder.construct(this, + this.addAbilities(LevelerCardBuilder.construct( new LevelerCardBuilder.LevelAbility(1, 3, abilities1, 2, 3), new LevelerCardBuilder.LevelAbility(4, -1, abilities2, 4, 4) - ); + )); + setMaxLevelCounters(4); } public KnightOfCliffhaven(final KnightOfCliffhaven card) { diff --git a/Mage.Sets/src/mage/sets/riseoftheeldrazi/LighthouseChronologist.java b/Mage.Sets/src/mage/sets/riseoftheeldrazi/LighthouseChronologist.java index e9b3f9688c6..0deac7c55b3 100644 --- a/Mage.Sets/src/mage/sets/riseoftheeldrazi/LighthouseChronologist.java +++ b/Mage.Sets/src/mage/sets/riseoftheeldrazi/LighthouseChronologist.java @@ -75,10 +75,11 @@ public class LighthouseChronologist extends LevelerCard { Abilities abilities2 = new AbilitiesImpl<>(); abilities2.add(new LighthouseChronologistAbility()); - LevelerCardBuilder.construct(this, + this.addAbilities(LevelerCardBuilder.construct( new LevelerCardBuilder.LevelAbility(4, 6, abilities1, 2, 4), new LevelerCardBuilder.LevelAbility(7, -1, abilities2, 3, 5) - ); + )); + setMaxLevelCounters(7); } public LighthouseChronologist (final LighthouseChronologist card) { diff --git a/Mage.Sets/src/mage/sets/riseoftheeldrazi/LordOfShatterskullPass.java b/Mage.Sets/src/mage/sets/riseoftheeldrazi/LordOfShatterskullPass.java index 95dd1b6ec65..982e7bb67a3 100644 --- a/Mage.Sets/src/mage/sets/riseoftheeldrazi/LordOfShatterskullPass.java +++ b/Mage.Sets/src/mage/sets/riseoftheeldrazi/LordOfShatterskullPass.java @@ -42,6 +42,7 @@ import mage.abilities.effects.OneShotEffect; import mage.abilities.keyword.LevelUpAbility; import mage.abilities.keyword.LevelerCardBuilder; import mage.cards.CardImpl; +import mage.cards.LevelerCard; import mage.filter.common.FilterCreaturePermanent; import mage.filter.predicate.permanent.ControllerIdPredicate; import mage.game.Game; @@ -51,7 +52,7 @@ import mage.game.permanent.Permanent; * * @author North */ -public class LordOfShatterskullPass extends CardImpl { +public class LordOfShatterskullPass extends LevelerCard { public LordOfShatterskullPass(UUID ownerId) { super(ownerId, 156, "Lord of Shatterskull Pass", Rarity.RARE, new CardType[]{CardType.CREATURE}, "{3}{R}"); @@ -74,9 +75,10 @@ public class LordOfShatterskullPass extends CardImpl { Abilities abilities2 = new AbilitiesImpl(); abilities2.add(new AttacksTriggeredAbility(new LordOfShatterskullPassEffect(), false)); - LevelerCardBuilder.construct(this, + this.addAbilities(LevelerCardBuilder.construct( new LevelerCardBuilder.LevelAbility(1, 5, abilities1, 6, 6), - new LevelerCardBuilder.LevelAbility(6, -1, abilities2, 6, 6)); + new LevelerCardBuilder.LevelAbility(6, -1, abilities2, 6, 6))); + setMaxLevelCounters(6); } public LordOfShatterskullPass(final LordOfShatterskullPass card) { diff --git a/Mage.Sets/src/mage/sets/riseoftheeldrazi/NirkanaCutthroat.java b/Mage.Sets/src/mage/sets/riseoftheeldrazi/NirkanaCutthroat.java index ed9227aab82..9dd8c7308af 100644 --- a/Mage.Sets/src/mage/sets/riseoftheeldrazi/NirkanaCutthroat.java +++ b/Mage.Sets/src/mage/sets/riseoftheeldrazi/NirkanaCutthroat.java @@ -66,10 +66,11 @@ public class NirkanaCutthroat extends LevelerCard { abilities2.add(FirstStrikeAbility.getInstance()); abilities2.add(DeathtouchAbility.getInstance()); - LevelerCardBuilder.construct(this, + this.addAbilities(LevelerCardBuilder.construct( new LevelerCardBuilder.LevelAbility(1, 2, abilities1, 4, 3), new LevelerCardBuilder.LevelAbility(3, -1, abilities2, 5, 4) - ); + )); + setMaxLevelCounters(3); } public NirkanaCutthroat (final NirkanaCutthroat card) { diff --git a/Mage.Sets/src/mage/sets/riseoftheeldrazi/NullChampion.java b/Mage.Sets/src/mage/sets/riseoftheeldrazi/NullChampion.java index 94bac57d56b..1ed7b81a3ba 100644 --- a/Mage.Sets/src/mage/sets/riseoftheeldrazi/NullChampion.java +++ b/Mage.Sets/src/mage/sets/riseoftheeldrazi/NullChampion.java @@ -65,10 +65,11 @@ public class NullChampion extends LevelerCard { Abilities abilities2 = new AbilitiesImpl(); abilities2.add(new SimpleActivatedAbility(Zone.BATTLEFIELD, new RegenerateSourceEffect(), new ManaCostsImpl("{B}"))); - LevelerCardBuilder.construct(this, + this.addAbilities(LevelerCardBuilder.construct( new LevelerCardBuilder.LevelAbility(1, 3, abilities1, 4, 2), new LevelerCardBuilder.LevelAbility(4, -1, abilities2, 7, 3) - ); + )); + setMaxLevelCounters(4); } public NullChampion (final NullChampion card) { diff --git a/Mage.Sets/src/mage/sets/riseoftheeldrazi/SkywatcherAdept.java b/Mage.Sets/src/mage/sets/riseoftheeldrazi/SkywatcherAdept.java index 87a777a2d11..26b7cdbb066 100644 --- a/Mage.Sets/src/mage/sets/riseoftheeldrazi/SkywatcherAdept.java +++ b/Mage.Sets/src/mage/sets/riseoftheeldrazi/SkywatcherAdept.java @@ -65,10 +65,11 @@ public class SkywatcherAdept extends LevelerCard { Abilities abilities2 = new AbilitiesImpl(); abilities2.add(FlyingAbility.getInstance()); - LevelerCardBuilder.construct(this, + this.addAbilities(LevelerCardBuilder.construct( new LevelerCardBuilder.LevelAbility(1, 2, abilities1, 2, 2), new LevelerCardBuilder.LevelAbility(3, -1, abilities2, 4, 2) - ); + )); + setMaxLevelCounters(3); } public SkywatcherAdept(final SkywatcherAdept card) { diff --git a/Mage.Sets/src/mage/sets/riseoftheeldrazi/StudentOfWarfare.java b/Mage.Sets/src/mage/sets/riseoftheeldrazi/StudentOfWarfare.java index 3c26482792b..252126bbce9 100644 --- a/Mage.Sets/src/mage/sets/riseoftheeldrazi/StudentOfWarfare.java +++ b/Mage.Sets/src/mage/sets/riseoftheeldrazi/StudentOfWarfare.java @@ -65,10 +65,11 @@ public class StudentOfWarfare extends LevelerCard { Abilities abilities2 = new AbilitiesImpl(); abilities2.add(DoubleStrikeAbility.getInstance()); - LevelerCardBuilder.construct(this, + this.addAbilities(LevelerCardBuilder.construct( new LevelerCardBuilder.LevelAbility(2, 6, abilities1, 3, 3), new LevelerCardBuilder.LevelAbility(7, -1, abilities2, 4, 4) - ); + )); + setMaxLevelCounters(7); } public StudentOfWarfare (final StudentOfWarfare card) { diff --git a/Mage.Sets/src/mage/sets/riseoftheeldrazi/TranscendentMaster.java b/Mage.Sets/src/mage/sets/riseoftheeldrazi/TranscendentMaster.java index 7b2f5d0a9a9..0fa33fe5292 100644 --- a/Mage.Sets/src/mage/sets/riseoftheeldrazi/TranscendentMaster.java +++ b/Mage.Sets/src/mage/sets/riseoftheeldrazi/TranscendentMaster.java @@ -70,10 +70,11 @@ public class TranscendentMaster extends LevelerCard { LifelinkAbility.getInstance(), IndestructibleAbility.getInstance()); - LevelerCardBuilder.construct(this, + this.addAbilities(LevelerCardBuilder.construct( new LevelerCardBuilder.LevelAbility(6, 11, abilities1, 6, 6), new LevelerCardBuilder.LevelAbility(12, -1, abilities2, 9, 9) - ); + )); + setMaxLevelCounters(12); } public TranscendentMaster(final TranscendentMaster card) { diff --git a/Mage.Sets/src/mage/sets/riseoftheeldrazi/ZulaportEnforcer.java b/Mage.Sets/src/mage/sets/riseoftheeldrazi/ZulaportEnforcer.java index 2849c56709c..effbf4a5858 100644 --- a/Mage.Sets/src/mage/sets/riseoftheeldrazi/ZulaportEnforcer.java +++ b/Mage.Sets/src/mage/sets/riseoftheeldrazi/ZulaportEnforcer.java @@ -70,10 +70,11 @@ public class ZulaportEnforcer extends LevelerCard { Abilities levelAbilities = new AbilitiesImpl(); levelAbilities.add(ZulaportEnforcerAbility.getInstance()); - LevelerCardBuilder.construct(this, + this.addAbilities(LevelerCardBuilder.construct( new LevelerCardBuilder.LevelAbility(1, 2, new AbilitiesImpl(), 3, 3), new LevelerCardBuilder.LevelAbility(3, -1, levelAbilities, 5, 5) - ); + )); + setMaxLevelCounters(3); } public ZulaportEnforcer(final ZulaportEnforcer card) { diff --git a/Mage.Sets/src/mage/sets/shardsofalara/SedrisTheTraitorKing.java b/Mage.Sets/src/mage/sets/shardsofalara/SedrisTheTraitorKing.java index 786c46e3780..314389d2499 100644 --- a/Mage.Sets/src/mage/sets/shardsofalara/SedrisTheTraitorKing.java +++ b/Mage.Sets/src/mage/sets/shardsofalara/SedrisTheTraitorKing.java @@ -93,7 +93,7 @@ class SedrisTheTraitorKingEffect extends ContinuousEffectImpl { UnearthAbility ability = new UnearthAbility(new ManaCostsImpl("{2}{B}")); ability.setSourceId(cardId); ability.setControllerId(card.getOwnerId()); - game.getState().addOtherAbility(cardId, ability); + game.getState().addOtherAbility(card, ability); } } return true; diff --git a/Mage.Sets/src/mage/sets/tenth/CrucibleOfWorlds.java b/Mage.Sets/src/mage/sets/tenth/CrucibleOfWorlds.java index bc9d1215638..f3e393789b2 100644 --- a/Mage.Sets/src/mage/sets/tenth/CrucibleOfWorlds.java +++ b/Mage.Sets/src/mage/sets/tenth/CrucibleOfWorlds.java @@ -94,7 +94,7 @@ class CrucibleOfWorldsEffect extends ContinuousEffectImpl { PlayLandFromGraveyardAbility ability = new PlayLandFromGraveyardAbility(card.getName()); ability.setSourceId(cardId); ability.setControllerId(card.getOwnerId()); - game.getState().addOtherAbility(cardId, ability); + game.getState().addOtherAbility(card, ability); } } return true; diff --git a/Mage.Sets/src/mage/sets/theros/WhipOfErebos.java b/Mage.Sets/src/mage/sets/theros/WhipOfErebos.java index ed35f987963..97b1633f236 100644 --- a/Mage.Sets/src/mage/sets/theros/WhipOfErebos.java +++ b/Mage.Sets/src/mage/sets/theros/WhipOfErebos.java @@ -116,7 +116,6 @@ class WhipOfErebosEffect extends OneShotEffect { Card card = game.getCard(this.getTargetPointer().getFirst(game, source)); Player controller = game.getPlayer(source.getControllerId()); if (controller != null && card != null) { - card.addAbility(HasteAbility.getInstance()); if (controller.putOntoBattlefieldWithInfo(card, game, Zone.GRAVEYARD, source.getSourceId())) { // gains haste ContinuousEffect effect = new GainAbilityTargetEffect(HasteAbility.getInstance(), Duration.Custom); diff --git a/Mage.Sets/src/mage/sets/timespiral/DralnuLichLord.java b/Mage.Sets/src/mage/sets/timespiral/DralnuLichLord.java index 1feb56de880..089ec737548 100644 --- a/Mage.Sets/src/mage/sets/timespiral/DralnuLichLord.java +++ b/Mage.Sets/src/mage/sets/timespiral/DralnuLichLord.java @@ -162,7 +162,7 @@ class DralnuLichLordFlashbackEffect extends ContinuousEffectImpl { } ability.setSourceId(card.getId()); ability.setControllerId(card.getOwnerId()); - game.getState().addOtherAbility(card.getId(), ability); + game.getState().addOtherAbility(card, ability); return true; } return false; diff --git a/Mage.Sets/src/mage/sets/timespiral/TeferiMageOfZhalfir.java b/Mage.Sets/src/mage/sets/timespiral/TeferiMageOfZhalfir.java index e512296b4a0..e545ced4af1 100644 --- a/Mage.Sets/src/mage/sets/timespiral/TeferiMageOfZhalfir.java +++ b/Mage.Sets/src/mage/sets/timespiral/TeferiMageOfZhalfir.java @@ -110,20 +110,20 @@ class TeferiMageOfZhalfirAddFlashEffect extends ContinuousEffectImpl { for (UUID cardId: controller.getGraveyard()) { Card card = game.getCard(cardId); if (card.getCardType().contains(CardType.CREATURE)) { - game.getState().addOtherAbility(cardId, FlashAbility.getInstance()); + game.getState().addOtherAbility(card, FlashAbility.getInstance()); } } // on Hand for (UUID cardId: controller.getHand()) { Card card = game.getCard(cardId); if (card.getCardType().contains(CardType.CREATURE)) { - game.getState().addOtherAbility(cardId, FlashAbility.getInstance()); + game.getState().addOtherAbility(card, FlashAbility.getInstance()); } } // in Exile for (Card card: game.getState().getExile().getAllCards(game)) { if (card.getOwnerId().equals(controller.getId()) && card.getCardType().contains(CardType.CREATURE)) { - game.getState().addOtherAbility(card.getId(), FlashAbility.getInstance()); + game.getState().addOtherAbility(card, FlashAbility.getInstance()); } } // in Library seems not relevant yet diff --git a/Mage.Sets/src/mage/sets/visions/Necromancy.java b/Mage.Sets/src/mage/sets/visions/Necromancy.java index 336801771ae..35c1c5ffc0d 100644 --- a/Mage.Sets/src/mage/sets/visions/Necromancy.java +++ b/Mage.Sets/src/mage/sets/visions/Necromancy.java @@ -80,7 +80,8 @@ public class Necromancy extends CardImpl { // You may cast Necromancy as though it had flash. If you cast it any time a sorcery couldn't have been cast, the controller of the permanent it becomes sacrifices it at the beginning of the next cleanup step. this.addAbility(new SimpleStaticAbility(Zone.ALL, new CastSourceAsThoughItHadFlashEffect(this, Duration.EndOfGame, true))); - + this.addAbility(new CastAtInstantTimeTriggeredAbility()); + // When Necromancy enters the battlefield, if it's on the battlefield, it becomes an Aura with "enchant creature put onto the battlefield with Necromancy." // Put target creature card from a graveyard onto the battlefield under your control and attach Necromancy to it. // When Necromancy leaves the battlefield, that creature's controller sacrifices it. @@ -111,9 +112,6 @@ class CastSourceAsThoughItHadFlashEffect extends AsThoughEffectImpl { public CastSourceAsThoughItHadFlashEffect(Card card, Duration duration, boolean sacrificeIfCastAsInstant) { super(AsThoughEffectType.CAST_AS_INSTANT, duration, Outcome.Benefit); this.sacrificeIfCastAsInstant = sacrificeIfCastAsInstant; - if (sacrificeIfCastAsInstant) { - card.addAbility(new CastAtInstantTimeTriggeredAbility()); - } staticText = "You may cast {this} as though it had flash"; } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/ChampionTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/ChampionTest.java new file mode 100644 index 00000000000..8f1f28707cd --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/ChampionTest.java @@ -0,0 +1,103 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ + +package org.mage.test.cards.abilities.keywords; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * + * @author BetaSteward + */ +public class ChampionTest extends CardTestPlayerBase { + + /** + * 702.71. Champion + * 702.71a Champion represents two triggered abilities. “Champion an [object]” means “When this + * permanent enters the battlefield, sacrifice it unless you exile another [object] you control” and + * “When this permanent leaves the battlefield, return the exiled card to the battlefield under its + * owner’s control.” + * + * 702.71b The two abilities represented by champion are linked. See rule 607, “Linked Abilities.” + * + * 702.71c A permanent is “championed” by another permanent if the latter exiles the former as the + * direct result of a champion ability. + * + */ + + /** + * Lightning Crafter + * Creature — Goblin Shaman 3/3, 3R (4) + * Champion a Goblin or Shaman (When this enters the battlefield, sacrifice + * it unless you exile another Goblin or Shaman you control. When this + * leaves the battlefield, that card returns to the battlefield.) + * {T}: Lightning Crafter deals 3 damage to target creature or player. + * + */ + + @Test + public void testChampionCreature() { + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 4); + addCard(Zone.BATTLEFIELD, playerA, "Goblin Roughrider"); + addCard(Zone.HAND, playerA, "Lightning Crafter"); + + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Crafter"); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertPermanentCount(playerA, "Lightning Crafter", 1); + assertExileCount("Goblin Roughrider", 1); + + } + + @Test + public void testExiledCreatureReturns() { + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 4); + addCard(Zone.BATTLEFIELD, playerA, "Goblin Roughrider"); + addCard(Zone.HAND, playerA, "Lightning Crafter"); + + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Crafter"); + activateAbility(3, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: {source} deals 3 damage to target creature or player.", "Lightning Crafter"); + + setStopAt(3, PhaseStep.BEGIN_COMBAT); + execute(); + + assertPermanentCount(playerA, "Lightning Crafter", 0); + assertPermanentCount(playerA, "Goblin Roughrider", 1); + assertExileCount("Goblin Roughrider", 0); + assertGraveyardCount(playerA, "Lightning Crafter", 1); + + } + +} 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 new file mode 100644 index 00000000000..eda72663473 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/ConspireTest.java @@ -0,0 +1,110 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ + +package org.mage.test.cards.abilities.keywords; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * + * @author BetaSteward + */ +public class ConspireTest extends CardTestPlayerBase { + + /** + * 702.77. Conspire + * 702.77a Conspire is a keyword that represents two abilities. The first is a static ability that functions + * while the spell with conspire is on the stack. The second is a triggered ability that functions + * while the spell with conspire is on the stack. “Conspire” means “As an additional cost to cast + * this spell, you may tap two untapped creatures you control that each share a color with it” and + * “When you cast this spell, if its conspire cost was paid, copy it. If the spell has any targets, you + * may choose new targets for the copy.” Paying a spell’s conspire cost follows the rules for + * paying additional costs in rules 601.2b and 601.2e–g. + * + * 702.77b If a spell has multiple instances of conspire, each is paid separately and triggers based on + * its own payment, not any other instance of conspire + * + */ + + /** + * Burn Trail + * Sorcery, 3R (4) + * Burn Trail deals 3 damage to target creature or player. + * + * Conspire (As you cast this spell, you may tap two untapped creatures you + * control that share a color with it. When you do, copy it and you may + * choose a new target for the copy.) + * + */ + + @Test + public void testConspire() { + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 4); + addCard(Zone.BATTLEFIELD, playerA, "Goblin Roughrider"); + addCard(Zone.BATTLEFIELD, playerA, "Raging Goblin"); + addCard(Zone.HAND, playerA, "Burn Trail"); + + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Burn Trail", playerB); + setChoice(playerA, "Yes"); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertLife(playerB, 14); + assertGraveyardCount(playerA, "Burn Trail", 1); + assertTapped("Goblin Roughrider", true); + assertTapped("Raging Goblin", true); + + } + + @Test + public void testConspireNotUsed() { + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 4); + addCard(Zone.BATTLEFIELD, playerA, "Goblin Roughrider"); + addCard(Zone.BATTLEFIELD, playerA, "Raging Goblin"); + addCard(Zone.HAND, playerA, "Burn Trail"); + + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Burn Trail", playerB); + setChoice(playerA, "No"); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertLife(playerB, 17); + assertGraveyardCount(playerA, "Burn Trail", 1); + assertTapped("Goblin Roughrider", false); + assertTapped("Raging Goblin", false); + + } + +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/DashTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/DashTest.java new file mode 100644 index 00000000000..5305854a07a --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/DashTest.java @@ -0,0 +1,99 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ + +package org.mage.test.cards.abilities.keywords; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * + * @author BetaSteward + */ +public class DashTest extends CardTestPlayerBase { + + /** + * 702.108. Dash + * 702.108a Dash represents three abilities: two static abilities that function while the card with dash is + * on the stack, one of which may create a delayed triggered ability, and a static ability that + * functions while the object with dash is on the battlefield. “Dash [cost]” means “You may cast + * this card by paying [cost] rather that its mana cost,” “If this spell’s dash cost was paid, return the + * permanent this spell becomes to its owner’s hand at the beginning of the next end step,” and “As + * long as this permanent’s dash cost was paid, it has haste.” Paying a card’s dash cost follows the + * rules for paying alternative costs in rules 601.2b and 601.2e–g. + * + */ + + /** + * Screamreach Brawler + * Creature — Orc Berserker 2/3, 2R (3) + * Dash {1}{R} (You may cast this spell for its dash cost. If you do, it + * gains haste, and it's returned from the battlefield to its owner's hand + * at the beginning of the next end step.) + * + */ + + @Test + public void testDash() { + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 2); + addCard(Zone.HAND, playerA, "Screamreach Brawler"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Screamreach Brawler"); + setChoice(playerA, "Yes"); + attack(1, playerA, "Screamreach Brawler"); + + setStopAt(2, PhaseStep.UNTAP); + execute(); + + assertLife(playerB, 18); + assertPermanentCount(playerA, "Screamreach Brawler", 0); + assertHandCount(playerA, "Screamreach Brawler", 1); + + } + + @Test + public void testNoDash() { + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3); + addCard(Zone.HAND, playerA, "Screamreach Brawler"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Screamreach Brawler"); + setChoice(playerA, "No"); + attack(1, playerA, "Screamreach Brawler"); + + setStopAt(2, PhaseStep.UNTAP); + execute(); + + assertLife(playerB, 20); + assertPermanentCount(playerA, "Screamreach Brawler", 1); + assertHandCount(playerA, "Screamreach Brawler", 0); + + } + +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/FadingTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/FadingTest.java new file mode 100644 index 00000000000..77cee8a7741 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/FadingTest.java @@ -0,0 +1,105 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ + +package org.mage.test.cards.abilities.keywords; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import mage.counters.CounterType; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * + * @author BetaSteward + */ +public class FadingTest extends CardTestPlayerBase { + + /** + * 702.31. Fading + * 702.31a Fading is a keyword that represents two abilities. “Fading N” means “This permanent + * enters the battlefield with N fade counters on it” and “At the beginning of your upkeep, remove + * a fade counter from this permanent. If you can’t, sacrifice the permanent.” + */ + + /** + * Blastoderm + * Creature — Beast 5/5, 2GG (4) + * Shroud (This creature can't be the target of spells or abilities.) + * Fading 3 (This creature enters the battlefield with three fade counters + * on it. At the beginning of your upkeep, remove a fade counter from it. + * If you can't, sacrifice it.) + * + */ + + @Test + public void testFading() { + addCard(Zone.BATTLEFIELD, playerA, "Forest", 4); + addCard(Zone.HAND, playerA, "Blastoderm"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Blastoderm"); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertPermanentCount(playerA, "Blastoderm", 1); + this.assertCounterCount("Blastoderm", CounterType.FADE, 3); + + } + + @Test + public void testFades() { + addCard(Zone.BATTLEFIELD, playerA, "Forest", 4); + addCard(Zone.HAND, playerA, "Blastoderm"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Blastoderm"); + + setStopAt(5, PhaseStep.BEGIN_COMBAT); + execute(); + + assertPermanentCount(playerA, "Blastoderm", 1); + this.assertCounterCount("Blastoderm", CounterType.FADE, 1); + + } + + @Test + public void testFadesAway() { + addCard(Zone.BATTLEFIELD, playerA, "Forest", 4); + addCard(Zone.HAND, playerA, "Blastoderm"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Blastoderm"); + + setStopAt(9, PhaseStep.BEGIN_COMBAT); + execute(); + + assertPermanentCount(playerA, "Blastoderm", 0); + assertGraveyardCount(playerA, "Blastoderm", 1); + + } + +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/GraftTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/GraftTest.java new file mode 100644 index 00000000000..0ee4f28e521 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/GraftTest.java @@ -0,0 +1,133 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ + +package org.mage.test.cards.abilities.keywords; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import mage.counters.CounterType; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * + * @author BetaSteward + */ +public class GraftTest extends CardTestPlayerBase { + + /** + * 702.57. Graft + * 702.57a Graft represents both a static ability and a triggered ability. “Graft N” means “This + * permanent enters the battlefield with N +1/+1 counters on it” and “Whenever another creature + * enters the battlefield, if this permanent has a +1/+1 counter on it, you may move a +1/+1 + * counter from this permanent onto that creature.” + * 702.57b If a creature has multiple instances of graft, each one works separately. + * + */ + + /** + * Sporeback Troll + * Creature — Troll Mutant 0/0, 3G (4) + * Graft 2 (This creature enters the battlefield with two +1/+1 counters on it. + * Whenever another creature enters the battlefield, you may move a +1/+1 + * counter from this creature onto it.) + * {1}{G}: Regenerate target creature with a +1/+1 counter on it. + * + */ + + /** + * Cytoplast Root-Kin + * Creature — Elemental Mutant 0/0, 2GG (4) + * Graft 4 (This creature enters the battlefield with four +1/+1 counters on + * it. Whenever another creature enters the battlefield, you may move a +1/+1 + * counter from this creature onto it.) + * When Cytoplast Root-Kin enters the battlefield, put a +1/+1 counter on + * each other creature you control with a +1/+1 counter on it. + * {2}: Move a +1/+1 counter from target creature you control onto Cytoplast Root-Kin. + * + */ + + @Test + public void testGraft() { + addCard(Zone.BATTLEFIELD, playerA, "Forest", 4); + addCard(Zone.HAND, playerA, "Sporeback Troll"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Sporeback Troll"); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertPermanentCount(playerA, "Sporeback Troll", 1); + assertPowerToughness(playerA, "Sporeback Troll", 2, 2); + assertCounterCount("Sporeback Troll", CounterType.P1P1, 2); + + } + + @Test + public void testMoveCounters() { + addCard(Zone.BATTLEFIELD, playerA, "Forest", 8); + addCard(Zone.HAND, playerA, "Cytoplast Root-Kin"); + addCard(Zone.HAND, playerA, "Sporeback Troll"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cytoplast Root-Kin"); + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Sporeback Troll"); + setChoice(playerA, "Yes"); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertPermanentCount(playerA, "Sporeback Troll", 1); + assertPermanentCount(playerA, "Cytoplast Root-Kin", 1); + assertPowerToughness(playerA, "Sporeback Troll", 3, 3); + assertPowerToughness(playerA, "Cytoplast Root-Kin", 3, 3); + assertCounterCount("Sporeback Troll", CounterType.P1P1, 3); + assertCounterCount("Cytoplast Root-Kin", CounterType.P1P1, 3); + } + + @Test + public void testDontMoveCounters() { + addCard(Zone.BATTLEFIELD, playerA, "Forest", 8); + addCard(Zone.HAND, playerA, "Cytoplast Root-Kin"); + addCard(Zone.HAND, playerA, "Sporeback Troll"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cytoplast Root-Kin"); + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Sporeback Troll"); + setChoice(playerA, "No"); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertPermanentCount(playerA, "Sporeback Troll", 1); + assertPermanentCount(playerA, "Cytoplast Root-Kin", 1); + assertPowerToughness(playerA, "Sporeback Troll", 2, 2); + assertPowerToughness(playerA, "Cytoplast Root-Kin", 4, 4); + assertCounterCount("Sporeback Troll", CounterType.P1P1, 2); + assertCounterCount("Cytoplast Root-Kin", CounterType.P1P1, 4); + } + +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/HideawayTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/HideawayTest.java new file mode 100644 index 00000000000..3bf79b639d2 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/HideawayTest.java @@ -0,0 +1,84 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ + +package org.mage.test.cards.abilities.keywords; + +import mage.cards.Card; +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Assert; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * + * @author BetaSteward + */ +public class HideawayTest extends CardTestPlayerBase { + + /** + * 702.74. Hideaway + * 702.74a Hideaway represents a static ability and a triggered ability. “Hideaway” means “This + * permanent enters the battlefield tapped” and “When this permanent enters the battlefield, look at + * the top four cards of your library. Exile one of them face down and put the rest on the bottom of + * your library in any order. The exiled card gains ‘Any player who has controlled the permanent + * that exiled this card may look at this card in the exile zone.’” + * + */ + + /** + * Shelldock Isle + * Land + * Hideaway (This land enters the battlefield tapped. When it does, look at + * the top four cards of your library, exile one face down, then put the + * rest on the bottom of your library.) + * {T}: Add {U} to your mana pool. + * {U}, {T}: You may play the exiled card without paying its mana cost if a + * library has twenty or fewer cards in it. + * + */ + + @Test + public void testHideaway() { + addCard(Zone.HAND, playerA, "Shelldock Isle"); + + this.playLand(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Shelldock Isle"); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertPermanentCount(playerA, "Shelldock Isle", 1); + assertExileCount(playerA, 1); + for (Card card :currentGame.getExile().getAllCards(currentGame)){ + Assert.assertTrue("Exiled card is not face down", card.isFaceDown(currentGame)); + } + + + } + +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/MadnessTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/MadnessTest.java new file mode 100644 index 00000000000..55b465490ea --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/MadnessTest.java @@ -0,0 +1,115 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ + +package org.mage.test.cards.abilities.keywords; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * + * @author BetaSteward + */ +public class MadnessTest extends CardTestPlayerBase { + + /** + * 702.34. Madness + * 702.34a Madness is a keyword that represents two abilities. The first is a static ability that functions + * while the card with madness is in a player’s hand. The second is a triggered ability that + * functions when the first ability is applied. “Madness [cost]” means “If a player would discard + * this card, that player discards it, but may exile it instead of putting it into his or her graveyard” + * and “When this card is exiled this way, its owner may cast it by paying [cost] rather than paying + * its mana cost. If that player doesn’t, he or she puts this card into his or her graveyard.” + * 702.34b Casting a spell using its madness ability follows the rules for paying alternative costs in + * rules 601.2b and 601.2e–g. + * + */ + + /** + * Arrogant Wurm + * 3GG + * Creature -- Wurm + * 4/4 + * Trample + * Madness {2}{G} (If you discard this card, you may cast it for its + * madness cost instead of putting it into your graveyard.) + * + */ + + /** + * Raven's Crime + * B + * Sorcery + * Target player discards a card. + * Retrace (You may cast this card from your graveyard by discarding a land + * card in addition to paying its other costs.) + * + */ + + @Test + public void testMadness() { + addCard(Zone.BATTLEFIELD, playerA, "Forest", 3); + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 1); + addCard(Zone.HAND, playerA, "Arrogant Wurm"); + addCard(Zone.HAND, playerA, "Raven's Crime"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Raven's Crime", playerA); + setChoice(playerA, "Yes"); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertPermanentCount(playerA, "Arrogant Wurm", 1); + assertGraveyardCount(playerA, "Raven's Crime", 1); + assertHandCount(playerA, 0); + + } + + @Test + public void testNoMadness() { + addCard(Zone.BATTLEFIELD, playerA, "Forest", 3); + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 1); + addCard(Zone.HAND, playerA, "Arrogant Wurm"); + addCard(Zone.HAND, playerA, "Raven's Crime"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Raven's Crime", playerA); + setChoice(playerA, "No"); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertPermanentCount(playerA, "Arrogant Wurm", 0); + assertGraveyardCount(playerA, "Raven's Crime", 1); + assertGraveyardCount(playerA, "Arrogant Wurm", 1); + assertHandCount(playerA, 0); + + } + +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/ModularTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/ModularTest.java new file mode 100644 index 00000000000..61609e10486 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/ModularTest.java @@ -0,0 +1,111 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ + +package org.mage.test.cards.abilities.keywords; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * + * @author BetaSteward + */ +public class ModularTest extends CardTestPlayerBase { + + /** + * 702.42. Modular + * 702.42a Modular represents both a static ability and a triggered ability. “Modular N” means “This + * permanent enters the battlefield with N +1/+1 counters on it” and “When this permanent is put + * into a graveyard from the battlefield, you may put a +1/+1 counter on target artifact creature for + * each +1/+1 counter on this permanent.” + * 702.42b If a creature has multiple instances of modular, each one works separately. + * + */ + + /** + * Arcbound Bruiser + * Artifact Creature — Golem 0/0, 5 (5) + * Modular 3 (This enters the battlefield with three +1/+1 counters on it. + * When it dies, you may put its +1/+1 counters on target artifact creature.) + * + */ + + /** + * Arcbound Hybrid + * Artifact Creature — Beast 0/0, 4 (4) + * Haste + * Modular 2 (This enters the battlefield with two +1/+1 counters on it. + * When it dies, you may put its +1/+1 counters on target artifact creature.) + * + */ + + @Test + public void testModularEnters() { + addCard(Zone.BATTLEFIELD, playerA, "Forest", 5); + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 4); + addCard(Zone.HAND, playerA, "Arcbound Bruiser"); + addCard(Zone.HAND, playerA, "Arcbound Hybrid"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Arcbound Bruiser"); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Arcbound Hybrid"); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertPermanentCount(playerA, "Arcbound Bruiser", 1); + assertPermanentCount(playerA, "Arcbound Hybrid", 1); + assertPowerToughness(playerA, "Arcbound Bruiser", 3, 3); + assertPowerToughness(playerA, "Arcbound Hybrid", 2, 2); + + } + + @Test + public void testModularLeaves() { + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 4); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 5); + addCard(Zone.HAND, playerA, "Arcbound Bruiser"); + addCard(Zone.HAND, playerA, "Arcbound Hybrid"); + addCard(Zone.HAND, playerA, "Lightning Bolt"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Arcbound Bruiser"); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Arcbound Hybrid"); + castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", "Arcbound Bruiser"); + setStopAt(3, PhaseStep.BEGIN_COMBAT); + setChoice(playerA, "Yes"); + execute(); + + assertPermanentCount(playerA, "Arcbound Bruiser", 0); + assertPermanentCount(playerA, "Arcbound Hybrid", 1); + assertGraveyardCount(playerA, "Arcbound Bruiser", 1); + assertPowerToughness(playerA, "Arcbound Hybrid", 5, 5); + + } + +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/ReplicateTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/ReplicateTest.java new file mode 100644 index 00000000000..e7c180a528b --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/ReplicateTest.java @@ -0,0 +1,115 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ + +package org.mage.test.cards.abilities.keywords; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * + * @author BetaSteward + */ +public class ReplicateTest extends CardTestPlayerBase { + + /** + * 702.55. Replicate + * 702.55a Replicate is a keyword that represents two abilities. The first is a static ability that + * functions while the spell with replicate is on the stack. The second is a triggered ability that + * functions while the spell with replicate is on the stack. “Replicate [cost]” means “As an + * additional cost to cast this spell, you may pay [cost] any number of times” and “When you cast + * this spell, if a replicate cost was paid for it, copy it for each time its replicate cost was paid. If + * the spell has any targets, you may choose new targets for any of the copies.” Paying a spell’s + * replicate cost follows the rules for paying additional costs in rules 601.2b and 601.2e–g. + * 702.55b If a spell has multiple instances of replicate, each is paid separately and triggers based on + * the payments made for it, not any other instance of replicate. + * + */ + + /** + * Train of Thought + * Sorcery, 1U (2) + * Replicate {1}{U} (When you cast this spell, copy it for each time you paid its replicate cost.) + * Draw a card. + * + */ + + @Test + public void testReplicate1Time() { + addCard(Zone.BATTLEFIELD, playerA, "Island", 6); + addCard(Zone.HAND, playerA, "Train of Thought"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Train of Thought"); + setChoice(playerA, "Yes"); + setChoice(playerA, "No"); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertGraveyardCount(playerA, "Train of Thought", 1); + assertHandCount(playerA, 2); + + } + + @Test + public void testReplicate2Times() { + addCard(Zone.BATTLEFIELD, playerA, "Island", 6); + addCard(Zone.HAND, playerA, "Train of Thought"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Train of Thought"); + setChoice(playerA, "Yes"); + setChoice(playerA, "Yes"); + setChoice(playerA, "No"); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertGraveyardCount(playerA, "Train of Thought", 1); + assertHandCount(playerA, 3); + + } + + @Test + public void testNotReplicate() { + addCard(Zone.BATTLEFIELD, playerA, "Island", 6); + addCard(Zone.HAND, playerA, "Train of Thought"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Train of Thought"); + setChoice(playerA, "No"); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertGraveyardCount(playerA, "Train of Thought", 1); + assertHandCount(playerA, 1); + + } + +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/other/ChiefEngineerTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/other/ChiefEngineerTest.java new file mode 100644 index 00000000000..a1ca7b9b613 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/other/ChiefEngineerTest.java @@ -0,0 +1,70 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ + +package org.mage.test.cards.abilities.other; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Ignore; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * + * @author BetaSteward + */ +public class ChiefEngineerTest extends CardTestPlayerBase { + + /** + * Chief Engineer + * Creature — Vedalken Artificer 1/3, 1U (2) + * Artifact spells you cast have convoke. (Your creatures can help cast + * those spells. Each creature you tap while casting an artifact spell pays + * for {1} or one mana of that creature's color.) + * + */ + + @Ignore // at this time player.getPlayable() does not take into account convoke payments + @Test + public void testGainsConvoke() { + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 2); + addCard(Zone.BATTLEFIELD, playerA, "Chief Engineer"); + addCard(Zone.BATTLEFIELD, playerA, "Alpha Myr"); + addCard(Zone.HAND, playerA, "Goblin Roughrider"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Goblin Roughrider"); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertPermanentCount(playerA, "Goblin Roughrider", 1); + assertTapped("Alpha Myr", true); + + } + +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/other/ChorusOfTheConclaveTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/other/ChorusOfTheConclaveTest.java new file mode 100644 index 00000000000..595f65bc48d --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/other/ChorusOfTheConclaveTest.java @@ -0,0 +1,73 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ + +package org.mage.test.cards.abilities.other; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import mage.counters.CounterType; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * + * @author BetaSteward + */ +public class ChorusOfTheConclaveTest extends CardTestPlayerBase { + + /** + * Chorus of the Conclave + * 4GGWW + * Legendary Creature -- Dryad + * 3/8 + * Forestwalk + * As an additional cost to cast creature spells, you may pay any amount of + * mana. If you do, that creature enters the battlefield with that many + * additional +1/+1 counters on it. + * + */ + + @Test + public void testPlayCreature() { + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 4); + addCard(Zone.BATTLEFIELD, playerA, "Chorus of the Conclave"); + addCard(Zone.HAND, playerA, "Goblin Roughrider"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Goblin Roughrider"); + setChoice(playerA, "Yes"); + setChoice(playerA, "X=1"); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertPermanentCount(playerA, "Goblin Roughrider", 1); + assertCounterCount("Goblin Roughrider", CounterType.P1P1, 1); + + } + +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/other/CrucibleOfWorldsTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/other/CrucibleOfWorldsTest.java new file mode 100644 index 00000000000..4a556a8d608 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/other/CrucibleOfWorldsTest.java @@ -0,0 +1,83 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ + +package org.mage.test.cards.abilities.other; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * + * @author BetaSteward + */ +public class CrucibleOfWorldsTest extends CardTestPlayerBase { + + /** + * Crucible of Worlds + * Artifact, 3 (3) + * You may play land cards from your graveyard. + * + */ + + @Test + public void testPlayLand() { + addCard(Zone.BATTLEFIELD, playerA, "Crucible of Worlds"); + addCard(Zone.GRAVEYARD, playerA, "Swamp"); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Play Swamp"); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertPermanentCount(playerA, "Swamp", 1); + assertGraveyardCount(playerA, "Swamp", 0); + + } + + @Test + public void testCantPlayMoreThanOneLandPerTurn() { + addCard(Zone.BATTLEFIELD, playerA, "Crucible of Worlds"); + addCard(Zone.GRAVEYARD, playerA, "Swamp"); + addCard(Zone.GRAVEYARD, playerA, "Plains"); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Play Swamp"); + activateAbility(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Play Plains"); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertPermanentCount(playerA, "Swamp", 1); + assertGraveyardCount(playerA, "Swamp", 0); + assertPermanentCount(playerA, "Plains", 0); + assertGraveyardCount(playerA, "Plains", 1); + + } + +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/other/MycosynthGolemTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/other/MycosynthGolemTest.java new file mode 100644 index 00000000000..69cefe72618 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/other/MycosynthGolemTest.java @@ -0,0 +1,70 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ + +package org.mage.test.cards.abilities.other; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Ignore; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * + * @author BetaSteward + */ +public class MycosynthGolemTest extends CardTestPlayerBase { + + /** + * Mycosynth Golem + * Artifact Creature — Golem 4/5, 11 (11) + * Affinity for artifacts (This spell costs {1} less to cast for each + * artifact you control.) + * Artifact creature spells you cast have affinity for artifacts. (They cost + * {1} less to cast for each artifact you control.) + * + */ + + @Ignore // at this time player.getPlayable() does not account for spells that gain abilities + @Test + public void testSpellsAffinity() { + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 1); + addCard(Zone.BATTLEFIELD, playerA, "Mycosynth Golem"); + addCard(Zone.HAND, playerA, "Alpha Myr"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Alpha Myr"); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertPermanentCount(playerA, "Alpha Myr", 1); + assertHandCount(playerA, "Alpha Myr", 0); + + } + +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/other/NecromancyTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/other/NecromancyTest.java new file mode 100644 index 00000000000..22dcb3d668c --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/other/NecromancyTest.java @@ -0,0 +1,127 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ + +package org.mage.test.cards.abilities.other; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * + * @author BetaSteward + */ +public class NecromancyTest extends CardTestPlayerBase { + + /** + * Necromancy + * Enchantment, 2B (3) + * You may cast Necromancy as though it had flash. If you cast it any time a + * sorcery couldn't have been cast, the controller of the permanent it + * becomes sacrifices it at the beginning of the next cleanup step. + * When Necromancy enters the battlefield, if it's on the battlefield, it + * becomes an Aura with "enchant creature put onto the battlefield with + * Necromancy." Put target creature card from a graveyard onto the + * battlefield under your control and attach Necromancy to it. When + * Necromancy leaves the battlefield, that creature's controller sacrifices it. + * + */ + + @Test + public void testNecromancy() { + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 3); + addCard(Zone.HAND, playerA, "Necromancy"); + addCard(Zone.GRAVEYARD, playerA, "Craw Wurm"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Necromancy"); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertPermanentCount(playerA, "Craw Wurm", 1); + assertPermanentCount(playerA, "Necromancy", 1); + assertGraveyardCount(playerA, "Craw Wurm", 0); + + } + + @Test + public void testNecromancyFlash() { + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 3); + addCard(Zone.HAND, playerA, "Necromancy"); + addCard(Zone.GRAVEYARD, playerA, "Craw Wurm"); + + castSpell(1, PhaseStep.UPKEEP, playerA, "Necromancy"); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertPermanentCount(playerA, "Craw Wurm", 1); + assertPermanentCount(playerA, "Necromancy", 1); + assertGraveyardCount(playerA, "Craw Wurm", 0); + + } + + @Test + public void testNecromancyFlashSacrifice() { + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 3); + addCard(Zone.HAND, playerA, "Necromancy"); + addCard(Zone.GRAVEYARD, playerA, "Craw Wurm"); + + castSpell(1, PhaseStep.UPKEEP, playerA, "Necromancy"); + + setStopAt(2, PhaseStep.BEGIN_COMBAT); + execute(); + + assertPermanentCount(playerA, "Craw Wurm", 0); + assertPermanentCount(playerA, "Necromancy", 0); + assertGraveyardCount(playerA, "Craw Wurm", 1); + assertGraveyardCount(playerA, "Necromancy", 1); + } + + @Test + public void testNecromancyLeaves() { + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 3); + addCard(Zone.BATTLEFIELD, playerA, "Plains", 2); + addCard(Zone.HAND, playerA, "Necromancy"); + addCard(Zone.HAND, playerA, "Disenchant"); + addCard(Zone.GRAVEYARD, playerA, "Craw Wurm"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Necromancy"); + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Disenchant", "Necromancy"); + + setStopAt(2, PhaseStep.END_TURN); + execute(); + + assertPermanentCount(playerA, "Craw Wurm", 0); + assertPermanentCount(playerA, "Necromancy", 0); + assertGraveyardCount(playerA, "Necromancy", 1); + assertGraveyardCount(playerA, "Craw Wurm", 1); + } + +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/other/PastInFlamesTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/other/PastInFlamesTest.java new file mode 100644 index 00000000000..a0325e26810 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/other/PastInFlamesTest.java @@ -0,0 +1,71 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ + +package org.mage.test.cards.abilities.other; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * + * @author BetaSteward + */ +public class PastInFlamesTest extends CardTestPlayerBase { + + /** + * Past in Flames + * Sorcery, 3R (4) + * Each instant and sorcery card in your graveyard gains flashback until end + * of turn. The flashback cost is equal to its mana cost. + * Flashback {4}{R} (You may cast this card from your graveyard for its + * flashback cost. Then exile it.) + * + */ + + @Test + public void testSpellsGainFlashback() { + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 5); + addCard(Zone.HAND, playerA, "Past in Flames"); + addCard(Zone.GRAVEYARD, playerA, "Lightning Bolt"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Past in Flames"); + activateAbility(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Flashback"); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertExileCount("Lightning Bolt", 1); + assertGraveyardCount(playerA, "Lightning Bolt", 0); + assertLife(playerB, 17); + assertLife(playerA, 20); + + } + +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/other/SoulfireGrandMasterTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/other/SoulfireGrandMasterTest.java new file mode 100644 index 00000000000..656fa20f114 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/other/SoulfireGrandMasterTest.java @@ -0,0 +1,93 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ + +package org.mage.test.cards.abilities.other; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Ignore; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * + * @author BetaSteward + */ +public class SoulfireGrandMasterTest extends CardTestPlayerBase { + + /** + * Soulfire Grand Master + * Creature — Human Monk 2/2, 1W (2) + * Lifelink + * Instant and sorcery spells you control have lifelink. + * {2}{U/R}{U/R}: The next time you cast an instant or sorcery spell from + * your hand this turn, put that card into your hand instead of into your + * graveyard as it resolves. + * + */ + + @Ignore // at this time player.getPlayable() does not account for spells that gain abilities + @Test + public void testSpellsGainLifelink() { + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 1); + addCard(Zone.BATTLEFIELD, playerA, "Soulfire Grand Master"); + addCard(Zone.HAND, playerA, "Lightning Bolt"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", playerB); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertGraveyardCount(playerA, "Lightning Bolt", 1); + assertHandCount(playerA, "Lightning Bolt", 0); + assertLife(playerB, 17); + assertLife(playerA, 23); + + } + + @Ignore // at this time player.getPlayable() does not account for spells that gain abilities + @Test + public void testSpellsReturnToHand() { + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 5); + addCard(Zone.BATTLEFIELD, playerA, "Soulfire Grand Master"); + addCard(Zone.HAND, playerA, "Lightning Bolt"); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{2}{U/R}{U/R}:"); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", playerB); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertGraveyardCount(playerA, "Lightning Bolt", 0); + assertHandCount(playerA, "Lightning Bolt", 1); + assertLife(playerA, 23); + assertLife(playerB, 17); + + } + +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/other/VarolzTheScarStripedTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/other/VarolzTheScarStripedTest.java new file mode 100644 index 00000000000..5e1fb62663a --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/other/VarolzTheScarStripedTest.java @@ -0,0 +1,73 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ + +package org.mage.test.cards.abilities.other; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import mage.counters.CounterType; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * + * @author BetaSteward + */ +public class VarolzTheScarStripedTest extends CardTestPlayerBase { + + /** + * Varolz, the Scar-Striped + * Legendary Creature — Troll Warrior 2/2, 1BG (3) + * Each creature card in your graveyard has scavenge. The scavenge cost is + * equal to its mana cost. (Exile a creature card from your graveyard and + * pay its mana cost: Put a number of +1/+1 counters equal to that card's + * power on target creature. Scavenge only as a sorcery.) + * Sacrifice another creature: Regenerate Varolz, the Scar-Striped. + * + */ + + @Test + public void testUseScavenge() { + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3); + addCard(Zone.BATTLEFIELD, playerA, "Varolz, the Scar-Striped"); + addCard(Zone.BATTLEFIELD, playerA, "Goblin Roughrider"); + addCard(Zone.GRAVEYARD, playerA, "Goblin Roughrider"); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Scavenge", "Goblin Roughrider"); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertPermanentCount(playerA, "Goblin Roughrider", 1); + assertGraveyardCount(playerA, "Goblin Roughrider", 0); + assertExileCount("Goblin Roughrider", 1); + assertCounterCount("Goblin Roughrider", CounterType.P1P1, 3); + + } + +} diff --git a/Mage/src/mage/abilities/Ability.java b/Mage/src/mage/abilities/Ability.java index 5d31f50d971..44a9fc1336b 100644 --- a/Mage/src/mage/abilities/Ability.java +++ b/Mage/src/mage/abilities/Ability.java @@ -361,6 +361,19 @@ public interface Ability extends Controllable, Serializable { boolean canChooseTarget(Game game); + /** + * Gets the list of sub-abilities associated with this ability. + * @return + */ + List getSubAbilities(); + + /** + * Adds a sub-ability to this ability. + * + * @param ability The {@link Ability} to add. + */ + void addSubAbility(Ability ability); + List getWatchers(); void addWatcher(Watcher watcher); diff --git a/Mage/src/mage/abilities/AbilityImpl.java b/Mage/src/mage/abilities/AbilityImpl.java index 6f4ac5ffa67..46f3d004394 100644 --- a/Mage/src/mage/abilities/AbilityImpl.java +++ b/Mage/src/mage/abilities/AbilityImpl.java @@ -86,6 +86,7 @@ public abstract class AbilityImpl implements Ability { private static final transient Logger logger = Logger.getLogger(AbilityImpl.class); private static final List emptyWatchers = new ArrayList<>(); + private static final List emptyAbilities = new ArrayList<>(); protected UUID id; protected UUID originalId; @@ -110,6 +111,7 @@ public abstract class AbilityImpl implements Ability { protected boolean worksFaceDown = false; protected MageObject sourceObject; protected List watchers = null; + protected List subAbilities = null; public AbilityImpl(AbilityType abilityType, Zone zone) { this.id = UUID.randomUUID(); @@ -145,6 +147,12 @@ public abstract class AbilityImpl implements Ability { watchers.add(watcher.copy()); } } + if (ability.subAbilities != null) { + this.subAbilities = new ArrayList<>(); + for (Ability subAbility: ability.subAbilities) { + subAbilities.add(subAbility.copy()); + } + } this.modes = ability.modes.copy(); this.ruleAtTheTop = ability.ruleAtTheTop; this.ruleVisible = ability.ruleVisible; @@ -562,6 +570,11 @@ public abstract class AbilityImpl implements Ability { watcher.setControllerId(controllerId); } } + if (subAbilities != null) { + for (Ability subAbility: subAbilities) { + subAbility.setControllerId(controllerId); + } + } } @@ -579,6 +592,11 @@ public abstract class AbilityImpl implements Ability { this.sourceId = sourceId; } } + if (subAbilities != null) { + for (Ability subAbility: subAbilities) { + subAbility.setSourceId(sourceId); + } + } if (watchers != null) { for (Watcher watcher: watchers) { watcher.setSourceId(sourceId); @@ -660,6 +678,23 @@ public abstract class AbilityImpl implements Ability { watchers.add(watcher); } + @Override + public List getSubAbilities() { + if (subAbilities != null) + return subAbilities; + else + return emptyAbilities; + } + + @Override + public void addSubAbility(Ability ability) { + if (subAbilities == null) + subAbilities = new ArrayList<>(); + ability.setSourceId(this.sourceId); + ability.setControllerId(this.controllerId); + subAbilities.add(ability); + } + @Override public boolean isUsesStack() { return usesStack; diff --git a/Mage/src/mage/abilities/effects/common/continuous/GainAbilitySourceEffect.java b/Mage/src/mage/abilities/effects/common/continuous/GainAbilitySourceEffect.java index 5368ff61fe2..cd691be7fe4 100644 --- a/Mage/src/mage/abilities/effects/common/continuous/GainAbilitySourceEffect.java +++ b/Mage/src/mage/abilities/effects/common/continuous/GainAbilitySourceEffect.java @@ -107,8 +107,7 @@ public class GainAbilitySourceEffect extends ContinuousEffectImpl implements Sou } if (card != null) { // add ability to card only once - card.addAbility(ability); - discard(); + game.getState().addOtherAbility(card, ability); return true; } } else { diff --git a/Mage/src/mage/abilities/effects/common/continuous/GainAbilityTargetEffect.java b/Mage/src/mage/abilities/effects/common/continuous/GainAbilityTargetEffect.java index 8d75f739825..d8635ef0f26 100644 --- a/Mage/src/mage/abilities/effects/common/continuous/GainAbilityTargetEffect.java +++ b/Mage/src/mage/abilities/effects/common/continuous/GainAbilityTargetEffect.java @@ -127,7 +127,7 @@ public class GainAbilityTargetEffect extends ContinuousEffectImpl { for (UUID cardId : targetPointer.getTargets(game, source)) { Card card = game.getCard(cardId); if (card != null) { - card.addAbility(ability); + game.getState().addOtherAbility(card, ability); affectedTargets++; } } diff --git a/Mage/src/mage/abilities/keyword/BestowAbility.java b/Mage/src/mage/abilities/keyword/BestowAbility.java index e1efa389490..dd79c1c121f 100644 --- a/Mage/src/mage/abilities/keyword/BestowAbility.java +++ b/Mage/src/mage/abilities/keyword/BestowAbility.java @@ -116,7 +116,7 @@ public class BestowAbility extends SpellAbility { this.addEffect(new AttachEffect(Outcome.BoostCreature)); Ability ability = new SimpleStaticAbility(Zone.BATTLEFIELD, new BestowTypeChangingEffect()); ability.setRuleVisible(false); - card.addAbility(ability); + addSubAbility(ability); } public BestowAbility(final BestowAbility ability) { diff --git a/Mage/src/mage/abilities/keyword/ChampionAbility.java b/Mage/src/mage/abilities/keyword/ChampionAbility.java index 5320f39111f..47853817d5f 100644 --- a/Mage/src/mage/abilities/keyword/ChampionAbility.java +++ b/Mage/src/mage/abilities/keyword/ChampionAbility.java @@ -105,12 +105,12 @@ public class ChampionAbility extends StaticAbility { Ability ability1 = new EntersBattlefieldTriggeredAbility( new SacrificeSourceUnlessPaysEffect(new ChampionExileCost(filter, new StringBuilder(card.getName()).append(" championed permanents").toString())),false); ability1.setRuleVisible(false); - card.addAbility(ability1); + addSubAbility(ability1); // When this permanent leaves the battlefield, return the exiled card to the battlefield under its owner's control. Ability ability2 = new LeavesBattlefieldTriggeredAbility(new ReturnFromExileForSourceEffect(Zone.BATTLEFIELD), false); ability2.setRuleVisible(false); - card.addAbility(ability2); + addSubAbility(ability2); } public ChampionAbility(final ChampionAbility ability) { diff --git a/Mage/src/mage/abilities/keyword/ConspireAbility.java b/Mage/src/mage/abilities/keyword/ConspireAbility.java index 82c3bd9cab9..36f3321616b 100644 --- a/Mage/src/mage/abilities/keyword/ConspireAbility.java +++ b/Mage/src/mage/abilities/keyword/ConspireAbility.java @@ -86,7 +86,7 @@ public class ConspireAbility extends StaticAbility implements OptionalAdditional public ConspireAbility(Card card) { super(Zone.STACK, null); setRuleAtTheTop(false); - card.addAbility(new ConspireTriggeredAbility()); + addSubAbility(new ConspireTriggeredAbility()); } public ConspireAbility(final ConspireAbility ability) { diff --git a/Mage/src/mage/abilities/keyword/DashAbility.java b/Mage/src/mage/abilities/keyword/DashAbility.java index 29ee44af944..4bf36f2e1be 100644 --- a/Mage/src/mage/abilities/keyword/DashAbility.java +++ b/Mage/src/mage/abilities/keyword/DashAbility.java @@ -79,7 +79,7 @@ public class DashAbility extends StaticAbility implements AlternativeSourceCosts Effect effect = new ReturnToHandTargetEffect(); effect.setTargetPointer(new FixedTarget(card.getId())); ability.addEffect(new CreateDelayedTriggeredAbilityEffect(new AtTheBeginOfNextEndStepDelayedTriggeredAbility(effect), false)); - card.addAbility(ability); + addSubAbility(ability); } diff --git a/Mage/src/mage/abilities/keyword/EvokeAbility.java b/Mage/src/mage/abilities/keyword/EvokeAbility.java index d3e07b4e726..c7a2af06c13 100644 --- a/Mage/src/mage/abilities/keyword/EvokeAbility.java +++ b/Mage/src/mage/abilities/keyword/EvokeAbility.java @@ -72,7 +72,7 @@ public class EvokeAbility extends StaticAbility implements AlternativeSourceCost this.addEvokeCost(manaString); Ability ability = new ConditionalTriggeredAbility(new EntersBattlefieldTriggeredAbility(new SacrificeSourceEffect()), EvokedCondition.getInstance(), "Sacrifice {this} when it enters the battlefield and was evoked."); ability.setRuleVisible(false); - card.addAbility(ability); + addSubAbility(ability); } diff --git a/Mage/src/mage/abilities/keyword/FadingAbility.java b/Mage/src/mage/abilities/keyword/FadingAbility.java index fa5276ed96d..4a66d7dcda7 100644 --- a/Mage/src/mage/abilities/keyword/FadingAbility.java +++ b/Mage/src/mage/abilities/keyword/FadingAbility.java @@ -28,7 +28,7 @@ public class FadingAbility extends EntersBattlefieldAbility { super(new AddCountersSourceEffect(CounterType.FADE.createInstance(fadeCounter)), "with"); Ability ability = new BeginningOfUpkeepTriggeredAbility(new FadingEffect(), TargetController.YOU, false); ability.setRuleVisible(false); - card.addAbility(ability); + addSubAbility(ability); StringBuilder sb = new StringBuilder("Fading "); sb.append(fadeCounter); sb.append(" (This permanent enters the battlefield with ") diff --git a/Mage/src/mage/abilities/keyword/GraftAbility.java b/Mage/src/mage/abilities/keyword/GraftAbility.java index 496ed814f32..224eafe4767 100644 --- a/Mage/src/mage/abilities/keyword/GraftAbility.java +++ b/Mage/src/mage/abilities/keyword/GraftAbility.java @@ -73,7 +73,7 @@ public class GraftAbility extends TriggeredAbilityImpl { sb.append(theCardtype.toString().toLowerCase(Locale.ENGLISH)).append(" "); } this.cardtype = sb.toString().trim(); - card.addAbility(new GraftStaticAbility(amount)); + addSubAbility(new GraftStaticAbility(amount)); } public GraftAbility(GraftAbility ability) { diff --git a/Mage/src/mage/abilities/keyword/HauntAbility.java b/Mage/src/mage/abilities/keyword/HauntAbility.java index 694d80af8d3..f6db0f248f4 100644 --- a/Mage/src/mage/abilities/keyword/HauntAbility.java +++ b/Mage/src/mage/abilities/keyword/HauntAbility.java @@ -67,7 +67,7 @@ public class HauntAbility extends TriggeredAbilityImpl { public HauntAbility(Card card, Effect effect) { super(Zone.ALL, effect , false); - card.addAbility(new HauntExileAbility()); + addSubAbility(new HauntExileAbility()); } public HauntAbility(final HauntAbility ability) { diff --git a/Mage/src/mage/abilities/keyword/HideawayAbility.java b/Mage/src/mage/abilities/keyword/HideawayAbility.java index 83809b634ef..b59172bccf0 100644 --- a/Mage/src/mage/abilities/keyword/HideawayAbility.java +++ b/Mage/src/mage/abilities/keyword/HideawayAbility.java @@ -68,11 +68,11 @@ public class HideawayAbility extends StaticAbility { super(Zone.BATTLEFIELD, new EntersBattlefieldEffect(new TapSourceEffect(true))); Ability ability = new EntersBattlefieldTriggeredAbility(new HideawayExileEffect(), false); ability.setRuleVisible(false); - card.addAbility(ability); + addSubAbility(ability); // Allow controller to look at face down card ability = new SimpleStaticAbility(Zone.BATTLEFIELD, new HideawayLookAtFaceDownCardEffect()); ability.setRuleVisible(false); - card.addAbility(ability); + addSubAbility(ability); } public HideawayAbility(final HideawayAbility ability) { diff --git a/Mage/src/mage/abilities/keyword/LevelerCardBuilder.java b/Mage/src/mage/abilities/keyword/LevelerCardBuilder.java index f1747f7ea5c..a5691ed3222 100644 --- a/Mage/src/mage/abilities/keyword/LevelerCardBuilder.java +++ b/Mage/src/mage/abilities/keyword/LevelerCardBuilder.java @@ -134,9 +134,11 @@ public class LevelerCardBuilder { * * @param card * @param levelAbilities + * @return list of levelAbilities to add to card */ - public static void construct(Card card, LevelAbility... levelAbilities) { + public static List construct(LevelAbility... levelAbilities) { LevelerCardBuilder builder = new LevelerCardBuilder(); + List abilities = new ArrayList<>(); for (LevelAbility levelAbility : levelAbilities) { // set main params @@ -153,22 +155,11 @@ public class LevelerCardBuilder { builder.addAbility(addedAbility); } - // build static abilities and add them to card - for (Ability simpleStaticAbility : builder.build()) { - card.addAbility(simpleStaticAbility); - } + // build static abilities and add them to list + abilities.addAll(builder.build()); } - // set max level counters (for ai) - if (card instanceof LevelerCard) { - int maxValue = 0; - for (LevelAbility levelAbility : levelAbilities) { - if (levelAbility.getLevel1() > maxValue) { - maxValue = levelAbility.getLevel1(); - } - } - ((LevelerCard) card).setMaxLevelCounters(maxValue); - } + return abilities; } public static class LevelAbility { diff --git a/Mage/src/mage/abilities/keyword/MadnessAbility.java b/Mage/src/mage/abilities/keyword/MadnessAbility.java index 2ecdb0bf3c1..40bf42b7e48 100644 --- a/Mage/src/mage/abilities/keyword/MadnessAbility.java +++ b/Mage/src/mage/abilities/keyword/MadnessAbility.java @@ -41,7 +41,7 @@ public class MadnessAbility extends StaticAbility { @SuppressWarnings("unchecked") public MadnessAbility(Card card, ManaCosts madnessCost) { super(Zone.HAND, new MadnessReplacementEffect((ManaCosts)madnessCost)); - card.addAbility(new MadnessTriggeredAbility((ManaCosts)madnessCost)); + addSubAbility(new MadnessTriggeredAbility((ManaCosts)madnessCost)); rule = "Madness " + madnessCost.getText() + " (If you discard this card, you may cast it for its madness cost instead of putting it into your graveyard.)"; } diff --git a/Mage/src/mage/abilities/keyword/ModularAbility.java b/Mage/src/mage/abilities/keyword/ModularAbility.java index e66908edf4a..19219e21b2d 100644 --- a/Mage/src/mage/abilities/keyword/ModularAbility.java +++ b/Mage/src/mage/abilities/keyword/ModularAbility.java @@ -59,9 +59,9 @@ public class ModularAbility extends DiesTriggeredAbility { if (sunburst) { Ability ability = new SunburstAbility(card); ability.setRuleVisible(false); - card.addAbility(ability); + addSubAbility(ability); } else { - card.addAbility(new ModularStaticAbility(amount)); + addSubAbility(new ModularStaticAbility(amount)); } } diff --git a/Mage/src/mage/abilities/keyword/MorphAbility.java b/Mage/src/mage/abilities/keyword/MorphAbility.java index ced44656612..a2fc3a02398 100644 --- a/Mage/src/mage/abilities/keyword/MorphAbility.java +++ b/Mage/src/mage/abilities/keyword/MorphAbility.java @@ -148,7 +148,7 @@ public class MorphAbility extends StaticAbility implements AlternativeSourceCost Ability ability = new SimpleStaticAbility(Zone.BATTLEFIELD, new BecomesFaceDownCreatureEffect(morphCosts, (megamorph ? FaceDownType.MEGAMORPHED :FaceDownType.MORPHED))); ability.setWorksFaceDown(true); ability.setRuleVisible(false); - card.addAbility(ability); + addSubAbility(ability); } diff --git a/Mage/src/mage/abilities/keyword/ReboundAbility.java b/Mage/src/mage/abilities/keyword/ReboundAbility.java index 59f56ad6f6e..2459909c04e 100644 --- a/Mage/src/mage/abilities/keyword/ReboundAbility.java +++ b/Mage/src/mage/abilities/keyword/ReboundAbility.java @@ -96,7 +96,7 @@ public class ReboundAbility extends TriggeredAbilityImpl { ((ZoneChangeEvent) event).getToZone() == Zone.STACK) { Card card = (Card) game.getObject(event.getTargetId()); - if (card.getAbilities().contains(this)) { + if (card.getAbilities(game).contains(this)) { this.installReboundEffect = true; } } diff --git a/Mage/src/mage/abilities/keyword/ReplicateAbility.java b/Mage/src/mage/abilities/keyword/ReplicateAbility.java index 34260cbdf03..059623d0d66 100644 --- a/Mage/src/mage/abilities/keyword/ReplicateAbility.java +++ b/Mage/src/mage/abilities/keyword/ReplicateAbility.java @@ -66,7 +66,7 @@ public class ReplicateAbility extends StaticAbility implements OptionalAdditiona this.additionalCost = new OptionalAdditionalCostImpl(keywordText, reminderTextMana, new ManaCostsImpl(manaString)); this.additionalCost.setRepeatable(true); setRuleAtTheTop(true); - card.addAbility(new ReplicateTriggeredAbility()); + addSubAbility(new ReplicateTriggeredAbility()); } public ReplicateAbility(final ReplicateAbility ability) { diff --git a/Mage/src/mage/abilities/keyword/SuspendAbility.java b/Mage/src/mage/abilities/keyword/SuspendAbility.java index 96ec8241322..37f5dad5a12 100644 --- a/Mage/src/mage/abilities/keyword/SuspendAbility.java +++ b/Mage/src/mage/abilities/keyword/SuspendAbility.java @@ -179,8 +179,8 @@ public class SuspendAbility extends ActivatedAbilityImpl { if (card.getManaCost().isEmpty()) { setRuleAtTheTop(true); } - card.addAbility(new SuspendBeginningOfUpkeepTriggeredAbility()); - card.addAbility(new SuspendPlayCardAbility(card.getCardType().contains(CardType.CREATURE))); + addSubAbility(new SuspendBeginningOfUpkeepTriggeredAbility()); + addSubAbility(new SuspendPlayCardAbility(card.getCardType().contains(CardType.CREATURE))); } ruleText = sb.toString(); } @@ -196,18 +196,18 @@ public class SuspendAbility extends ActivatedAbilityImpl { SuspendAbility ability = new SuspendAbility(0, null, card, false); ability.setSourceId(card.getId()); ability.setControllerId(card.getOwnerId()); - game.getState().addOtherAbility(card.getId(), ability); + game.getState().addOtherAbility(card, ability); SuspendBeginningOfUpkeepTriggeredAbility ability1 = new SuspendBeginningOfUpkeepTriggeredAbility(); ability1.setSourceId(card.getId()); ability1.setControllerId(card.getOwnerId()); - game.getState().addOtherAbility(card.getId(), ability1); + game.getState().addOtherAbility(card, ability1); game.getState().addAbility(ability1, source.getSourceId(), card); SuspendPlayCardAbility ability2 = new SuspendPlayCardAbility(card.getCardType().contains(CardType.CREATURE)); ability2.setSourceId(card.getId()); ability2.setControllerId(card.getOwnerId()); - game.getState().addOtherAbility(card.getId(), ability2); + game.getState().addOtherAbility(card, ability2); game.getState().addAbility(ability2, source.getSourceId(), card); } diff --git a/Mage/src/mage/cards/Card.java b/Mage/src/mage/cards/Card.java index e2fe537f0d8..93f780270ef 100644 --- a/Mage/src/mage/cards/Card.java +++ b/Mage/src/mage/cards/Card.java @@ -33,6 +33,7 @@ import java.util.List; import java.util.UUID; import mage.MageObject; import mage.Mana; +import mage.abilities.Abilities; import mage.abilities.Ability; import mage.abilities.SpellAbility; import mage.constants.Rarity; @@ -47,7 +48,7 @@ public interface Card extends MageObject { int getCardNumber(); Rarity getRarity(); void setOwnerId(UUID ownerId); - void addAbility(Ability ability); + public Abilities getAbilities(Game game); void setSpellAbility(SpellAbility ability); SpellAbility getSpellAbility(); List getRules(); // gets base card rules diff --git a/Mage/src/mage/cards/CardImpl.java b/Mage/src/mage/cards/CardImpl.java index cd41fee396b..95de70b38ab 100644 --- a/Mage/src/mage/cards/CardImpl.java +++ b/Mage/src/mage/cards/CardImpl.java @@ -36,10 +36,11 @@ import java.util.UUID; import mage.MageObject; import mage.MageObjectImpl; import mage.Mana; +import mage.abilities.Abilities; +import mage.abilities.AbilitiesImpl; import mage.abilities.Ability; import mage.abilities.PlayLandAbility; import mage.abilities.SpellAbility; -import mage.abilities.keyword.MorphAbility; import mage.abilities.mana.ManaAbility; import mage.constants.CardType; import mage.constants.ColoredManaSymbol; @@ -226,11 +227,11 @@ public abstract class CardImpl extends MageObjectImpl implements Card { for (String data : cardState.getInfo().values()) { rules.add(data); } + for (Ability ability: cardState.getAbilities()) { + rules.add(ability.getRule()); + } } } -// for (Ability ability: state.getAbilities()) { -// rules.add(ability.getRule()); -// } return rules; } catch (Exception e) { logger.error("Exception in rules generation for card: " + this.getName(), e); @@ -238,12 +239,48 @@ public abstract class CardImpl extends MageObjectImpl implements Card { return rulesError; } + /** + * Gets all base abilities - does not include additional abilities added by + * other cards or effects + * @return A list of {@link Ability} - this collection is modifiable + */ @Override - public void addAbility(Ability ability) { - ability.setSourceId(this.getId()); - abilities.add(ability); + public Abilities getAbilities() { + return super.getAbilities(); } + /** + * Gets all current abilities - includes additional abilities added by + * other cards or effects + * @param game + * @return A list of {@link Ability} - this collection is not modifiable + */ + @Override + public Abilities getAbilities(Game game) { + Abilities otherAbilities = game.getState().getAllOtherAbilities(objectId); + if (otherAbilities == null) { + return abilities; + } + Abilities all = new AbilitiesImpl<>(); + all.addAll(abilities); + all.addAll(otherAbilities); + return all; + } + + protected void addAbility(Ability ability) { + ability.setSourceId(this.getId()); + abilities.add(ability); + for (Ability subAbility: ability.getSubAbilities()) { + abilities.add(subAbility); + } + } + + protected void addAbilities(List abilities) { + for (Ability ability: abilities) { + addAbility(ability); + } + } + protected void addAbility(Ability ability, Watcher watcher) { addAbility(ability); ability.addWatcher(watcher); diff --git a/Mage/src/mage/cards/LevelerCard.java b/Mage/src/mage/cards/LevelerCard.java index 9ac0b72725c..a6566d6443f 100644 --- a/Mage/src/mage/cards/LevelerCard.java +++ b/Mage/src/mage/cards/LevelerCard.java @@ -53,7 +53,7 @@ public abstract class LevelerCard extends CardImpl { return maxLevelCounters; } - public void setMaxLevelCounters(int maxLevelCounters) { + protected void setMaxLevelCounters(int maxLevelCounters) { this.maxLevelCounters = maxLevelCounters; } diff --git a/Mage/src/mage/cards/SplitCard.java b/Mage/src/mage/cards/SplitCard.java index 1a5fb845fcd..1bfe550d540 100644 --- a/Mage/src/mage/cards/SplitCard.java +++ b/Mage/src/mage/cards/SplitCard.java @@ -145,6 +145,19 @@ public abstract class SplitCard extends CardImpl { return allAbilites; } + @Override + public Abilities getAbilities(Game game) { + Abilities allAbilites = new AbilitiesImpl<>(); + for (Ability ability : super.getAbilities(game)) { + if (ability instanceof SpellAbility && !((SpellAbility)ability).getSpellAbilityType().equals(SpellAbilityType.SPLIT)) { + allAbilites.add(ability); + } + } + allAbilites.addAll(leftHalfCard.getAbilities(game)); + allAbilites.addAll(rightHalfCard.getAbilities(game)); + return allAbilites; + } + @Override public List getRules() { List rules = new ArrayList<>(); diff --git a/Mage/src/mage/game/CardState.java b/Mage/src/mage/game/CardState.java index 44ab1257f3e..0421c8ec720 100644 --- a/Mage/src/mage/game/CardState.java +++ b/Mage/src/mage/game/CardState.java @@ -2,6 +2,9 @@ package mage.game; import java.util.HashMap; import java.util.Map; +import mage.abilities.Abilities; +import mage.abilities.AbilitiesImpl; +import mage.abilities.Ability; import mage.counters.Counters; /** * @@ -12,8 +15,10 @@ public class CardState { protected boolean faceDown; protected Map info; protected Counters counters; + protected Abilities abilities; private static final Map emptyInfo = new HashMap<>(); + private static final Abilities emptyAbilities = new AbilitiesImpl<>(); public CardState() { counters = new Counters(); @@ -26,6 +31,12 @@ public class CardState { info.putAll(state.info); } counters = state.counters.copy(); + if (state.abilities != null) { + abilities = new AbilitiesImpl<>(); + for (Ability ability: state.abilities) { + abilities.add(ability.copy()); + } + } } public CardState copy() { @@ -62,10 +73,37 @@ public class CardState { return info; } + public Abilities getAbilities() { + if (abilities == null) { + return emptyAbilities; + } + return abilities; + } + + public void addAbility(Ability ability) { + if (abilities == null) { + abilities = new AbilitiesImpl<>(); + } + abilities.add(ability); + for (Ability sub: ability.getSubAbilities()) { + abilities.add(sub); + } + } + + public void clearAbilities() { + if (abilities != null) { + for (Ability ability: abilities) { + ability.setSourceId(null); + ability.setControllerId(null); + } + abilities = null; + } + } public void clear() { counters.clear(); info = null; + clearAbilities(); } } diff --git a/Mage/src/mage/game/GameState.java b/Mage/src/mage/game/GameState.java index 1924a9ec3f0..8ba886624fe 100644 --- a/Mage/src/mage/game/GameState.java +++ b/Mage/src/mage/game/GameState.java @@ -58,7 +58,6 @@ import mage.watchers.Watchers; import java.io.Serializable; import java.util.*; -import org.apache.log4j.Logger; /** * @@ -80,7 +79,6 @@ public class GameState implements Serializable, Copyable { private final Map lookedAt = new HashMap<>(); private final DelayedTriggeredAbilities delayed; private final SpecialActions specialActions; - private final Map> otherAbilities = new HashMap<>(); private final TurnMods turnMods; private final Watchers watchers; @@ -156,9 +154,6 @@ public class GameState implements Serializable, Copyable { this.values.put(entry.getKey(), entry.getValue()); } this.zones.putAll(state.zones); - for (Map.Entry> entry: state.otherAbilities.entrySet()) { - otherAbilities.put(entry.getKey(), entry.getValue().copy()); - } this.paused = state.paused; this.simultaneousEvents.addAll(state.simultaneousEvents); for (Map.Entry entry: state.cardState.entrySet()) { @@ -601,6 +596,9 @@ public class GameState implements Serializable, Copyable { watcher.setSourceId(attachedTo.getId()); watchers.add(watcher); } + for (Ability sub: ability.getSubAbilities()) { + addAbility(sub, sourceId, attachedTo); + } } public void addCommandObject(CommandObject commandObject) { @@ -678,41 +676,32 @@ public class GameState implements Serializable, Copyable { * @return */ public Abilities getActivatedOtherAbilities(UUID objectId, Zone zone) { - if (otherAbilities.containsKey(objectId)) { - return otherAbilities.get(objectId).getActivatedAbilities(zone); + if (cardState.containsKey(objectId)) { + return cardState.get(objectId).getAbilities().getActivatedAbilities(zone); } return null; } public Abilities getAllOtherAbilities(UUID objectId) { - if (otherAbilities.containsKey(objectId)) { - return otherAbilities.get(objectId); + if (cardState.containsKey(objectId)) { + return cardState.get(objectId).getAbilities(); } return null; } - - - public void addOtherAbility(UUID objectId, Ability ability) { - if (!otherAbilities.containsKey(objectId)) { - otherAbilities.put(objectId, new AbilitiesImpl(ability)); - } else { - otherAbilities.get(objectId).add(ability); - } - } - /** * Adds the ability to continuous or triggered abilities * @param ability * @param card */ - public void addOtherAbility(Ability ability, Card card) { - addOtherAbility(card.getId(), ability); - addAbility(ability, card.getId(), card); - } - - private void resetOtherAbilities() { - otherAbilities.clear(); + public void addOtherAbility(Card attachedTo, Ability ability) { + ability.setSourceId(attachedTo.getId()); + ability.setControllerId(attachedTo.getOwnerId()); + if (!cardState.containsKey(attachedTo.getId())) { + cardState.put(attachedTo.getId(), new CardState()); + } + cardState.get(attachedTo.getId()).addAbility(ability); + addAbility(ability, attachedTo.getId(), attachedTo); } /** @@ -734,7 +723,9 @@ public class GameState implements Serializable, Copyable { triggers.removeAllGainedAbilities(); getContinuousEffects().removeAllTemporaryEffects(); this.setLegendaryRuleActive(true); - this.resetOtherAbilities(); + for (CardState state: cardState.values()) { + state.clearAbilities(); + } } public void clear() { @@ -754,7 +745,6 @@ public class GameState implements Serializable, Copyable { legendaryRuleActive = true; gameOver = false; specialActions.clear(); - otherAbilities.clear(); cardState.clear(); combat.clear(); turnMods.clear(); diff --git a/Mage/src/mage/game/permanent/Permanent.java b/Mage/src/mage/game/permanent/Permanent.java index aab2f10f6a7..49210d311f0 100644 --- a/Mage/src/mage/game/permanent/Permanent.java +++ b/Mage/src/mage/game/permanent/Permanent.java @@ -125,7 +125,6 @@ public interface Permanent extends Card, Controllable { String getValue(); @Deprecated - @Override void addAbility(Ability ability); @Deprecated void addAbility(Ability ability, Game game); diff --git a/Mage/src/mage/game/permanent/PermanentImpl.java b/Mage/src/mage/game/permanent/PermanentImpl.java index 742b6c77b9b..a408978198f 100644 --- a/Mage/src/mage/game/permanent/PermanentImpl.java +++ b/Mage/src/mage/game/permanent/PermanentImpl.java @@ -38,6 +38,8 @@ import java.util.Map; import java.util.UUID; import mage.MageObject; import mage.MageObjectReference; +import mage.abilities.Abilities; +import mage.abilities.AbilitiesImpl; import mage.abilities.Ability; import mage.abilities.effects.ContinuousEffect; import mage.abilities.effects.Effect; @@ -244,6 +246,16 @@ public abstract class PermanentImpl extends CardImpl implements Permanent { } } + @Override + public Abilities getAbilities() { + return abilities; + } + + @Override + public Abilities getAbilities(Game game) { + return abilities; + } + @Override @Deprecated public void addAbility(Ability ability) { diff --git a/Mage/src/mage/game/stack/Spell.java b/Mage/src/mage/game/stack/Spell.java index 026576123d2..64c1ab0b917 100644 --- a/Mage/src/mage/game/stack/Spell.java +++ b/Mage/src/mage/game/stack/Spell.java @@ -601,6 +601,11 @@ public class Spell implements StackObject, Card { return card.getAbilities(); } + @Override + public Abilities getAbilities(Game game) { + return card.getAbilities(game); + } + @Override public boolean hasAbility(UUID abilityId, Game game) { return card.hasAbility(abilityId, game); @@ -674,7 +679,6 @@ public class Spell implements StackObject, Card { spellAbilities.add(spellAbility); } - @Override public void addAbility(Ability ability) {} @Override diff --git a/Mage/src/mage/game/stack/StackAbility.java b/Mage/src/mage/game/stack/StackAbility.java index 3eb4254f2ed..520846bd141 100644 --- a/Mage/src/mage/game/stack/StackAbility.java +++ b/Mage/src/mage/game/stack/StackAbility.java @@ -487,6 +487,16 @@ public class StackAbility implements StackObject, Ability { throw new UnsupportedOperationException("Not supported."); } + @Override + public List getSubAbilities() { + return this.ability.getSubAbilities(); + } + + @Override + public void addSubAbility(Ability ability) { + throw new UnsupportedOperationException("Not supported."); + } + @Override public MageObject getSourceObject(Game game) { throw new UnsupportedOperationException("Not supported."); //To change body of generated methods, choose Tools | Templates.