From 947351932b10a5f59029445caeae8d16c4b41217 Mon Sep 17 00:00:00 2001 From: Evan Kranzler Date: Thu, 13 Apr 2023 20:03:16 -0400 Subject: [PATCH] Ready for Review: Implementing Battles (#10156) * add types and subtypes * add startingDefense attribute * [MOM] Implement Invasion of Ravnica / Guildpact Paragon * fix two small errors * refactor various instances of "any target" * fully implement defense counters * battles can now be attacked * [MOM] Implement Invasion of Dominaria / Serra Faithkeeper * [MOM] Implement Invasion of Innistrad / Deluge of the Dead * [MOM] Implement Invasion of Kaladesh / Aetherwing, Golden-Scale Flagship * [MOM] Implement Invasion of Kamigawa / Rooftop Saboteurs * [MOM] Implement Invasion of Karsus / Refraction Elemental * [MOM] Implement Invasion of Tolvada / The Broken Sky * simplify battle info ability * fix verify failure * some more fixes for attacking battles * [MOM] Implement Invasion of Kaldheim / Pyre of the World Tree * [MOM] Implement Invasion of Lorwyn / Winnowing Forces * [MOM] Implement Invasion of Moag / Bloomwielder Dryads * [MOM] Implement Invasion of Shandalar / Leyline Surge * [MOM] Implement Invasion of Belenon / Belenon War Anthem * [MOM] Implement Invasion of Pyrulea / Gargantuan Slabhorn * [MOM] Implement Invasion of Vryn / Overloaded Mage-Ring * [MOM] Implement Marshal of Zhalfir * [MOM] Implement Sunfall * implement protectors for sieges * partially implement siege defeated trigger * fix verify failure * some updates to blocking * [MOM] Implement Invasion of Mercadia / Kyren Flamewright * [MOM] Implement Invasion of Theros / Ephara, Ever-Sheltering * [MOM] Implement Invasion of Ulgrotha / Grandmother Ravi Sengir * [MOM] Implement Invasion of Xerex / Vertex Paladin * add initial battle test * fix verify failure * [MOM] Implement Invasion of Amonkhet / Lazotep Convert * [MOM] update spoiler * update how protectors are chosen * update text * battles can't block * add control change test * rename battle test for duel * add multiplayer test * [MOM] Implement Invasion of Alara / Awaken the Maelstrom * [MOM] Implement Invasion of Eldraine * [MOM] Implement Invasion of Ergamon / Truga Cliffhanger * [MOM] Implement Invasion of Ixalan / Belligerent Regisaur * battles now cast transformed (this is super hacky but we need to refactor TDFCs anyway) * add TODO * add ignore for randomly failing test * a few small fixes * add defense to MtgJsonCard (unused like loyalty) * implement ProtectorIdPredicate * small fixes --- .../java/mage/client/cards/DragCardGrid.java | 9 +- .../CardViewEDHPowerLevelComparator.java | 2 +- .../mage/client/util/gui/GuiDisplayUtil.java | 8 +- .../java/org/mage/card/arcane/CardPanel.java | 4 +- .../card/arcane/CardPanelRenderModeImage.java | 6 +- .../card/arcane/CardPanelRenderModeMTGO.java | 2 + .../mage/card/arcane/ModernCardRenderer.java | 124 ++++++---- .../src/main/java/mage/view/AbilityView.java | 1 + .../src/main/java/mage/view/CardView.java | 38 ++- .../main/java/mage/view/StackAbilityView.java | 1 + .../java/mage/player/ai/ComputerPlayer.java | 10 +- .../src/mage/player/human/HumanPlayer.java | 4 +- .../a/AetherwingGoldenScaleFlagship.java | 49 ++++ Mage.Sets/src/mage/cards/a/AllWillBeOne.java | 9 +- Mage.Sets/src/mage/cards/a/ArcTrail.java | 27 +-- .../src/mage/cards/a/AwakenTheMaelstrom.java | 127 ++++++++++ .../src/mage/cards/b/BelenonWarAnthem.java | 35 +++ .../src/mage/cards/b/BelligerentRegisaur.java | 47 ++++ .../src/mage/cards/b/BloomwielderDryads.java | 53 +++++ Mage.Sets/src/mage/cards/b/BondOfPassion.java | 12 +- .../src/mage/cards/c/CaptainsManeuver.java | 32 +-- Mage.Sets/src/mage/cards/c/CauterySliver.java | 20 +- .../src/mage/cards/c/ChandraHopesBeacon.java | 10 +- Mage.Sets/src/mage/cards/c/ConeOfFlame.java | 39 ++- .../src/mage/cards/d/DelugeOfTheDead.java | 82 +++++++ .../mage/cards/d/DrakusethMawOfFlames.java | 24 +- Mage.Sets/src/mage/cards/e/Endure.java | 9 +- .../mage/cards/e/EpharaEverSheltering.java | 81 +++++++ .../src/mage/cards/e/ExplosiveWelcome.java | 8 +- Mage.Sets/src/mage/cards/g/GaeasLiege.java | 80 ++----- .../src/mage/cards/g/GargantuanSlabhorn.java | 66 ++++++ .../mage/cards/g/GrandmotherRaviSengir.java | 55 +++++ .../src/mage/cards/g/GuildpactParagon.java | 63 +++++ .../src/mage/cards/i/ImperialGunner.java | 21 +- .../src/mage/cards/i/InvasionOfAlara.java | 103 ++++++++ .../src/mage/cards/i/InvasionOfAmonkhet.java | 49 ++++ .../src/mage/cards/i/InvasionOfBelenon.java | 41 ++++ .../src/mage/cards/i/InvasionOfDominaria.java | 44 ++++ .../src/mage/cards/i/InvasionOfEldraine.java | 44 ++++ .../src/mage/cards/i/InvasionOfErgamon.java | 47 ++++ .../src/mage/cards/i/InvasionOfInnistrad.java | 48 ++++ .../src/mage/cards/i/InvasionOfIxalan.java | 45 ++++ .../src/mage/cards/i/InvasionOfKaladesh.java | 41 ++++ .../src/mage/cards/i/InvasionOfKaldheim.java | 75 ++++++ .../src/mage/cards/i/InvasionOfKamigawa.java | 48 ++++ .../src/mage/cards/i/InvasionOfKarsus.java | 47 ++++ .../src/mage/cards/i/InvasionOfLorwyn.java | 74 ++++++ .../src/mage/cards/i/InvasionOfMercadia.java | 42 ++++ .../src/mage/cards/i/InvasionOfMoag.java | 44 ++++ .../src/mage/cards/i/InvasionOfPyrulea.java | 75 ++++++ .../src/mage/cards/i/InvasionOfRavnica.java | 66 ++++++ .../src/mage/cards/i/InvasionOfShandalar.java | 45 ++++ .../src/mage/cards/i/InvasionOfTheros.java | 55 +++++ .../src/mage/cards/i/InvasionOfTolvada.java | 53 +++++ .../src/mage/cards/i/InvasionOfUlgrotha.java | 55 +++++ .../src/mage/cards/i/InvasionOfVryn.java | 40 ++++ .../src/mage/cards/i/InvasionOfXerex.java | 44 ++++ .../src/mage/cards/j/JoyfulStormsculptor.java | 4 +- .../src/mage/cards/k/KyrenFlamewright.java | 59 +++++ .../src/mage/cards/l/LazotepConvert.java | 106 +++++++++ Mage.Sets/src/mage/cards/l/LeylineSurge.java | 39 +++ .../mage/cards/l/LightningCoreExcavator.java | 6 +- .../src/mage/cards/l/LozhanDragonsLegacy.java | 11 +- .../src/mage/cards/m/MercadiasDownfall.java | 90 ++++--- Mage.Sets/src/mage/cards/n/NeedleDrop.java | 37 ++- .../mage/cards/n/NicolBolasGodPharaoh.java | 7 +- .../src/mage/cards/o/OverloadedMageRing.java | 53 +++++ .../src/mage/cards/p/PhyrexianVindicator.java | 29 ++- Mage.Sets/src/mage/cards/p/PortalMage.java | 2 +- .../src/mage/cards/p/PrickleFaeries.java | 74 ++++++ .../src/mage/cards/p/PyreOfTheWorldTree.java | 52 ++++ .../src/mage/cards/r/RefractionElemental.java | 47 ++++ .../src/mage/cards/r/RooftopSaboteurs.java | 46 ++++ .../src/mage/cards/s/SerraFaithkeeper.java | 42 ++++ Mage.Sets/src/mage/cards/t/TerraRavager.java | 64 ++--- Mage.Sets/src/mage/cards/t/TheBrokenSky.java | 63 +++++ .../src/mage/cards/t/ToralfGodOfFury.java | 11 +- .../src/mage/cards/t/TrugaCliffcharger.java | 62 +++++ Mage.Sets/src/mage/cards/v/VertexPaladin.java | 48 ++++ .../src/mage/cards/w/WinnowingForces.java | 46 ++++ .../src/mage/cards/w/WrathfulRedDragon.java | 11 +- .../src/mage/sets/MarchOfTheMachine.java | 46 ++++ .../test/cards/battle/BattleBaseTest.java | 29 +++ .../test/cards/battle/BattleDuelTest.java | 157 ++++++++++++ .../cards/battle/BattleMultiplayerTest.java | 101 ++++++++ .../java/org/mage/test/player/TestPlayer.java | 19 +- .../base/impl/CardTestPlayerAPIImpl.java | 6 +- .../java/mage/verify/mtgjson/MtgJsonCard.java | 1 + Mage/src/main/java/mage/MageObject.java | 4 + Mage/src/main/java/mage/MageObjectImpl.java | 12 + .../main/java/mage/abilities/AbilityImpl.java | 4 +- .../mage/abilities/common/SiegeAbility.java | 147 ++++++++++++ .../abilities/effects/common/CopyEffect.java | 4 +- .../abilities/keyword/DisturbAbility.java | 2 +- .../keyword/MoreThanMeetsTheEyeAbility.java | 2 +- .../abilities/keyword/TransformAbility.java | 1 + Mage/src/main/java/mage/cards/CardImpl.java | 7 +- .../java/mage/cards/repository/CardInfo.java | 5 +- .../mage/constants/SpellAbilityCastMode.java | 2 +- .../src/main/java/mage/constants/SubType.java | 12 +- .../main/java/mage/constants/SubTypeSet.java | 1 + .../java/mage/designations/Designation.java | 9 + .../main/java/mage/filter/StaticFilters.java | 6 + .../mage/filter/common/FilterAnyTarget.java | 32 +++ .../FilterCreaturePlayerOrPlaneswalker.java | 25 +- .../mage/filter/common/FilterDefender.java | 46 ++++ .../common/FilterPlaneswalkerOrPlayer.java | 78 ------ .../ProtectedByOpponentPredicate.java | 3 +- .../permanent/ProtectorIdPredicate.java | 3 +- Mage/src/main/java/mage/game/GameImpl.java | 27 +++ .../main/java/mage/game/combat/Combat.java | 223 +++++++++--------- .../java/mage/game/combat/CombatGroup.java | 138 +++++------ .../java/mage/game/command/Commander.java | 14 +- .../main/java/mage/game/command/Dungeon.java | 9 + .../main/java/mage/game/command/Emblem.java | 11 +- .../main/java/mage/game/command/Plane.java | 9 + .../java/mage/game/permanent/Permanent.java | 10 + .../mage/game/permanent/PermanentCard.java | 1 + .../mage/game/permanent/PermanentImpl.java | 85 ++++++- .../mage/game/permanent/PermanentToken.java | 1 + Mage/src/main/java/mage/game/stack/Spell.java | 15 +- .../java/mage/game/stack/StackAbility.java | 10 +- .../mage/target/common/TargetAnyTarget.java | 216 +---------------- .../target/common/TargetAnyTargetAmount.java | 7 +- .../mage/target/common/TargetDefender.java | 195 +-------------- Mage/src/main/java/mage/util/CardUtil.java | 6 +- .../util/functions/CopyTokenFunction.java | 2 + Utils/cardClass.tmpl | 4 +- Utils/gen-card.pl | 5 + 129 files changed, 4057 insertions(+), 1087 deletions(-) create mode 100644 Mage.Sets/src/mage/cards/a/AetherwingGoldenScaleFlagship.java create mode 100644 Mage.Sets/src/mage/cards/a/AwakenTheMaelstrom.java create mode 100644 Mage.Sets/src/mage/cards/b/BelenonWarAnthem.java create mode 100644 Mage.Sets/src/mage/cards/b/BelligerentRegisaur.java create mode 100644 Mage.Sets/src/mage/cards/b/BloomwielderDryads.java create mode 100644 Mage.Sets/src/mage/cards/d/DelugeOfTheDead.java create mode 100644 Mage.Sets/src/mage/cards/e/EpharaEverSheltering.java create mode 100644 Mage.Sets/src/mage/cards/g/GargantuanSlabhorn.java create mode 100644 Mage.Sets/src/mage/cards/g/GrandmotherRaviSengir.java create mode 100644 Mage.Sets/src/mage/cards/g/GuildpactParagon.java create mode 100644 Mage.Sets/src/mage/cards/i/InvasionOfAlara.java create mode 100644 Mage.Sets/src/mage/cards/i/InvasionOfAmonkhet.java create mode 100644 Mage.Sets/src/mage/cards/i/InvasionOfBelenon.java create mode 100644 Mage.Sets/src/mage/cards/i/InvasionOfDominaria.java create mode 100644 Mage.Sets/src/mage/cards/i/InvasionOfEldraine.java create mode 100644 Mage.Sets/src/mage/cards/i/InvasionOfErgamon.java create mode 100644 Mage.Sets/src/mage/cards/i/InvasionOfInnistrad.java create mode 100644 Mage.Sets/src/mage/cards/i/InvasionOfIxalan.java create mode 100644 Mage.Sets/src/mage/cards/i/InvasionOfKaladesh.java create mode 100644 Mage.Sets/src/mage/cards/i/InvasionOfKaldheim.java create mode 100644 Mage.Sets/src/mage/cards/i/InvasionOfKamigawa.java create mode 100644 Mage.Sets/src/mage/cards/i/InvasionOfKarsus.java create mode 100644 Mage.Sets/src/mage/cards/i/InvasionOfLorwyn.java create mode 100644 Mage.Sets/src/mage/cards/i/InvasionOfMercadia.java create mode 100644 Mage.Sets/src/mage/cards/i/InvasionOfMoag.java create mode 100644 Mage.Sets/src/mage/cards/i/InvasionOfPyrulea.java create mode 100644 Mage.Sets/src/mage/cards/i/InvasionOfRavnica.java create mode 100644 Mage.Sets/src/mage/cards/i/InvasionOfShandalar.java create mode 100644 Mage.Sets/src/mage/cards/i/InvasionOfTheros.java create mode 100644 Mage.Sets/src/mage/cards/i/InvasionOfTolvada.java create mode 100644 Mage.Sets/src/mage/cards/i/InvasionOfUlgrotha.java create mode 100644 Mage.Sets/src/mage/cards/i/InvasionOfVryn.java create mode 100644 Mage.Sets/src/mage/cards/i/InvasionOfXerex.java create mode 100644 Mage.Sets/src/mage/cards/k/KyrenFlamewright.java create mode 100644 Mage.Sets/src/mage/cards/l/LazotepConvert.java create mode 100644 Mage.Sets/src/mage/cards/l/LeylineSurge.java create mode 100644 Mage.Sets/src/mage/cards/o/OverloadedMageRing.java create mode 100644 Mage.Sets/src/mage/cards/p/PrickleFaeries.java create mode 100644 Mage.Sets/src/mage/cards/p/PyreOfTheWorldTree.java create mode 100644 Mage.Sets/src/mage/cards/r/RefractionElemental.java create mode 100644 Mage.Sets/src/mage/cards/r/RooftopSaboteurs.java create mode 100644 Mage.Sets/src/mage/cards/s/SerraFaithkeeper.java create mode 100644 Mage.Sets/src/mage/cards/t/TheBrokenSky.java create mode 100644 Mage.Sets/src/mage/cards/t/TrugaCliffcharger.java create mode 100644 Mage.Sets/src/mage/cards/v/VertexPaladin.java create mode 100644 Mage.Sets/src/mage/cards/w/WinnowingForces.java create mode 100644 Mage.Tests/src/test/java/org/mage/test/cards/battle/BattleBaseTest.java create mode 100644 Mage.Tests/src/test/java/org/mage/test/cards/battle/BattleDuelTest.java create mode 100644 Mage.Tests/src/test/java/org/mage/test/cards/battle/BattleMultiplayerTest.java create mode 100644 Mage/src/main/java/mage/abilities/common/SiegeAbility.java create mode 100644 Mage/src/main/java/mage/filter/common/FilterAnyTarget.java create mode 100644 Mage/src/main/java/mage/filter/common/FilterDefender.java delete mode 100644 Mage/src/main/java/mage/filter/common/FilterPlaneswalkerOrPlayer.java diff --git a/Mage.Client/src/main/java/mage/client/cards/DragCardGrid.java b/Mage.Client/src/main/java/mage/client/cards/DragCardGrid.java index da33c5acac0..24ddc461366 100644 --- a/Mage.Client/src/main/java/mage/client/cards/DragCardGrid.java +++ b/Mage.Client/src/main/java/mage/client/cards/DragCardGrid.java @@ -524,7 +524,13 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg private final CardTypeCounter planeswalkerCounter = new CardTypeCounter() { @Override protected boolean is(CardView card) { - return card.isPlanesWalker(); + return card.isPlaneswalker(); + } + }; + private final CardTypeCounter battleCounter = new CardTypeCounter() { + @Override + protected boolean is(CardView card) { + return card.isBattle(); } }; private final CardTypeCounter tribalCounter = new CardTypeCounter() { @@ -542,6 +548,7 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg instantCounter, planeswalkerCounter, sorceryCounter, + battleCounter, tribalCounter }; diff --git a/Mage.Client/src/main/java/mage/client/util/comparators/CardViewEDHPowerLevelComparator.java b/Mage.Client/src/main/java/mage/client/util/comparators/CardViewEDHPowerLevelComparator.java index 06609df4ca8..9f70be139de 100644 --- a/Mage.Client/src/main/java/mage/client/util/comparators/CardViewEDHPowerLevelComparator.java +++ b/Mage.Client/src/main/java/mage/client/util/comparators/CardViewEDHPowerLevelComparator.java @@ -314,7 +314,7 @@ public class CardViewEDHPowerLevelComparator implements CardViewComparator { thisMaxPower = Math.max(thisMaxPower, 1); } - if (card.isPlanesWalker()) { + if (card.isPlaneswalker()) { thisMaxPower = Math.max(thisMaxPower, 6); } diff --git a/Mage.Client/src/main/java/mage/client/util/gui/GuiDisplayUtil.java b/Mage.Client/src/main/java/mage/client/util/gui/GuiDisplayUtil.java index 9e860593aa2..3d25c7f77ae 100644 --- a/Mage.Client/src/main/java/mage/client/util/gui/GuiDisplayUtil.java +++ b/Mage.Client/src/main/java/mage/client/util/gui/GuiDisplayUtil.java @@ -313,11 +313,15 @@ public final class GuiDisplayUtil { } buffer.append(""); - String pt = ""; + String pt; if (card.isCreature()) { pt = card.getPower() + '/' + card.getToughness(); - } else if (card.isPlanesWalker()) { + } else if (card.isPlaneswalker()) { pt = card.getLoyalty(); + } else if (card.isBattle()) { + pt = card.getDefense(); + } else { + pt = ""; } buffer.append("
"); diff --git a/Mage.Client/src/main/java/org/mage/card/arcane/CardPanel.java b/Mage.Client/src/main/java/org/mage/card/arcane/CardPanel.java index 53f67633b0d..79695acf17d 100644 --- a/Mage.Client/src/main/java/org/mage/card/arcane/CardPanel.java +++ b/Mage.Client/src/main/java/org/mage/card/arcane/CardPanel.java @@ -797,8 +797,10 @@ public abstract class CardPanel extends MagePermanent implements ComponentListen } if (card.isCreature()) { sb.append('\n').append(card.getPower()).append('/').append(card.getToughness()); - } else if (card.isPlanesWalker()) { + } else if (card.isPlaneswalker()) { sb.append('\n').append(card.getLoyalty()); + } else if (card.isBattle()) { + sb.append('\n').append(card.getDefense()); } if (card.getRules() == null) { card.overrideRules(new ArrayList<>()); diff --git a/Mage.Client/src/main/java/org/mage/card/arcane/CardPanelRenderModeImage.java b/Mage.Client/src/main/java/org/mage/card/arcane/CardPanelRenderModeImage.java index a69be37a6f7..fc4e6dde9b2 100644 --- a/Mage.Client/src/main/java/org/mage/card/arcane/CardPanelRenderModeImage.java +++ b/Mage.Client/src/main/java/org/mage/card/arcane/CardPanelRenderModeImage.java @@ -803,10 +803,14 @@ public class CardPanelRenderModeImage extends CardPanel { ptText1.setText(getGameCard().getPower()); ptText2.setText("/"); ptText3.setText(CardRendererUtils.getCardLifeWithDamage(getGameCard())); - } else if (card.isPlanesWalker()) { + } else if (card.isPlaneswalker()) { ptText1.setText(""); ptText2.setText(""); ptText3.setText(getGameCard().getLoyalty()); + } else if (card.isBattle()) { + ptText1.setText(""); + ptText2.setText(""); + ptText3.setText(getGameCard().getDefense()); } else { ptText1.setText(""); ptText2.setText(""); diff --git a/Mage.Client/src/main/java/org/mage/card/arcane/CardPanelRenderModeMTGO.java b/Mage.Client/src/main/java/org/mage/card/arcane/CardPanelRenderModeMTGO.java index 2eadd185b56..655b45840c9 100644 --- a/Mage.Client/src/main/java/org/mage/card/arcane/CardPanelRenderModeMTGO.java +++ b/Mage.Client/src/main/java/org/mage/card/arcane/CardPanelRenderModeMTGO.java @@ -62,6 +62,7 @@ public class CardPanelRenderModeMTGO extends CardPanel { && a.getPower().equals(b.getPower()) && a.getToughness().equals(b.getToughness()) && a.getLoyalty().equals(b.getLoyalty()) + && a.getDefense().equals(b.getDefense()) && 0 == a.getColor().compareTo(b.getColor()) && a.getCardTypes().equals(b.getCardTypes()) && a.getSubTypes().equals(b.getSubTypes()) @@ -128,6 +129,7 @@ public class CardPanelRenderModeMTGO extends CardPanel { sb.append(this.view.getPower()); sb.append(this.view.getToughness()); sb.append(this.view.getLoyalty()); + sb.append(this.view.getDefense()); sb.append(this.view.getColor().toString()); sb.append(this.view.getType()); sb.append(this.view.getExpansionSetCode()); diff --git a/Mage.Client/src/main/java/org/mage/card/arcane/ModernCardRenderer.java b/Mage.Client/src/main/java/org/mage/card/arcane/ModernCardRenderer.java index da544debd8f..dc26ab922db 100644 --- a/Mage.Client/src/main/java/org/mage/card/arcane/ModernCardRenderer.java +++ b/Mage.Client/src/main/java/org/mage/card/arcane/ModernCardRenderer.java @@ -530,37 +530,37 @@ public class ModernCardRenderer extends CardRenderer { g.setPaint(getSpiralLandTextboxColor(twoColors.get(0), twoColors.get(1), true)); // Horizontal bars - g.fillRect(totalContentInset + 1 , typeLineY + boxHeight + 1 , contentWidth - 2 , height_of_spiral); - g.fillRect(totalContentInset + 1 + 2*height_of_spiral, typeLineY + boxHeight + 1 + 2*height_of_spiral , contentWidth - 2 - 4*height_of_spiral , height_of_spiral); - g.fillRect(totalContentInset + 1 + 4*height_of_spiral, typeLineY + boxHeight + 1 + 4*height_of_spiral , contentWidth - 2 - 8*height_of_spiral , height_of_spiral); - g.fillRect(totalContentInset + 1 + 6*height_of_spiral, typeLineY + boxHeight + 1 + 6*height_of_spiral , contentWidth - 2 - 12*height_of_spiral, height_of_spiral); + g.fillRect(totalContentInset + 1, typeLineY + boxHeight + 1, contentWidth - 2, height_of_spiral); + g.fillRect(totalContentInset + 1 + 2 * height_of_spiral, typeLineY + boxHeight + 1 + 2 * height_of_spiral, contentWidth - 2 - 4 * height_of_spiral, height_of_spiral); + g.fillRect(totalContentInset + 1 + 4 * height_of_spiral, typeLineY + boxHeight + 1 + 4 * height_of_spiral, contentWidth - 2 - 8 * height_of_spiral, height_of_spiral); + g.fillRect(totalContentInset + 1 + 6 * height_of_spiral, typeLineY + boxHeight + 1 + 6 * height_of_spiral, contentWidth - 2 - 12 * height_of_spiral, height_of_spiral); - g.fillRect(totalContentInset + 1 + 6*height_of_spiral, typeLineY + boxHeight + 1 + total_height_of_box - 7*height_of_spiral, contentWidth - 2 - 12*height_of_spiral, height_of_spiral); - g.fillRect(totalContentInset + 1 + 4*height_of_spiral, typeLineY + boxHeight + 1 + total_height_of_box - 5*height_of_spiral, contentWidth - 2 - 8*height_of_spiral , height_of_spiral); - g.fillRect(totalContentInset + 1 + 2*height_of_spiral, typeLineY + boxHeight + 1 + total_height_of_box - 3*height_of_spiral, contentWidth - 2 - 4*height_of_spiral , height_of_spiral); - g.fillRect(totalContentInset + 1 , typeLineY + boxHeight + 1 + total_height_of_box - height_of_spiral , contentWidth - 2 , height_of_spiral); + g.fillRect(totalContentInset + 1 + 6 * height_of_spiral, typeLineY + boxHeight + 1 + total_height_of_box - 7 * height_of_spiral, contentWidth - 2 - 12 * height_of_spiral, height_of_spiral); + g.fillRect(totalContentInset + 1 + 4 * height_of_spiral, typeLineY + boxHeight + 1 + total_height_of_box - 5 * height_of_spiral, contentWidth - 2 - 8 * height_of_spiral, height_of_spiral); + g.fillRect(totalContentInset + 1 + 2 * height_of_spiral, typeLineY + boxHeight + 1 + total_height_of_box - 3 * height_of_spiral, contentWidth - 2 - 4 * height_of_spiral, height_of_spiral); + g.fillRect(totalContentInset + 1, typeLineY + boxHeight + 1 + total_height_of_box - height_of_spiral, contentWidth - 2, height_of_spiral); // Vertical bars - g.fillRect(totalContentInset + 1 , typeLineY + boxHeight + 1 , height_of_spiral, total_height_spiral - 1 ); - g.fillRect(totalContentInset + 1 + 2*height_of_spiral, typeLineY + boxHeight + 1 + 2*height_of_spiral, height_of_spiral, total_height_spiral - 1 - 4*height_of_spiral ); - g.fillRect(totalContentInset + 1 + 4*height_of_spiral, typeLineY + boxHeight + 1 + 4*height_of_spiral, height_of_spiral, total_height_spiral - 1 - 8*height_of_spiral ); - g.fillRect(totalContentInset + 1 + 6*height_of_spiral, typeLineY + boxHeight + 1 + 6*height_of_spiral, height_of_spiral, total_height_spiral - 1 - 12*height_of_spiral); + g.fillRect(totalContentInset + 1, typeLineY + boxHeight + 1, height_of_spiral, total_height_spiral - 1); + g.fillRect(totalContentInset + 1 + 2 * height_of_spiral, typeLineY + boxHeight + 1 + 2 * height_of_spiral, height_of_spiral, total_height_spiral - 1 - 4 * height_of_spiral); + g.fillRect(totalContentInset + 1 + 4 * height_of_spiral, typeLineY + boxHeight + 1 + 4 * height_of_spiral, height_of_spiral, total_height_spiral - 1 - 8 * height_of_spiral); + g.fillRect(totalContentInset + 1 + 6 * height_of_spiral, typeLineY + boxHeight + 1 + 6 * height_of_spiral, height_of_spiral, total_height_spiral - 1 - 12 * height_of_spiral); - g.fillRect(totalContentInset + contentWidth - 7*height_of_spiral, typeLineY + boxHeight + 1 + 6*height_of_spiral, height_of_spiral, total_height_spiral - 1 - 12*height_of_spiral); - g.fillRect(totalContentInset + contentWidth - 5*height_of_spiral, typeLineY + boxHeight + 1 + 4*height_of_spiral, height_of_spiral, total_height_spiral - 1 - 8*height_of_spiral ); - g.fillRect(totalContentInset + contentWidth - 3*height_of_spiral, typeLineY + boxHeight + 1 + 2*height_of_spiral, height_of_spiral, total_height_spiral - 1 - 4*height_of_spiral ); - g.fillRect(totalContentInset + contentWidth - 1*height_of_spiral, typeLineY + boxHeight + 1 + 0*height_of_spiral, height_of_spiral, total_height_spiral - 1 ); + g.fillRect(totalContentInset + contentWidth - 7 * height_of_spiral, typeLineY + boxHeight + 1 + 6 * height_of_spiral, height_of_spiral, total_height_spiral - 1 - 12 * height_of_spiral); + g.fillRect(totalContentInset + contentWidth - 5 * height_of_spiral, typeLineY + boxHeight + 1 + 4 * height_of_spiral, height_of_spiral, total_height_spiral - 1 - 8 * height_of_spiral); + g.fillRect(totalContentInset + contentWidth - 3 * height_of_spiral, typeLineY + boxHeight + 1 + 2 * height_of_spiral, height_of_spiral, total_height_spiral - 1 - 4 * height_of_spiral); + g.fillRect(totalContentInset + contentWidth - 1 * height_of_spiral, typeLineY + boxHeight + 1 + 0 * height_of_spiral, height_of_spiral, total_height_spiral - 1); } } } else { g.fillRect( - totalContentInset + 1, typeLineY, - contentWidth - 2, cardHeight - borderWidth * 3 - typeLineY - 1); + totalContentInset + 1, typeLineY, + contentWidth - 2, cardHeight - borderWidth * 3 - typeLineY - 1); } } // If it's a planeswalker, extend the textbox left border by some - if (cardView.isPlanesWalker()) { + if (cardView.isPlaneswalker()) { g.setPaint(borderPaint); g.fillRect( totalContentInset, typeLineY + boxHeight, @@ -1116,7 +1116,7 @@ public class ModernCardRenderer extends CardRenderer { // Is it a walker? (But don't draw the box if it's a non-permanent view // of a walker without a starting loyalty (EG: Arlin Kord's flipped side). - if (cardView.isPlanesWalker() + if (cardView.isPlaneswalker() && (cardView instanceof PermanentView || !cardView.getStartingLoyalty().equals("0"))) { // Draw the PW loyalty box int w = partBoxWidth; @@ -1124,26 +1124,15 @@ public class ModernCardRenderer extends CardRenderer { int x = cardWidth - partBoxWidth - borderWidth; int y = curY - h; - Polygon symbol = new Polygon( - new int[]{ - x + w / 2, - (int) (x + w * 0.9), - x + w, - (int) (x + w * 0.6), - x + w / 2, - (int) (x + w * 0.4), - x, - (int) (x + w * 0.1),}, - new int[]{ - y + h, - (int) (y + 0.8 * h), - y, - (int) (y - 0.2 * h), - y, - (int) (y - 0.2 * h), - y, - (int) (y + 0.8 * h),}, - 8); + Polygon symbol = new Polygon(); + symbol.addPoint(x + w / 2, y + h); + symbol.addPoint((int) (x + w * 0.9), (int) (y + 0.8 * h)); + symbol.addPoint(x + w, y); + symbol.addPoint((int) (x + w * 0.6), (int) (y - 0.2 * h)); + symbol.addPoint(x + w / 2, y); + symbol.addPoint((int) (x + w * 0.4), (int) (y - 0.2 * h)); + symbol.addPoint(x, y); + symbol.addPoint((int) (x + w * 0.1), (int) (y + 0.8 * h)); // Draw + stroke g.setColor(Color.black); @@ -1170,6 +1159,59 @@ public class ModernCardRenderer extends CardRenderer { curY -= (int) (1.2 * y); } + // Is it a battle? + if (cardView.isBattle() + && (cardView instanceof PermanentView || !cardView.getStartingDefense().equals("0"))) { + // Draw the PW loyalty box + int w = 3 * partBoxWidth / 4; + int h = 3 * partBoxWidth / 4; + int x = cardWidth - w - borderWidth; + int y = curY - h; + + Polygon symbol = new Polygon(); + symbol.addPoint(x + (0 * w) / 80, y + (2 * h) / 80); + symbol.addPoint(x + (12 * w) / 80, y + (30 * h) / 80); + symbol.addPoint(x + (3 * w) / 80, y + (40 * h) / 80); + symbol.addPoint(x + (12 * w) / 80, y + (50 * h) / 80); + symbol.addPoint(x + (0 * w) / 80, y + (78 * h) / 80); + symbol.addPoint(x + (30 * w) / 80, y + (71 * h) / 80); + symbol.addPoint(x + (40 * w) / 80, y + (80 * h) / 80); + symbol.addPoint(x + (50 * w) / 80, y + (71 * h) / 80); + symbol.addPoint(x + (80 * w) / 80, y + (78 * h) / 80); + symbol.addPoint(x + (68 * w) / 80, y + (50 * h) / 80); + symbol.addPoint(x + (77 * w) / 80, y + (40 * h) / 80); + symbol.addPoint(x + (68 * w) / 80, y + (30 * h) / 80); + symbol.addPoint(x + (80 * w) / 80, y + (2 * h) / 80); + symbol.addPoint(x + (48 * w) / 80, y + (9 * h) / 80); + symbol.addPoint(x + (40 * w) / 80, y + (0 * h) / 80); + symbol.addPoint(x + (32 * w) / 80, y + (9 * h) / 80); + + + // Draw + stroke + g.setColor(Color.black); + g.fillPolygon(symbol); + g.setColor(new Color(200, 200, 200)); + g.setStroke(new BasicStroke(2)); + g.drawPolygon(symbol); + g.setStroke(new BasicStroke(1)); + + // Loyalty number + String defense; + if (cardView instanceof PermanentView) { + defense = cardView.getDefense(); + } else { + defense = cardView.getStartingDefense(); + } + + g.setFont(ptTextFont); + g.setColor(Color.white); + int defenseWidth = g.getFontMetrics().stringWidth(defense); + g.drawString(defense, x + 1 + (w - defenseWidth) / 2, y - 1 + ptTextHeight + (h - ptTextHeight) / 2); + + // Advance + curY -= (int) (1.2 * y); + } + // does it have damage on it? if ((cardView instanceof PermanentView) && ((PermanentView) cardView).getDamage() > 0) { int x = cardWidth - partBoxWidth - borderWidth; @@ -1769,7 +1811,7 @@ public class ModernCardRenderer extends CardRenderer { private static Color getLessOpaqueColor(Color color, boolean lessOpaqueRulesTextBox) { if (lessOpaqueRulesTextBox) { - Color lessOpaque = new Color (color.getRed(), color.getGreen(), color.getBlue(), color.getAlpha() - 50); + Color lessOpaque = new Color(color.getRed(), color.getGreen(), color.getBlue(), color.getAlpha() - 50); return lessOpaque; } return color; diff --git a/Mage.Common/src/main/java/mage/view/AbilityView.java b/Mage.Common/src/main/java/mage/view/AbilityView.java index 1ad8657c060..7098690e70c 100644 --- a/Mage.Common/src/main/java/mage/view/AbilityView.java +++ b/Mage.Common/src/main/java/mage/view/AbilityView.java @@ -27,6 +27,7 @@ public class AbilityView extends CardView { this.power = ""; this.toughness = ""; this.loyalty = ""; + this.defense = ""; this.cardTypes = new ArrayList<>(); this.subTypes = new SubTypes(); this.superTypes = EnumSet.noneOf(SuperType.class); diff --git a/Mage.Common/src/main/java/mage/view/CardView.java b/Mage.Common/src/main/java/mage/view/CardView.java index 838047983fc..41739eddb4b 100644 --- a/Mage.Common/src/main/java/mage/view/CardView.java +++ b/Mage.Common/src/main/java/mage/view/CardView.java @@ -64,7 +64,10 @@ public class CardView extends SimpleCardView { protected String toughness; @Expose protected String loyalty = ""; + @Expose + protected String defense = ""; protected String startingLoyalty; + protected String startingDefense; protected List cardTypes; protected SubTypes subTypes; protected Set superTypes; @@ -172,6 +175,8 @@ public class CardView extends SimpleCardView { this.toughness = cardView.toughness; this.loyalty = cardView.loyalty; this.startingLoyalty = cardView.startingLoyalty; + this.defense = cardView.defense; + this.startingDefense = cardView.startingDefense; this.cardTypes = new ArrayList<>(cardView.cardTypes); this.subTypes = new SubTypes(cardView.subTypes); this.superTypes = cardView.superTypes; @@ -397,6 +402,7 @@ public class CardView extends SimpleCardView { if (game != null) { if (permanent.getCounters(game) != null && !permanent.getCounters(game).isEmpty()) { this.loyalty = Integer.toString(permanent.getCounters(game).getCount(CounterType.LOYALTY)); + this.defense = Integer.toString(permanent.getCounters(game).getCount(CounterType.DEFENSE)); counters = new ArrayList<>(); for (Counter counter : permanent.getCounters(game).values()) { counters.add(new CounterView(counter)); @@ -435,6 +441,7 @@ public class CardView extends SimpleCardView { this.mageObjectType = MageObjectType.CARD; } this.loyalty = ""; + this.defense = ""; if (game != null && card.getCounters(game) != null && !card.getCounters(game).isEmpty()) { counters = new ArrayList<>(); for (Counter counter : card.getCounters(game).values()) { @@ -591,7 +598,10 @@ public class CardView extends SimpleCardView { this.frameStyle = card.getFrameStyle(); // Get starting loyalty - this.startingLoyalty = CardUtil.convertStartingLoyalty(card.getStartingLoyalty()); + this.startingLoyalty = CardUtil.convertLoyaltyOrDefense(card.getStartingLoyalty()); + + // Get starting defense + this.startingDefense = CardUtil.convertLoyaltyOrDefense(card.getStartingDefense()); } public CardView(MageObject object, Game game) { @@ -606,10 +616,12 @@ public class CardView extends SimpleCardView { this.power = Integer.toString(object.getPower().getValue()); this.toughness = Integer.toString(object.getToughness().getValue()); this.loyalty = Integer.toString(((Permanent) object).getCounters((Game) null).getCount(CounterType.LOYALTY)); + this.defense = Integer.toString(((Permanent) object).getCounters((Game) null).getCount(CounterType.DEFENSE)); } else { this.power = object.getPower().toString(); this.toughness = object.getToughness().toString(); this.loyalty = ""; + this.defense = ""; } this.cardTypes = new ArrayList<>(object.getCardType(game)); this.subTypes = new SubTypes(object.getSubtype(game)); @@ -664,8 +676,10 @@ public class CardView extends SimpleCardView { this.frameColor = object.getFrameColor(game).copy(); // Frame style this.frameStyle = object.getFrameStyle(); - // Starting loyalty. Must be extracted from an ability - this.startingLoyalty = CardUtil.convertStartingLoyalty(object.getStartingLoyalty()); + // Starting loyalty + this.startingLoyalty = CardUtil.convertLoyaltyOrDefense(object.getStartingLoyalty()); + // Starting defense + this.startingDefense = CardUtil.convertLoyaltyOrDefense(object.getStartingDefense()); } protected CardView() { @@ -750,6 +764,8 @@ public class CardView extends SimpleCardView { this.toughness = ""; this.loyalty = ""; this.startingLoyalty = ""; + this.defense = ""; + this.startingDefense = ""; this.cardTypes = new ArrayList<>(); this.subTypes = new SubTypes(); this.superTypes = EnumSet.noneOf(SuperType.class); @@ -800,6 +816,8 @@ public class CardView extends SimpleCardView { this.toughness = token.getToughness().toString(); this.loyalty = ""; this.startingLoyalty = ""; + this.defense = ""; + this.startingDefense = ""; this.cardTypes = new ArrayList<>(token.getCardType(game)); this.subTypes = new SubTypes(token.getSubtype(game)); this.superTypes = token.getSuperType(); @@ -891,6 +909,14 @@ public class CardView extends SimpleCardView { return startingLoyalty; } + public String getDefense() { + return defense; + } + + public String getStartingDefense() { + return startingDefense; + } + public List getCardTypes() { return cardTypes; } @@ -1147,10 +1173,14 @@ public class CardView extends SimpleCardView { return cardTypes.contains(CardType.CREATURE); } - public boolean isPlanesWalker() { + public boolean isPlaneswalker() { return cardTypes.contains(CardType.PLANESWALKER); } + public boolean isBattle() { + return cardTypes.contains(CardType.BATTLE); + } + public String getColorText() { String colorText = getColor().getDescription(); return colorText.substring(0, 1).toUpperCase(Locale.ENGLISH) + colorText.substring(1); diff --git a/Mage.Common/src/main/java/mage/view/StackAbilityView.java b/Mage.Common/src/main/java/mage/view/StackAbilityView.java index 3511e17d84a..11f15f7d444 100644 --- a/Mage.Common/src/main/java/mage/view/StackAbilityView.java +++ b/Mage.Common/src/main/java/mage/view/StackAbilityView.java @@ -43,6 +43,7 @@ public class StackAbilityView extends CardView { this.sourceCard.setMageObjectType(mageObjectType); this.name = "Ability"; this.loyalty = ""; + this.defense = ""; this.cardTypes = ability.getCardType(game); this.subTypes = ability.getSubtype(game); diff --git a/Mage.Server.Plugins/Mage.Player.AI/src/main/java/mage/player/ai/ComputerPlayer.java b/Mage.Server.Plugins/Mage.Player.AI/src/main/java/mage/player/ai/ComputerPlayer.java index e62d33a7373..4f601ad2fff 100644 --- a/Mage.Server.Plugins/Mage.Player.AI/src/main/java/mage/player/ai/ComputerPlayer.java +++ b/Mage.Server.Plugins/Mage.Player.AI/src/main/java/mage/player/ai/ComputerPlayer.java @@ -282,9 +282,9 @@ public class ComputerPlayer extends PlayerImpl implements Player { List targets; TargetAnyTarget origTarget = (TargetAnyTarget) target.getOriginalTarget(); if (outcome.isGood()) { - targets = threats(abilityControllerId, source, ((FilterCreaturePlayerOrPlaneswalker) origTarget.getFilter()).getPermanentFilter(), game, target.getTargets()); + targets = threats(abilityControllerId, source, ((FilterAnyTarget) origTarget.getFilter()).getPermanentFilter(), game, target.getTargets()); } else { - targets = threats(randomOpponentId, source, ((FilterCreaturePlayerOrPlaneswalker) origTarget.getFilter()).getPermanentFilter(), game, target.getTargets()); + targets = threats(randomOpponentId, source, ((FilterAnyTarget) origTarget.getFilter()).getPermanentFilter(), game, target.getTargets()); } for (Permanent permanent : targets) { List alreadyTargetted = target.getTargets(); @@ -749,9 +749,9 @@ public class ComputerPlayer extends PlayerImpl implements Player { List targets; TargetAnyTarget origTarget = ((TargetAnyTarget) target.getOriginalTarget()); if (outcome.isGood()) { - targets = threats(abilityControllerId, source, ((FilterCreaturePlayerOrPlaneswalker) origTarget.getFilter()).getPermanentFilter(), game, target.getTargets()); + targets = threats(abilityControllerId, source, ((FilterAnyTarget) origTarget.getFilter()).getPermanentFilter(), game, target.getTargets()); } else { - targets = threats(randomOpponentId, source, ((FilterCreaturePlayerOrPlaneswalker) origTarget.getFilter()).getPermanentFilter(), game, target.getTargets()); + targets = threats(randomOpponentId, source, ((FilterAnyTarget) origTarget.getFilter()).getPermanentFilter(), game, target.getTargets()); } if (targets.isEmpty()) { @@ -765,7 +765,7 @@ public class ComputerPlayer extends PlayerImpl implements Player { } if (targets.isEmpty() && required) { - targets = game.getBattlefield().getActivePermanents(((FilterCreaturePlayerOrPlaneswalker) origTarget.getFilter()).getPermanentFilter(), playerId, game); + targets = game.getBattlefield().getActivePermanents(((FilterAnyTarget) origTarget.getFilter()).getPermanentFilter(), playerId, game); } for (Permanent permanent : targets) { List alreadyTargeted = target.getTargets(); diff --git a/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/HumanPlayer.java b/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/HumanPlayer.java index 0a283cd4ba7..856f3bcd28e 100644 --- a/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/HumanPlayer.java +++ b/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/HumanPlayer.java @@ -1795,7 +1795,7 @@ public class HumanPlayer extends PlayerImpl { declareAttacker(attackerId, possibleDefender.iterator().next(), game, true); return true; } else { - TargetDefender target = new TargetDefender(possibleDefender, attackerId); + TargetDefender target = new TargetDefender(possibleDefender); if (forcedToAttack) { StringBuilder sb = new StringBuilder(target.getTargetName()); Permanent attacker = game.getPermanent(attackerId); @@ -1814,7 +1814,7 @@ public class HumanPlayer extends PlayerImpl { } protected UUID selectDefenderForAllAttack(Set defenders, Game game) { - TargetDefender target = new TargetDefender(defenders, null); + TargetDefender target = new TargetDefender(defenders); if (chooseTarget(Outcome.Damage, target, null, game)) { return getFixedResponseUUID(game); } diff --git a/Mage.Sets/src/mage/cards/a/AetherwingGoldenScaleFlagship.java b/Mage.Sets/src/mage/cards/a/AetherwingGoldenScaleFlagship.java new file mode 100644 index 00000000000..c66eef0a473 --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/AetherwingGoldenScaleFlagship.java @@ -0,0 +1,49 @@ +package mage.cards.a; + +import mage.MageInt; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.dynamicvalue.common.ArtifactYouControlCount; +import mage.abilities.effects.common.continuous.SetBasePowerSourceEffect; +import mage.abilities.keyword.CrewAbility; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class AetherwingGoldenScaleFlagship extends CardImpl { + + public AetherwingGoldenScaleFlagship(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, ""); + + this.addSuperType(SuperType.LEGENDARY); + this.subtype.add(SubType.VEHICLE); + this.power = new MageInt(0); + this.toughness = new MageInt(4); + this.color.setBlue(true); + this.color.setRed(true); + this.nightCard = true; + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Aetherwing, Golden-Scale Flagship's power is equal to the number of artifacts you control. + this.addAbility(new SimpleStaticAbility(Zone.ALL, new SetBasePowerSourceEffect(ArtifactYouControlCount.instance, Duration.Custom))); + + // Crew 1 + this.addAbility(new CrewAbility(1)); + } + + private AetherwingGoldenScaleFlagship(final AetherwingGoldenScaleFlagship card) { + super(card); + } + + @Override + public AetherwingGoldenScaleFlagship copy() { + return new AetherwingGoldenScaleFlagship(this); + } +} diff --git a/Mage.Sets/src/mage/cards/a/AllWillBeOne.java b/Mage.Sets/src/mage/cards/a/AllWillBeOne.java index d42e471a51c..6ee9aa8e73b 100644 --- a/Mage.Sets/src/mage/cards/a/AllWillBeOne.java +++ b/Mage.Sets/src/mage/cards/a/AllWillBeOne.java @@ -9,9 +9,10 @@ import mage.constants.CardType; import mage.constants.TargetController; import mage.constants.Zone; import mage.filter.common.FilterCreaturePlayerOrPlaneswalker; +import mage.filter.common.FilterPermanentOrPlayer; import mage.game.Game; import mage.game.events.GameEvent; -import mage.target.common.TargetAnyTarget; +import mage.target.common.TargetPermanentOrPlayer; import java.util.UUID; @@ -40,8 +41,8 @@ public final class AllWillBeOne extends CardImpl { class AllWillBeOneTriggeredAbility extends TriggeredAbilityImpl { - private static final FilterCreaturePlayerOrPlaneswalker filter = - new FilterCreaturePlayerOrPlaneswalker("target opponent, creature an opponent controls, or planeswalker an opponent controls"); + private static final FilterPermanentOrPlayer filter + = new FilterCreaturePlayerOrPlaneswalker("opponent, creature an opponent controls, or planeswalker an opponent controls."); static { filter.getPermanentFilter().add(TargetController.NOT_YOU.getControllerPredicate()); @@ -50,7 +51,7 @@ class AllWillBeOneTriggeredAbility extends TriggeredAbilityImpl { AllWillBeOneTriggeredAbility() { super(Zone.BATTLEFIELD, new DamageTargetEffect(SavedDamageValue.MUCH)); - this.addTarget(new TargetAnyTarget(filter)); + this.addTarget(new TargetPermanentOrPlayer(filter)); } private AllWillBeOneTriggeredAbility(final AllWillBeOneTriggeredAbility ability) { diff --git a/Mage.Sets/src/mage/cards/a/ArcTrail.java b/Mage.Sets/src/mage/cards/a/ArcTrail.java index 1ff4c47d473..2348f00bf81 100644 --- a/Mage.Sets/src/mage/cards/a/ArcTrail.java +++ b/Mage.Sets/src/mage/cards/a/ArcTrail.java @@ -7,13 +7,14 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Outcome; -import mage.filter.common.FilterCreaturePlayerOrPlaneswalker; +import mage.filter.common.FilterAnyTarget; +import mage.filter.common.FilterPermanentOrPlayer; import mage.filter.predicate.other.AnotherTargetPredicate; import mage.game.Game; import mage.game.permanent.Permanent; import mage.players.Player; import mage.target.Target; -import mage.target.common.TargetAnyTarget; +import mage.target.common.TargetPermanentOrPlayer; import java.io.ObjectStreamException; import java.util.UUID; @@ -23,23 +24,21 @@ import java.util.UUID; */ public final class ArcTrail extends CardImpl { + private static final FilterPermanentOrPlayer filter1 = new FilterAnyTarget("creature, player or planeswalker to deal 2 damage"); + private static final FilterPermanentOrPlayer filter2 = new FilterAnyTarget("another creature, player or planeswalker to deal 1 damage"); + + static { + filter2.getPermanentFilter().add(new AnotherTargetPredicate(2)); + filter2.getPlayerFilter().add(new AnotherTargetPredicate(2)); + } + public ArcTrail(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{1}{R}"); // Arc Trail deals 2 damage to any target and 1 damage to another any target - FilterCreaturePlayerOrPlaneswalker filter1 = new FilterCreaturePlayerOrPlaneswalker("creature, player or planeswalker to deal 2 damage"); - TargetAnyTarget target1 = new TargetAnyTarget(1, 1, filter1); - target1.setTargetTag(1); - this.getSpellAbility().addTarget(target1); - - FilterCreaturePlayerOrPlaneswalker filter2 = new FilterCreaturePlayerOrPlaneswalker("another creature, player or planeswalker to deal 1 damage"); - filter2.getPermanentFilter().add(new AnotherTargetPredicate(2)); - filter2.getPlayerFilter().add(new AnotherTargetPredicate(2)); - TargetAnyTarget target2 = new TargetAnyTarget(1, 1, filter2); - target2.setTargetTag(2); - this.getSpellAbility().addTarget(target2); - this.getSpellAbility().addEffect(ArcTrailEffect.getInstance()); + this.getSpellAbility().addTarget(new TargetPermanentOrPlayer(filter1).setTargetTag(1)); + this.getSpellAbility().addTarget(new TargetPermanentOrPlayer(filter2).setTargetTag(2)); } private ArcTrail(final ArcTrail card) { diff --git a/Mage.Sets/src/mage/cards/a/AwakenTheMaelstrom.java b/Mage.Sets/src/mage/cards/a/AwakenTheMaelstrom.java new file mode 100644 index 00000000000..cb50cb2594c --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/AwakenTheMaelstrom.java @@ -0,0 +1,127 @@ +package mage.cards.a; + +import mage.abilities.Ability; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.*; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.Zone; +import mage.counters.CounterType; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.TargetPermanent; +import mage.target.TargetPlayer; +import mage.target.common.TargetControlledCreaturePermanent; +import mage.target.common.TargetCreaturePermanentAmount; +import mage.target.common.TargetPermanentAmount; +import mage.target.targetpointer.SecondTargetPointer; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class AwakenTheMaelstrom extends CardImpl { + + public AwakenTheMaelstrom(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, ""); + + this.nightCard = true; + + // Awaken the Maelstrom is all colors. + this.color.setWhite(true); + this.color.setBlue(true); + this.color.setBlack(true); + this.color.setRed(true); + this.color.setGreen(true); + this.addAbility(new SimpleStaticAbility(Zone.ALL, new InfoEffect("{this} is all colors"))); + + // Target player draws two cards. + this.getSpellAbility().addEffect(new DrawCardTargetEffect(1)); + this.getSpellAbility().addTarget(new TargetPlayer().withChooseHint("to draw two cards")); + + // You may put an artifact card from your hand onto the battlefield. + this.getSpellAbility().addEffect(new PutCardFromHandOntoBattlefieldEffect(StaticFilters.FILTER_CARD_ARTIFACT_AN)); + + // Create a token that's a copy of a permanent you control. + // Distribute three +1/+1 counters among one, two, or three creatures you control. + this.getSpellAbility().addEffect(new AwakenTheMaelstromEffect()); + + // Destroy target permanent an opponent controls. + this.getSpellAbility().addEffect(new DestroyTargetEffect().setTargetPointer(new SecondTargetPointer())); + this.getSpellAbility().addTarget(new TargetPermanent(StaticFilters.FILTER_OPPONENTS_PERMANENT).withChooseHint("to destroy")); + } + + private AwakenTheMaelstrom(final AwakenTheMaelstrom card) { + super(card); + } + + @Override + public AwakenTheMaelstrom copy() { + return new AwakenTheMaelstrom(this); + } +} + +class AwakenTheMaelstromEffect extends OneShotEffect { + + AwakenTheMaelstromEffect() { + super(Outcome.Benefit); + staticText = "Create a token that's a copy of a permanent you control. " + + "Distribute three +1/+1 counters among one, two, or three creatures you control."; + } + + private AwakenTheMaelstromEffect(final AwakenTheMaelstromEffect effect) { + super(effect); + } + + @Override + public AwakenTheMaelstromEffect copy() { + return new AwakenTheMaelstromEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null) { + return false; + } + makeToken(player, game, source); + distributeCounters(player, game, source); + return true; + } + + private void makeToken(Player player, Game game, Ability source) { + TargetPermanent target = new TargetControlledCreaturePermanent(); + target.setNotTarget(true); + target.withChooseHint("to copy"); + if (!target.canChoose(player.getId(), source, game)) { + return; + } + player.choose(outcome, target, source, game); + Permanent permanent = game.getPermanent(target.getFirstTarget()); + if (permanent != null) { + new CreateTokenCopyTargetEffect().setSavedPermanent(permanent).apply(game, source); + } + } + + private void distributeCounters(Player player, Game game, Ability source) { + if (game.getBattlefield().count(StaticFilters.FILTER_CONTROLLED_CREATURE, player.getId(), source, game) < 1) { + return; + } + TargetPermanentAmount target = new TargetCreaturePermanentAmount(3); + target.setNotTarget(true); + target.withChooseHint("to distribute counters"); + player.choose(outcome, target, source, game); + for (UUID targetId : target.getTargets()) { + Permanent permanent = game.getPermanent(targetId); + if (permanent != null) { + permanent.addCounters(CounterType.P1P1.createInstance(target.getTargetAmount(targetId)), source, game); + } + } + } +} diff --git a/Mage.Sets/src/mage/cards/b/BelenonWarAnthem.java b/Mage.Sets/src/mage/cards/b/BelenonWarAnthem.java new file mode 100644 index 00000000000..432533a985b --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BelenonWarAnthem.java @@ -0,0 +1,35 @@ +package mage.cards.b; + +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.common.continuous.BoostControlledEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class BelenonWarAnthem extends CardImpl { + + public BelenonWarAnthem(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, ""); + + this.color.setWhite(true); + this.nightCard = true; + + // Creatures you control get +1/+1. + this.addAbility(new SimpleStaticAbility(new BoostControlledEffect(1, 1, Duration.WhileOnBattlefield))); + } + + private BelenonWarAnthem(final BelenonWarAnthem card) { + super(card); + } + + @Override + public BelenonWarAnthem copy() { + return new BelenonWarAnthem(this); + } +} diff --git a/Mage.Sets/src/mage/cards/b/BelligerentRegisaur.java b/Mage.Sets/src/mage/cards/b/BelligerentRegisaur.java new file mode 100644 index 00000000000..030117c9ab4 --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BelligerentRegisaur.java @@ -0,0 +1,47 @@ +package mage.cards.b; + +import mage.MageInt; +import mage.abilities.common.SpellCastControllerTriggeredAbility; +import mage.abilities.effects.common.continuous.GainAbilitySourceEffect; +import mage.abilities.keyword.IndestructibleAbility; +import mage.abilities.keyword.TrampleAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class BelligerentRegisaur extends CardImpl { + + public BelligerentRegisaur(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, ""); + + this.subtype.add(SubType.DINOSAUR); + this.power = new MageInt(4); + this.toughness = new MageInt(3); + this.color.setGreen(true); + this.nightCard = true; + + // Trample + this.addAbility(TrampleAbility.getInstance()); + + // Whenever you cast a spell, Belligerent Regisaur gains indestructible until end of turn. + this.addAbility(new SpellCastControllerTriggeredAbility(new GainAbilitySourceEffect( + IndestructibleAbility.getInstance(), Duration.EndOfTurn + ), false)); + } + + private BelligerentRegisaur(final BelligerentRegisaur card) { + super(card); + } + + @Override + public BelligerentRegisaur copy() { + return new BelligerentRegisaur(this); + } +} diff --git a/Mage.Sets/src/mage/cards/b/BloomwielderDryads.java b/Mage.Sets/src/mage/cards/b/BloomwielderDryads.java new file mode 100644 index 00000000000..87117bf661a --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BloomwielderDryads.java @@ -0,0 +1,53 @@ +package mage.cards.b; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.BeginningOfEndStepTriggeredAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.counter.AddCountersTargetEffect; +import mage.abilities.keyword.WardAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.TargetController; +import mage.counters.CounterType; +import mage.target.common.TargetControlledCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class BloomwielderDryads extends CardImpl { + + public BloomwielderDryads(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, ""); + + this.subtype.add(SubType.DRYAD); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + this.color.setWhite(true); + this.color.setGreen(true); + this.nightCard = true; + + // Ward {2} + this.addAbility(new WardAbility(new ManaCostsImpl<>("{2}"))); + + // At the beginning of your end step, put a +1/+1 counter on target creature you control. + Ability ability = new BeginningOfEndStepTriggeredAbility( + new AddCountersTargetEffect(CounterType.P1P1.createInstance()), TargetController.YOU, false + ); + ability.addTarget(new TargetControlledCreaturePermanent()); + this.addAbility(ability); + } + + private BloomwielderDryads(final BloomwielderDryads card) { + super(card); + } + + @Override + public BloomwielderDryads copy() { + return new BloomwielderDryads(this); + } +} diff --git a/Mage.Sets/src/mage/cards/b/BondOfPassion.java b/Mage.Sets/src/mage/cards/b/BondOfPassion.java index bf7c770cd31..69088a90c6f 100644 --- a/Mage.Sets/src/mage/cards/b/BondOfPassion.java +++ b/Mage.Sets/src/mage/cards/b/BondOfPassion.java @@ -12,15 +12,15 @@ import mage.constants.CardType; import mage.constants.Duration; import mage.constants.Outcome; import mage.filter.FilterPermanent; +import mage.filter.common.FilterAnyTarget; import mage.filter.common.FilterCreaturePermanent; -import mage.filter.common.FilterCreaturePlayerOrPlaneswalker; import mage.filter.predicate.other.AnotherTargetPredicate; import mage.game.Game; import mage.game.permanent.Permanent; import mage.players.Player; import mage.target.Target; import mage.target.TargetPermanent; -import mage.target.common.TargetAnyTarget; +import mage.target.common.TargetPermanentOrPlayer; import mage.target.targetpointer.FixedTarget; import java.util.UUID; @@ -32,12 +32,12 @@ public final class BondOfPassion extends CardImpl { private static final FilterPermanent filter = new FilterCreaturePermanent(); - private static final FilterCreaturePlayerOrPlaneswalker otherFilter - = new FilterCreaturePlayerOrPlaneswalker("any other target"); + private static final FilterAnyTarget otherFilter + = new FilterAnyTarget("any other target"); static { otherFilter.getPlayerFilter().add(new AnotherTargetPredicate(2)); - otherFilter.getPlayerFilter().add(new AnotherTargetPredicate(2)); + otherFilter.getPermanentFilter().add(new AnotherTargetPredicate(2)); } public BondOfPassion(UUID ownerId, CardSetInfo setInfo) { @@ -48,7 +48,7 @@ public final class BondOfPassion extends CardImpl { Target target = new TargetPermanent(filter); target.setTargetTag(1); this.getSpellAbility().addTarget(target); - target = new TargetAnyTarget(otherFilter); + target = new TargetPermanentOrPlayer(otherFilter); target.setTargetTag(2); this.getSpellAbility().addTarget(target); } diff --git a/Mage.Sets/src/mage/cards/c/CaptainsManeuver.java b/Mage.Sets/src/mage/cards/c/CaptainsManeuver.java index 009a07a8e7e..8a7ce213126 100644 --- a/Mage.Sets/src/mage/cards/c/CaptainsManeuver.java +++ b/Mage.Sets/src/mage/cards/c/CaptainsManeuver.java @@ -1,7 +1,5 @@ - package mage.cards.c; -import java.util.UUID; import mage.MageObjectReference; import mage.abilities.Ability; import mage.abilities.dynamicvalue.common.ManacostVariableValue; @@ -11,34 +9,36 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Duration; import mage.filter.common.FilterCreaturePlayerOrPlaneswalker; +import mage.filter.common.FilterPermanentOrPlayer; import mage.filter.predicate.other.AnotherTargetPredicate; import mage.game.Game; import mage.game.events.GameEvent; -import mage.target.common.TargetAnyTarget; +import mage.target.common.TargetPermanentOrPlayer; + +import java.util.UUID; /** - * * @author sprangg */ public final class CaptainsManeuver extends CardImpl { + private static final FilterPermanentOrPlayer filter + = new FilterCreaturePlayerOrPlaneswalker("creature, planeswalker or player (damage is redirected from)"); + private static final FilterPermanentOrPlayer filter2 + = new FilterCreaturePlayerOrPlaneswalker("another creature, planeswalker or player (damage is redirected to)"); + + static { + filter2.getPlayerFilter().add(new AnotherTargetPredicate(2)); + filter2.getPermanentFilter().add(new AnotherTargetPredicate(2)); + } + public CaptainsManeuver(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{X}{R}{W}"); //The next X damage that would be dealt to target creature, planeswalker, or player this turn is dealt to another target creature, planeswalker, or player instead. this.getSpellAbility().addEffect(new CaptainsManeuverEffect()); - - FilterCreaturePlayerOrPlaneswalker filter = new FilterCreaturePlayerOrPlaneswalker("creature, planeswalker or player (damage is redirected from)"); - TargetAnyTarget target = new TargetAnyTarget(filter); - target.setTargetTag(1); - this.getSpellAbility().addTarget(target); - - FilterCreaturePlayerOrPlaneswalker filter2 = new FilterCreaturePlayerOrPlaneswalker("another creature, planeswalker or player (damage is redirected to)"); - filter2.getPlayerFilter().add(new AnotherTargetPredicate(2)); - filter2.getPermanentFilter().add(new AnotherTargetPredicate(2)); - TargetAnyTarget target2 = new TargetAnyTarget(filter2); - target2.setTargetTag(2); - this.getSpellAbility().addTarget(target2); + this.getSpellAbility().addTarget(new TargetPermanentOrPlayer(filter).setTargetTag(1)); + this.getSpellAbility().addTarget(new TargetPermanentOrPlayer(filter2).setTargetTag(2)); } private CaptainsManeuver(final CaptainsManeuver card) { diff --git a/Mage.Sets/src/mage/cards/c/CauterySliver.java b/Mage.Sets/src/mage/cards/c/CauterySliver.java index e3a443e068e..363ef256ca6 100644 --- a/Mage.Sets/src/mage/cards/c/CauterySliver.java +++ b/Mage.Sets/src/mage/cards/c/CauterySliver.java @@ -15,8 +15,10 @@ import mage.constants.CardType; import mage.constants.Duration; import mage.constants.SubType; import mage.filter.FilterPermanent; -import mage.filter.common.FilterCreaturePlayerOrPlaneswalker; +import mage.filter.common.FilterPermanentOrPlayer; +import mage.filter.predicate.Predicates; import mage.target.common.TargetAnyTarget; +import mage.target.common.TargetPermanentOrPlayer; import java.util.UUID; @@ -26,8 +28,18 @@ import java.util.UUID; public final class CauterySliver extends CardImpl { private static final FilterPermanent filter = new FilterPermanent(SubType.SLIVER, "All Slivers"); - private static final FilterCreaturePlayerOrPlaneswalker filter2 - = new FilterCreaturePlayerOrPlaneswalker("player, planeswalker, or Sliver creature", SubType.SLIVER); + private static final FilterPermanentOrPlayer filter2 + = new FilterPermanentOrPlayer("player, planeswalker, or Sliver creature"); + + static { + filter2.getPermanentFilter().add(Predicates.or( + CardType.PLANESWALKER.getPredicate(), + Predicates.and( + CardType.CREATURE.getPredicate(), + SubType.SLIVER.getPredicate() + ) + )); + } public CauterySliver(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{R}{W}"); @@ -51,7 +63,7 @@ public final class CauterySliver extends CardImpl { new PreventDamageToTargetEffect(Duration.EndOfTurn, 1), new GenericManaCost(1) ); ability.addCost(new SacrificeSourceCost()); - ability.addTarget(new TargetAnyTarget(filter2)); + ability.addTarget(new TargetPermanentOrPlayer(filter2)); this.addAbility(new SimpleStaticAbility(new GainAbilityAllEffect( ability, Duration.WhileOnBattlefield, filter, "All Slivers have " + "\"{1}, Sacrifice this permanent: Prevent the next 1 damage " + diff --git a/Mage.Sets/src/mage/cards/c/ChandraHopesBeacon.java b/Mage.Sets/src/mage/cards/c/ChandraHopesBeacon.java index 0a30517ec1f..a6a9853083d 100644 --- a/Mage.Sets/src/mage/cards/c/ChandraHopesBeacon.java +++ b/Mage.Sets/src/mage/cards/c/ChandraHopesBeacon.java @@ -8,9 +8,13 @@ import mage.abilities.common.SpellCastControllerTriggeredAbility; import mage.abilities.dynamicvalue.common.GetXLoyaltyValue; import mage.abilities.effects.AsThoughEffectImpl; import mage.abilities.effects.OneShotEffect; -import mage.abilities.effects.common.*; +import mage.abilities.effects.common.CopyTargetSpellEffect; +import mage.abilities.effects.common.DamageTargetEffect; import mage.abilities.effects.mana.AddManaInAnyCombinationEffect; -import mage.cards.*; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.cards.Cards; +import mage.cards.CardsImpl; import mage.constants.*; import mage.filter.StaticFilters; import mage.game.Game; @@ -29,7 +33,7 @@ public class ChandraHopesBeacon extends CardImpl { super(ownerId, setInfo, new CardType[]{CardType.PLANESWALKER}, "{4}{R}{R}"); this.addSuperType(SuperType.LEGENDARY); this.addSubType(SubType.CHANDRA); - this.startingLoyalty = 5; + this.setStartingLoyalty(5); //Whenever you cast an instant or sorcery spell, copy it. You may choose new targets for the copy. This ability //triggers only once each turn. diff --git a/Mage.Sets/src/mage/cards/c/ConeOfFlame.java b/Mage.Sets/src/mage/cards/c/ConeOfFlame.java index c02a90518af..638f04bb6b4 100644 --- a/Mage.Sets/src/mage/cards/c/ConeOfFlame.java +++ b/Mage.Sets/src/mage/cards/c/ConeOfFlame.java @@ -6,13 +6,14 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Outcome; -import mage.filter.common.FilterCreaturePlayerOrPlaneswalker; +import mage.filter.common.FilterAnyTarget; +import mage.filter.common.FilterPermanentOrPlayer; import mage.filter.predicate.other.AnotherTargetPredicate; import mage.game.Game; import mage.game.permanent.Permanent; import mage.players.Player; import mage.target.Target; -import mage.target.common.TargetAnyTarget; +import mage.target.common.TargetPermanentOrPlayer; import java.util.UUID; @@ -21,30 +22,24 @@ import java.util.UUID; */ public final class ConeOfFlame extends CardImpl { + private static final FilterPermanentOrPlayer filter1 = new FilterAnyTarget("any target to deal 1 damage"); + private static final FilterPermanentOrPlayer filter2 = new FilterAnyTarget("another target to deal 2 damage"); + private static final FilterPermanentOrPlayer filter3 = new FilterAnyTarget("third target to deal 3 damage"); + + static { + filter2.getPermanentFilter().add(new AnotherTargetPredicate(2)); + filter2.getPlayerFilter().add(new AnotherTargetPredicate(2)); + filter3.getPermanentFilter().add(new AnotherTargetPredicate(3)); + filter3.getPlayerFilter().add(new AnotherTargetPredicate(3)); + } + public ConeOfFlame(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{3}{R}{R}"); // Cone of Flame deals 1 damage to any target, 2 damage to another target, and 3 damage to a third target. - // 1 - FilterCreaturePlayerOrPlaneswalker filter1 = new FilterCreaturePlayerOrPlaneswalker("any target to deal 1 damage"); - TargetAnyTarget target1 = new TargetAnyTarget(1, 1, filter1); - target1.setTargetTag(1); - this.getSpellAbility().addTarget(target1); - // 2 - FilterCreaturePlayerOrPlaneswalker filter2 = new FilterCreaturePlayerOrPlaneswalker("another target to deal 2 damage"); - filter2.getPermanentFilter().add(new AnotherTargetPredicate(2)); - filter2.getPlayerFilter().add(new AnotherTargetPredicate(2)); - TargetAnyTarget target2 = new TargetAnyTarget(1, 1, filter2); - target2.setTargetTag(2); - this.getSpellAbility().addTarget(target2); - // 3 - FilterCreaturePlayerOrPlaneswalker filter3 = new FilterCreaturePlayerOrPlaneswalker("third target to deal 3 damage"); - filter3.getPermanentFilter().add(new AnotherTargetPredicate(3)); - filter3.getPlayerFilter().add(new AnotherTargetPredicate(3)); - TargetAnyTarget target3 = new TargetAnyTarget(1, 1, filter3); - target3.setTargetTag(3); - this.getSpellAbility().addTarget(target3); - + this.getSpellAbility().addTarget(new TargetPermanentOrPlayer(filter1).setTargetTag(1)); + this.getSpellAbility().addTarget(new TargetPermanentOrPlayer(filter2).setTargetTag(2)); + this.getSpellAbility().addTarget(new TargetPermanentOrPlayer(filter3).setTargetTag(3)); this.getSpellAbility().addEffect(new ConeOfFlameEffect()); } diff --git a/Mage.Sets/src/mage/cards/d/DelugeOfTheDead.java b/Mage.Sets/src/mage/cards/d/DelugeOfTheDead.java new file mode 100644 index 00000000000..a8c1e8478aa --- /dev/null +++ b/Mage.Sets/src/mage/cards/d/DelugeOfTheDead.java @@ -0,0 +1,82 @@ +package mage.cards.d; + +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.Zone; +import mage.game.Game; +import mage.game.permanent.token.ZombieToken; +import mage.players.Player; +import mage.target.common.TargetCardInGraveyard; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class DelugeOfTheDead extends CardImpl { + + public DelugeOfTheDead(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, ""); + + this.color.setBlack(true); + this.nightCard = true; + + // When Deluge of the Dead enters the battlefield, create two 2/2 black Zombie creature tokens. + this.addAbility(new EntersBattlefieldTriggeredAbility(new CreateTokenEffect(new ZombieToken(), 2))); + + // {2}{B}: Exile target card from a graveyard. If it was a creature card, create a 2/2 black Zombie creature token. + Ability ability = new SimpleActivatedAbility(new DelugeOfTheDeadEffect(), new ManaCostsImpl<>("{2}{B}")); + ability.addTarget(new TargetCardInGraveyard()); + this.addAbility(ability); + } + + private DelugeOfTheDead(final DelugeOfTheDead card) { + super(card); + } + + @Override + public DelugeOfTheDead copy() { + return new DelugeOfTheDead(this); + } +} + +class DelugeOfTheDeadEffect extends OneShotEffect { + + DelugeOfTheDeadEffect() { + super(Outcome.Benefit); + staticText = "exile target card from a graveyard. " + + "If it was a creature card, create a 2/2 black Zombie creature token"; + } + + private DelugeOfTheDeadEffect(final DelugeOfTheDeadEffect effect) { + super(effect); + } + + @Override + public DelugeOfTheDeadEffect copy() { + return new DelugeOfTheDeadEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + Card card = game.getCard(getTargetPointer().getFirst(game, source)); + if (player == null || card == null) { + return false; + } + player.moveCards(card, Zone.EXILED, source, game); + if (card.isCreature(game)) { + new ZombieToken().putOntoBattlefield(1, game, source); + } + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/d/DrakusethMawOfFlames.java b/Mage.Sets/src/mage/cards/d/DrakusethMawOfFlames.java index d0ad46a3831..e97f6206fe2 100644 --- a/Mage.Sets/src/mage/cards/d/DrakusethMawOfFlames.java +++ b/Mage.Sets/src/mage/cards/d/DrakusethMawOfFlames.java @@ -11,13 +11,14 @@ import mage.constants.CardType; import mage.constants.Outcome; import mage.constants.SubType; import mage.constants.SuperType; -import mage.filter.common.FilterCreaturePlayerOrPlaneswalker; +import mage.filter.common.FilterAnyTarget; +import mage.filter.common.FilterPermanentOrPlayer; import mage.filter.predicate.other.AnotherTargetPredicate; import mage.game.Game; import mage.game.permanent.Permanent; import mage.players.Player; -import mage.target.Target; import mage.target.common.TargetAnyTarget; +import mage.target.common.TargetPermanentOrPlayer; import java.util.UUID; @@ -25,6 +26,12 @@ import java.util.UUID; * @author TheElk801 */ public final class DrakusethMawOfFlames extends CardImpl { + private static final FilterPermanentOrPlayer filter = new FilterAnyTarget("any target"); + + static { + filter.getPlayerFilter().add(new AnotherTargetPredicate(2, true)); + filter.getPermanentFilter().add(new AnotherTargetPredicate(2, true)); + } public DrakusethMawOfFlames(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{R}{R}{R}"); @@ -39,15 +46,10 @@ public final class DrakusethMawOfFlames extends CardImpl { // Whenever Drakuseth, Maw of Flames attacks, it deals 4 damage to any target and 3 damage to each of up to two other targets. Ability ability = new AttacksTriggeredAbility(new DrakusethMawOfFlamesEffect(), false); - Target target = new TargetAnyTarget().withChooseHint("to deal 4 damage"); - target.setTargetTag(1); - ability.addTarget(target); - FilterCreaturePlayerOrPlaneswalker filter = new FilterCreaturePlayerOrPlaneswalker("any target"); - filter.getPlayerFilter().add(new AnotherTargetPredicate(2, true)); - filter.getPermanentFilter().add(new AnotherTargetPredicate(2, true)); - target = new TargetAnyTarget(0, 2, filter).withChooseHint("to deal 3 damage"); - target.setTargetTag(2); - ability.addTarget(target); + ability.addTarget(new TargetAnyTarget().withChooseHint("to deal 4 damage").setTargetTag(1)); + ability.addTarget(new TargetPermanentOrPlayer( + 0, 2, filter, false + ).withChooseHint("to deal 3 damage").setTargetTag(2)); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/e/Endure.java b/Mage.Sets/src/mage/cards/e/Endure.java index 7287dd26cb1..9ed14ec33f2 100644 --- a/Mage.Sets/src/mage/cards/e/Endure.java +++ b/Mage.Sets/src/mage/cards/e/Endure.java @@ -6,7 +6,7 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Duration; import mage.constants.TargetController; -import mage.filter.common.FilterCreaturePlayerOrPlaneswalker; +import mage.filter.common.FilterPermanentOrPlayer; import java.util.UUID; @@ -15,11 +15,11 @@ import java.util.UUID; */ public final class Endure extends CardImpl { - private static final FilterCreaturePlayerOrPlaneswalker filter = new FilterCreaturePlayerOrPlaneswalker("you and permanents you control"); + private static final FilterPermanentOrPlayer filter = new FilterPermanentOrPlayer("you and permanents you control"); static { - filter.getPermanentFilter().add(TargetController.YOU.getControllerPredicate()); filter.getPlayerFilter().add(TargetController.YOU.getPlayerPredicate()); + filter.getPermanentFilter().add(TargetController.YOU.getControllerPredicate()); } public Endure(UUID ownerId, CardSetInfo setInfo) { @@ -27,7 +27,6 @@ public final class Endure extends CardImpl { // Prevent all damage that would be dealt to you and permanents you control this turn. this.getSpellAbility().addEffect(new PreventAllDamageToAllEffect(Duration.EndOfTurn, filter)); - } private Endure(final Endure card) { @@ -38,4 +37,4 @@ public final class Endure extends CardImpl { public Endure copy() { return new Endure(this); } -} \ No newline at end of file +} diff --git a/Mage.Sets/src/mage/cards/e/EpharaEverSheltering.java b/Mage.Sets/src/mage/cards/e/EpharaEverSheltering.java new file mode 100644 index 00000000000..8c7d100b2d7 --- /dev/null +++ b/Mage.Sets/src/mage/cards/e/EpharaEverSheltering.java @@ -0,0 +1,81 @@ +package mage.cards.e; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldControlledTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.Condition; +import mage.abilities.condition.common.PermanentsOnTheBattlefieldCondition; +import mage.abilities.decorator.ConditionalContinuousEffect; +import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.continuous.GainAbilitySourceEffect; +import mage.abilities.hint.Hint; +import mage.abilities.hint.ValueHint; +import mage.abilities.keyword.IndestructibleAbility; +import mage.abilities.keyword.LifelinkAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.ComparisonType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterControlledEnchantmentPermanent; +import mage.filter.predicate.mageobject.AnotherPredicate; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class EpharaEverSheltering extends CardImpl { + + private static final FilterPermanent filter = new FilterControlledEnchantmentPermanent("another enchantment"); + + static { + filter.add(AnotherPredicate.instance); + } + + private static final Condition condition = new PermanentsOnTheBattlefieldCondition( + filter, ComparisonType.MORE_THAN, 2, true + ); + private static final Hint hint = new ValueHint( + "Other enchantments you control", new PermanentsOnBattlefieldCount(filter) + ); + + public EpharaEverSheltering(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT, CardType.CREATURE}, ""); + + this.addSuperType(SuperType.LEGENDARY); + this.subtype.add(SubType.GOD); + this.power = new MageInt(4); + this.toughness = new MageInt(4); + this.color.setWhite(true); + this.color.setBlue(true); + this.nightCard = true; + + // Ephara, Ever-Sheltering has lifelink and indestructible as long as you control at least three other enchantments. + Ability ability = new SimpleStaticAbility(new ConditionalContinuousEffect( + new GainAbilitySourceEffect(LifelinkAbility.getInstance()), + condition, "{this} has lifelink" + )); + ability.addEffect(new ConditionalContinuousEffect(new GainAbilitySourceEffect( + IndestructibleAbility.getInstance()), condition, + "and indestructible as long as you control at least three other enchantments" + )); + this.addAbility(ability.addHint(hint)); + + // Whenever another enchantment enters the battlefield under your control, draw a card. + this.addAbility(new EntersBattlefieldControlledTriggeredAbility(new DrawCardSourceControllerEffect(1), filter)); + } + + private EpharaEverSheltering(final EpharaEverSheltering card) { + super(card); + } + + @Override + public EpharaEverSheltering copy() { + return new EpharaEverSheltering(this); + } +} diff --git a/Mage.Sets/src/mage/cards/e/ExplosiveWelcome.java b/Mage.Sets/src/mage/cards/e/ExplosiveWelcome.java index 680f87869e9..a38deebd541 100644 --- a/Mage.Sets/src/mage/cards/e/ExplosiveWelcome.java +++ b/Mage.Sets/src/mage/cards/e/ExplosiveWelcome.java @@ -8,10 +8,12 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.ManaType; -import mage.filter.common.FilterCreaturePlayerOrPlaneswalker; +import mage.filter.common.FilterAnyTarget; +import mage.filter.common.FilterPermanentOrPlayer; import mage.filter.predicate.other.AnotherTargetPredicate; import mage.target.Target; import mage.target.common.TargetAnyTarget; +import mage.target.common.TargetPermanentOrPlayer; import mage.target.targetpointer.SecondTargetPointer; import java.util.UUID; @@ -21,7 +23,7 @@ import java.util.UUID; */ public final class ExplosiveWelcome extends CardImpl { - private static final FilterCreaturePlayerOrPlaneswalker filter = new FilterCreaturePlayerOrPlaneswalker(); + private static final FilterPermanentOrPlayer filter = new FilterAnyTarget(); static { filter.getPermanentFilter().add(new AnotherTargetPredicate(2)); @@ -42,7 +44,7 @@ public final class ExplosiveWelcome extends CardImpl { Target target = new TargetAnyTarget(); target.setTargetTag(1); this.getSpellAbility().addTarget(target); - target = new TargetAnyTarget(filter); + target = new TargetPermanentOrPlayer(filter); target.setTargetTag(2); this.getSpellAbility().addTarget(target); } diff --git a/Mage.Sets/src/mage/cards/g/GaeasLiege.java b/Mage.Sets/src/mage/cards/g/GaeasLiege.java index d3cbf08466c..886fffb428d 100644 --- a/Mage.Sets/src/mage/cards/g/GaeasLiege.java +++ b/Mage.Sets/src/mage/cards/g/GaeasLiege.java @@ -1,41 +1,44 @@ package mage.cards.g; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.condition.InvertCondition; import mage.abilities.condition.common.SourceAttackingCondition; import mage.abilities.costs.common.TapSourceCost; import mage.abilities.decorator.ConditionalContinuousEffect; import mage.abilities.dynamicvalue.DynamicValue; import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; -import mage.abilities.effects.Effect; import mage.abilities.effects.common.continuous.BecomesBasicLandTargetEffect; import mage.abilities.effects.common.continuous.SetBasePowerToughnessSourceEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.*; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.constants.Zone; +import mage.filter.FilterPermanent; import mage.filter.common.FilterControlledPermanent; -import mage.filter.common.FilterLandPermanent; -import mage.game.Game; -import mage.game.combat.CombatGroup; -import mage.game.permanent.Permanent; +import mage.filter.predicate.permanent.DefendingPlayerControlsPredicate; import mage.target.common.TargetLandPermanent; +import java.util.UUID; + /** - * * @author anonymous */ public final class GaeasLiege extends CardImpl { - static final FilterControlledPermanent filterLands = new FilterControlledPermanent("Forests you control"); + private static final FilterPermanent filter = new FilterControlledPermanent(SubType.FOREST); + private static final FilterPermanent filter2 = new FilterPermanent(SubType.FOREST, ""); static { - filterLands.add(SubType.FOREST.getPredicate()); + filter2.add(DefendingPlayerControlsPredicate.instance); } + private static final DynamicValue xValue1 = new PermanentsOnBattlefieldCount(filter); + private static final DynamicValue xValue2 = new PermanentsOnBattlefieldCount(filter2); + public GaeasLiege(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{G}{G}{G}"); @@ -45,12 +48,18 @@ public final class GaeasLiege extends CardImpl { // As long as Gaea's Liege isn't attacking, its power and toughness are each equal to the number of Forests you control. As long as Gaea's Liege is attacking, its power and toughness are each equal to the number of Forests defending player controls. this.addAbility(new SimpleStaticAbility(Zone.ALL, new ConditionalContinuousEffect( - new SetBasePowerToughnessSourceEffect(new PermanentsOnBattlefieldCount(filterLands), Duration.EndOfGame, SubLayer.SetPT_7b), - new SetBasePowerToughnessSourceEffect(new DefendersForestCount(), Duration.EndOfCombat, SubLayer.SetPT_7b), - new InvertCondition(SourceAttackingCondition.instance), - "As long as {this} isn't attacking, its power and toughness are each equal to the number of Forests you control. As long as {this} is attacking, its power and toughness are each equal to the number of Forests defending player controls."))); + new SetBasePowerToughnessSourceEffect(xValue2, Duration.EndOfGame), + new SetBasePowerToughnessSourceEffect(xValue1, Duration.EndOfGame), + SourceAttackingCondition.instance, "as long as {this} isn't attacking, " + + "its power and toughness are each equal to the number of Forests you control. " + + "As long as {this} is attacking, its power and toughness are each equal " + + "to the number of Forests defending player controls" + ))); + // {T}: Target land becomes a Forest until Gaea's Liege leaves the battlefield. - Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new BecomesBasicLandTargetEffect(Duration.UntilSourceLeavesBattlefield, SubType.FOREST), new TapSourceCost()); + Ability ability = new SimpleActivatedAbility( + new BecomesBasicLandTargetEffect(Duration.UntilSourceLeavesBattlefield, SubType.FOREST), new TapSourceCost() + ); ability.addTarget(new TargetLandPermanent()); this.addAbility(ability); } @@ -64,42 +73,3 @@ public final class GaeasLiege extends CardImpl { return new GaeasLiege(this); } } - -class DefendersForestCount implements DynamicValue { - - @Override - public int calculate(Game game, Ability sourceAbility, Effect effect) { - for (CombatGroup group : game.getCombat().getGroups()) { - if (group.getAttackers().contains(sourceAbility.getSourceId())) { - UUID defenderId = group.getDefenderId(); - if (group.isDefenderIsPlaneswalker()) { - Permanent permanent = game.getPermanent(defenderId); - if (permanent != null) { - defenderId = permanent.getControllerId(); - } - } - - FilterLandPermanent filter = new FilterLandPermanent("forest"); - filter.add(SubType.FOREST.getPredicate()); - return game.getBattlefield().countAll(filter, defenderId, game); - - } - } - return 0; - } - - @Override - public DynamicValue copy() { - return new DefendersForestCount(); - } - - @Override - public String toString() { - return "X"; - } - - @Override - public String getMessage() { - return "the number of Forests defending player controls"; - } -} diff --git a/Mage.Sets/src/mage/cards/g/GargantuanSlabhorn.java b/Mage.Sets/src/mage/cards/g/GargantuanSlabhorn.java new file mode 100644 index 00000000000..dd19a481a83 --- /dev/null +++ b/Mage.Sets/src/mage/cards/g/GargantuanSlabhorn.java @@ -0,0 +1,66 @@ +package mage.cards.g; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; +import mage.abilities.keyword.TrampleAbility; +import mage.abilities.keyword.WardAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.filter.FilterPermanent; +import mage.filter.predicate.permanent.TransformedPredicate; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class GargantuanSlabhorn extends CardImpl { + + private static final FilterPermanent filter = new FilterPermanent("transformed permanents"); + + static { + filter.add(TransformedPredicate.instance); + } + + public GargantuanSlabhorn(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, ""); + + this.subtype.add(SubType.BEAST); + this.power = new MageInt(4); + this.toughness = new MageInt(4); + this.color.setBlue(true); + this.color.setGreen(true); + this.nightCard = true; + + // Trample + this.addAbility(TrampleAbility.getInstance()); + + // Ward {2} + this.addAbility(new WardAbility(new ManaCostsImpl<>("{2}"), false)); + + // Other transformed permanents you control have trample and ward {2}. + Ability ability = new SimpleStaticAbility(new GainAbilityControlledEffect( + TrampleAbility.getInstance(), Duration.WhileOnBattlefield, filter, true + )); + ability.addEffect(new GainAbilityControlledEffect( + new WardAbility(new GenericManaCost(2)), Duration.WhileOnBattlefield, filter, true + ).setText("and ward {2}")); + this.addAbility(ability); + } + + private GargantuanSlabhorn(final GargantuanSlabhorn card) { + super(card); + } + + @Override + public GargantuanSlabhorn copy() { + return new GargantuanSlabhorn(this); + } +} diff --git a/Mage.Sets/src/mage/cards/g/GrandmotherRaviSengir.java b/Mage.Sets/src/mage/cards/g/GrandmotherRaviSengir.java new file mode 100644 index 00000000000..8b78e3518f3 --- /dev/null +++ b/Mage.Sets/src/mage/cards/g/GrandmotherRaviSengir.java @@ -0,0 +1,55 @@ +package mage.cards.g; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.DiesCreatureTriggeredAbility; +import mage.abilities.effects.common.GainLifeEffect; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.counters.CounterType; +import mage.filter.StaticFilters; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class GrandmotherRaviSengir extends CardImpl { + + public GrandmotherRaviSengir(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, ""); + + this.addSuperType(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.WIZARD); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + this.color.setBlack(true); + this.nightCard = true; + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Whenever a creature an opponent controls dies, put a +1/+1 counter on Grandmother Ravi Sengir and you gain 1 life. + Ability ability = new DiesCreatureTriggeredAbility( + new AddCountersSourceEffect(CounterType.P1P1.createInstance()), + false, StaticFilters.FILTER_OPPONENTS_PERMANENT_A_CREATURE + ); + ability.addEffect(new GainLifeEffect(1).concatBy("and")); + this.addAbility(ability); + } + + private GrandmotherRaviSengir(final GrandmotherRaviSengir card) { + super(card); + } + + @Override + public GrandmotherRaviSengir copy() { + return new GrandmotherRaviSengir(this); + } +} diff --git a/Mage.Sets/src/mage/cards/g/GuildpactParagon.java b/Mage.Sets/src/mage/cards/g/GuildpactParagon.java new file mode 100644 index 00000000000..c0dd3510078 --- /dev/null +++ b/Mage.Sets/src/mage/cards/g/GuildpactParagon.java @@ -0,0 +1,63 @@ +package mage.cards.g; + +import mage.MageInt; +import mage.MageObject; +import mage.abilities.common.SpellCastControllerTriggeredAbility; +import mage.abilities.effects.common.LookLibraryAndPickControllerEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.PutCards; +import mage.constants.SubType; +import mage.filter.FilterCard; +import mage.filter.FilterSpell; +import mage.filter.predicate.Predicate; +import mage.game.Game; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class GuildpactParagon extends CardImpl { + + private static final FilterSpell filter = new FilterSpell("a spell that's exactly two colors"); + private static final FilterCard filter2 = new FilterCard("a card that's exactly two colors"); + + static { + filter.add(GuildpactParagonPredicate.instance); + filter2.add(GuildpactParagonPredicate.instance); + } + + public GuildpactParagon(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, ""); + + this.subtype.add(SubType.CONSTRUCT); + this.power = new MageInt(5); + this.toughness = new MageInt(5); + this.nightCard = true; + + // Whenever you cast a spell that's exactly two colors, look at the top six cards of your library. You may reveal a card that's exactly two colors from among them and put it into your hand. Put the rest on the bottom of your library in a random order. + this.addAbility(new SpellCastControllerTriggeredAbility(new LookLibraryAndPickControllerEffect( + 6, 1, filter2, PutCards.HAND, PutCards.BOTTOM_RANDOM + ), filter, false)); + } + + private GuildpactParagon(final GuildpactParagon card) { + super(card); + } + + @Override + public GuildpactParagon copy() { + return new GuildpactParagon(this); + } +} + +enum GuildpactParagonPredicate implements Predicate { + instance; + + @Override + public boolean apply(MageObject input, Game game) { + return input.getColor(game).getColorCount() == 2; + } +} diff --git a/Mage.Sets/src/mage/cards/i/ImperialGunner.java b/Mage.Sets/src/mage/cards/i/ImperialGunner.java index e0c96dc9e42..7b161bf5056 100644 --- a/Mage.Sets/src/mage/cards/i/ImperialGunner.java +++ b/Mage.Sets/src/mage/cards/i/ImperialGunner.java @@ -11,8 +11,9 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; import mage.constants.Zone; -import mage.filter.common.FilterCreaturePlayerOrPlaneswalker; -import mage.target.common.TargetAnyTarget; +import mage.filter.common.FilterPermanentOrPlayer; +import mage.filter.predicate.Predicates; +import mage.target.common.TargetPermanentOrPlayer; import java.util.UUID; @@ -21,8 +22,18 @@ import java.util.UUID; */ public final class ImperialGunner extends CardImpl { - private static final FilterCreaturePlayerOrPlaneswalker filter = new FilterCreaturePlayerOrPlaneswalker( - "target player, planeswalker or Starship creature", SubType.STARSHIP); + private static final FilterPermanentOrPlayer filter + = new FilterPermanentOrPlayer("player, planeswalker, or Starship creature"); + + static { + filter.getPermanentFilter().add(Predicates.or( + CardType.PLANESWALKER.getPredicate(), + Predicates.and( + CardType.CREATURE.getPredicate(), + SubType.STARSHIP.getPredicate() + ) + )); + } public ImperialGunner(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{R}"); @@ -33,7 +44,7 @@ public final class ImperialGunner extends CardImpl { // {1},{T}: Imperial Gunner deals 1 damage to target player or Starship creature. Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new DamageTargetEffect(1), new ManaCostsImpl<>("{1}")); - ability.addTarget(new TargetAnyTarget(filter)); + ability.addTarget(new TargetPermanentOrPlayer(filter)); ability.addCost(new TapSourceCost()); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/i/InvasionOfAlara.java b/Mage.Sets/src/mage/cards/i/InvasionOfAlara.java new file mode 100644 index 00000000000..078087f0f1c --- /dev/null +++ b/Mage.Sets/src/mage/cards/i/InvasionOfAlara.java @@ -0,0 +1,103 @@ +package mage.cards.i; + +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SiegeAbility; +import mage.abilities.effects.OneShotEffect; +import mage.cards.*; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.constants.Zone; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.players.Player; +import mage.target.TargetCard; +import mage.target.common.TargetCardInExile; +import mage.util.CardUtil; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class InvasionOfAlara extends CardImpl { + + public InvasionOfAlara(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.BATTLE}, "{W}{U}{B}{R}{G}"); + + this.subtype.add(SubType.SIEGE); + this.setStartingDefense(7); + this.secondSideCardClazz = mage.cards.a.AwakenTheMaelstrom.class; + + Ability ability = new SiegeAbility(); + ability.setRuleVisible(false); + this.addAbility(ability); + + // When Invasion of Alara enters the battlefield, exile cards from the top of your library until you exile two nonland cards with mana value 4 or less. You may cast one of those two cards without paying its mana cost. Put one of them into your hand. Then put the other cards exiled this way on the bottom of your library in a random order. + this.addAbility(new EntersBattlefieldTriggeredAbility(new InvasionOfAlaraEffect())); + } + + private InvasionOfAlara(final InvasionOfAlara card) { + super(card); + } + + @Override + public InvasionOfAlara copy() { + return new InvasionOfAlara(this); + } +} + +class InvasionOfAlaraEffect extends OneShotEffect { + + InvasionOfAlaraEffect() { + super(Outcome.Benefit); + staticText = "exile cards from the top of your library until you exile two nonland cards " + + "with mana value 4 or less. You may cast one of those two cards without paying its mana cost. " + + "Put one of them into your hand. Then put the other cards exiled this way on the bottom of your library in a random order"; + } + + private InvasionOfAlaraEffect(final InvasionOfAlaraEffect effect) { + super(effect); + } + + @Override + public InvasionOfAlaraEffect copy() { + return new InvasionOfAlaraEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null) { + return false; + } + Cards cards = new CardsImpl(); + Cards castable = new CardsImpl(); + int count = 0; + for (Card card : player.getLibrary().getCards(game)) { + player.moveCards(card, Zone.EXILED, source, game); + cards.add(card); + if (!card.isLand(game) && card.getManaValue() <= 4) { + castable.add(card); + count++; + } + if (count >= 2) { + break; + } + } + CardUtil.castSpellWithAttributesForFree(player, source, game, castable, StaticFilters.FILTER_CARD); + castable.retainZone(Zone.EXILED, game); + if (castable.size() > 1) { + TargetCard target = new TargetCardInExile(StaticFilters.FILTER_CARD); + target.setNotTarget(true); + player.choose(outcome, castable, target, game); + player.moveCards(game.getCard(target.getFirstTarget()), Zone.HAND, source, game); + } else { + player.moveCards(castable, Zone.HAND, source, game); + } + cards.retainZone(Zone.EXILED, game); + player.putCardsOnBottomOfLibrary(cards, game, source, false); + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/i/InvasionOfAmonkhet.java b/Mage.Sets/src/mage/cards/i/InvasionOfAmonkhet.java new file mode 100644 index 00000000000..804e097c03a --- /dev/null +++ b/Mage.Sets/src/mage/cards/i/InvasionOfAmonkhet.java @@ -0,0 +1,49 @@ +package mage.cards.i; + +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SiegeAbility; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.MillCardsEachPlayerEffect; +import mage.abilities.effects.common.discard.DiscardEachPlayerEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.TargetController; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class InvasionOfAmonkhet extends CardImpl { + + public InvasionOfAmonkhet(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.BATTLE}, "{1}{U}{B}"); + + this.subtype.add(SubType.SIEGE); + this.setStartingDefense(4); + this.secondSideCardClazz = mage.cards.l.LazotepConvert.class; + + // (As a Siege enters, choose an opponent to protect it. You and others can attack it. When it's defeated, exile it, then cast it transformed.) + this.addAbility(new SiegeAbility()); + + // When Invasion of Amonkhet enters the battlefield, each player mills three cards, then each opponent discards a card and you draw a card. + Ability ability = new EntersBattlefieldTriggeredAbility( + new MillCardsEachPlayerEffect(3, TargetController.EACH_PLAYER) + ); + ability.addEffect(new DiscardEachPlayerEffect(TargetController.OPPONENT).concatBy(", then")); + ability.addEffect(new DrawCardSourceControllerEffect(1).concatBy("and you")); + this.addAbility(ability); + } + + private InvasionOfAmonkhet(final InvasionOfAmonkhet card) { + super(card); + } + + @Override + public InvasionOfAmonkhet copy() { + return new InvasionOfAmonkhet(this); + } +} diff --git a/Mage.Sets/src/mage/cards/i/InvasionOfBelenon.java b/Mage.Sets/src/mage/cards/i/InvasionOfBelenon.java new file mode 100644 index 00000000000..717ac38bdc8 --- /dev/null +++ b/Mage.Sets/src/mage/cards/i/InvasionOfBelenon.java @@ -0,0 +1,41 @@ +package mage.cards.i; + +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SiegeAbility; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.game.permanent.token.KnightWhiteBlueToken; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class InvasionOfBelenon extends CardImpl { + + public InvasionOfBelenon(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.BATTLE}, "{2}{W}"); + + this.subtype.add(SubType.SIEGE); + this.setStartingDefense(5); + this.secondSideCardClazz = mage.cards.b.BelenonWarAnthem.class; + + // (As a Siege enters, choose an opponent to protect it. You and others can attack it. When it's defeated, exile it, then cast it transformed.) + this.addAbility(new SiegeAbility()); + + // When Invasion of Belenon enters the battlefield, create a 2/2 white and blue Knight creature token with vigilance. + this.addAbility(new EntersBattlefieldTriggeredAbility(new CreateTokenEffect(new KnightWhiteBlueToken()))); + } + + private InvasionOfBelenon(final InvasionOfBelenon card) { + super(card); + } + + @Override + public InvasionOfBelenon copy() { + return new InvasionOfBelenon(this); + } +} diff --git a/Mage.Sets/src/mage/cards/i/InvasionOfDominaria.java b/Mage.Sets/src/mage/cards/i/InvasionOfDominaria.java new file mode 100644 index 00000000000..37ac542843b --- /dev/null +++ b/Mage.Sets/src/mage/cards/i/InvasionOfDominaria.java @@ -0,0 +1,44 @@ +package mage.cards.i; + +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SiegeAbility; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.GainLifeEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class InvasionOfDominaria extends CardImpl { + + public InvasionOfDominaria(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.BATTLE}, "{2}{W}"); + + this.subtype.add(SubType.SIEGE); + this.setStartingDefense(5); + this.secondSideCardClazz = mage.cards.s.SerraFaithkeeper.class; + + // (As a Siege enters, choose an opponent to protect it. You and others can attack it. When it's defeated, exile it, then cast it transformed.) + this.addAbility(new SiegeAbility()); + + // When Invasion of Dominaria enters the battlefield, you gain 4 life and draw a card. + Ability ability = new EntersBattlefieldTriggeredAbility(new GainLifeEffect(4)); + ability.addEffect(new DrawCardSourceControllerEffect(1)); + this.addAbility(ability); + } + + private InvasionOfDominaria(final InvasionOfDominaria card) { + super(card); + } + + @Override + public InvasionOfDominaria copy() { + return new InvasionOfDominaria(this); + } +} diff --git a/Mage.Sets/src/mage/cards/i/InvasionOfEldraine.java b/Mage.Sets/src/mage/cards/i/InvasionOfEldraine.java new file mode 100644 index 00000000000..c42444b31d7 --- /dev/null +++ b/Mage.Sets/src/mage/cards/i/InvasionOfEldraine.java @@ -0,0 +1,44 @@ +package mage.cards.i; + +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SiegeAbility; +import mage.abilities.effects.common.discard.DiscardTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.target.common.TargetOpponent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class InvasionOfEldraine extends CardImpl { + + public InvasionOfEldraine(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.BATTLE}, "{3}{B}"); + + this.subtype.add(SubType.SIEGE); + this.setStartingDefense(4); + this.secondSideCardClazz = mage.cards.p.PrickleFaeries.class; + + // (As a Siege enters, choose an opponent to protect it. You and others can attack it. When it's defeated, exile it, then cast it transformed.) + this.addAbility(new SiegeAbility()); + + // When Invasion of Eldraine enters the battlefield, target opponent discards two cards. + Ability ability = new EntersBattlefieldTriggeredAbility(new DiscardTargetEffect(2)); + ability.addTarget(new TargetOpponent()); + this.addAbility(ability); + } + + private InvasionOfEldraine(final InvasionOfEldraine card) { + super(card); + } + + @Override + public InvasionOfEldraine copy() { + return new InvasionOfEldraine(this); + } +} diff --git a/Mage.Sets/src/mage/cards/i/InvasionOfErgamon.java b/Mage.Sets/src/mage/cards/i/InvasionOfErgamon.java new file mode 100644 index 00000000000..48f56f8f12c --- /dev/null +++ b/Mage.Sets/src/mage/cards/i/InvasionOfErgamon.java @@ -0,0 +1,47 @@ +package mage.cards.i; + +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SiegeAbility; +import mage.abilities.costs.common.DiscardCardCost; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.DoIfCostPaid; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.game.permanent.token.TreasureToken; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class InvasionOfErgamon extends CardImpl { + + public InvasionOfErgamon(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.BATTLE}, "{R}{G}"); + + this.subtype.add(SubType.SIEGE); + this.setStartingDefense(5); + this.secondSideCardClazz = mage.cards.t.TrugaCliffcharger.class; + + // (As a Siege enters, choose an opponent to protect it. You and others can attack it. When it's defeated, exile it, then cast it transformed.) + this.addAbility(new SiegeAbility()); + + // When Invasion of Ergamon enters the battlefield, create a Treasure token. Then you may discard a card. If you do, draw a card. + Ability ability = new EntersBattlefieldTriggeredAbility(new CreateTokenEffect(new TreasureToken())); + ability.addEffect(new DoIfCostPaid(new DrawCardSourceControllerEffect(1), new DiscardCardCost())); + this.addAbility(ability); + } + + private InvasionOfErgamon(final InvasionOfErgamon card) { + super(card); + } + + @Override + public InvasionOfErgamon copy() { + return new InvasionOfErgamon(this); + } +} diff --git a/Mage.Sets/src/mage/cards/i/InvasionOfInnistrad.java b/Mage.Sets/src/mage/cards/i/InvasionOfInnistrad.java new file mode 100644 index 00000000000..5505e05ceae --- /dev/null +++ b/Mage.Sets/src/mage/cards/i/InvasionOfInnistrad.java @@ -0,0 +1,48 @@ +package mage.cards.i; + +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SiegeAbility; +import mage.abilities.effects.common.continuous.BoostTargetEffect; +import mage.abilities.keyword.FlashAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.target.common.TargetOpponentsCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class InvasionOfInnistrad extends CardImpl { + + public InvasionOfInnistrad(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.BATTLE}, "{2}{B}{B}"); + + this.subtype.add(SubType.SIEGE); + this.setStartingDefense(5); + this.secondSideCardClazz = mage.cards.d.DelugeOfTheDead.class; + + // (As a Siege enters, choose an opponent to protect it. You and others can attack it. When it's defeated, exile it, then cast it transformed.) + this.addAbility(new SiegeAbility()); + + // Flash + this.addAbility(FlashAbility.getInstance()); + + // When Invasion of Innistrad enters the battlefield, target creature an opponent controls gets -13/-13 until end of turn. + Ability ability = new EntersBattlefieldTriggeredAbility(new BoostTargetEffect(-13, -13)); + ability.addTarget(new TargetOpponentsCreaturePermanent()); + this.addAbility(ability); + } + + private InvasionOfInnistrad(final InvasionOfInnistrad card) { + super(card); + } + + @Override + public InvasionOfInnistrad copy() { + return new InvasionOfInnistrad(this); + } +} diff --git a/Mage.Sets/src/mage/cards/i/InvasionOfIxalan.java b/Mage.Sets/src/mage/cards/i/InvasionOfIxalan.java new file mode 100644 index 00000000000..e9f5063c2d7 --- /dev/null +++ b/Mage.Sets/src/mage/cards/i/InvasionOfIxalan.java @@ -0,0 +1,45 @@ +package mage.cards.i; + +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SiegeAbility; +import mage.abilities.effects.common.LookLibraryAndPickControllerEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.PutCards; +import mage.constants.SubType; +import mage.filter.StaticFilters; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class InvasionOfIxalan extends CardImpl { + + public InvasionOfIxalan(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.BATTLE}, "{1}{G}"); + + this.subtype.add(SubType.SIEGE); + this.setStartingDefense(4); + this.secondSideCardClazz = mage.cards.b.BelligerentRegisaur.class; + + // (As a Siege enters, choose an opponent to protect it. You and others can attack it. When it's defeated, exile it, then cast it transformed.) + this.addAbility(new SiegeAbility()); + + // When Invasion of Ixalan enters the battlefield, look at the top five cards of your library. You may reveal a permanent card from among them and put it into your hand. Put the rest on the bottom of your library in a random order. + this.addAbility(new EntersBattlefieldTriggeredAbility(new LookLibraryAndPickControllerEffect( + 5, 1, StaticFilters.FILTER_CARD_A_PERMANENT, + PutCards.HAND, PutCards.BOTTOM_RANDOM + ))); + } + + private InvasionOfIxalan(final InvasionOfIxalan card) { + super(card); + } + + @Override + public InvasionOfIxalan copy() { + return new InvasionOfIxalan(this); + } +} diff --git a/Mage.Sets/src/mage/cards/i/InvasionOfKaladesh.java b/Mage.Sets/src/mage/cards/i/InvasionOfKaladesh.java new file mode 100644 index 00000000000..c4ed5fb6da3 --- /dev/null +++ b/Mage.Sets/src/mage/cards/i/InvasionOfKaladesh.java @@ -0,0 +1,41 @@ +package mage.cards.i; + +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SiegeAbility; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.game.permanent.token.ThopterColorlessToken; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class InvasionOfKaladesh extends CardImpl { + + public InvasionOfKaladesh(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.BATTLE}, "{U}{R}"); + + this.subtype.add(SubType.SIEGE); + this.setStartingDefense(4); + this.secondSideCardClazz = mage.cards.a.AetherwingGoldenScaleFlagship.class; + + // (As a Siege enters, choose an opponent to protect it. You and others can attack it. When it's defeated, exile it, then cast it transformed.) + this.addAbility(new SiegeAbility()); + + // When Invasion of Kaladesh enters the battlefield, create a 1/1 colorless Thopter artifact creature token with flying. + this.addAbility(new EntersBattlefieldTriggeredAbility(new CreateTokenEffect(new ThopterColorlessToken()))); + } + + private InvasionOfKaladesh(final InvasionOfKaladesh card) { + super(card); + } + + @Override + public InvasionOfKaladesh copy() { + return new InvasionOfKaladesh(this); + } +} diff --git a/Mage.Sets/src/mage/cards/i/InvasionOfKaldheim.java b/Mage.Sets/src/mage/cards/i/InvasionOfKaldheim.java new file mode 100644 index 00000000000..52f0350e0b5 --- /dev/null +++ b/Mage.Sets/src/mage/cards/i/InvasionOfKaldheim.java @@ -0,0 +1,75 @@ +package mage.cards.i; + +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SiegeAbility; +import mage.abilities.effects.OneShotEffect; +import mage.cards.*; +import mage.constants.*; +import mage.game.Game; +import mage.players.Player; +import mage.util.CardUtil; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class InvasionOfKaldheim extends CardImpl { + + public InvasionOfKaldheim(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.BATTLE}, "{3}{R}"); + + this.subtype.add(SubType.SIEGE); + this.setStartingDefense(4); + this.secondSideCardClazz = mage.cards.p.PyreOfTheWorldTree.class; + + // (As a Siege enters, choose an opponent to protect it. You and others can attack it. When it's defeated, exile it, then cast it transformed.) + this.addAbility(new SiegeAbility()); + + // When Invasion of Kaldheim enters the battlefield, exile all cards from your hand, then draw that many cards. Until the end of your next turn, you may play cards exiled this way. + this.addAbility(new EntersBattlefieldTriggeredAbility(new InvasionOfKaldheimEffect())); + } + + private InvasionOfKaldheim(final InvasionOfKaldheim card) { + super(card); + } + + @Override + public InvasionOfKaldheim copy() { + return new InvasionOfKaldheim(this); + } +} + +class InvasionOfKaldheimEffect extends OneShotEffect { + + InvasionOfKaldheimEffect() { + super(Outcome.Benefit); + staticText = "exile all cards from your hand, then draw that many cards. " + + "Until the end of your next turn, you may play cards exiled this way"; + } + + private InvasionOfKaldheimEffect(final InvasionOfKaldheimEffect effect) { + super(effect); + } + + @Override + public InvasionOfKaldheimEffect copy() { + return new InvasionOfKaldheimEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null || player.getHand().isEmpty()) { + return false; + } + Cards cards = new CardsImpl(player.getHand()); + player.moveCards(cards, Zone.EXILED, source, game); + player.drawCards(cards.size(), source, game); + for (Card card : cards.getCards(game)) { + CardUtil.makeCardPlayable(game, source, card, Duration.UntilEndOfYourNextTurn, false); + } + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/i/InvasionOfKamigawa.java b/Mage.Sets/src/mage/cards/i/InvasionOfKamigawa.java new file mode 100644 index 00000000000..fbbba61f607 --- /dev/null +++ b/Mage.Sets/src/mage/cards/i/InvasionOfKamigawa.java @@ -0,0 +1,48 @@ +package mage.cards.i; + +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SiegeAbility; +import mage.abilities.effects.common.TapTargetEffect; +import mage.abilities.effects.common.counter.AddCountersTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.counters.CounterType; +import mage.filter.StaticFilters; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class InvasionOfKamigawa extends CardImpl { + + public InvasionOfKamigawa(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.BATTLE}, "{3}{U}"); + + this.subtype.add(SubType.SIEGE); + this.setStartingDefense(4); + this.secondSideCardClazz = mage.cards.r.RooftopSaboteurs.class; + + // (As a Siege enters, choose an opponent to protect it. You and others can attack it. When it's defeated, exile it, then cast it transformed.) + this.addAbility(new SiegeAbility()); + + // When Invasion of Kamigawa enters the battlefield, tap target artifact or creature an opponent controls and put a stun counter on it. + Ability ability = new EntersBattlefieldTriggeredAbility(new TapTargetEffect()); + ability.addEffect(new AddCountersTargetEffect(CounterType.STUN.createInstance()).setText("and put a stun counter on it")); + ability.addTarget(new TargetPermanent(StaticFilters.FILTER_OPPONENTS_PERMANENT_ARTIFACT_OR_CREATURE)); + this.addAbility(ability); + } + + private InvasionOfKamigawa(final InvasionOfKamigawa card) { + super(card); + } + + @Override + public InvasionOfKamigawa copy() { + return new InvasionOfKamigawa(this); + } +} diff --git a/Mage.Sets/src/mage/cards/i/InvasionOfKarsus.java b/Mage.Sets/src/mage/cards/i/InvasionOfKarsus.java new file mode 100644 index 00000000000..3e0ad498ef9 --- /dev/null +++ b/Mage.Sets/src/mage/cards/i/InvasionOfKarsus.java @@ -0,0 +1,47 @@ +package mage.cards.i; + +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SiegeAbility; +import mage.abilities.effects.common.DamageAllEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterCreatureOrPlaneswalkerPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class InvasionOfKarsus extends CardImpl { + + private static final FilterPermanent filter + = new FilterCreatureOrPlaneswalkerPermanent("creature and each planeswalker"); + + public InvasionOfKarsus(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.BATTLE}, "{2}{R}{R}"); + + this.subtype.add(SubType.SIEGE); + this.setStartingDefense(4); + this.secondSideCardClazz = mage.cards.r.RefractionElemental.class; + + // (As a Siege enters, choose an opponent to protect it. You and others can attack it. When it's defeated, exile it, then cast it transformed.) + this.addAbility(new SiegeAbility()); + + // When Invasion of Karsus enters the battlefield, it deals 3 damage to each creature and each planeswalker. + this.addAbility(new EntersBattlefieldTriggeredAbility( + new DamageAllEffect(3, "it", filter) + )); + } + + private InvasionOfKarsus(final InvasionOfKarsus card) { + super(card); + } + + @Override + public InvasionOfKarsus copy() { + return new InvasionOfKarsus(this); + } +} diff --git a/Mage.Sets/src/mage/cards/i/InvasionOfLorwyn.java b/Mage.Sets/src/mage/cards/i/InvasionOfLorwyn.java new file mode 100644 index 00000000000..128f73733a2 --- /dev/null +++ b/Mage.Sets/src/mage/cards/i/InvasionOfLorwyn.java @@ -0,0 +1,74 @@ +package mage.cards.i; + +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SiegeAbility; +import mage.abilities.effects.common.DestroyTargetEffect; +import mage.abilities.hint.common.LandsYouControlHint; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.filter.FilterPermanent; +import mage.filter.StaticFilters; +import mage.filter.common.FilterOpponentsCreaturePermanent; +import mage.filter.predicate.ObjectSourcePlayer; +import mage.filter.predicate.ObjectSourcePlayerPredicate; +import mage.filter.predicate.Predicates; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class InvasionOfLorwyn extends CardImpl { + + private static final FilterPermanent filter = new FilterOpponentsCreaturePermanent( + "non-Elf creature an opponent controls with power X or less, where X is the number of lands you control" + ); + + static { + filter.add(Predicates.not(SubType.ELF.getPredicate())); + filter.add(InvasionOfLorwynPredicate.instance); + } + + public InvasionOfLorwyn(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.BATTLE}, "{4}{B}{G}"); + + this.subtype.add(SubType.SIEGE); + this.setStartingDefense(5); + this.secondSideCardClazz = mage.cards.w.WinnowingForces.class; + + // (As a Siege enters, choose an opponent to protect it. You and others can attack it. When it's defeated, exile it, then cast it transformed.) + this.addAbility(new SiegeAbility()); + + // When Invasion of Lorwyn enters the battlefield, destroy target non-Elf creature an opponent controls with power X or less, where X is the number of lands you control. + Ability ability = new EntersBattlefieldTriggeredAbility(new DestroyTargetEffect()); + ability.addTarget(new TargetPermanent(filter)); + this.addAbility(ability.addHint(LandsYouControlHint.instance)); + } + + private InvasionOfLorwyn(final InvasionOfLorwyn card) { + super(card); + } + + @Override + public InvasionOfLorwyn copy() { + return new InvasionOfLorwyn(this); + } +} + +enum InvasionOfLorwynPredicate implements ObjectSourcePlayerPredicate { + instance; + + @Override + public boolean apply(ObjectSourcePlayer input, Game game) { + return input.getObject().getPower().getValue() <= game.getBattlefield().count( + StaticFilters.FILTER_CONTROLLED_PERMANENT_LAND, + input.getSource().getControllerId(), input.getSource(), game + ); + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/i/InvasionOfMercadia.java b/Mage.Sets/src/mage/cards/i/InvasionOfMercadia.java new file mode 100644 index 00000000000..fc5c4e94077 --- /dev/null +++ b/Mage.Sets/src/mage/cards/i/InvasionOfMercadia.java @@ -0,0 +1,42 @@ +package mage.cards.i; + +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SiegeAbility; +import mage.abilities.costs.common.DiscardCardCost; +import mage.abilities.effects.common.DoIfCostPaid; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class InvasionOfMercadia extends CardImpl { + + public InvasionOfMercadia(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.BATTLE}, "{1}{R}"); + + this.subtype.add(SubType.SIEGE); + this.setStartingDefense(4); + this.secondSideCardClazz = mage.cards.k.KyrenFlamewright.class; + + // (As a Siege enters, choose an opponent to protect it. You and others can attack it. When it's defeated, exile it, then cast it transformed.) + this.addAbility(new SiegeAbility()); + + // When Invasion of Mercadia enters the battlefield, you may discard a card. If you do, draw two cards. + this.addAbility(new EntersBattlefieldTriggeredAbility(new DoIfCostPaid(new DrawCardSourceControllerEffect(2), new DiscardCardCost()))); + } + + private InvasionOfMercadia(final InvasionOfMercadia card) { + super(card); + } + + @Override + public InvasionOfMercadia copy() { + return new InvasionOfMercadia(this); + } +} diff --git a/Mage.Sets/src/mage/cards/i/InvasionOfMoag.java b/Mage.Sets/src/mage/cards/i/InvasionOfMoag.java new file mode 100644 index 00000000000..00f81345e85 --- /dev/null +++ b/Mage.Sets/src/mage/cards/i/InvasionOfMoag.java @@ -0,0 +1,44 @@ +package mage.cards.i; + +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SiegeAbility; +import mage.abilities.effects.common.counter.AddCountersAllEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.counters.CounterType; +import mage.filter.StaticFilters; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class InvasionOfMoag extends CardImpl { + + public InvasionOfMoag(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.BATTLE}, "{2}{G}{W}"); + + this.subtype.add(SubType.SIEGE); + this.setStartingDefense(5); + this.secondSideCardClazz = mage.cards.b.BloomwielderDryads.class; + + // (As a Siege enters, choose an opponent to protect it. You and others can attack it. When it's defeated, exile it, then cast it transformed.) + this.addAbility(new SiegeAbility()); + + // When Invasion of Moag enters the battlefield, put a +1/+1 counter on each creature you control. + this.addAbility(new EntersBattlefieldTriggeredAbility(new AddCountersAllEffect( + CounterType.P1P1.createInstance(), StaticFilters.FILTER_CONTROLLED_CREATURE + ))); + } + + private InvasionOfMoag(final InvasionOfMoag card) { + super(card); + } + + @Override + public InvasionOfMoag copy() { + return new InvasionOfMoag(this); + } +} diff --git a/Mage.Sets/src/mage/cards/i/InvasionOfPyrulea.java b/Mage.Sets/src/mage/cards/i/InvasionOfPyrulea.java new file mode 100644 index 00000000000..16fdc1768ec --- /dev/null +++ b/Mage.Sets/src/mage/cards/i/InvasionOfPyrulea.java @@ -0,0 +1,75 @@ +package mage.cards.i; + +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SiegeAbility; +import mage.abilities.effects.OneShotEffect; +import mage.cards.*; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.game.Game; +import mage.players.Player; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class InvasionOfPyrulea extends CardImpl { + + public InvasionOfPyrulea(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.BATTLE}, "{G}{U}"); + + this.subtype.add(SubType.SIEGE); + this.setStartingDefense(4); + this.secondSideCardClazz = mage.cards.g.GargantuanSlabhorn.class; + + // (As a Siege enters, choose an opponent to protect it. You and others can attack it. When it's defeated, exile it, then cast it transformed.) + this.addAbility(new SiegeAbility()); + + // When Invasion of Pyrulea enters the battlefield, scry 3, then reveal the top card of your library. If it's a land or double-faced card, draw a card. + this.addAbility(new EntersBattlefieldTriggeredAbility(new InvasionOfPyruleaEffect())); + } + + private InvasionOfPyrulea(final InvasionOfPyrulea card) { + super(card); + } + + @Override + public InvasionOfPyrulea copy() { + return new InvasionOfPyrulea(this); + } +} + +class InvasionOfPyruleaEffect extends OneShotEffect { + + InvasionOfPyruleaEffect() { + super(Outcome.Benefit); + staticText = "scry 3, then reveal the top card of your library. If it's a land or double-faced card, draw a card"; + } + + private InvasionOfPyruleaEffect(final InvasionOfPyruleaEffect effect) { + super(effect); + } + + @Override + public InvasionOfPyruleaEffect copy() { + return new InvasionOfPyruleaEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null) { + return false; + } + player.scry(3, source, game); + Card card = player.getLibrary().getFromTop(game); + player.revealCards(source, new CardsImpl(card), game); + if (card != null && (card.isLand(game) || card instanceof ModalDoubleFacesCard || card.getSecondCardFace() != null)) { + player.drawCards(1, source, game); + } + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/i/InvasionOfRavnica.java b/Mage.Sets/src/mage/cards/i/InvasionOfRavnica.java new file mode 100644 index 00000000000..49b3a60b26f --- /dev/null +++ b/Mage.Sets/src/mage/cards/i/InvasionOfRavnica.java @@ -0,0 +1,66 @@ +package mage.cards.i; + +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SiegeAbility; +import mage.abilities.effects.common.ExileTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.TargetController; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterNonlandPermanent; +import mage.filter.predicate.Predicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class InvasionOfRavnica extends CardImpl { + + private static final FilterPermanent filter = new FilterNonlandPermanent("nonland permanent an opponent controls that isn't exactly two colors"); + + static { + filter.add(TargetController.OPPONENT.getControllerPredicate()); + filter.add(InvasionOfRavnicaPredicate.instance); + } + + public InvasionOfRavnica(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.BATTLE}, "{5}"); + + this.subtype.add(SubType.SIEGE); + this.setStartingDefense(4); + this.secondSideCardClazz = mage.cards.g.GuildpactParagon.class; + + // (As a Siege enters, choose an opponent to protect it. You and others can attack it. When it's defeated, exile it, then cast it transformed.) + this.addAbility(new SiegeAbility()); + + // When Invasion of Ravnica enters the battlefield, exile target nonland permanent an opponent controls that isn't exactly two colors. + Ability ability = new EntersBattlefieldTriggeredAbility(new ExileTargetEffect()); + ability.addTarget(new TargetPermanent(filter)); + this.addAbility(ability); + } + + private InvasionOfRavnica(final InvasionOfRavnica card) { + super(card); + } + + @Override + public InvasionOfRavnica copy() { + return new InvasionOfRavnica(this); + } +} + +enum InvasionOfRavnicaPredicate implements Predicate { + instance; + + @Override + public boolean apply(Permanent input, Game game) { + return input.getColor(game).getColorCount() != 2; + } +} diff --git a/Mage.Sets/src/mage/cards/i/InvasionOfShandalar.java b/Mage.Sets/src/mage/cards/i/InvasionOfShandalar.java new file mode 100644 index 00000000000..c1d9fa06b7a --- /dev/null +++ b/Mage.Sets/src/mage/cards/i/InvasionOfShandalar.java @@ -0,0 +1,45 @@ +package mage.cards.i; + +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SiegeAbility; +import mage.abilities.effects.common.ReturnFromGraveyardToHandTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.filter.StaticFilters; +import mage.target.common.TargetCardInYourGraveyard; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class InvasionOfShandalar extends CardImpl { + + public InvasionOfShandalar(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.BATTLE}, "{3}{G}{G}"); + + this.subtype.add(SubType.SIEGE); + this.setStartingDefense(4); + this.secondSideCardClazz = mage.cards.l.LeylineSurge.class; + + // (As a Siege enters, choose an opponent to protect it. You and others can attack it. When it's defeated, exile it, then cast it transformed.) + this.addAbility(new SiegeAbility()); + + // When Invasion of Shandalar enters the battlefield, return up to three target permanent cards from your graveyard to your hand. + Ability ability = new EntersBattlefieldTriggeredAbility(new ReturnFromGraveyardToHandTargetEffect()); + ability.addTarget(new TargetCardInYourGraveyard(0, 3, StaticFilters.FILTER_CARD_PERMANENTS)); + this.addAbility(ability); + } + + private InvasionOfShandalar(final InvasionOfShandalar card) { + super(card); + } + + @Override + public InvasionOfShandalar copy() { + return new InvasionOfShandalar(this); + } +} diff --git a/Mage.Sets/src/mage/cards/i/InvasionOfTheros.java b/Mage.Sets/src/mage/cards/i/InvasionOfTheros.java new file mode 100644 index 00000000000..4e8111d1050 --- /dev/null +++ b/Mage.Sets/src/mage/cards/i/InvasionOfTheros.java @@ -0,0 +1,55 @@ +package mage.cards.i; + +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SiegeAbility; +import mage.abilities.effects.common.search.SearchLibraryPutInHandEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.filter.FilterCard; +import mage.filter.predicate.Predicates; +import mage.target.common.TargetCardInLibrary; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class InvasionOfTheros extends CardImpl { + + private static final FilterCard filter = new FilterCard("an Aura, God, or Demigod card"); + + static { + filter.add(Predicates.or( + SubType.AURA.getPredicate(), + SubType.GOD.getPredicate(), + SubType.DEMIGOD.getPredicate() + )); + } + + public InvasionOfTheros(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.BATTLE}, "{2}{W}"); + + this.subtype.add(SubType.SIEGE); + this.setStartingDefense(4); + this.secondSideCardClazz = mage.cards.e.EpharaEverSheltering.class; + + // (As a Siege enters, choose an opponent to protect it. You and others can attack it. When it's defeated, exile it, then cast it transformed.) + this.addAbility(new SiegeAbility()); + + // When Invasion of Theros enters the battlefield, search your library for an Aura, God, or Demigod card, reveal it, put it into your hand, then shuffle. + this.addAbility(new EntersBattlefieldTriggeredAbility( + new SearchLibraryPutInHandEffect(new TargetCardInLibrary(filter), true, true) + )); + } + + private InvasionOfTheros(final InvasionOfTheros card) { + super(card); + } + + @Override + public InvasionOfTheros copy() { + return new InvasionOfTheros(this); + } +} diff --git a/Mage.Sets/src/mage/cards/i/InvasionOfTolvada.java b/Mage.Sets/src/mage/cards/i/InvasionOfTolvada.java new file mode 100644 index 00000000000..b1197325294 --- /dev/null +++ b/Mage.Sets/src/mage/cards/i/InvasionOfTolvada.java @@ -0,0 +1,53 @@ +package mage.cards.i; + +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SiegeAbility; +import mage.abilities.effects.common.ReturnFromGraveyardToHandTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.filter.FilterCard; +import mage.filter.common.FilterPermanentCard; +import mage.filter.predicate.Predicates; +import mage.target.common.TargetCardInYourGraveyard; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class InvasionOfTolvada extends CardImpl { + + private static final FilterCard filter = new FilterPermanentCard("nonbattle permanent card from your graveyard"); + + static { + filter.add(Predicates.not(CardType.BATTLE.getPredicate())); + } + + public InvasionOfTolvada(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.BATTLE}, "{3}{W}{B}"); + + this.subtype.add(SubType.SIEGE); + this.setStartingDefense(5); + this.secondSideCardClazz = mage.cards.t.TheBrokenSky.class; + + // (As a Siege enters, choose an opponent to protect it. You and others can attack it. When it's defeated, exile it, then cast it transformed.) + this.addAbility(new SiegeAbility()); + + // When Invasion of Tolvada enters the battlefield, return target nonbattle permanent card from your graveyard to the battlefield. + Ability ability = new EntersBattlefieldTriggeredAbility(new ReturnFromGraveyardToHandTargetEffect()); + ability.addTarget(new TargetCardInYourGraveyard(filter)); + this.addAbility(ability); + } + + private InvasionOfTolvada(final InvasionOfTolvada card) { + super(card); + } + + @Override + public InvasionOfTolvada copy() { + return new InvasionOfTolvada(this); + } +} diff --git a/Mage.Sets/src/mage/cards/i/InvasionOfUlgrotha.java b/Mage.Sets/src/mage/cards/i/InvasionOfUlgrotha.java new file mode 100644 index 00000000000..41aa12198ec --- /dev/null +++ b/Mage.Sets/src/mage/cards/i/InvasionOfUlgrotha.java @@ -0,0 +1,55 @@ +package mage.cards.i; + +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SiegeAbility; +import mage.abilities.effects.common.DamageTargetEffect; +import mage.abilities.effects.common.GainLifeEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.filter.common.FilterAnyTarget; +import mage.filter.common.FilterPermanentOrPlayer; +import mage.filter.predicate.mageobject.AnotherPredicate; +import mage.target.common.TargetPermanentOrPlayer; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class InvasionOfUlgrotha extends CardImpl { + + private static final FilterPermanentOrPlayer filter = new FilterAnyTarget("any other target"); + + static { + filter.getPermanentFilter().add(AnotherPredicate.instance); + } + + public InvasionOfUlgrotha(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.BATTLE}, "{4}{B}"); + + this.subtype.add(SubType.SIEGE); + this.setStartingDefense(5); + this.secondSideCardClazz = mage.cards.g.GrandmotherRaviSengir.class; + + // (As a Siege enters, choose an opponent to protect it. You and others can attack it. When it's defeated, exile it, then cast it transformed.) + this.addAbility(new SiegeAbility()); + + // When Invasion of Ulgrotha enters the battlefield, it deals 3 damage to any other target and you gain 3 life. + Ability ability = new EntersBattlefieldTriggeredAbility(new DamageTargetEffect(3)); + ability.addEffect(new GainLifeEffect(3).concatBy("and")); + ability.addTarget(new TargetPermanentOrPlayer(filter)); + this.addAbility(ability); + } + + private InvasionOfUlgrotha(final InvasionOfUlgrotha card) { + super(card); + } + + @Override + public InvasionOfUlgrotha copy() { + return new InvasionOfUlgrotha(this); + } +} diff --git a/Mage.Sets/src/mage/cards/i/InvasionOfVryn.java b/Mage.Sets/src/mage/cards/i/InvasionOfVryn.java new file mode 100644 index 00000000000..c4107e3b0fe --- /dev/null +++ b/Mage.Sets/src/mage/cards/i/InvasionOfVryn.java @@ -0,0 +1,40 @@ +package mage.cards.i; + +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SiegeAbility; +import mage.abilities.effects.common.DrawDiscardControllerEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class InvasionOfVryn extends CardImpl { + + public InvasionOfVryn(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.BATTLE}, "{3}{U}"); + + this.subtype.add(SubType.SIEGE); + this.setStartingDefense(4); + this.secondSideCardClazz = mage.cards.o.OverloadedMageRing.class; + + // (As a Siege enters, choose an opponent to protect it. You and others can attack it. When it's defeated, exile it, then cast it transformed.) + this.addAbility(new SiegeAbility()); + + // When Invasion of Vryn enters the battlefield, draw three cards, then discard a card. + this.addAbility(new EntersBattlefieldTriggeredAbility(new DrawDiscardControllerEffect(3, 1))); + } + + private InvasionOfVryn(final InvasionOfVryn card) { + super(card); + } + + @Override + public InvasionOfVryn copy() { + return new InvasionOfVryn(this); + } +} diff --git a/Mage.Sets/src/mage/cards/i/InvasionOfXerex.java b/Mage.Sets/src/mage/cards/i/InvasionOfXerex.java new file mode 100644 index 00000000000..fa5253a7bc9 --- /dev/null +++ b/Mage.Sets/src/mage/cards/i/InvasionOfXerex.java @@ -0,0 +1,44 @@ +package mage.cards.i; + +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SiegeAbility; +import mage.abilities.effects.common.ReturnToHandTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class InvasionOfXerex extends CardImpl { + + public InvasionOfXerex(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.BATTLE}, "{2}{W}{U}"); + + this.subtype.add(SubType.SIEGE); + this.setStartingDefense(4); + this.secondSideCardClazz = mage.cards.v.VertexPaladin.class; + + // (As a Siege enters, choose an opponent to protect it. You and others can attack it. When it's defeated, exile it, then cast it transformed.) + this.addAbility(new SiegeAbility()); + + // When Invasion of Xerex enters the battlefield, return up to one target creature to its owner's hand. + Ability ability = new EntersBattlefieldTriggeredAbility(new ReturnToHandTargetEffect()); + ability.addTarget(new TargetCreaturePermanent(0, 1)); + this.addAbility(ability); + } + + private InvasionOfXerex(final InvasionOfXerex card) { + super(card); + } + + @Override + public InvasionOfXerex copy() { + return new InvasionOfXerex(this); + } +} diff --git a/Mage.Sets/src/mage/cards/j/JoyfulStormsculptor.java b/Mage.Sets/src/mage/cards/j/JoyfulStormsculptor.java index 3800de51492..01fe43e9644 100644 --- a/Mage.Sets/src/mage/cards/j/JoyfulStormsculptor.java +++ b/Mage.Sets/src/mage/cards/j/JoyfulStormsculptor.java @@ -15,6 +15,7 @@ import mage.constants.SubType; import mage.constants.TargetController; import mage.filter.FilterPermanent; import mage.filter.FilterSpell; +import mage.filter.common.FilterBattlePermanent; import mage.filter.predicate.mageobject.AbilityPredicate; import mage.filter.predicate.permanent.ProtectedByOpponentPredicate; import mage.game.permanent.token.Elemental11BlueRedToken; @@ -27,11 +28,10 @@ import java.util.UUID; public final class JoyfulStormsculptor extends CardImpl { private static final FilterSpell filter = new FilterSpell("a spell that has convoke"); - private static final FilterPermanent filter2 = new FilterPermanent(); + private static final FilterPermanent filter2 = new FilterBattlePermanent(); static { filter.add(new AbilityPredicate(ConvokeAbility.class)); - filter2.add(CardType.BATTLE.getPredicate()); filter2.add(ProtectedByOpponentPredicate.instance); } diff --git a/Mage.Sets/src/mage/cards/k/KyrenFlamewright.java b/Mage.Sets/src/mage/cards/k/KyrenFlamewright.java new file mode 100644 index 00000000000..d7fd866fe90 --- /dev/null +++ b/Mage.Sets/src/mage/cards/k/KyrenFlamewright.java @@ -0,0 +1,59 @@ +package mage.cards.k; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.DiscardCardCost; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.continuous.BoostControlledEffect; +import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; +import mage.abilities.keyword.HasteAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.game.permanent.token.Elemental11BlueRedToken; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class KyrenFlamewright extends CardImpl { + + public KyrenFlamewright(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, ""); + + this.subtype.add(SubType.GOBLIN); + this.subtype.add(SubType.SPELLSHAPER); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + this.color.setRed(true); + this.nightCard = true; + + // {2}{R}, {T}, Discard a card: Create two 1/1 blue and red Elemental creature tokens. Creatures you control get +1/+0 and gain haste until end of turn. + Ability ability = new SimpleActivatedAbility( + new CreateTokenEffect(new Elemental11BlueRedToken(), 2), new ManaCostsImpl<>("{2}{R}") + ); + ability.addCost(new TapSourceCost()); + ability.addCost(new DiscardCardCost()); + ability.addEffect(new BoostControlledEffect(1, 0, Duration.EndOfTurn) + .setText("creatures you control get +1/+0")); + ability.addEffect(new GainAbilityControlledEffect( + HasteAbility.getInstance(), Duration.EndOfTurn + ).setText("and gain haste until end of turn")); + this.addAbility(ability); + } + + private KyrenFlamewright(final KyrenFlamewright card) { + super(card); + } + + @Override + public KyrenFlamewright copy() { + return new KyrenFlamewright(this); + } +} diff --git a/Mage.Sets/src/mage/cards/l/LazotepConvert.java b/Mage.Sets/src/mage/cards/l/LazotepConvert.java new file mode 100644 index 00000000000..73e43f25818 --- /dev/null +++ b/Mage.Sets/src/mage/cards/l/LazotepConvert.java @@ -0,0 +1,106 @@ +package mage.cards.l; + +import mage.MageInt; +import mage.MageObject; +import mage.ObjectColor; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.CopyEffect; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.filter.FilterCard; +import mage.filter.common.FilterCreatureCard; +import mage.game.Game; +import mage.players.Player; +import mage.target.Target; +import mage.target.common.TargetCardInGraveyard; +import mage.util.functions.CopyApplier; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class LazotepConvert extends CardImpl { + + public LazotepConvert(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, ""); + + this.subtype.add(SubType.ZOMBIE); + this.power = new MageInt(4); + this.toughness = new MageInt(4); + this.color.setBlue(true); + this.color.setBlack(true); + this.nightCard = true; + + // You may have Lazotep Convert enter the battlefield as a copy of any creature card in a graveyard, except it's a 4/4 black Zombie in addition to its other types. + this.addAbility(new EntersBattlefieldAbility(new LazotepConvertCopyEffect())); + } + + private LazotepConvert(final LazotepConvert card) { + super(card); + } + + @Override + public LazotepConvert copy() { + return new LazotepConvert(this); + } +} + +class LazotepConvertCopyEffect extends OneShotEffect { + + private static final CopyApplier applier = new CopyApplier() { + + @Override + public boolean apply(Game game, MageObject blueprint, Ability source, UUID copyToObjectId) { + blueprint.removePTCDA(); + blueprint.getPower().setModifiedBaseValue(4); + blueprint.getToughness().setModifiedBaseValue(4); + blueprint.addSubType(SubType.ZOMBIE); + blueprint.getColor().setColor(ObjectColor.BLACK); + return true; + } + }; + + private static final FilterCard filter = new FilterCreatureCard("creature card in a graveyard"); + + public LazotepConvertCopyEffect() { + super(Outcome.Copy); + this.staticText = "as a copy of any creature card in a graveyard, " + + "except it's a 4/4 black Zombie in addition to its other types"; + } + + public LazotepConvertCopyEffect(final LazotepConvertCopyEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null) { + return false; + } + Target target = new TargetCardInGraveyard(0, 1, filter); + target.setNotTarget(true); + player.choose(outcome, target, source, game); + Card copyFromCard = game.getCard(target.getFirstTarget()); + if (copyFromCard == null) { + return true; + } + game.addEffect(new CopyEffect( + Duration.Custom, copyFromCard, source.getSourceId() + ).setApplier(applier), source); + return true; + } + + @Override + public LazotepConvertCopyEffect copy() { + return new LazotepConvertCopyEffect(this); + } +} diff --git a/Mage.Sets/src/mage/cards/l/LeylineSurge.java b/Mage.Sets/src/mage/cards/l/LeylineSurge.java new file mode 100644 index 00000000000..610322ac740 --- /dev/null +++ b/Mage.Sets/src/mage/cards/l/LeylineSurge.java @@ -0,0 +1,39 @@ +package mage.cards.l; + +import mage.abilities.common.BeginningOfUpkeepTriggeredAbility; +import mage.abilities.effects.common.PutCardFromHandOntoBattlefieldEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.TargetController; +import mage.filter.StaticFilters; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class LeylineSurge extends CardImpl { + + public LeylineSurge(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, ""); + + this.color.setGreen(true); + this.nightCard = true; + + // At the beginning of your upkeep, you may put a permanent card from your hand onto the battlefield. + this.addAbility(new BeginningOfUpkeepTriggeredAbility( + new PutCardFromHandOntoBattlefieldEffect(StaticFilters.FILTER_CARD_PERMANENT), + TargetController.YOU, false + )); + } + + private LeylineSurge(final LeylineSurge card) { + super(card); + } + + @Override + public LeylineSurge copy() { + return new LeylineSurge(this); + } +} diff --git a/Mage.Sets/src/mage/cards/l/LightningCoreExcavator.java b/Mage.Sets/src/mage/cards/l/LightningCoreExcavator.java index b0ee776149c..d3c7b5e35a1 100644 --- a/Mage.Sets/src/mage/cards/l/LightningCoreExcavator.java +++ b/Mage.Sets/src/mage/cards/l/LightningCoreExcavator.java @@ -11,10 +11,10 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import java.util.UUID; -import mage.filter.common.FilterCreaturePlayerOrPlaneswalker; import mage.target.common.TargetAnyTarget; +import java.util.UUID; + /** * @author TheElk801 */ @@ -33,7 +33,7 @@ public final class LightningCoreExcavator extends CardImpl { ); ability.addCost(new TapSourceCost()); ability.addCost(new SacrificeSourceCost()); - ability.addTarget(new TargetAnyTarget(new FilterCreaturePlayerOrPlaneswalker("any target"))); + ability.addTarget(new TargetAnyTarget()); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/l/LozhanDragonsLegacy.java b/Mage.Sets/src/mage/cards/l/LozhanDragonsLegacy.java index da10e619931..a0c04a3d601 100644 --- a/Mage.Sets/src/mage/cards/l/LozhanDragonsLegacy.java +++ b/Mage.Sets/src/mage/cards/l/LozhanDragonsLegacy.java @@ -13,12 +13,13 @@ import mage.constants.CardType; import mage.constants.SubType; import mage.constants.SuperType; import mage.filter.FilterSpell; -import mage.filter.common.FilterCreaturePlayerOrPlaneswalker; +import mage.filter.common.FilterAnyTarget; +import mage.filter.common.FilterPermanentOrPlayer; import mage.filter.predicate.Predicates; import mage.filter.predicate.mageobject.CommanderPredicate; import mage.game.Game; import mage.game.stack.Spell; -import mage.target.common.TargetAnyTarget; +import mage.target.common.TargetPermanentOrPlayer; import java.util.Optional; import java.util.UUID; @@ -29,8 +30,8 @@ import java.util.UUID; public final class LozhanDragonsLegacy extends CardImpl { private static final FilterSpell filter = new FilterSpell("an Adventure spell or Dragon spell"); - private static final FilterCreaturePlayerOrPlaneswalker filter2 - = new FilterCreaturePlayerOrPlaneswalker("any target that isn't a commander"); + private static final FilterPermanentOrPlayer filter2 + = new FilterAnyTarget("any target that isn't a commander"); static { filter.add(Predicates.or( @@ -57,7 +58,7 @@ public final class LozhanDragonsLegacy extends CardImpl { new DamageTargetEffect(LozhanDragonsLegacyValue.instance) .setText("{this} deals damage equal to that spell's mana value to any target that isn't a commander"), filter, false ); - ability.addTarget(new TargetAnyTarget(filter2)); + ability.addTarget(new TargetPermanentOrPlayer(filter2)); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/m/MercadiasDownfall.java b/Mage.Sets/src/mage/cards/m/MercadiasDownfall.java index 346cd645615..42909c1a0b9 100644 --- a/Mage.Sets/src/mage/cards/m/MercadiasDownfall.java +++ b/Mage.Sets/src/mage/cards/m/MercadiasDownfall.java @@ -1,38 +1,34 @@ - package mage.cards.m; -import java.util.UUID; import mage.abilities.Ability; -import mage.abilities.dynamicvalue.DynamicValue; -import mage.abilities.dynamicvalue.common.StaticValue; -import mage.abilities.effects.Effect; -import mage.abilities.effects.common.continuous.BoostAllEffect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.continuous.BoostTargetEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Duration; +import mage.constants.Outcome; import mage.constants.SuperType; -import mage.filter.common.FilterAttackingCreature; +import mage.filter.FilterPermanent; +import mage.filter.StaticFilters; import mage.filter.common.FilterLandPermanent; import mage.filter.predicate.Predicates; import mage.game.Game; -import mage.game.combat.CombatGroup; import mage.game.permanent.Permanent; +import mage.target.targetpointer.FixedTarget; + +import java.util.UUID; /** - * - * @author jeffwadsworth + * @author TheElk801 */ public final class MercadiasDownfall extends CardImpl { - - private static String rule = "Each attacking creature gets +1/+0 until end of turn for each nonbasic land defending player controls"; public MercadiasDownfall(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{2}{R}"); // Each attacking creature gets +1/+0 until end of turn for each nonbasic land defending player controls. - this.getSpellAbility().addEffect(new BoostAllEffect(new DefendersNonBasicLandCount(), StaticValue.get(0), Duration.EndOfTurn, new FilterAttackingCreature(), true, rule)); - + this.getSpellAbility().addEffect(new MercadiasDownfallEffect()); } private MercadiasDownfall(final MercadiasDownfall card) { @@ -43,42 +39,42 @@ public final class MercadiasDownfall extends CardImpl { public MercadiasDownfall copy() { return new MercadiasDownfall(this); } +} - static class DefendersNonBasicLandCount implements DynamicValue { - - @Override - public int calculate(Game game, Ability sourceAbility, Effect effect) { - UUID defenderId; - for (CombatGroup group : game.getCombat().getGroups()) { - defenderId = group.getDefenderId(); - if (group.isDefenderIsPlaneswalker()) { - Permanent permanent = game.getPermanent(defenderId); - if (permanent != null) { - defenderId = permanent.getControllerId(); - } - } - FilterLandPermanent filter = new FilterLandPermanent("nonbasic land"); - filter.add(Predicates.not(SuperType.BASIC.getPredicate())); - System.out.println("The number of nonbasic lands is " + game.getBattlefield().countAll(filter, defenderId, game)); - return game.getBattlefield().countAll(filter, defenderId, game); - } - return 0; - } +class MercadiasDownfallEffect extends OneShotEffect { - @Override - public DefendersNonBasicLandCount copy() { - return new DefendersNonBasicLandCount(); - } + private static final FilterPermanent filter = new FilterLandPermanent(); - @Override - public String toString() { - return "X"; - } - - @Override - public String getMessage() { - return "the number of nonbasic lands defending player controls"; - } + static { + filter.add(Predicates.not(SuperType.BASIC.getPredicate())); } + MercadiasDownfallEffect() { + super(Outcome.Benefit); + staticText = "each attacking creature gets +1/+0 until end of turn for each nonbasic land defending player controls"; + } + + private MercadiasDownfallEffect(final MercadiasDownfallEffect effect) { + super(effect); + } + + @Override + public MercadiasDownfallEffect copy() { + return new MercadiasDownfallEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + for (Permanent permanent : game.getBattlefield().getActivePermanents( + StaticFilters.FILTER_ATTACKING_CREATURE, source.getControllerId(), source, game + )) { + int count = game.getBattlefield().count( + filter, game.getCombat().getDefendingPlayerId(permanent.getId(), game), source, game + ); + game.addEffect(new BoostTargetEffect( + count, 0, Duration.EndOfTurn + ).setTargetPointer(new FixedTarget(permanent, game)), source); + } + return true; + } } diff --git a/Mage.Sets/src/mage/cards/n/NeedleDrop.java b/Mage.Sets/src/mage/cards/n/NeedleDrop.java index 7f7cb3f0680..1bc8bb02d48 100644 --- a/Mage.Sets/src/mage/cards/n/NeedleDrop.java +++ b/Mage.Sets/src/mage/cards/n/NeedleDrop.java @@ -2,17 +2,17 @@ package mage.cards.n; import mage.MageItem; import mage.MageObject; -import mage.abilities.effects.Effect; import mage.abilities.effects.common.DamageTargetEffect; import mage.abilities.effects.common.DrawCardSourceControllerEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.filter.common.FilterCreaturePlayerOrPlaneswalker; +import mage.filter.common.FilterAnyTarget; +import mage.filter.common.FilterPermanentOrPlayer; import mage.filter.predicate.Predicate; import mage.game.Game; import mage.players.Player; -import mage.target.common.TargetAnyTarget; +import mage.target.common.TargetPermanentOrPlayer; import mage.watchers.common.DamageDoneWatcher; import java.util.UUID; @@ -22,21 +22,19 @@ import java.util.UUID; */ public final class NeedleDrop extends CardImpl { - private static final FilterCreaturePlayerOrPlaneswalker filer = new FilterCreaturePlayerOrPlaneswalker(); + private static final FilterPermanentOrPlayer filter = new FilterAnyTarget("any target that was dealt damage this turn"); static { - filer.getPlayerFilter().add(new DamagedThisTurnPredicate()); - filer.getPermanentFilter().add(new DamagedThisTurnPredicate()); + filter.getPlayerFilter().add(DamagedThisTurnPredicate.instance); + filter.getPermanentFilter().add(DamagedThisTurnPredicate.instance); } public NeedleDrop(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{R}"); // Needle Drop deals 1 damage to any target that was dealt damage this turn. - Effect effect = new DamageTargetEffect(1); - effect.setText("{this} deals 1 damage to any target that was dealt damage this turn"); - this.getSpellAbility().addEffect(effect); - this.getSpellAbility().addTarget(new TargetAnyTarget(1, 1, filer)); + this.getSpellAbility().addEffect(new DamageTargetEffect(1)); + this.getSpellAbility().addTarget(new TargetPermanentOrPlayer(filter)); // Draw a card. this.getSpellAbility().addEffect(new DrawCardSourceControllerEffect(1).concatBy("
")); @@ -52,20 +50,21 @@ public final class NeedleDrop extends CardImpl { } } -class DamagedThisTurnPredicate implements Predicate { +enum DamagedThisTurnPredicate implements Predicate { + instance; @Override public boolean apply(MageItem input, Game game) { DamageDoneWatcher watcher = game.getState().getWatcher(DamageDoneWatcher.class); - if (watcher != null) { - if (input instanceof MageObject) { - return watcher.isDamaged(input.getId(), ((MageObject) input).getZoneChangeCounter(game), game); - } - if (input instanceof Player) { - return watcher.isDamaged(input.getId(), 0, game); - } + if (watcher == null) { + return false; + } + if (input instanceof MageObject) { + return watcher.isDamaged(input.getId(), ((MageObject) input).getZoneChangeCounter(game), game); + } + if (input instanceof Player) { + return watcher.isDamaged(input.getId(), 0, game); } return false; } - } diff --git a/Mage.Sets/src/mage/cards/n/NicolBolasGodPharaoh.java b/Mage.Sets/src/mage/cards/n/NicolBolasGodPharaoh.java index b61ac321907..936d2d878fc 100644 --- a/Mage.Sets/src/mage/cards/n/NicolBolasGodPharaoh.java +++ b/Mage.Sets/src/mage/cards/n/NicolBolasGodPharaoh.java @@ -13,13 +13,14 @@ import mage.filter.FilterCard; import mage.filter.FilterPermanent; import mage.filter.common.FilterCreaturePlayerOrPlaneswalker; import mage.filter.common.FilterNonlandPermanent; +import mage.filter.common.FilterPermanentOrPlayer; import mage.game.Game; import mage.players.Library; import mage.players.Player; import mage.target.Target; -import mage.target.common.TargetAnyTarget; import mage.target.common.TargetCardInHand; import mage.target.common.TargetOpponent; +import mage.target.common.TargetPermanentOrPlayer; import mage.target.targetpointer.FixedTarget; import java.util.HashMap; @@ -31,7 +32,7 @@ import java.util.UUID; */ public final class NicolBolasGodPharaoh extends CardImpl { - private static final FilterCreaturePlayerOrPlaneswalker damageFilter + private static final FilterPermanentOrPlayer damageFilter = new FilterCreaturePlayerOrPlaneswalker("opponent, creature an opponent controls, or planeswalker an opponent controls."); private static final FilterPermanent exileFilter = new FilterNonlandPermanent(); @@ -58,7 +59,7 @@ public final class NicolBolasGodPharaoh extends CardImpl { // -4: Nicol Bolas, God-Pharaoh deals 7 damage to target opponent, creature an opponent controls, or planeswalker an opponent controls. ability = new LoyaltyAbility(new DamageTargetEffect(7), -4); - ability.addTarget(new TargetAnyTarget(damageFilter)); + ability.addTarget(new TargetPermanentOrPlayer(damageFilter)); this.addAbility(ability); // -12: Exile each nonland permanent your opponents control. diff --git a/Mage.Sets/src/mage/cards/o/OverloadedMageRing.java b/Mage.Sets/src/mage/cards/o/OverloadedMageRing.java new file mode 100644 index 00000000000..5dd55a9e182 --- /dev/null +++ b/Mage.Sets/src/mage/cards/o/OverloadedMageRing.java @@ -0,0 +1,53 @@ +package mage.cards.o; + +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.SacrificeSourceCost; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.common.CopyTargetSpellEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.TargetController; +import mage.filter.FilterSpell; +import mage.target.TargetSpell; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class OverloadedMageRing extends CardImpl { + + private static final FilterSpell filter = new FilterSpell("spell you control"); + + static { + filter.add(TargetController.YOU.getControllerPredicate()); + } + + public OverloadedMageRing(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, ""); + + this.color.setBlue(true); + this.nightCard = true; + + // {1}, {T}, Sacrifice Overloaded Mage-Ring: Copy target spell you control. + Ability ability = new SimpleActivatedAbility( + new CopyTargetSpellEffect(false, false, false), new GenericManaCost(1) + ); + ability.addCost(new TapSourceCost()); + ability.addCost(new SacrificeSourceCost()); + ability.addTarget(new TargetSpell(filter)); + this.addAbility(ability); + } + + private OverloadedMageRing(final OverloadedMageRing card) { + super(card); + } + + @Override + public OverloadedMageRing copy() { + return new OverloadedMageRing(this); + } +} diff --git a/Mage.Sets/src/mage/cards/p/PhyrexianVindicator.java b/Mage.Sets/src/mage/cards/p/PhyrexianVindicator.java index 966b47fe7fe..682e5c487fb 100644 --- a/Mage.Sets/src/mage/cards/p/PhyrexianVindicator.java +++ b/Mage.Sets/src/mage/cards/p/PhyrexianVindicator.java @@ -13,11 +13,11 @@ import mage.constants.CardType; import mage.constants.Duration; import mage.constants.Outcome; import mage.constants.SubType; -import mage.filter.common.FilterCreaturePlayerOrPlaneswalker; +import mage.filter.common.FilterAnyTarget; import mage.filter.predicate.mageobject.AnotherPredicate; import mage.game.Game; import mage.game.events.GameEvent; -import mage.target.common.TargetAnyTarget; +import mage.target.common.TargetPermanentOrPlayer; import java.util.UUID; @@ -50,6 +50,12 @@ public class PhyrexianVindicator extends CardImpl { class PhyrexianVindicatorEffect extends ReplacementEffectImpl { + private static final FilterAnyTarget filter = new FilterAnyTarget("any other target"); + + static { + filter.getPermanentFilter().add(AnotherPredicate.instance); + } + public PhyrexianVindicatorEffect() { super(Duration.WhileOnBattlefield, Outcome.PreventDamage); staticText = "If damage would be dealt to {this}, prevent that damage. When damage is prevented this way, " + @@ -67,16 +73,15 @@ class PhyrexianVindicatorEffect extends ReplacementEffectImpl { @Override public boolean replaceEvent(GameEvent event, Ability source, Game game) { - int damage = event.getAmount(); - game.preventDamage(event, source, game, Integer.MAX_VALUE); - ReflexiveTriggeredAbility ability = new ReflexiveTriggeredAbility( - new DamageTargetEffect(damage), false, - "{this} deals that much damage to any other target" - ); - FilterCreaturePlayerOrPlaneswalker filter = new FilterCreaturePlayerOrPlaneswalker("any other target"); - filter.getPermanentFilter().add(AnotherPredicate.instance); - ability.addTarget(new TargetAnyTarget(filter)); - game.fireReflexiveTriggeredAbility(ability, source); + int damage = game.preventDamage(event, source, game, Integer.MAX_VALUE).getPreventedDamage(); + if (damage > 0) { + ReflexiveTriggeredAbility ability = new ReflexiveTriggeredAbility( + new DamageTargetEffect(damage), false, + "{this} deals that much damage to any other target" + ); + ability.addTarget(new TargetPermanentOrPlayer(filter)); + game.fireReflexiveTriggeredAbility(ability, source); + } return false; } diff --git a/Mage.Sets/src/mage/cards/p/PortalMage.java b/Mage.Sets/src/mage/cards/p/PortalMage.java index 2ba54f086a7..c10f7e46ec7 100644 --- a/Mage.Sets/src/mage/cards/p/PortalMage.java +++ b/Mage.Sets/src/mage/cards/p/PortalMage.java @@ -108,7 +108,7 @@ class PortalMageEffect extends OneShotEffect { } } // Select the new defender - TargetDefender target = new TargetDefender(defenders, null); + TargetDefender target = new TargetDefender(defenders); if (controller.chooseTarget(Outcome.Damage, target, source, game)) { if (!combatGroupTarget.getDefenderId().equals(target.getFirstTarget())) { if (combatGroupTarget.changeDefenderPostDeclaration(target.getFirstTarget(), game)) { diff --git a/Mage.Sets/src/mage/cards/p/PrickleFaeries.java b/Mage.Sets/src/mage/cards/p/PrickleFaeries.java new file mode 100644 index 00000000000..05f62d714a9 --- /dev/null +++ b/Mage.Sets/src/mage/cards/p/PrickleFaeries.java @@ -0,0 +1,74 @@ +package mage.cards.p; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.BeginningOfUpkeepTriggeredAbility; +import mage.abilities.condition.Condition; +import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility; +import mage.abilities.effects.common.DamageTargetEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.TargetController; +import mage.constants.Zone; +import mage.game.Game; +import mage.players.Player; + +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class PrickleFaeries extends CardImpl { + + public PrickleFaeries(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, ""); + + this.subtype.add(SubType.FAERIE); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + this.color.setBlack(true); + this.nightCard = true; + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // At the beginning of each opponent's upkeep, if that player has two or fewer cards in hand, Prickle Faeries deals 2 damage to them. + this.addAbility(new ConditionalInterveningIfTriggeredAbility( + new BeginningOfUpkeepTriggeredAbility( + Zone.BATTLEFIELD, new DamageTargetEffect(2), + TargetController.OPPONENT, false, true + ), PrickleFaeriesCondition.instance, "At the beginning of each opponent's upkeep, " + + "if that player has two or fewer cards in hand, {this} deals 2 damage to them." + )); + } + + private PrickleFaeries(final PrickleFaeries card) { + super(card); + } + + @Override + public PrickleFaeries copy() { + return new PrickleFaeries(this); + } +} + +enum PrickleFaeriesCondition implements Condition { + instance; + + @Override + public boolean apply(Game game, Ability source) { + return Optional + .ofNullable(game.getActivePlayerId()) + .map(game::getPlayer) + .filter(Objects::nonNull) + .map(Player::getHand) + .map(Set::size) + .orElse(0) <= 2; + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/p/PyreOfTheWorldTree.java b/Mage.Sets/src/mage/cards/p/PyreOfTheWorldTree.java new file mode 100644 index 00000000000..31cc3cd1d13 --- /dev/null +++ b/Mage.Sets/src/mage/cards/p/PyreOfTheWorldTree.java @@ -0,0 +1,52 @@ +package mage.cards.p; + +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.DiscardTargetCost; +import mage.abilities.effects.common.DamageTargetEffect; +import mage.abilities.effects.common.DiscardCardControllerTriggeredAbility; +import mage.abilities.effects.common.ExileTopXMayPlayUntilEndOfTurnEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.filter.StaticFilters; +import mage.target.common.TargetAnyTarget; +import mage.target.common.TargetCardInHand; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class PyreOfTheWorldTree extends CardImpl { + + public PyreOfTheWorldTree(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, ""); + + this.color.setRed(true); + this.nightCard = true; + + // Discard a land card: Pyre of the World Tree deals 2 damage to any target. + Ability ability = new SimpleActivatedAbility( + new DamageTargetEffect(2), + new DiscardTargetCost(new TargetCardInHand(StaticFilters.FILTER_CARD_LAND_A)) + ); + ability.addTarget(new TargetAnyTarget()); + this.addAbility(ability); + + // Whenever you discard a land card, exile the top card of your library. You may play that card this turn. + this.addAbility(new DiscardCardControllerTriggeredAbility( + new ExileTopXMayPlayUntilEndOfTurnEffect(1), + false, StaticFilters.FILTER_CARD_LAND_A + )); + } + + private PyreOfTheWorldTree(final PyreOfTheWorldTree card) { + super(card); + } + + @Override + public PyreOfTheWorldTree copy() { + return new PyreOfTheWorldTree(this); + } +} diff --git a/Mage.Sets/src/mage/cards/r/RefractionElemental.java b/Mage.Sets/src/mage/cards/r/RefractionElemental.java new file mode 100644 index 00000000000..1c89dd18e14 --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/RefractionElemental.java @@ -0,0 +1,47 @@ +package mage.cards.r; + +import mage.MageInt; +import mage.abilities.common.SpellCastControllerTriggeredAbility; +import mage.abilities.costs.common.PayLifeCost; +import mage.abilities.effects.common.DamagePlayersEffect; +import mage.abilities.keyword.WardAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.TargetController; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class RefractionElemental extends CardImpl { + + public RefractionElemental(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, ""); + + this.subtype.add(SubType.ELEMENTAL); + this.power = new MageInt(4); + this.toughness = new MageInt(4); + this.color.setRed(true); + this.nightCard = true; + + // Ward--Pay 2 life. + this.addAbility(new WardAbility(new PayLifeCost(2), false)); + + // Whenever you cast a spell, Refraction Elemental deals 3 damage to each opponent. + this.addAbility(new SpellCastControllerTriggeredAbility( + new DamagePlayersEffect(3, TargetController.OPPONENT), false + )); + } + + private RefractionElemental(final RefractionElemental card) { + super(card); + } + + @Override + public RefractionElemental copy() { + return new RefractionElemental(this); + } +} diff --git a/Mage.Sets/src/mage/cards/r/RooftopSaboteurs.java b/Mage.Sets/src/mage/cards/r/RooftopSaboteurs.java new file mode 100644 index 00000000000..9b7a5e21f53 --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/RooftopSaboteurs.java @@ -0,0 +1,46 @@ +package mage.cards.r; + +import mage.MageInt; +import mage.abilities.common.DealsCombatDamageToAPlayerTriggeredAbility; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class RooftopSaboteurs extends CardImpl { + + public RooftopSaboteurs(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, ""); + + this.subtype.add(SubType.MOONFOLK); + this.subtype.add(SubType.NINJA); + this.power = new MageInt(2); + this.toughness = new MageInt(3); + this.color.setBlue(true); + this.nightCard = true; + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Whenever Rooftop Saboteurs deals combat damage to a player or battle, draw a card. + this.addAbility(new DealsCombatDamageToAPlayerTriggeredAbility( + new DrawCardSourceControllerEffect(1), false + ).setOrBattle(true)); + } + + private RooftopSaboteurs(final RooftopSaboteurs card) { + super(card); + } + + @Override + public RooftopSaboteurs copy() { + return new RooftopSaboteurs(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SerraFaithkeeper.java b/Mage.Sets/src/mage/cards/s/SerraFaithkeeper.java new file mode 100644 index 00000000000..21a7c081401 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SerraFaithkeeper.java @@ -0,0 +1,42 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.VigilanceAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SerraFaithkeeper extends CardImpl { + + public SerraFaithkeeper(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, ""); + + this.subtype.add(SubType.ANGEL); + this.power = new MageInt(4); + this.toughness = new MageInt(4); + this.color.setWhite(true); + this.nightCard = true; + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Vigilance + this.addAbility(VigilanceAbility.getInstance()); + } + + private SerraFaithkeeper(final SerraFaithkeeper card) { + super(card); + } + + @Override + public SerraFaithkeeper copy() { + return new SerraFaithkeeper(this); + } +} diff --git a/Mage.Sets/src/mage/cards/t/TerraRavager.java b/Mage.Sets/src/mage/cards/t/TerraRavager.java index 68aa82d8e59..cd23bbc0d25 100644 --- a/Mage.Sets/src/mage/cards/t/TerraRavager.java +++ b/Mage.Sets/src/mage/cards/t/TerraRavager.java @@ -1,32 +1,38 @@ package mage.cards.t; -import java.util.UUID; import mage.MageInt; -import mage.abilities.Ability; import mage.abilities.common.AttacksTriggeredAbility; import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; import mage.abilities.dynamicvalue.common.StaticValue; -import mage.abilities.effects.Effect; import mage.abilities.effects.common.continuous.BoostSourceEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.SubType; import mage.constants.Duration; +import mage.constants.SubType; +import mage.filter.FilterPermanent; import mage.filter.common.FilterLandPermanent; -import mage.game.Game; -import mage.game.combat.CombatGroup; -import mage.game.permanent.Permanent; +import mage.filter.predicate.permanent.DefendingPlayerControlsPredicate; + +import java.util.UUID; /** - * * @author LevelX2 */ public final class TerraRavager extends CardImpl { + private static final FilterPermanent filter = new FilterLandPermanent("lands defending player controls"); + + static { + filter.add(DefendingPlayerControlsPredicate.instance); + } + + private static final DynamicValue xValue = new PermanentsOnBattlefieldCount(filter, null); + public TerraRavager(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{2}{R}{R}"); + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{R}{R}"); this.subtype.add(SubType.ELEMENTAL); this.subtype.add(SubType.BEAST); @@ -34,7 +40,9 @@ public final class TerraRavager extends CardImpl { this.toughness = new MageInt(4); // Whenever Terra Ravager attacks, it gets +X/+0 until end of turn, where X is the number of lands defending player controls. - this.addAbility(new AttacksTriggeredAbility(new BoostSourceEffect(new TerraRavagerLandCount(), StaticValue.get(0), Duration.EndOfTurn, true), false)); + this.addAbility(new AttacksTriggeredAbility(new BoostSourceEffect( + xValue, StaticValue.get(0), Duration.EndOfTurn, true, "it" + ), false)); } private TerraRavager(final TerraRavager card) { @@ -46,39 +54,3 @@ public final class TerraRavager extends CardImpl { return new TerraRavager(this); } } - -class TerraRavagerLandCount implements DynamicValue { - - @Override - public int calculate(Game game, Ability sourceAbility, Effect effect) { - for (CombatGroup group :game.getCombat().getGroups()) { - if (group.getAttackers().contains(sourceAbility.getSourceId())) { - UUID defenderId = group.getDefenderId(); - if (group.isDefenderIsPlaneswalker()) { - Permanent permanent = game.getPermanent(defenderId); - if (permanent != null) { - defenderId = permanent.getControllerId(); - } - } - return game.getBattlefield().countAll(new FilterLandPermanent(), defenderId, game); - - } - } - return 0; - } - - @Override - public TerraRavagerLandCount copy() { - return new TerraRavagerLandCount(); - } - - @Override - public String toString() { - return "X"; - } - - @Override - public String getMessage() { - return "the number of lands defending player controls"; - } -} diff --git a/Mage.Sets/src/mage/cards/t/TheBrokenSky.java b/Mage.Sets/src/mage/cards/t/TheBrokenSky.java new file mode 100644 index 00000000000..614baeb2cf1 --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TheBrokenSky.java @@ -0,0 +1,63 @@ +package mage.cards.t; + +import mage.abilities.Ability; +import mage.abilities.common.BeginningOfEndStepTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.continuous.BoostControlledEffect; +import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; +import mage.abilities.keyword.LifelinkAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.TargetController; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.permanent.TokenPredicate; +import mage.game.permanent.token.WhiteBlackSpiritToken; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class TheBrokenSky extends CardImpl { + + private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("creature tokens"); + + static { + filter.add(TokenPredicate.TRUE); + } + + public TheBrokenSky(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, ""); + + this.color.setWhite(true); + this.color.setBlack(true); + this.nightCard = true; + + // Creature tokens you control get +1/+0 and have lifelink. + Ability ability = new SimpleStaticAbility(new BoostControlledEffect( + 1, 0, Duration.WhileOnBattlefield, filter + )); + ability.addEffect(new GainAbilityControlledEffect( + LifelinkAbility.getInstance(), Duration.WhileOnBattlefield, filter + ).setText("and have lifelink")); + this.addAbility(ability); + + // At the beginning of your end step, create a 1/1 white and black Spirit creature token with flying. + this.addAbility(new BeginningOfEndStepTriggeredAbility( + new CreateTokenEffect(new WhiteBlackSpiritToken()), + TargetController.YOU, false + )); + } + + private TheBrokenSky(final TheBrokenSky card) { + super(card); + } + + @Override + public TheBrokenSky copy() { + return new TheBrokenSky(this); + } +} diff --git a/Mage.Sets/src/mage/cards/t/ToralfGodOfFury.java b/Mage.Sets/src/mage/cards/t/ToralfGodOfFury.java index 444762fb37f..ddbce1c611c 100644 --- a/Mage.Sets/src/mage/cards/t/ToralfGodOfFury.java +++ b/Mage.Sets/src/mage/cards/t/ToralfGodOfFury.java @@ -1,7 +1,6 @@ package mage.cards.t; import mage.MageInt; -import mage.MageObjectReference; import mage.abilities.Ability; import mage.abilities.Mode; import mage.abilities.TriggeredAbilityImpl; @@ -22,7 +21,8 @@ import mage.cards.CardSetInfo; import mage.cards.ModalDoubleFacesCard; import mage.constants.*; import mage.filter.StaticFilters; -import mage.filter.common.FilterCreaturePlayerOrPlaneswalker; +import mage.filter.common.FilterAnyTarget; +import mage.filter.common.FilterPermanentOrPlayer; import mage.filter.predicate.Predicates; import mage.filter.predicate.mageobject.MageObjectReferencePredicate; import mage.game.Game; @@ -31,6 +31,7 @@ import mage.game.events.GameEvent; import mage.game.permanent.Permanent; import mage.players.Player; import mage.target.common.TargetAnyTarget; +import mage.target.common.TargetPermanentOrPlayer; import java.util.UUID; @@ -118,9 +119,9 @@ class ToralfGodOfFuryTriggeredAbility extends TriggeredAbilityImpl { this.getTargets().clear(); int excessDamage = dEvent.getExcess(); this.addEffect(new DamageTargetEffect(excessDamage)); - FilterCreaturePlayerOrPlaneswalker filter = new FilterCreaturePlayerOrPlaneswalker(); - filter.getPermanentFilter().add(Predicates.not(new MageObjectReferencePredicate(new MageObjectReference(event.getTargetId(), game)))); - this.addTarget(new TargetAnyTarget(filter).withChooseHint(Integer.toString(excessDamage) + " damage")); + FilterPermanentOrPlayer filter = new FilterAnyTarget(); + filter.getPermanentFilter().add(Predicates.not(new MageObjectReferencePredicate(event.getTargetId(), game))); + this.addTarget(new TargetPermanentOrPlayer(filter).withChooseHint(Integer.toString(excessDamage) + " damage")); return true; } diff --git a/Mage.Sets/src/mage/cards/t/TrugaCliffcharger.java b/Mage.Sets/src/mage/cards/t/TrugaCliffcharger.java new file mode 100644 index 00000000000..179ae89246f --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TrugaCliffcharger.java @@ -0,0 +1,62 @@ +package mage.cards.t; + +import mage.MageInt; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.costs.common.DiscardCardCost; +import mage.abilities.effects.common.DoIfCostPaid; +import mage.abilities.effects.common.search.SearchLibraryPutInHandEffect; +import mage.abilities.keyword.TrampleAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.filter.FilterCard; +import mage.filter.predicate.Predicates; +import mage.target.common.TargetCardInLibrary; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class TrugaCliffcharger extends CardImpl { + + private static final FilterCard filter = new FilterCard("a land or battle card"); + + static { + filter.add(Predicates.or( + CardType.LAND.getPredicate(), + CardType.BATTLE.getPredicate() + )); + } + + public TrugaCliffcharger(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, ""); + + this.subtype.add(SubType.RHINO); + this.power = new MageInt(3); + this.toughness = new MageInt(4); + this.color.setRed(true); + this.color.setGreen(true); + this.nightCard = true; + + // Trample + this.addAbility(TrampleAbility.getInstance()); + + // When Truga Cliffcharger enters the battlefield, you may discard a card. If you do, search your library for a land or battle card, reveal it, put it into your hand, then shuffle. + this.addAbility(new EntersBattlefieldTriggeredAbility( + new DoIfCostPaid(new SearchLibraryPutInHandEffect( + new TargetCardInLibrary(filter), true, true + ), new DiscardCardCost()) + )); + } + + private TrugaCliffcharger(final TrugaCliffcharger card) { + super(card); + } + + @Override + public TrugaCliffcharger copy() { + return new TrugaCliffcharger(this); + } +} diff --git a/Mage.Sets/src/mage/cards/v/VertexPaladin.java b/Mage.Sets/src/mage/cards/v/VertexPaladin.java new file mode 100644 index 00000000000..061d0206175 --- /dev/null +++ b/Mage.Sets/src/mage/cards/v/VertexPaladin.java @@ -0,0 +1,48 @@ +package mage.cards.v; + +import mage.MageInt; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.dynamicvalue.common.CreaturesYouControlCount; +import mage.abilities.effects.common.continuous.SetBasePowerToughnessSourceEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.constants.Zone; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class VertexPaladin extends CardImpl { + + public VertexPaladin(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, ""); + + this.subtype.add(SubType.ANGEL); + this.subtype.add(SubType.KNIGHT); + this.power = new MageInt(0); + this.toughness = new MageInt(0); + this.color.setWhite(true); + this.color.setBlue(true); + this.nightCard = true; + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Vertex Paladin's power and toughness are each equal to the number of creatures you control. + this.addAbility(new SimpleStaticAbility(Zone.ALL, new SetBasePowerToughnessSourceEffect(CreaturesYouControlCount.instance, Duration.Custom))); + } + + private VertexPaladin(final VertexPaladin card) { + super(card); + } + + @Override + public VertexPaladin copy() { + return new VertexPaladin(this); + } +} diff --git a/Mage.Sets/src/mage/cards/w/WinnowingForces.java b/Mage.Sets/src/mage/cards/w/WinnowingForces.java new file mode 100644 index 00000000000..c4e816a8f83 --- /dev/null +++ b/Mage.Sets/src/mage/cards/w/WinnowingForces.java @@ -0,0 +1,46 @@ +package mage.cards.w; + +import mage.MageInt; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.dynamicvalue.common.LandsYouControlCount; +import mage.abilities.effects.common.continuous.SetBasePowerToughnessSourceEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.constants.Zone; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class WinnowingForces extends CardImpl { + + public WinnowingForces(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, ""); + + this.subtype.add(SubType.ELF); + this.subtype.add(SubType.WARRIOR); + this.power = new MageInt(0); + this.toughness = new MageInt(0); + this.color.setGreen(true); + this.color.setBlack(true); + this.nightCard = true; + + // Winnowing Forces's power and toughness are each equal to the number of lands you control. + this.addAbility(new SimpleStaticAbility( + Zone.ALL, new SetBasePowerToughnessSourceEffect(LandsYouControlCount.instance, Duration.Custom) + )); + } + + private WinnowingForces(final WinnowingForces card) { + super(card); + } + + @Override + public WinnowingForces copy() { + return new WinnowingForces(this); + } +} diff --git a/Mage.Sets/src/mage/cards/w/WrathfulRedDragon.java b/Mage.Sets/src/mage/cards/w/WrathfulRedDragon.java index d04bf67808b..3e2b9314554 100644 --- a/Mage.Sets/src/mage/cards/w/WrathfulRedDragon.java +++ b/Mage.Sets/src/mage/cards/w/WrathfulRedDragon.java @@ -11,13 +11,14 @@ import mage.constants.CardType; import mage.constants.Outcome; import mage.constants.SubType; import mage.constants.Zone; -import mage.filter.common.FilterCreaturePlayerOrPlaneswalker; +import mage.filter.common.FilterAnyTarget; +import mage.filter.common.FilterPermanentOrPlayer; import mage.filter.predicate.Predicates; import mage.game.Game; import mage.game.events.GameEvent; import mage.game.permanent.Permanent; import mage.players.Player; -import mage.target.common.TargetAnyTarget; +import mage.target.common.TargetPermanentOrPlayer; import java.util.UUID; @@ -52,8 +53,8 @@ public final class WrathfulRedDragon extends CardImpl { class WrathfulRedDragonTriggeredAbility extends TriggeredAbilityImpl { - private static final FilterCreaturePlayerOrPlaneswalker filter - = new FilterCreaturePlayerOrPlaneswalker("any target that isn't a Dragon"); + private static final FilterPermanentOrPlayer filter + = new FilterAnyTarget("any target that isn't a Dragon"); static { filter.getPermanentFilter().add(Predicates.not(SubType.DRAGON.getPredicate())); @@ -61,7 +62,7 @@ class WrathfulRedDragonTriggeredAbility extends TriggeredAbilityImpl { WrathfulRedDragonTriggeredAbility() { super(Zone.BATTLEFIELD, new WrathfulRedDragonEffect()); - this.addTarget(new TargetAnyTarget(filter)); + this.addTarget(new TargetPermanentOrPlayer(filter)); } private WrathfulRedDragonTriggeredAbility(final WrathfulRedDragonTriggeredAbility ability) { diff --git a/Mage.Sets/src/mage/sets/MarchOfTheMachine.java b/Mage.Sets/src/mage/sets/MarchOfTheMachine.java index 7913353a3d8..38b3d23c361 100644 --- a/Mage.Sets/src/mage/sets/MarchOfTheMachine.java +++ b/Mage.Sets/src/mage/sets/MarchOfTheMachine.java @@ -27,6 +27,7 @@ public final class MarchOfTheMachine extends ExpansionSet { cards.add(new SetCardInfo("Aerial Boost", 2, Rarity.COMMON, mage.cards.a.AerialBoost.class)); cards.add(new SetCardInfo("Aetherblade Agent", 88, Rarity.COMMON, mage.cards.a.AetherbladeAgent.class)); + cards.add(new SetCardInfo("Aetherwing, Golden-Scale Flagship", 234, Rarity.UNCOMMON, mage.cards.a.AetherwingGoldenScaleFlagship.class)); cards.add(new SetCardInfo("Akki Scrapchomper", 130, Rarity.COMMON, mage.cards.a.AkkiScrapchomper.class)); cards.add(new SetCardInfo("Alabaster Host Intercessor", 3, Rarity.COMMON, mage.cards.a.AlabasterHostIntercessor.class)); cards.add(new SetCardInfo("Alabaster Host Sanctifier", 4, Rarity.COMMON, mage.cards.a.AlabasterHostSanctifier.class)); @@ -40,12 +41,16 @@ public final class MarchOfTheMachine extends ExpansionSet { cards.add(new SetCardInfo("Astral Wingspan", 48, Rarity.UNCOMMON, mage.cards.a.AstralWingspan.class)); cards.add(new SetCardInfo("Atraxa's Fall", 176, Rarity.COMMON, mage.cards.a.AtraxasFall.class)); cards.add(new SetCardInfo("Attentive Skywarden", 7, Rarity.COMMON, mage.cards.a.AttentiveSkywarden.class)); + cards.add(new SetCardInfo("Awaken the Maelstrom", 230, Rarity.RARE, mage.cards.a.AwakenTheMaelstrom.class)); cards.add(new SetCardInfo("Baral and Kari Zev", 218, Rarity.RARE, mage.cards.b.BaralAndKariZev.class)); cards.add(new SetCardInfo("Beamtown Beatstick", 131, Rarity.COMMON, mage.cards.b.BeamtownBeatstick.class)); + cards.add(new SetCardInfo("Belenon War Anthem", 20, Rarity.UNCOMMON, mage.cards.b.BelenonWarAnthem.class)); + cards.add(new SetCardInfo("Belligerent Regisaur", 191, Rarity.RARE, mage.cards.b.BelligerentRegisaur.class)); cards.add(new SetCardInfo("Bladed Battle-Fan", 91, Rarity.COMMON, mage.cards.b.BladedBattleFan.class)); cards.add(new SetCardInfo("Blighted Burgeoning", 177, Rarity.COMMON, mage.cards.b.BlightedBurgeoning.class)); cards.add(new SetCardInfo("Bloated Processor", 93, Rarity.RARE, mage.cards.b.BloatedProcessor.class)); cards.add(new SetCardInfo("Bloodfell Caves", 267, Rarity.COMMON, mage.cards.b.BloodfellCaves.class)); + cards.add(new SetCardInfo("Bloomwielder Dryads", 237, Rarity.UNCOMMON, mage.cards.b.BloomwielderDryads.class)); cards.add(new SetCardInfo("Blossoming Sands", 268, Rarity.COMMON, mage.cards.b.BlossomingSands.class)); cards.add(new SetCardInfo("Bola Slinger", 8, Rarity.COMMON, mage.cards.b.BolaSlinger.class)); cards.add(new SetCardInfo("Bonded Herdbeast", 178, Rarity.COMMON, mage.cards.b.BondedHerdbeast.class)); @@ -74,6 +79,7 @@ public final class MarchOfTheMachine extends ExpansionSet { cards.add(new SetCardInfo("Cut Short", 10, Rarity.COMMON, mage.cards.c.CutShort.class)); cards.add(new SetCardInfo("Deadly Derision", 99, Rarity.COMMON, mage.cards.d.DeadlyDerision.class)); cards.add(new SetCardInfo("Deeproot Wayfinder", 184, Rarity.RARE, mage.cards.d.DeeprootWayfinder.class)); + cards.add(new SetCardInfo("Deluge of the Dead", 115, Rarity.MYTHIC, mage.cards.d.DelugeOfTheDead.class)); cards.add(new SetCardInfo("Dismal Backwater", 269, Rarity.COMMON, mage.cards.d.DismalBackwater.class)); cards.add(new SetCardInfo("Disturbing Conversion", 54, Rarity.COMMON, mage.cards.d.DisturbingConversion.class)); cards.add(new SetCardInfo("Doomskar Warrior", 185, Rarity.RARE, mage.cards.d.DoomskarWarrior.class)); @@ -83,6 +89,7 @@ public final class MarchOfTheMachine extends ExpansionSet { cards.add(new SetCardInfo("Elvish Vatkeeper", 223, Rarity.UNCOMMON, mage.cards.e.ElvishVatkeeper.class)); cards.add(new SetCardInfo("Enduring Bondwarden", 14, Rarity.COMMON, mage.cards.e.EnduringBondwarden.class)); cards.add(new SetCardInfo("Ephara's Dispersal", 55, Rarity.COMMON, mage.cards.e.EpharasDispersal.class)); + cards.add(new SetCardInfo("Ephara, Ever-Sheltering", 23, Rarity.RARE, mage.cards.e.EpharaEverSheltering.class)); cards.add(new SetCardInfo("Errant and Giada", 224, Rarity.RARE, mage.cards.e.ErrantAndGiada.class)); cards.add(new SetCardInfo("Essence of Orthodoxy", 323, Rarity.RARE, mage.cards.e.EssenceOfOrthodoxy.class)); cards.add(new SetCardInfo("Etali, Primal Conqueror", 137, Rarity.RARE, mage.cards.e.EtaliPrimalConqueror.class)); @@ -105,6 +112,7 @@ public final class MarchOfTheMachine extends ExpansionSet { cards.add(new SetCardInfo("Furnace Reins", 141, Rarity.UNCOMMON, mage.cards.f.FurnaceReins.class)); cards.add(new SetCardInfo("Furnace-Blessed Conqueror", 38, Rarity.UNCOMMON, mage.cards.f.FurnaceBlessedConqueror.class)); cards.add(new SetCardInfo("Furtive Analyst", 59, Rarity.COMMON, mage.cards.f.FurtiveAnalyst.class)); + cards.add(new SetCardInfo("Gargantuan Slabhorn", 240, Rarity.UNCOMMON, mage.cards.g.GargantuanSlabhorn.class)); cards.add(new SetCardInfo("Gift of Compleation", 106, Rarity.UNCOMMON, mage.cards.g.GiftOfCompleation.class)); cards.add(new SetCardInfo("Gitaxian Mindstinger", 88, Rarity.COMMON, mage.cards.g.GitaxianMindstinger.class)); cards.add(new SetCardInfo("Gitaxian Spellstalker", 151, Rarity.UNCOMMON, mage.cards.g.GitaxianSpellstalker.class)); @@ -116,6 +124,8 @@ public final class MarchOfTheMachine extends ExpansionSet { cards.add(new SetCardInfo("Gnottvold Hermit", 188, Rarity.UNCOMMON, mage.cards.g.GnottvoldHermit.class)); cards.add(new SetCardInfo("Golden-Scale Aeronaut", 15, Rarity.COMMON, mage.cards.g.GoldenScaleAeronaut.class)); cards.add(new SetCardInfo("Grafted Butcher", 109, Rarity.RARE, mage.cards.g.GraftedButcher.class)); + cards.add(new SetCardInfo("Grandmother Ravi Sengir", 116, Rarity.UNCOMMON, mage.cards.g.GrandmotherRaviSengir.class)); + cards.add(new SetCardInfo("Guildpact Paragon", 1, Rarity.MYTHIC, mage.cards.g.GuildpactParagon.class)); cards.add(new SetCardInfo("Halo Hopper", 260, Rarity.COMMON, mage.cards.h.HaloHopper.class)); cards.add(new SetCardInfo("Halo-Charged Skaab", 60, Rarity.COMMON, mage.cards.h.HaloChargedSkaab.class)); cards.add(new SetCardInfo("Hangar Scrounger", 142, Rarity.COMMON, mage.cards.h.HangarScrounger.class)); @@ -131,6 +141,29 @@ public final class MarchOfTheMachine extends ExpansionSet { cards.add(new SetCardInfo("Inspired Charge", 19, Rarity.COMMON, mage.cards.i.InspiredCharge.class)); cards.add(new SetCardInfo("Interdisciplinary Mascot", 326, Rarity.RARE, mage.cards.i.InterdisciplinaryMascot.class)); cards.add(new SetCardInfo("Into the Fire", 144, Rarity.RARE, mage.cards.i.IntoTheFire.class)); + cards.add(new SetCardInfo("Invasion of Alara", 230, Rarity.RARE, mage.cards.i.InvasionOfAlara.class)); + cards.add(new SetCardInfo("Invasion of Amonkhet", 231, Rarity.UNCOMMON, mage.cards.i.InvasionOfAmonkhet.class)); + cards.add(new SetCardInfo("Invasion of Belenon", 20, Rarity.UNCOMMON, mage.cards.i.InvasionOfBelenon.class)); + cards.add(new SetCardInfo("Invasion of Dominaria", 21, Rarity.UNCOMMON, mage.cards.i.InvasionOfDominaria.class)); + cards.add(new SetCardInfo("Invasion of Eldraine", 113, Rarity.UNCOMMON, mage.cards.i.InvasionOfEldraine.class)); + cards.add(new SetCardInfo("Invasion of Ergamon", 233, Rarity.UNCOMMON, mage.cards.i.InvasionOfErgamon.class)); + cards.add(new SetCardInfo("Invasion of Innistrad", 115, Rarity.MYTHIC, mage.cards.i.InvasionOfInnistrad.class)); + cards.add(new SetCardInfo("Invasion of Ixalan", 191, Rarity.RARE, mage.cards.i.InvasionOfIxalan.class)); + cards.add(new SetCardInfo("Invasion of Kaladesh", 234, Rarity.UNCOMMON, mage.cards.i.InvasionOfKaladesh.class)); + cards.add(new SetCardInfo("Invasion of Kaldheim", 145, Rarity.RARE, mage.cards.i.InvasionOfKaldheim.class)); + cards.add(new SetCardInfo("Invasion of Kamigawa", 62, Rarity.UNCOMMON, mage.cards.i.InvasionOfKamigawa.class)); + cards.add(new SetCardInfo("Invasion of Karsus", 146, Rarity.RARE, mage.cards.i.InvasionOfKarsus.class)); + cards.add(new SetCardInfo("Invasion of Lorwyn", 236, Rarity.UNCOMMON, mage.cards.i.InvasionOfLorwyn.class)); + cards.add(new SetCardInfo("Invasion of Mercadia", 147, Rarity.UNCOMMON, mage.cards.i.InvasionOfMercadia.class)); + cards.add(new SetCardInfo("Invasion of Moag", 237, Rarity.UNCOMMON, mage.cards.i.InvasionOfMoag.class)); + cards.add(new SetCardInfo("Invasion of Pyrulea", 240, Rarity.UNCOMMON, mage.cards.i.InvasionOfPyrulea.class)); + cards.add(new SetCardInfo("Invasion of Ravnica", 1, Rarity.MYTHIC, mage.cards.i.InvasionOfRavnica.class)); + cards.add(new SetCardInfo("Invasion of Shandalar", 193, Rarity.MYTHIC, mage.cards.i.InvasionOfShandalar.class)); + cards.add(new SetCardInfo("Invasion of Theros", 23, Rarity.RARE, mage.cards.i.InvasionOfTheros.class)); + cards.add(new SetCardInfo("Invasion of Tolvada", 241, Rarity.RARE, mage.cards.i.InvasionOfTolvada.class)); + cards.add(new SetCardInfo("Invasion of Ulgrotha", 116, Rarity.UNCOMMON, mage.cards.i.InvasionOfUlgrotha.class)); + cards.add(new SetCardInfo("Invasion of Vryn", 64, Rarity.UNCOMMON, mage.cards.i.InvasionOfVryn.class)); + cards.add(new SetCardInfo("Invasion of Xerex", 242, Rarity.UNCOMMON, mage.cards.i.InvasionOfXerex.class)); cards.add(new SetCardInfo("Iridescent Blademaster", 195, Rarity.COMMON, mage.cards.i.IridescentBlademaster.class)); cards.add(new SetCardInfo("Island", 278, Rarity.LAND, mage.cards.basiclands.Island.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Joyful Stormsculptor", 243, Rarity.UNCOMMON, mage.cards.j.JoyfulStormsculptor.class)); @@ -145,6 +178,9 @@ public final class MarchOfTheMachine extends ExpansionSet { cards.add(new SetCardInfo("Kogla and Yidaro", 244, Rarity.RARE, mage.cards.k.KoglaAndYidaro.class)); cards.add(new SetCardInfo("Kor Halberd", 27, Rarity.COMMON, mage.cards.k.KorHalberd.class)); cards.add(new SetCardInfo("Kroxa and Kunoros", 245, Rarity.MYTHIC, mage.cards.k.KroxaAndKunoros.class)); + cards.add(new SetCardInfo("Kyren Flamewright", 147, Rarity.UNCOMMON, mage.cards.k.KyrenFlamewright.class)); + cards.add(new SetCardInfo("Lazotep Convert", 231, Rarity.UNCOMMON, mage.cards.l.LazotepConvert.class)); + cards.add(new SetCardInfo("Leyline Surge", 193, Rarity.MYTHIC, mage.cards.l.LeylineSurge.class)); cards.add(new SetCardInfo("Lithomantic Barrage", 152, Rarity.UNCOMMON, mage.cards.l.LithomanticBarrage.class)); cards.add(new SetCardInfo("Malady Invoker", 189, Rarity.UNCOMMON, mage.cards.m.MaladyInvoker.class)); cards.add(new SetCardInfo("Marauding Dreadship", 153, Rarity.COMMON, mage.cards.m.MaraudingDreadship.class)); @@ -170,6 +206,7 @@ public final class MarchOfTheMachine extends ExpansionSet { cards.add(new SetCardInfo("Order of the Mirror", 72, Rarity.COMMON, mage.cards.o.OrderOfTheMirror.class)); cards.add(new SetCardInfo("Orthion, Hero of Lavabrink", 334, Rarity.RARE, mage.cards.o.OrthionHeroOfLavabrink.class)); cards.add(new SetCardInfo("Overgrown Pest", 197, Rarity.COMMON, mage.cards.o.OvergrownPest.class)); + cards.add(new SetCardInfo("Overloaded Mage-Ring", 64, Rarity.UNCOMMON, mage.cards.o.OverloadedMageRing.class)); cards.add(new SetCardInfo("Ozolith, the Shattered Spire", 198, Rarity.RARE, mage.cards.o.OzolithTheShatteredSpire.class)); cards.add(new SetCardInfo("Phyrexian Archivist", 262, Rarity.COMMON, mage.cards.p.PhyrexianArchivist.class)); cards.add(new SetCardInfo("Phyrexian Awakening", 30, Rarity.UNCOMMON, mage.cards.p.PhyrexianAwakening.class)); @@ -184,8 +221,10 @@ public final class MarchOfTheMachine extends ExpansionSet { cards.add(new SetCardInfo("Polukranos, Engine of Ruin", 200, Rarity.RARE, mage.cards.p.PolukranosEngineOfRuin.class)); cards.add(new SetCardInfo("Portent Tracker", 201, Rarity.COMMON, mage.cards.p.PortentTracker.class)); cards.add(new SetCardInfo("Preening Champion", 73, Rarity.COMMON, mage.cards.p.PreeningChampion.class)); + cards.add(new SetCardInfo("Prickle Faeries", 113, Rarity.UNCOMMON, mage.cards.p.PrickleFaeries.class)); cards.add(new SetCardInfo("Progenitor Exarch", 32, Rarity.RARE, mage.cards.p.ProgenitorExarch.class)); cards.add(new SetCardInfo("Protocol Knight", 74, Rarity.COMMON, mage.cards.p.ProtocolKnight.class)); + cards.add(new SetCardInfo("Pyre of the World Tree", 145, Rarity.RARE, mage.cards.p.PyreOfTheWorldTree.class)); cards.add(new SetCardInfo("Pyretic Prankster", 157, Rarity.COMMON, mage.cards.p.PyreticPrankster.class)); cards.add(new SetCardInfo("Ral's Reinforcements", 158, Rarity.COMMON, mage.cards.r.RalsReinforcements.class)); cards.add(new SetCardInfo("Ramosian Greatsword", 159, Rarity.UNCOMMON, mage.cards.r.RamosianGreatsword.class)); @@ -196,8 +235,10 @@ public final class MarchOfTheMachine extends ExpansionSet { cards.add(new SetCardInfo("Realmbreaker's Grasp", 33, Rarity.COMMON, mage.cards.r.RealmbreakersGrasp.class)); cards.add(new SetCardInfo("Redcap Heelslasher", 161, Rarity.COMMON, mage.cards.r.RedcapHeelslasher.class)); cards.add(new SetCardInfo("Referee Squad", 327, Rarity.UNCOMMON, mage.cards.r.RefereeSquad.class)); + cards.add(new SetCardInfo("Refraction Elemental", 146, Rarity.RARE, mage.cards.r.RefractionElemental.class)); cards.add(new SetCardInfo("Rona, Herald of Invasion", 75, Rarity.RARE, mage.cards.r.RonaHeraldOfInvasion.class)); cards.add(new SetCardInfo("Rona, Tolarian Obliterator", 75, Rarity.RARE, mage.cards.r.RonaTolarianObliterator.class)); + cards.add(new SetCardInfo("Rooftop Saboteurs", 62, Rarity.UNCOMMON, mage.cards.r.RooftopSaboteurs.class)); cards.add(new SetCardInfo("Rugged Highlands", 271, Rarity.COMMON, mage.cards.r.RuggedHighlands.class)); cards.add(new SetCardInfo("Ruins Recluse", 336, Rarity.UNCOMMON, mage.cards.r.RuinsRecluse.class)); cards.add(new SetCardInfo("Saiba Cryptomancer", 76, Rarity.COMMON, mage.cards.s.SaibaCryptomancer.class)); @@ -213,6 +254,7 @@ public final class MarchOfTheMachine extends ExpansionSet { cards.add(new SetCardInfo("Seraph of New Capenna", 36, Rarity.UNCOMMON, mage.cards.s.SeraphOfNewCapenna.class)); cards.add(new SetCardInfo("Seraph of New Phyrexia", 36, Rarity.UNCOMMON, mage.cards.s.SeraphOfNewPhyrexia.class)); cards.add(new SetCardInfo("Serpent-Blade Assailant", 205, Rarity.COMMON, mage.cards.s.SerpentBladeAssailant.class)); + cards.add(new SetCardInfo("Serra Faithkeeper", 21, Rarity.UNCOMMON, mage.cards.s.SerraFaithkeeper.class)); cards.add(new SetCardInfo("Shatter the Source", 164, Rarity.COMMON, mage.cards.s.ShatterTheSource.class)); cards.add(new SetCardInfo("Shivan Branch-Burner", 165, Rarity.UNCOMMON, mage.cards.s.ShivanBranchBurner.class)); cards.add(new SetCardInfo("Sigiled Sentinel", 37, Rarity.COMMON, mage.cards.s.SigiledSentinel.class)); @@ -235,6 +277,7 @@ public final class MarchOfTheMachine extends ExpansionSet { cards.add(new SetCardInfo("Tarkir Duneshaper", 43, Rarity.COMMON, mage.cards.t.TarkirDuneshaper.class)); cards.add(new SetCardInfo("Temporal Cleansing", 80, Rarity.COMMON, mage.cards.t.TemporalCleansing.class)); cards.add(new SetCardInfo("Tenured Oilcaster", 126, Rarity.COMMON, mage.cards.t.TenuredOilcaster.class)); + cards.add(new SetCardInfo("The Broken Sky", 241, Rarity.RARE, mage.cards.t.TheBrokenSky.class)); cards.add(new SetCardInfo("Thornwood Falls", 274, Rarity.COMMON, mage.cards.t.ThornwoodFalls.class)); cards.add(new SetCardInfo("Thrashing Frontliner", 167, Rarity.COMMON, mage.cards.t.ThrashingFrontliner.class)); cards.add(new SetCardInfo("Thunderhead Squadron", 81, Rarity.COMMON, mage.cards.t.ThunderheadSquadron.class)); @@ -246,10 +289,12 @@ public final class MarchOfTheMachine extends ExpansionSet { cards.add(new SetCardInfo("Transcendent Message", 83, Rarity.RARE, mage.cards.t.TranscendentMessage.class)); cards.add(new SetCardInfo("Traumatic Revelation", 127, Rarity.COMMON, mage.cards.t.TraumaticRevelation.class)); cards.add(new SetCardInfo("Tribute to the World Tree", 211, Rarity.RARE, mage.cards.t.TributeToTheWorldTree.class)); + cards.add(new SetCardInfo("Truga Cliffcharger", 233, Rarity.UNCOMMON, mage.cards.t.TrugaCliffcharger.class)); cards.add(new SetCardInfo("Unseal the Necropolis", 128, Rarity.COMMON, mage.cards.u.UnsealTheNecropolis.class)); cards.add(new SetCardInfo("Urn of Godfire", 266, Rarity.COMMON, mage.cards.u.UrnOfGodfire.class)); cards.add(new SetCardInfo("Vanquish the Weak", 129, Rarity.COMMON, mage.cards.v.VanquishTheWeak.class)); cards.add(new SetCardInfo("Vengeant Earth", 212, Rarity.COMMON, mage.cards.v.VengeantEarth.class)); + cards.add(new SetCardInfo("Vertex Paladin", 242, Rarity.UNCOMMON, mage.cards.v.VertexPaladin.class)); cards.add(new SetCardInfo("Volcanic Spite", 170, Rarity.COMMON, mage.cards.v.VolcanicSpite.class)); cards.add(new SetCardInfo("Voldaren Thrillseeker", 171, Rarity.RARE, mage.cards.v.VoldarenThrillseeker.class)); cards.add(new SetCardInfo("War Historian", 214, Rarity.COMMON, mage.cards.w.WarHistorian.class)); @@ -258,6 +303,7 @@ public final class MarchOfTheMachine extends ExpansionSet { cards.add(new SetCardInfo("Wicked Slumber", 84, Rarity.UNCOMMON, mage.cards.w.WickedSlumber.class)); cards.add(new SetCardInfo("Wildwood Escort", 216, Rarity.COMMON, mage.cards.w.WildwoodEscort.class)); cards.add(new SetCardInfo("Wind-Scarred Crag", 276, Rarity.COMMON, mage.cards.w.WindScarredCrag.class)); + cards.add(new SetCardInfo("Winnowing Forces", 236, Rarity.UNCOMMON, mage.cards.w.WinnowingForces.class)); cards.add(new SetCardInfo("Wrenn and Realmbreaker", 217, Rarity.MYTHIC, mage.cards.w.WrennAndRealmbreaker.class)); cards.add(new SetCardInfo("Wrenn's Resolve", 173, Rarity.COMMON, mage.cards.w.WrennsResolve.class)); cards.add(new SetCardInfo("Xerex Strobe-Knight", 85, Rarity.UNCOMMON, mage.cards.x.XerexStrobeKnight.class)); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/battle/BattleBaseTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/battle/BattleBaseTest.java new file mode 100644 index 00000000000..c4aeec55780 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/battle/BattleBaseTest.java @@ -0,0 +1,29 @@ +package org.mage.test.cards.battle; + +import mage.game.permanent.Permanent; +import mage.players.Player; +import org.junit.jupiter.api.Assertions; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author TheElk801 + */ +public class BattleBaseTest extends CardTestPlayerBase { + + protected static final String belenon = "Invasion of Belenon"; + protected static final String warAnthem = "Belenon War Anthem"; + protected static final String kaladesh = "Invasion of Kaladesh"; + protected static final String bear = "Grizzly Bears"; + protected static final String confiscate = "Confiscate"; + protected static final String impact = "Explosive Impact"; + protected static final String stifle = "Stifle"; + + protected void assertBattle(Player controller, Player protector, String name) { + assertPermanentCount(controller, name, 1); + Permanent permanent = getPermanent(name); + Assertions.assertTrue( + permanent.isProtectedBy(protector.getId()), + "Battle " + name + " should be protected by " + protector.getName() + ); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/battle/BattleDuelTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/battle/BattleDuelTest.java new file mode 100644 index 00000000000..6ceb6c78452 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/battle/BattleDuelTest.java @@ -0,0 +1,157 @@ +package org.mage.test.cards.battle; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import mage.counters.CounterType; +import org.junit.Test; + +/** + * @author TheElk801 + */ +public class BattleDuelTest extends BattleBaseTest { + + @Test + public void testRegularCastAndTrigger() { + addCard(Zone.BATTLEFIELD, playerA, "Plains", 3); + addCard(Zone.HAND, playerA, belenon); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, belenon); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertBattle(playerA, playerB, belenon); + assertPermanentCount(playerA, "Knight Token", 1); + } + + @Test + public void testAttackBattle() { + addCard(Zone.BATTLEFIELD, playerA, "Plains", 3); + addCard(Zone.BATTLEFIELD, playerA, bear); + addCard(Zone.HAND, playerA, belenon); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, belenon); + + attack(1, playerA, bear, belenon); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertBattle(playerA, playerB, belenon); + assertPermanentCount(playerA, "Knight Token", 1); + assertTapped(bear, true); + assertLife(playerB, 20); + assertCounterCount(belenon, CounterType.DEFENSE, 5 - 2); + } + + @Test + public void testAttackBattleBlock() { + addCard(Zone.BATTLEFIELD, playerA, "Plains", 3); + addCard(Zone.BATTLEFIELD, playerA, bear); + addCard(Zone.BATTLEFIELD, playerB, bear); + addCard(Zone.HAND, playerA, belenon); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, belenon); + + attack(1, playerA, bear, belenon); + block(1, playerB, bear, bear); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertBattle(playerA, playerB, belenon); + assertPermanentCount(playerA, "Knight Token", 1); + assertPermanentCount(playerA, bear, 0); + assertGraveyardCount(playerA, bear, 1); + assertPermanentCount(playerB, bear, 0); + assertGraveyardCount(playerB, bear, 1); + assertLife(playerB, 20); + assertCounterCount(belenon, CounterType.DEFENSE, 5); + } + + @Test + public void testCantAttackBattleYouProtect() { + addCard(Zone.BATTLEFIELD, playerA, "Plains", 3); + addCard(Zone.BATTLEFIELD, playerB, bear); + addCard(Zone.HAND, playerA, belenon); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, belenon); + + attack(2, playerB, bear, belenon); + + setStrictChooseMode(true); + setStopAt(2, PhaseStep.END_TURN); + execute(); + + assertBattle(playerA, playerB, belenon); + assertPermanentCount(playerA, "Knight Token", 1); + assertTapped(bear, false); + assertLife(playerB, 20); + assertCounterCount(belenon, CounterType.DEFENSE, 5); + } + + @Test + public void testChangeControl() { + addCard(Zone.BATTLEFIELD, playerA, "Plains", 3); + addCard(Zone.BATTLEFIELD, playerB, "Island", 6); + addCard(Zone.HAND, playerB, confiscate); + addCard(Zone.HAND, playerA, belenon); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, belenon); + + castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, confiscate, belenon); + + setStrictChooseMode(true); + setStopAt(2, PhaseStep.END_TURN); + execute(); + + assertBattle(playerB, playerA, belenon); + assertPermanentCount(playerA, "Knight Token", 1); + } + + @Test + public void testDefeated() { + addCard(Zone.BATTLEFIELD, playerA, "Plateau", 3 + 6); + addCard(Zone.HAND, playerA, belenon); + addCard(Zone.HAND, playerA, impact); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, belenon); + + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, impact, belenon); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertPermanentCount(playerA, belenon, 0); + assertPermanentCount(playerA, warAnthem, 1); + assertPermanentCount(playerA, "Knight Token", 1); + assertPowerToughness(playerA, "Knight Token", 2 + 1, 2 + 1); + } + + @Test + public void testDefeatedStifle() { + addCard(Zone.BATTLEFIELD, playerA, "Volcanic Island", 2 + 6 + 1); + addCard(Zone.HAND, playerA, kaladesh); + addCard(Zone.HAND, playerA, impact); + addCard(Zone.HAND, playerA, stifle); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, kaladesh); + + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, impact, kaladesh); + waitStackResolved(1, PhaseStep.POSTCOMBAT_MAIN, playerA, true); + + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, stifle, "stack ability"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertPermanentCount(playerA, kaladesh, 0); + assertGraveyardCount(playerA, kaladesh, 1); + assertPermanentCount(playerA, "Thopter Token", 1); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/battle/BattleMultiplayerTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/battle/BattleMultiplayerTest.java new file mode 100644 index 00000000000..298fceb298d --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/battle/BattleMultiplayerTest.java @@ -0,0 +1,101 @@ +package org.mage.test.cards.battle; + +import mage.constants.MultiplayerAttackOption; +import mage.constants.PhaseStep; +import mage.constants.RangeOfInfluence; +import mage.constants.Zone; +import mage.counters.CounterType; +import mage.game.FreeForAll; +import mage.game.Game; +import mage.game.GameException; +import mage.game.mulligan.MulliganType; +import org.junit.Ignore; +import org.junit.Test; + +import java.io.FileNotFoundException; + +/** + * @author TheElk801 + */ +public class BattleMultiplayerTest extends BattleBaseTest { + + @Override + protected Game createNewGameAndPlayers() throws GameException, FileNotFoundException { + Game game = new FreeForAll( + MultiplayerAttackOption.MULTIPLE, RangeOfInfluence.ALL, + MulliganType.GAME_DEFAULT.getMulligan(0), 20 + ); + // Player order: A -> D -> C -> B + playerA = createPlayer(game, "PlayerA"); + playerB = createPlayer(game, "PlayerB"); + playerC = createPlayer(game, "PlayerC"); + playerD = createPlayer(game, "PlayerD"); + return game; + } + + @Test + public void testRegularCastAndTrigger() { + addCard(Zone.BATTLEFIELD, playerA, "Plains", 3); + addCard(Zone.HAND, playerA, belenon); + + setChoice(playerA, playerC.getName()); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, belenon); + + setStopAt(1, PhaseStep.END_TURN); + setStrictChooseMode(true); + execute(); + + assertBattle(playerA, playerC, belenon); + assertPermanentCount(playerA, "Knight Token", 1); + } + + @Test + public void testAttackBattle() { + addCard(Zone.BATTLEFIELD, playerA, "Plains", 3); + addCard(Zone.BATTLEFIELD, playerA, bear); + addCard(Zone.HAND, playerA, belenon); + + setChoice(playerA, playerC.getName()); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, belenon); + + attack(1, playerA, bear, belenon); + + setStopAt(1, PhaseStep.END_TURN); + setStrictChooseMode(true); + execute(); + + assertBattle(playerA, playerC, belenon); + assertPermanentCount(playerA, "Knight Token", 1); + assertTapped(bear, true); + assertLife(playerC, 20); + assertCounterCount(belenon, CounterType.DEFENSE, 5 - 2); + } + + @Ignore // TODO: this test fails randomly and it's not clear exactly why, it works correctly though + @Test + public void testAttackBattleBlock() { + addCard(Zone.BATTLEFIELD, playerA, "Plains", 3); + addCard(Zone.BATTLEFIELD, playerA, bear); + addCard(Zone.BATTLEFIELD, playerC, bear); + addCard(Zone.HAND, playerA, belenon); + + setChoice(playerA, playerC.getName()); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, belenon); + + attack(1, playerA, bear, belenon); + block(1, playerC, bear, bear); + + setStopAt(1, PhaseStep.END_TURN); + setStrictChooseMode(true); + execute(); + + assertBattle(playerA, playerC, belenon); + assertPermanentCount(playerA, "Knight Token", 1); + assertPermanentCount(playerA, bear, 0); + assertGraveyardCount(playerA, bear, 1); + assertPermanentCount(playerC, bear, 0); + assertGraveyardCount(playerC, bear, 1); + assertLife(playerC, 20); + assertCounterCount(belenon, CounterType.DEFENSE, 5); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java b/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java index bee78f88a08..77f58cd7e6d 100644 --- a/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java +++ b/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java @@ -1711,15 +1711,15 @@ public class TestPlayer implements Player { String[] groups = command.split("\\$"); for (int i = 1; i < groups.length; i++) { String group = groups[i]; - if (group.startsWith("planeswalker=")) { - String planeswalkerName = group.substring(group.indexOf("planeswalker=") + 13); - for (Permanent permanent : game.getBattlefield().getAllActivePermanents(StaticFilters.FILTER_PERMANENT_PLANESWALKER, game)) { - if (hasObjectTargetNameOrAlias(permanent, planeswalkerName)) { + if (group.startsWith("permanent=")) { + String permanentName = group.substring(group.indexOf("permanent=") + 10); + for (Permanent permanent : game.getBattlefield().getAllActivePermanents(StaticFilters.FILTER_PERMANENT, game)) { + if (hasObjectTargetNameOrAlias(permanent, permanentName)) { defenderId = permanent.getId(); + break; } } - } - if (group.startsWith("defendingPlayer=")) { + } else if (group.startsWith("defendingPlayer=")) { String defendingPlayerName = group.substring(group.indexOf("defendingPlayer=") + 16); for (Player defendingPlayer : game.getPlayers().values()) { if (defendingPlayer.getName().equals(defendingPlayerName)) { @@ -2322,8 +2322,7 @@ public class TestPlayer implements Player { if (target.getOriginalTarget() instanceof TargetPlayer || target.getOriginalTarget() instanceof TargetAnyTarget || target.getOriginalTarget() instanceof TargetCreatureOrPlayer - || target.getOriginalTarget() instanceof TargetPermanentOrPlayer - || target.getOriginalTarget() instanceof TargetDefender) { + || target.getOriginalTarget() instanceof TargetPermanentOrPlayer) { for (String targetDefinition : targets) { if (!targetDefinition.startsWith("targetPlayer=")) { continue; @@ -2346,7 +2345,6 @@ public class TestPlayer implements Player { || (target.getOriginalTarget() instanceof TargetPermanentOrPlayer) || (target.getOriginalTarget() instanceof TargetAnyTarget) || (target.getOriginalTarget() instanceof TargetCreatureOrPlayer) - || (target.getOriginalTarget() instanceof TargetDefender) || (target.getOriginalTarget() instanceof TargetPermanentOrSuspendedCard)) { for (String targetDefinition : targets) { if (targetDefinition.startsWith("targetPlayer=")) { @@ -2376,9 +2374,6 @@ public class TestPlayer implements Player { if (filter instanceof FilterPermanentOrPlayer) { filter = ((FilterPermanentOrPlayer) filter).getPermanentFilter(); } - if (filter instanceof FilterPlaneswalkerOrPlayer) { - filter = ((FilterPlaneswalkerOrPlayer) filter).getFilterPermanent(); - } if (filter instanceof FilterPermanentOrSuspendedCard) { filter = ((FilterPermanentOrSuspendedCard) filter).getPermanentFilter(); } diff --git a/Mage.Tests/src/test/java/org/mage/test/serverside/base/impl/CardTestPlayerAPIImpl.java b/Mage.Tests/src/test/java/org/mage/test/serverside/base/impl/CardTestPlayerAPIImpl.java index adcfd7ed9d6..d894913f310 100644 --- a/Mage.Tests/src/test/java/org/mage/test/serverside/base/impl/CardTestPlayerAPIImpl.java +++ b/Mage.Tests/src/test/java/org/mage/test/serverside/base/impl/CardTestPlayerAPIImpl.java @@ -1903,11 +1903,11 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement addPlayerAction(player, turnNum, PhaseStep.DECLARE_ATTACKERS, "attack:" + attacker + "$defendingPlayer=" + defendingPlayer.getName()); } - public void attack(int turnNum, TestPlayer player, String attacker, String planeswalker) { + public void attack(int turnNum, TestPlayer player, String attacker, String permanent) { //Assert.assertNotEquals("", attacker); assertAliaseSupportInActivateCommand(attacker, false); // it uses old special notation like card_name:index - assertAliaseSupportInActivateCommand(planeswalker, false); - addPlayerAction(player, turnNum, PhaseStep.DECLARE_ATTACKERS, "attack:" + attacker + "$planeswalker=" + planeswalker); + assertAliaseSupportInActivateCommand(permanent, false); + addPlayerAction(player, turnNum, PhaseStep.DECLARE_ATTACKERS, "attack:" + attacker + "$permanent=" + permanent); } public void attackSkip(int turnNum, TestPlayer player) { diff --git a/Mage.Verify/src/main/java/mage/verify/mtgjson/MtgJsonCard.java b/Mage.Verify/src/main/java/mage/verify/mtgjson/MtgJsonCard.java index 1702cf6cd9d..ec173ea9aa6 100644 --- a/Mage.Verify/src/main/java/mage/verify/mtgjson/MtgJsonCard.java +++ b/Mage.Verify/src/main/java/mage/verify/mtgjson/MtgJsonCard.java @@ -25,6 +25,7 @@ public final class MtgJsonCard { public String text; // rules splits by \n public String loyalty; + public String defense; public String power; public String toughness; diff --git a/Mage/src/main/java/mage/MageObject.java b/Mage/src/main/java/mage/MageObject.java index 7aed450b620..0262b52f03a 100644 --- a/Mage/src/main/java/mage/MageObject.java +++ b/Mage/src/main/java/mage/MageObject.java @@ -116,6 +116,10 @@ public interface MageObject extends MageItem, Serializable, Copyable void setStartingLoyalty(int startingLoyalty); + int getStartingDefense(); + + void setStartingDefense(int startingDefense); + // memory object copy (not mtg) @Override MageObject copy(); diff --git a/Mage/src/main/java/mage/MageObjectImpl.java b/Mage/src/main/java/mage/MageObjectImpl.java index 8b3798f3955..c82f5781acc 100644 --- a/Mage/src/main/java/mage/MageObjectImpl.java +++ b/Mage/src/main/java/mage/MageObjectImpl.java @@ -40,6 +40,7 @@ public abstract class MageObjectImpl implements MageObject { protected MageInt power; protected MageInt toughness; protected int startingLoyalty = -1; // -2 means X, -1 means none, 0 and up is normal + protected int startingDefense = -1; // -2 means X, -1 means none, 0 and up is normal protected boolean copy; protected MageObject copyFrom; // copied card INFO (used to call original adjusters) @@ -69,6 +70,7 @@ public abstract class MageObjectImpl implements MageObject { power = object.power.copy(); toughness = object.toughness.copy(); startingLoyalty = object.startingLoyalty; + startingDefense = object.startingDefense; abilities = object.abilities.copy(); this.cardType.addAll(object.cardType); this.subtype.copyFrom(object.subtype); @@ -176,6 +178,16 @@ public abstract class MageObjectImpl implements MageObject { this.startingLoyalty = startingLoyalty; } + @Override + public int getStartingDefense() { + return startingDefense; + } + + @Override + public void setStartingDefense(int startingDefense) { + this.startingDefense = startingDefense; + } + @Override public ObjectColor getColor() { return color; diff --git a/Mage/src/main/java/mage/abilities/AbilityImpl.java b/Mage/src/main/java/mage/abilities/AbilityImpl.java index 527daef39ca..0b873fdaf22 100644 --- a/Mage/src/main/java/mage/abilities/AbilityImpl.java +++ b/Mage/src/main/java/mage/abilities/AbilityImpl.java @@ -2,7 +2,6 @@ package mage.abilities; import mage.MageIdentifier; import mage.MageObject; -import mage.abilities.common.EntersBattlefieldAbility; import mage.abilities.condition.Condition; import mage.abilities.costs.*; import mage.abilities.costs.common.PayLifeCost; @@ -19,7 +18,6 @@ import mage.abilities.hint.Hint; import mage.abilities.icon.CardIcon; import mage.abilities.mana.ActivatedManaAbilityImpl; import mage.cards.Card; -import mage.cards.SplitCard; import mage.constants.*; import mage.game.Game; import mage.game.command.Dungeon; @@ -437,7 +435,7 @@ public abstract class AbilityImpl implements Ability { case FLASHBACK: case MADNESS: - case DISTURB: + case TRANSFORMED: // from Snapcaster Mage: // If you cast a spell from a graveyard using its flashback ability, you can't pay other alternative costs // (such as that of Foil). (2018-12-07) diff --git a/Mage/src/main/java/mage/abilities/common/SiegeAbility.java b/Mage/src/main/java/mage/abilities/common/SiegeAbility.java new file mode 100644 index 00000000000..43a9c6f497d --- /dev/null +++ b/Mage/src/main/java/mage/abilities/common/SiegeAbility.java @@ -0,0 +1,147 @@ +package mage.abilities.common; + +import mage.ApprovingObject; +import mage.abilities.Ability; +import mage.abilities.StaticAbility; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.effects.ContinuousEffectImpl; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.keyword.TransformAbility; +import mage.cards.Card; +import mage.constants.*; +import mage.counters.CounterType; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; +import mage.game.stack.Spell; +import mage.players.Player; +import mage.target.targetpointer.FixedTarget; + +/** + * @author TheElk801 + */ +public class SiegeAbility extends StaticAbility { + + public SiegeAbility() { + super(Zone.ALL, null); + this.addSubAbility(new TransformAbility()); + this.addSubAbility(new SiegeDefeatedTriggeredAbility()); + } + + private SiegeAbility(final SiegeAbility ability) { + super(ability); + } + + @Override + public SiegeAbility copy() { + return new SiegeAbility(this); + } + + @Override + public String getRule() { + return "(As a Siege enters, choose an opponent to protect it. You and others " + + "can attack it. When it's defeated, exile it, then cast it transformed.)"; + } +} + +class SiegeDefeatedTriggeredAbility extends TriggeredAbilityImpl { + + SiegeDefeatedTriggeredAbility() { + super(Zone.BATTLEFIELD, new SiegeDefeatedEffect()); + this.setRuleVisible(false); + } + + private SiegeDefeatedTriggeredAbility(final SiegeDefeatedTriggeredAbility ability) { + super(ability); + } + + @Override + public SiegeDefeatedTriggeredAbility copy() { + return new SiegeDefeatedTriggeredAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.COUNTERS_REMOVED; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + Permanent permanent = getSourcePermanentOrLKI(game); + return permanent != null + && permanent.getCounters(game).getCount(CounterType.DEFENSE) == 0 + && event.getTargetId().equals(this.getSourceId()) + && event.getData().equals("defense") && event.getAmount() > 0; + } + + @Override + public String getRule() { + return "When the last defense counter is removed from this permanent, exile it, " + + "then you may cast it transformed without paying its mana cost."; + } +} + +class SiegeDefeatedEffect extends OneShotEffect { + + SiegeDefeatedEffect() { + super(Outcome.Benefit); + } + + private SiegeDefeatedEffect(final SiegeDefeatedEffect effect) { + super(effect); + } + + @Override + public SiegeDefeatedEffect copy() { + return new SiegeDefeatedEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + Permanent permanent = source.getSourcePermanentIfItStillExists(game); + if (player == null || permanent == null) { + return false; + } + Card card = permanent.getMainCard(); + player.moveCards(permanent, Zone.EXILED, source, game); + if (card == null || card.getSecondFaceSpellAbility() == null) { + return true; + } + game.getState().setValue("PlayFromNotOwnHandZone" + card.getSecondCardFace().getId(), Boolean.TRUE); + if (player.cast(card.getSecondFaceSpellAbility(), game, true, new ApprovingObject(source, game))) { + game.getState().setValue(TransformAbility.VALUE_KEY_ENTER_TRANSFORMED + card.getId(), Boolean.TRUE); + game.addEffect(new SiegeTransformEffect().setTargetPointer(new FixedTarget(card, game)), source); + } + game.getState().setValue("PlayFromNotOwnHandZone" + card.getSecondCardFace().getId(), null); + return true; + } +} + +class SiegeTransformEffect extends ContinuousEffectImpl { + + public SiegeTransformEffect() { + super(Duration.WhileOnStack, Layer.CopyEffects_1, SubLayer.CopyEffects_1a, Outcome.BecomeCreature); + } + + private SiegeTransformEffect(final SiegeTransformEffect effect) { + super(effect); + } + + @Override + public SiegeTransformEffect copy() { + return new SiegeTransformEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Spell spell = game.getSpell(getTargetPointer().getFirst(game, source)); + if (spell == null || spell.getCard().getSecondCardFace() == null) { + return false; + } + + // simulate another side as new card (another code part in spell constructor) + TransformAbility.transformCardSpellDynamic(spell, spell.getCard().getSecondCardFace(), game); + return true; + } +} diff --git a/Mage/src/main/java/mage/abilities/effects/common/CopyEffect.java b/Mage/src/main/java/mage/abilities/effects/common/CopyEffect.java index 94df74668d4..3430b7d27a7 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/CopyEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/CopyEffect.java @@ -134,6 +134,7 @@ public class CopyEffect extends ContinuousEffectImpl { permanent.getPower().setModifiedBaseValue(copyFromObject.getPower().getModifiedBaseValue()); permanent.getToughness().setModifiedBaseValue(copyFromObject.getToughness().getModifiedBaseValue()); permanent.setStartingLoyalty(copyFromObject.getStartingLoyalty()); + permanent.setStartingDefense(copyFromObject.getStartingDefense()); if (copyFromObject instanceof Permanent) { Permanent targetPermanent = (Permanent) copyFromObject; permanent.setTransformed(targetPermanent.isTransformed()); @@ -199,8 +200,9 @@ public class CopyEffect extends ContinuousEffectImpl { return applier; } - public void setApplier(CopyApplier applier) { + public CopyEffect setApplier(CopyApplier applier) { this.applier = applier; + return this; } } diff --git a/Mage/src/main/java/mage/abilities/keyword/DisturbAbility.java b/Mage/src/main/java/mage/abilities/keyword/DisturbAbility.java index fa75c9fd8b8..f2b4d00575e 100644 --- a/Mage/src/main/java/mage/abilities/keyword/DisturbAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/DisturbAbility.java @@ -37,7 +37,7 @@ public class DisturbAbility extends SpellAbility { this.setCardName(card.getSecondCardFace().getName() + " with Disturb"); this.zone = Zone.GRAVEYARD; this.spellAbilityType = SpellAbilityType.BASE_ALTERNATE; - this.spellAbilityCastMode = SpellAbilityCastMode.DISTURB; + this.spellAbilityCastMode = SpellAbilityCastMode.TRANSFORMED; this.manaCost = manaCost; this.getManaCosts().clear(); diff --git a/Mage/src/main/java/mage/abilities/keyword/MoreThanMeetsTheEyeAbility.java b/Mage/src/main/java/mage/abilities/keyword/MoreThanMeetsTheEyeAbility.java index fac02e3cf48..ab19446e1f6 100644 --- a/Mage/src/main/java/mage/abilities/keyword/MoreThanMeetsTheEyeAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/MoreThanMeetsTheEyeAbility.java @@ -24,7 +24,7 @@ public class MoreThanMeetsTheEyeAbility extends SpellAbility { // getSecondFaceSpellAbility() already verified that second face exists this.setCardName(card.getSecondCardFace().getName() + " with Disturb"); this.spellAbilityType = SpellAbilityType.BASE_ALTERNATE; - this.spellAbilityCastMode = SpellAbilityCastMode.DISTURB; + this.spellAbilityCastMode = SpellAbilityCastMode.TRANSFORMED; this.manaCost = manaCost; this.getManaCosts().clear(); diff --git a/Mage/src/main/java/mage/abilities/keyword/TransformAbility.java b/Mage/src/main/java/mage/abilities/keyword/TransformAbility.java index 09ed03902dc..bf57719e2da 100644 --- a/Mage/src/main/java/mage/abilities/keyword/TransformAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/TransformAbility.java @@ -67,6 +67,7 @@ public class TransformAbility extends SimpleStaticAbility { permanent.getPower().setModifiedBaseValue(sourceCard.getPower().getValue()); permanent.getToughness().setModifiedBaseValue(sourceCard.getToughness().getValue()); permanent.setStartingLoyalty(sourceCard.getStartingLoyalty()); + permanent.setStartingDefense(sourceCard.getStartingDefense()); } public static Card transformCardSpellStatic(Card mainSide, Card otherSide, Game game) { diff --git a/Mage/src/main/java/mage/cards/CardImpl.java b/Mage/src/main/java/mage/cards/CardImpl.java index d73cd16ca9b..4fdbdd07c0a 100644 --- a/Mage/src/main/java/mage/cards/CardImpl.java +++ b/Mage/src/main/java/mage/cards/CardImpl.java @@ -614,12 +614,17 @@ public abstract class CardImpl extends MageObjectImpl implements Card { @Override public final Card getSecondCardFace() { // init card side on first call - if (secondSideCardClazz == null && secondSideCard == null) { + if (secondSideCardClazz == null) { return null; } if (secondSideCard == null) { secondSideCard = initSecondSideCard(secondSideCardClazz); + if (secondSideCard != null && secondSideCard.getSpellAbility() != null) { + secondSideCard.getSpellAbility().setSourceId(this.getId()); + secondSideCard.getSpellAbility().setSpellAbilityType(SpellAbilityType.BASE_ALTERNATE); + secondSideCard.getSpellAbility().setSpellAbilityCastMode(SpellAbilityCastMode.TRANSFORMED); + } } return secondSideCard; diff --git a/Mage/src/main/java/mage/cards/repository/CardInfo.java b/Mage/src/main/java/mage/cards/repository/CardInfo.java index f3e9f7f3e2a..5c78503c055 100644 --- a/Mage/src/main/java/mage/cards/repository/CardInfo.java +++ b/Mage/src/main/java/mage/cards/repository/CardInfo.java @@ -52,6 +52,8 @@ public class CardInfo { @DatabaseField protected String startingLoyalty; @DatabaseField + protected String startingDefense; + @DatabaseField protected int manaValue; @DatabaseField(dataType = DataType.ENUM_STRING) protected Rarity rarity; @@ -226,7 +228,8 @@ public class CardInfo { } // Starting loyalty - this.startingLoyalty = CardUtil.convertStartingLoyalty(card.getStartingLoyalty()); + this.startingLoyalty = CardUtil.convertLoyaltyOrDefense(card.getStartingLoyalty()); + this.startingDefense = CardUtil.convertLoyaltyOrDefense(card.getStartingDefense()); } public Card getCard() { diff --git a/Mage/src/main/java/mage/constants/SpellAbilityCastMode.java b/Mage/src/main/java/mage/constants/SpellAbilityCastMode.java index faa191afed3..200c99fd91a 100644 --- a/Mage/src/main/java/mage/constants/SpellAbilityCastMode.java +++ b/Mage/src/main/java/mage/constants/SpellAbilityCastMode.java @@ -13,7 +13,7 @@ public enum SpellAbilityCastMode { MADNESS("Madness"), FLASHBACK("Flashback"), BESTOW("Bestow"), - DISTURB("Disturb"); + TRANSFORMED("Transformed"); private final String text; diff --git a/Mage/src/main/java/mage/constants/SubType.java b/Mage/src/main/java/mage/constants/SubType.java index c85d1107b50..f15b8ca887b 100644 --- a/Mage/src/main/java/mage/constants/SubType.java +++ b/Mage/src/main/java/mage/constants/SubType.java @@ -14,6 +14,10 @@ public enum SubType { ARCANE("Arcane", SubTypeSet.SpellType), LESSON("Lesson", SubTypeSet.SpellType), TRAP("Trap", SubTypeSet.SpellType), + + // Battle subtypes + SIEGE("Siege", SubTypeSet.BattleType), + // 205.3i: Lands have their own unique set of subtypes; these subtypes are called land types. // Of that list, Forest, Island, Mountain, Plains, and Swamp are the basic land types. FOREST("Forest", SubTypeSet.BasicLandType), @@ -623,6 +627,8 @@ public enum SubType { return mageObject.isPlaneswalker(game); case SpellType: return mageObject.isInstantOrSorcery(game); + case BattleType: + return mageObject.isBattle(game); } return false; } @@ -637,12 +643,10 @@ public enum SubType { public static Set getPlaneswalkerTypes() { return subTypeSetMap.get(SubTypeSet.PlaneswalkerType); - } public static Set getCreatureTypes() { return subTypeSetMap.get(SubTypeSet.CreatureType); - } public static Set getBasicLands() { @@ -653,6 +657,10 @@ public enum SubType { return landTypes; } + public static Set getBattleTypes() { + return subTypeSetMap.get(SubTypeSet.BattleType); + } + public static Set getBySubTypeSet(SubTypeSet subTypeSet) { return subTypeSetMap.get(subTypeSet); } diff --git a/Mage/src/main/java/mage/constants/SubTypeSet.java b/Mage/src/main/java/mage/constants/SubTypeSet.java index 0c848f8281a..305bd3238d4 100644 --- a/Mage/src/main/java/mage/constants/SubTypeSet.java +++ b/Mage/src/main/java/mage/constants/SubTypeSet.java @@ -1,6 +1,7 @@ package mage.constants; public enum SubTypeSet { + BattleType, CreatureType, SpellType, BasicLandType(true), diff --git a/Mage/src/main/java/mage/designations/Designation.java b/Mage/src/main/java/mage/designations/Designation.java index 668940ac427..9d0b91a6465 100644 --- a/Mage/src/main/java/mage/designations/Designation.java +++ b/Mage/src/main/java/mage/designations/Designation.java @@ -219,6 +219,15 @@ public abstract class Designation implements MageObject { public void setStartingLoyalty(int startingLoyalty) { } + @Override + public int getStartingDefense() { + return 0; + } + + @Override + public void setStartingDefense(int startingDefense) { + } + @Override public int getZoneChangeCounter(Game game) { return 1; // Emblems can't move zones until now so return always 1 diff --git a/Mage/src/main/java/mage/filter/StaticFilters.java b/Mage/src/main/java/mage/filter/StaticFilters.java index 14ea7ecf173..46949cbfccd 100644 --- a/Mage/src/main/java/mage/filter/StaticFilters.java +++ b/Mage/src/main/java/mage/filter/StaticFilters.java @@ -230,6 +230,12 @@ public final class StaticFilters { FILTER_CARD_A_PERMANENT.setLockedFilter(true); } + public static final FilterPermanentCard FILTER_CARD_PERMANENTS = new FilterPermanentCard("permanent cards"); + + static { + FILTER_CARD_PERMANENTS.setLockedFilter(true); + } + public static final FilterPermanent FILTER_PERMANENT = new FilterPermanent(); static { diff --git a/Mage/src/main/java/mage/filter/common/FilterAnyTarget.java b/Mage/src/main/java/mage/filter/common/FilterAnyTarget.java new file mode 100644 index 00000000000..515b6fb9637 --- /dev/null +++ b/Mage/src/main/java/mage/filter/common/FilterAnyTarget.java @@ -0,0 +1,32 @@ +package mage.filter.common; + +import mage.constants.CardType; +import mage.filter.predicate.Predicates; + +/** + * @author TheElk801 + */ +public class FilterAnyTarget extends FilterPermanentOrPlayer { + + public FilterAnyTarget() { + this("any target"); + } + + public FilterAnyTarget(String name) { + super(name); + this.permanentFilter.add(Predicates.or( + CardType.CREATURE.getPredicate(), + CardType.PLANESWALKER.getPredicate(), + CardType.BATTLE.getPredicate() + )); + } + + public FilterAnyTarget(final FilterAnyTarget filter) { + super(filter); + } + + @Override + public FilterAnyTarget copy() { + return new FilterAnyTarget(this); + } +} diff --git a/Mage/src/main/java/mage/filter/common/FilterCreaturePlayerOrPlaneswalker.java b/Mage/src/main/java/mage/filter/common/FilterCreaturePlayerOrPlaneswalker.java index 92d5ddf9597..9c193eafd6b 100644 --- a/Mage/src/main/java/mage/filter/common/FilterCreaturePlayerOrPlaneswalker.java +++ b/Mage/src/main/java/mage/filter/common/FilterCreaturePlayerOrPlaneswalker.java @@ -1,16 +1,8 @@ package mage.filter.common; -import mage.MageObject; import mage.constants.CardType; -import mage.constants.SubType; -import mage.filter.predicate.Predicate; import mage.filter.predicate.Predicates; -import java.util.Arrays; -import java.util.List; -import java.util.Objects; -import java.util.stream.Collectors; - /** * If you add predicate to permanentFilter then it will be applied to planeswalker too * @@ -19,25 +11,14 @@ import java.util.stream.Collectors; public class FilterCreaturePlayerOrPlaneswalker extends FilterPermanentOrPlayer { public FilterCreaturePlayerOrPlaneswalker() { - this("any target"); + this("creature, player, or planeswalker"); } public FilterCreaturePlayerOrPlaneswalker(String name) { - this(name, (SubType) null); - } - - public FilterCreaturePlayerOrPlaneswalker(String name, SubType... andCreatureTypes) { super(name); - List> allCreaturePredicates = Arrays.stream(andCreatureTypes) - .filter(Objects::nonNull) - .map(SubType::getPredicate) - .collect(Collectors.toList()); - allCreaturePredicates.add(0, CardType.CREATURE.getPredicate()); - Predicate planeswalkerPredicate = CardType.PLANESWALKER.getPredicate(); - this.permanentFilter.add(Predicates.or( - Predicates.and(allCreaturePredicates), - planeswalkerPredicate + CardType.CREATURE.getPredicate(), + CardType.PLANESWALKER.getPredicate() )); } diff --git a/Mage/src/main/java/mage/filter/common/FilterDefender.java b/Mage/src/main/java/mage/filter/common/FilterDefender.java new file mode 100644 index 00000000000..61b335dd0a1 --- /dev/null +++ b/Mage/src/main/java/mage/filter/common/FilterDefender.java @@ -0,0 +1,46 @@ +package mage.filter.common; + +import mage.constants.CardType; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.other.PlayerIdPredicate; +import mage.filter.predicate.permanent.ControllerIdPredicate; +import mage.filter.predicate.permanent.PermanentIdPredicate; + +import java.util.Set; +import java.util.UUID; +import java.util.stream.Collectors; + +/** + * @author BetaSteward_at_googlemail.com + */ +public class FilterDefender extends FilterPermanentOrPlayer { + + public FilterDefender(Set defenders) { + super("player, planeswalker, or battle to attack"); + this.permanentFilter.add(Predicates.or( + CardType.PLANESWALKER.getPredicate(), + CardType.BATTLE.getPredicate() + )); + this.permanentFilter.add(Predicates.or( + defenders + .stream() + .map(PermanentIdPredicate::new) + .collect(Collectors.toList()) + )); + this.playerFilter.add(Predicates.or( + defenders + .stream() + .map(PlayerIdPredicate::new) + .collect(Collectors.toList()) + )); + } + + private FilterDefender(final FilterDefender filter) { + super(filter); + } + + @Override + public FilterDefender copy() { + return new FilterDefender(this); + } +} diff --git a/Mage/src/main/java/mage/filter/common/FilterPlaneswalkerOrPlayer.java b/Mage/src/main/java/mage/filter/common/FilterPlaneswalkerOrPlayer.java deleted file mode 100644 index 2819f50949d..00000000000 --- a/Mage/src/main/java/mage/filter/common/FilterPlaneswalkerOrPlayer.java +++ /dev/null @@ -1,78 +0,0 @@ - -package mage.filter.common; - -import java.util.ArrayList; -import java.util.List; -import java.util.Set; -import java.util.UUID; -import mage.filter.FilterImpl; -import mage.filter.FilterPlayer; -import mage.filter.predicate.Predicate; -import mage.filter.predicate.Predicates; -import mage.filter.predicate.other.PlayerIdPredicate; -import mage.filter.predicate.permanent.ControllerIdPredicate; -import mage.game.Game; -import mage.game.permanent.Permanent; -import mage.players.Player; - -/** - * - * @author BetaSteward_at_googlemail.com - */ -public class FilterPlaneswalkerOrPlayer extends FilterImpl { - - protected final FilterPlaneswalkerPermanent planeswalkerFilter; - protected final FilterPlayer playerFilter; - - public FilterPlaneswalkerOrPlayer(Set defenders) { - super("planeswalker or player"); - - List> permanentPredicates = new ArrayList<>(); - for (UUID defenderId : defenders) { - permanentPredicates.add(new ControllerIdPredicate(defenderId)); - } - planeswalkerFilter = new FilterPlaneswalkerPermanent(); - planeswalkerFilter.add(Predicates.or(permanentPredicates)); - - List> playerPredicates = new ArrayList<>(); - for (UUID defenderId : defenders) { - playerPredicates.add(new PlayerIdPredicate(defenderId)); - } - playerFilter = new FilterPlayer(); - playerFilter.add(Predicates.or(playerPredicates)); - } - - public FilterPlaneswalkerOrPlayer(final FilterPlaneswalkerOrPlayer filter) { - super(filter); - this.planeswalkerFilter = filter.planeswalkerFilter.copy(); - this.playerFilter = filter.playerFilter.copy(); - } - - public FilterPlaneswalkerPermanent getFilterPermanent() { - return this.planeswalkerFilter; - } - - public FilterPlayer getFilterPlayer() { - return this.playerFilter; - } - - @Override - public boolean checkObjectClass(Object object) { - return true; - } - - @Override - public boolean match(Object o, Game game) { - if (o instanceof Player) { - return playerFilter.match((Player) o, game); - } else if (o instanceof Permanent) { - return planeswalkerFilter.match((Permanent) o, game); - } - return false; - } - - @Override - public FilterPlaneswalkerOrPlayer copy() { - return new FilterPlaneswalkerOrPlayer(this); - } -} diff --git a/Mage/src/main/java/mage/filter/predicate/permanent/ProtectedByOpponentPredicate.java b/Mage/src/main/java/mage/filter/predicate/permanent/ProtectedByOpponentPredicate.java index dbc2bd28568..037d3898010 100644 --- a/Mage/src/main/java/mage/filter/predicate/permanent/ProtectedByOpponentPredicate.java +++ b/Mage/src/main/java/mage/filter/predicate/permanent/ProtectedByOpponentPredicate.java @@ -13,7 +13,6 @@ public enum ProtectedByOpponentPredicate implements ObjectSourcePlayerPredicate< @Override public boolean apply(ObjectSourcePlayer input, Game game) { - // TODO: Implement this - return false; + return game.getOpponents(input.getPlayerId()).contains(input.getObject().getProtectorId()); } } diff --git a/Mage/src/main/java/mage/filter/predicate/permanent/ProtectorIdPredicate.java b/Mage/src/main/java/mage/filter/predicate/permanent/ProtectorIdPredicate.java index 3a4248bc395..89768c01fc0 100644 --- a/Mage/src/main/java/mage/filter/predicate/permanent/ProtectorIdPredicate.java +++ b/Mage/src/main/java/mage/filter/predicate/permanent/ProtectorIdPredicate.java @@ -19,7 +19,6 @@ public class ProtectorIdPredicate implements Predicate { @Override public boolean apply(Permanent input, Game game) { - // TODO: implement this on battles branch - return false; + return input.isProtectedBy(protectorId); } } diff --git a/Mage/src/main/java/mage/game/GameImpl.java b/Mage/src/main/java/mage/game/GameImpl.java index eed7201d74a..200362239b0 100644 --- a/Mage/src/main/java/mage/game/GameImpl.java +++ b/Mage/src/main/java/mage/game/GameImpl.java @@ -48,6 +48,7 @@ import mage.game.permanent.Permanent; import mage.game.permanent.PermanentCard; import mage.game.stack.Spell; import mage.game.stack.SpellStack; +import mage.game.stack.StackAbility; import mage.game.stack.StackObject; import mage.game.turn.Phase; import mage.game.turn.Step; @@ -2498,6 +2499,32 @@ public abstract class GameImpl implements Game { somethingHappened = true; } } + + if (perm.isBattle(this)) { + if (perm + .getCounters(this) + .getCount(CounterType.DEFENSE) == 0 + && this.getStack() + .stream() + .filter(StackAbility.class::isInstance) + .filter(stackObject -> stackObject.getStackAbility() instanceof TriggeredAbilityImpl) + .map(StackObject::getSourceId) + .noneMatch(perm.getId()::equals) + && this.state + .getTriggered(perm.getControllerId()) + .stream() + .filter(TriggeredAbility.class::isInstance) + .map(Ability::getSourceId) + .noneMatch(perm.getId()::equals)) { + if (movePermanentToGraveyardWithInfo(perm)) { + somethingHappened = true; + } + } else if (this.getPlayer(perm.getProtectorId()) == null || perm.isControlledBy(perm.getProtectorId())) { + perm.chooseProtector(this, null); + somethingHappened = true; + } + } + if (perm.isLegendary() && perm.legendRuleApplies()) { legendary.add(perm); } diff --git a/Mage/src/main/java/mage/game/combat/Combat.java b/Mage/src/main/java/mage/game/combat/Combat.java index 80821c23fcf..e64d18ed337 100644 --- a/Mage/src/main/java/mage/game/combat/Combat.java +++ b/Mage/src/main/java/mage/game/combat/Combat.java @@ -11,7 +11,9 @@ import mage.abilities.keyword.VigilanceAbility; import mage.abilities.keyword.special.JohanVigilanceAbility; import mage.constants.Outcome; import mage.constants.Zone; +import mage.filter.FilterPermanent; import mage.filter.StaticFilters; +import mage.filter.common.FilterBattlePermanent; import mage.filter.common.FilterControlledCreaturePermanent; import mage.filter.common.FilterCreatureForCombatBlock; import mage.filter.common.FilterCreaturePermanent; @@ -21,6 +23,7 @@ import mage.filter.predicate.mageobject.AbilityPredicate; import mage.filter.predicate.mageobject.NamePredicate; import mage.filter.predicate.permanent.AttackingSameNotBandedPredicate; import mage.filter.predicate.permanent.PermanentIdPredicate; +import mage.filter.predicate.permanent.ProtectedByOpponentPredicate; import mage.game.Game; import mage.game.events.*; import mage.game.permanent.Permanent; @@ -45,6 +48,12 @@ public class Combat implements Serializable, Copyable { private static final Logger logger = Logger.getLogger(Combat.class); private static FilterCreatureForCombatBlock filterBlockers = new FilterCreatureForCombatBlock(); + private static final FilterPermanent filterBattles = new FilterBattlePermanent(); + + static { + filterBattles.add(ProtectedByOpponentPredicate.instance); + } + // There are effects that let creatures assigns combat damage equal to its toughness rather than its power private boolean useToughnessForDamage; private final List useToughnessForDamageFilters = new ArrayList<>(); @@ -213,10 +222,12 @@ public class Combat implements Serializable, Copyable { if (playerToAttack != null) { possibleDefenders = new HashSet<>(); for (UUID objectId : defenders) { - Permanent planeswalker = game.getPermanent(objectId); - if (planeswalker != null && planeswalker.isControlledBy(playerToAttack)) { + if (playerToAttack.equals(objectId)) { possibleDefenders.add(objectId); - } else if (playerToAttack.equals(objectId)) { + continue; + } + Permanent permanent = game.getPermanent(objectId); + if (permanent != null && permanent.canBeAttacked(creatureId, playerToAttack, game)) { possibleDefenders.add(objectId); } } @@ -231,8 +242,7 @@ public class Combat implements Serializable, Copyable { addAttackerToCombat(creatureId, possibleDefenders.iterator().next(), game); return true; } else { - TargetDefender target = new TargetDefender(possibleDefenders, creatureId); - target.setNotTarget(true); + TargetDefender target = new TargetDefender(possibleDefenders); target.setRequired(true); player.chooseTarget(Outcome.Damage, target, null, game); if (target.getFirstTarget() != null) { @@ -357,8 +367,8 @@ public class Combat implements Serializable, Copyable { if (game.replaceEvent(GameEvent.getEvent(GameEvent.EventType.DECLARING_ATTACKERS, attackingPlayerId, attackingPlayerId)) || (!canBand && !canBandWithOther) || !player.chooseUse(Outcome.Benefit, - (isBanded ? "Band " + attacker.getLogName() - + " with another " : "Form a band with " + attacker.getLogName() + " and an ") + (isBanded ? "Band " + attacker.getLogName() + + " with another " : "Form a band with " + attacker.getLogName() + " and an ") + "attacking creature?", null, game)) { break; } @@ -511,9 +521,9 @@ public class Combat implements Serializable, Copyable { player.declareAttacker(creature.getId(), defendersToChooseFrom.iterator().next(), game, false); continue; } - TargetDefender target = new TargetDefender(defendersToChooseFrom, creature.getId()); + TargetDefender target = new TargetDefender(defendersToChooseFrom); target.setRequired(true); - target.setTargetName("planeswalker or player for " + creature.getLogName() + " to attack (must attack effect)"); + target.setTargetName("permanent or player for " + creature.getLogName() + " to attack (must attack effect)"); if (player.chooseTarget(Outcome.Damage, target, null, game)) { player.declareAttacker(creature.getId(), target.getFirstTarget(), game, false); } @@ -595,7 +605,7 @@ public class Combat implements Serializable, Copyable { * Handle the blocker selection process * * @param blockController player that controls how to block, if null the - * defender is the controller + * defender is the controller * @param game */ public void selectBlockers(Player blockController, Ability source, Game game) { @@ -605,34 +615,35 @@ public class Combat implements Serializable, Copyable { Player controller; for (UUID defenderId : getPlayerDefenders(game)) { Player defender = game.getPlayer(defenderId); - if (defender != null) { - boolean choose = true; - if (blockController == null) { - controller = defender; - } else { - controller = blockController; + if (defender == null) { + continue; + } + boolean choose = true; + if (blockController == null) { + controller = defender; + } else { + controller = blockController; + } + while (choose) { + controller.selectBlockers(source, game, defenderId); + if (game.isPaused() || game.checkIfGameIsOver() || game.executingRollback()) { + return; } - while (choose) { - controller.selectBlockers(source, game, defenderId); - if (game.isPaused() || game.checkIfGameIsOver() || game.executingRollback()) { - return; - } - if (!game.getCombat().checkBlockRestrictions(defender, game)) { - if (controller.isHuman()) { // only human player can decide to do the block in another way - continue; - } - } - choose = !game.getCombat().checkBlockRequirementsAfter(defender, controller, game); - if (!choose) { - choose = !game.getCombat().checkBlockRestrictionsAfter(defender, controller, game); + if (!game.getCombat().checkBlockRestrictions(defender, game)) { + if (controller.isHuman()) { // only human player can decide to do the block in another way + continue; } } - game.fireEvent(GameEvent.getEvent(GameEvent.EventType.DECLARED_BLOCKERS, defenderId, defenderId)); + choose = !game.getCombat().checkBlockRequirementsAfter(defender, controller, game); + if (!choose) { + choose = !game.getCombat().checkBlockRestrictionsAfter(defender, controller, game); + } + } + game.fireEvent(GameEvent.getEvent(GameEvent.EventType.DECLARED_BLOCKERS, defenderId, defenderId)); - // add info about attacker blocked by blocker to the game log - if (!game.isSimulation()) { - game.getCombat().logBlockerInfo(defender, game); - } + // add info about attacker blocked by blocker to the game log + if (!game.isSimulation()) { + game.getCombat().logBlockerInfo(defender, game); } } // tool to catch the bug about flyers blocked by non flyers or intimidate blocked by creatures with other colors @@ -1259,6 +1270,9 @@ public class Combat implements Serializable, Copyable { for (UUID playerId : getAttackablePlayers(game)) { addDefender(playerId, game); } + for (Permanent permanent : game.getBattlefield().getActivePermanents(filterBattles, attackingPlayerId, game)) { + defenders.add(permanent.getId()); + } } public List getAttackablePlayers(Game game) { @@ -1351,7 +1365,17 @@ public class Combat implements Serializable, Copyable { if (attacker == null) { return false; } - CombatGroup newGroup = new CombatGroup(defenderId, defender != null, defender != null ? defender.getControllerId() : defenderId); + UUID defendingPlayerId; + if (defender == null) { + defendingPlayerId = defenderId; + } else if (defender.isPlaneswalker(game)) { + defendingPlayerId = defender.getControllerId(); + } else if (defender.isBattle(game)) { + defendingPlayerId = defender.getProtectorId(); + } else { + defendingPlayerId = null; + } + CombatGroup newGroup = new CombatGroup(defenderId, defender != null, defendingPlayerId); newGroup.attackers.add(attackerId); attacker.setAttacking(true); groups.add(newGroup); @@ -1413,7 +1437,7 @@ public class Combat implements Serializable, Copyable { * @param playerId * @param game * @param solveBanding check whether also add creatures banded with - * attackerId + * attackerId */ public void addBlockingGroup(UUID blockerId, UUID attackerId, UUID playerId, Game game, boolean solveBanding) { Permanent blocker = game.getPermanent(blockerId); @@ -1453,11 +1477,11 @@ public class Combat implements Serializable, Copyable { } } - public boolean removePlaneswalkerFromCombat(UUID planeswalkerId, Game game) { + public boolean removeDefendingPermanentFromCombat(UUID permanentId, Game game) { boolean result = false; for (CombatGroup group : groups) { - if (group.getDefenderId() != null && group.getDefenderId().equals(planeswalkerId)) { - group.removeAttackedPlaneswalker(planeswalkerId); + if (group.getDefenderId() != null && group.getDefenderId().equals(permanentId)) { + group.removeAttackedPermanent(permanentId); result = true; } } @@ -1465,31 +1489,32 @@ public class Combat implements Serializable, Copyable { } public boolean removeFromCombat(UUID creatureId, Game game, boolean withEvent) { - boolean result = false; Permanent creature = game.getPermanent(creatureId); - if (creature != null) { - if (withEvent) { - creature.setAttacking(false); - creature.setBlocking(0); - } - for (CombatGroup group : groups) { - for (UUID attackerId : group.attackers) { - Permanent attacker = game.getPermanent(attackerId); - if (attacker != null) { - attacker.removeBandedCard(creatureId); - } + if (creature == null) { + return false; + } + boolean result = false; + if (withEvent) { + creature.setAttacking(false); + creature.setBlocking(0); + } + for (CombatGroup group : groups) { + for (UUID attackerId : group.attackers) { + Permanent attacker = game.getPermanent(attackerId); + if (attacker != null) { + attacker.removeBandedCard(creatureId); } - result |= group.remove(creatureId); - } - for (CombatGroup blockingGroup : getBlockingGroups()) { - result |= blockingGroup.remove(creatureId); - } - creature.clearBandedCards(); - blockingGroups.remove(creatureId); - if (result && withEvent) { - game.fireEvent(GameEvent.getEvent(GameEvent.EventType.REMOVED_FROM_COMBAT, creatureId, null, null)); - game.informPlayers(creature.getLogName() + " removed from combat"); } + result |= group.remove(creatureId); + } + for (CombatGroup blockingGroup : getBlockingGroups()) { + result |= blockingGroup.remove(creatureId); + } + creature.clearBandedCards(); + blockingGroups.remove(creatureId); + if (result && withEvent) { + game.fireEvent(GameEvent.getEvent(GameEvent.EventType.REMOVED_FROM_COMBAT, creatureId, null, null)); + game.informPlayers(creature.getLogName() + " removed from combat"); } return result; } @@ -1540,15 +1565,6 @@ public class Combat implements Serializable, Copyable { return null; } - // public int totalUnblockedDamage(Game game) { -// int total = 0; -// for (CombatGroup group : groups) { -// if (group.getBlockers().isEmpty()) { -// total += group.totalAttackerDamage(game); -// } -// } -// return total; -// } public boolean attacksAlone() { return (groups.size() == 1 && groups.get(0).getAttackers().size() == 1); } @@ -1557,24 +1573,9 @@ public class Combat implements Serializable, Copyable { return groups.isEmpty() || getAttackers().isEmpty(); } - public boolean isAttacked(UUID defenderId, Game game) { - for (CombatGroup group : groups) { - if (group.getDefenderId().equals(defenderId)) { - return true; - } - if (group.defenderIsPlaneswalker) { - Permanent permanent = game.getPermanent(group.getDefenderId()); - if (permanent.isControlledBy(defenderId)) { - return true; - } - } - } - return false; - } - public boolean isPlaneswalkerAttacked(UUID defenderId, Game game) { for (CombatGroup group : groups) { - if (group.defenderIsPlaneswalker) { + if (group.isDefenderIsPermanent()) { Permanent permanent = game.getPermanent(group.getDefenderId()); if (permanent.isControlledBy(defenderId)) { return true; @@ -1589,14 +1590,12 @@ public class Combat implements Serializable, Copyable { * @return uuid of defending player or planeswalker */ public UUID getDefenderId(UUID attackerId) { - UUID defenderId = null; - for (CombatGroup group : groups) { - if (group.getAttackers().contains(attackerId)) { - defenderId = group.getDefenderId(); - break; - } - } - return defenderId; + return groups + .stream() + .filter(group -> group.getAttackers().contains(attackerId)) + .map(CombatGroup::getDefenderId) + .findFirst() + .orElse(null); } /** @@ -1608,40 +1607,32 @@ public class Combat implements Serializable, Copyable { * @return */ public UUID getDefendingPlayerId(UUID attackingCreatureId, Game game) { - UUID defenderId = null; - for (CombatGroup group : groups) { - if (group.getAttackers().contains(attackingCreatureId)) { - defenderId = group.getDefenderId(); - if (group.defenderIsPlaneswalker) { - Permanent permanent = game.getPermanentOrLKIBattlefield(defenderId); - if (permanent != null) { - defenderId = permanent.getControllerId(); - } else { - defenderId = null; - } - } - break; - } - } - return defenderId; + return groups + .stream() + .filter(group -> group.getAttackers().contains(attackingCreatureId)) + .map(CombatGroup::getDefendingPlayerId) + .findFirst() + .orElse(null); } public Set getPlayerDefenders(Game game) { return getPlayerDefenders(game, true); } - public Set getPlayerDefenders(Game game, boolean includePlaneswalkers) { + public Set getPlayerDefenders(Game game, boolean includePermanents) { Set playerDefenders = new HashSet<>(); for (CombatGroup group : groups) { - if (group.defenderIsPlaneswalker && !includePlaneswalkers) { + if (group.isDefenderIsPermanent() && !includePermanents) { continue; } - if (group.defenderIsPlaneswalker) { + if (group.isDefenderIsPermanent()) { Permanent permanent = game.getPermanent(group.getDefenderId()); - if (permanent != null) { - playerDefenders.add(permanent.getControllerId()); - } else { + if (permanent == null) { playerDefenders.add(group.getDefendingPlayerId()); + } else if (permanent.isPlaneswalker(game)) { + playerDefenders.add(permanent.getControllerId()); + } else if (permanent.isBattle(game)) { + playerDefenders.add(permanent.getProtectorId()); } } else { playerDefenders.add(group.getDefenderId()); diff --git a/Mage/src/main/java/mage/game/combat/CombatGroup.java b/Mage/src/main/java/mage/game/combat/CombatGroup.java index be15d044fdb..e5316975bb9 100644 --- a/Mage/src/main/java/mage/game/combat/CombatGroup.java +++ b/Mage/src/main/java/mage/game/combat/CombatGroup.java @@ -33,16 +33,16 @@ public class CombatGroup implements Serializable, Copyable { protected boolean blocked; protected UUID defenderId; // planeswalker or player protected UUID defendingPlayerId; - protected boolean defenderIsPlaneswalker; + protected boolean defenderIsPermanent; /** - * @param defenderId the player that controls the defending permanents - * @param defenderIsPlaneswalker is the defending permanent a planeswalker - * @param defendingPlayerId regular controller of the defending permanents + * @param defenderId the player that controls the defending permanents + * @param defenderIsPermanent is the defender a permanent + * @param defendingPlayerId regular controller of the defending permanents */ - public CombatGroup(UUID defenderId, boolean defenderIsPlaneswalker, UUID defendingPlayerId) { + public CombatGroup(UUID defenderId, boolean defenderIsPermanent, UUID defendingPlayerId) { this.defenderId = defenderId; - this.defenderIsPlaneswalker = defenderIsPlaneswalker; + this.defenderIsPermanent = defenderIsPermanent; this.defendingPlayerId = defendingPlayerId; } @@ -55,7 +55,7 @@ public class CombatGroup implements Serializable, Copyable { this.blocked = group.blocked; this.defenderId = group.defenderId; this.defendingPlayerId = group.defendingPlayerId; - this.defenderIsPlaneswalker = group.defenderIsPlaneswalker; + this.defenderIsPermanent = group.defenderIsPermanent; } public boolean hasFirstOrDoubleStrike(Game game) { @@ -217,7 +217,7 @@ public class CombatGroup implements Serializable, Copyable { permanent.applyDamage(game); } } - if (defenderIsPlaneswalker) { + if (defenderIsPermanent) { Permanent permanent = game.getPermanent(defenderId); if (permanent != null) { permanent.applyDamage(game); @@ -545,46 +545,44 @@ public class CombatGroup implements Serializable, Copyable { } } } + /** * Do damage to attacked player or planeswalker + * * @param attacker * @param amount * @param game * @param damageToDefenderController excess damage to defender's controller (example: trample over planeswalker) */ private void defenderDamage(Permanent attacker, int amount, Game game, boolean damageToDefenderController) { - UUID affectedDefenderId = this.defenderId; - if (damageToDefenderController) { - affectedDefenderId = game.getControllerId(this.defenderId); - } + UUID affectedDefenderId = damageToDefenderController ? game.getControllerId(this.defenderId) : this.defenderId; // on planeswalker - Permanent planeswalker = game.getPermanent(affectedDefenderId); - if (planeswalker != null) { - // apply excess damage from "trample over planeswaslkers" ability (example: Thrasta, Tempest's Roar) - if (hasTrampleOverPlaneswalkers(attacker)) { - int lethalDamage = planeswalker.getLethalDamage(attacker.getId(), game); - if (lethalDamage >= amount) { - // normal damage - planeswalker.markDamage(amount, attacker.getId(), null, game, true, true); - } else { - // damage with excess (additional damage to planeswalker's controller) - planeswalker.markDamage(lethalDamage, attacker.getId(), null, game, true, true); - amount -= lethalDamage; - if (amount > 0) { - defenderDamage(attacker, amount, game, true); - } - } - } else { - // normal damage - planeswalker.markDamage(amount, attacker.getId(), null, game, true, true); + Permanent permanent = game.getPermanent(affectedDefenderId); + if (permanent == null) {// on player + Player defender = game.getPlayer(affectedDefenderId); + if (defender != null) { + defender.damage(amount, attacker.getId(), null, game, true, true); } + return; } - - // on player - Player defender = game.getPlayer(affectedDefenderId); - if (defender != null) { - defender.damage(amount, attacker.getId(), null, game, true, true); + // apply excess damage from "trample over planeswaslkers" ability (example: Thrasta, Tempest's Roar) + if (permanent.isPlaneswalker(game) && hasTrampleOverPlaneswalkers(attacker)) { + int lethalDamage = permanent.getLethalDamage(attacker.getId(), game); + if (lethalDamage >= amount) { + // normal damage + permanent.markDamage(amount, attacker.getId(), null, game, true, true); + } else { + // damage with excess (additional damage to permanent's controller) + permanent.markDamage(lethalDamage, attacker.getId(), null, game, true, true); + amount -= lethalDamage; + if (amount > 0) { + defenderDamage(attacker, amount, game, true); + } + } + } else { + // normal damage + permanent.markDamage(amount, attacker.getId(), null, game, true, true); } } @@ -717,20 +715,12 @@ public class CombatGroup implements Serializable, Copyable { game.informPlayers(sb.toString()); } - public int totalAttackerDamage(Game game) { - int total = 0; - for (UUID attackerId : attackers) { - total += getDamageValueFromPermanent(game.getPermanent(attackerId), game); - } - return total; + public boolean isDefenderIsPermanent() { + return defenderIsPermanent; } - public boolean isDefenderIsPlaneswalker() { - return defenderIsPlaneswalker; - } - - public boolean removeAttackedPlaneswalker(UUID planeswalkerId) { - if (defenderIsPlaneswalker && defenderId.equals(planeswalkerId)) { + public boolean removeAttackedPermanent(UUID permanentId) { + if (defenderIsPermanent && defenderId.equals(permanentId)) { defenderId = null; return true; } @@ -874,36 +864,36 @@ public class CombatGroup implements Serializable, Copyable { } public boolean changeDefenderPostDeclaration(UUID newDefenderId, Game game) { - if (!defenderId.equals(newDefenderId)) { - for (UUID attackerId : attackers) { // changing defender will remove a banded attacker from its current band - Permanent attacker = game.getPermanent(attackerId); - if (attacker != null && attacker.getBandedCards() != null) { - for (UUID bandedId : attacker.getBandedCards()) { - Permanent banded = game.getPermanent(bandedId); - if (banded != null) { - banded.removeBandedCard(attackerId); - } + if (defenderId.equals(newDefenderId)) { + return false; + } + for (UUID attackerId : attackers) { // changing defender will remove a banded attacker from its current band + Permanent attacker = game.getPermanent(attackerId); + if (attacker != null && attacker.getBandedCards() != null) { + for (UUID bandedId : attacker.getBandedCards()) { + Permanent banded = game.getPermanent(bandedId); + if (banded != null) { + banded.removeBandedCard(attackerId); } } - attacker.clearBandedCards(); - } - Permanent permanent = game.getPermanent(newDefenderId); - if (permanent != null) { - defenderId = newDefenderId; - defendingPlayerId = permanent.getControllerId(); - defenderIsPlaneswalker = true; - return true; - } else { - Player defender = game.getPlayer(newDefenderId); - if (defender != null) { - defenderId = newDefenderId; - defendingPlayerId = newDefenderId; - defenderIsPlaneswalker = false; - return true; - } } + attacker.clearBandedCards(); } - return false; + Permanent permanent = game.getPermanent(newDefenderId); + if (permanent != null) { + defenderId = newDefenderId; + defendingPlayerId = permanent.isBattle(game) ? permanent.getProtectorId() : permanent.getControllerId(); + defenderIsPermanent = true; + return true; + } + Player defender = game.getPlayer(newDefenderId); + if (defender == null) { + return false; + } + defenderId = newDefenderId; + defendingPlayerId = newDefenderId; + defenderIsPermanent = false; + return true; } /** diff --git a/Mage/src/main/java/mage/game/command/Commander.java b/Mage/src/main/java/mage/game/command/Commander.java index 2ff8800fce8..de1092a500b 100644 --- a/Mage/src/main/java/mage/game/command/Commander.java +++ b/Mage/src/main/java/mage/game/command/Commander.java @@ -18,7 +18,10 @@ import mage.game.events.ZoneChangeEvent; import mage.util.GameLog; import mage.util.SubTypes; -import java.util.*; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.UUID; public class Commander implements CommandObject { @@ -272,6 +275,15 @@ public class Commander implements CommandObject { public void setStartingLoyalty(int startingLoyalty) { } + @Override + public int getStartingDefense() { + return sourceObject.getStartingDefense(); + } + + @Override + public void setStartingDefense(int startingDefense) { + } + @Override public UUID getId() { return sourceObject.getId(); diff --git a/Mage/src/main/java/mage/game/command/Dungeon.java b/Mage/src/main/java/mage/game/command/Dungeon.java index 1107e255bc0..a7e8b438e56 100644 --- a/Mage/src/main/java/mage/game/command/Dungeon.java +++ b/Mage/src/main/java/mage/game/command/Dungeon.java @@ -317,6 +317,15 @@ public class Dungeon implements CommandObject { public void setStartingLoyalty(int startingLoyalty) { } + @Override + public int getStartingDefense() { + return 0; + } + + @Override + public void setStartingDefense(int startingDefense) { + } + @Override public UUID getId() { return this.id; diff --git a/Mage/src/main/java/mage/game/command/Emblem.java b/Mage/src/main/java/mage/game/command/Emblem.java index 5af9a644698..a49d706fd3f 100644 --- a/Mage/src/main/java/mage/game/command/Emblem.java +++ b/Mage/src/main/java/mage/game/command/Emblem.java @@ -84,7 +84,7 @@ public class Emblem implements CommandObject { } if (!availableImageSetCodes.isEmpty()) { if (expansionSetCodeForImage.equals("") || !availableImageSetCodes.contains(expansionSetCodeForImage)) { - expansionSetCodeForImage = availableImageSetCodes.get(RandomUtil.nextInt(availableImageSetCodes.size())); + expansionSetCodeForImage = availableImageSetCodes.get(RandomUtil.nextInt(availableImageSetCodes.size())); } } } @@ -233,6 +233,15 @@ public class Emblem implements CommandObject { public void setStartingLoyalty(int startingLoyalty) { } + @Override + public int getStartingDefense() { + return 0; + } + + @Override + public void setStartingDefense(int startingDefense) { + } + @Override public UUID getId() { return this.id; diff --git a/Mage/src/main/java/mage/game/command/Plane.java b/Mage/src/main/java/mage/game/command/Plane.java index 37605ad013a..43d7c047a79 100644 --- a/Mage/src/main/java/mage/game/command/Plane.java +++ b/Mage/src/main/java/mage/game/command/Plane.java @@ -232,6 +232,15 @@ public class Plane implements CommandObject { public void setStartingLoyalty(int startingLoyalty) { } + @Override + public int getStartingDefense() { + return 0; + } + + @Override + public void setStartingDefense(int startingDefense) { + } + @Override public UUID getId() { return this.id; diff --git a/Mage/src/main/java/mage/game/permanent/Permanent.java b/Mage/src/main/java/mage/game/permanent/Permanent.java index 9976deca3f0..a410eb0a965 100644 --- a/Mage/src/main/java/mage/game/permanent/Permanent.java +++ b/Mage/src/main/java/mage/game/permanent/Permanent.java @@ -89,6 +89,14 @@ public interface Permanent extends Card, Controllable { Set getGoadingPlayers(); + void chooseProtector(Game game, Ability source); + + void setProtectorId(UUID playerId); + + UUID getProtectorId(); + + boolean isProtectedBy(UUID playerId); + void setCardNumber(String cid); void setExpansionSetCode(String expansionSetCode); @@ -292,6 +300,8 @@ public interface Permanent extends Card, Controllable { boolean canBlockAny(Game game); + boolean canBeAttacked(UUID attackerId, UUID playerToAttack, Game game); + /** * Checks by restriction effects if the permanent can use activated * abilities diff --git a/Mage/src/main/java/mage/game/permanent/PermanentCard.java b/Mage/src/main/java/mage/game/permanent/PermanentCard.java index 0f24a33334d..20b8a701737 100644 --- a/Mage/src/main/java/mage/game/permanent/PermanentCard.java +++ b/Mage/src/main/java/mage/game/permanent/PermanentCard.java @@ -62,6 +62,7 @@ public class PermanentCard extends PermanentImpl { power = card.getPower().copy(); toughness = card.getToughness().copy(); startingLoyalty = card.getStartingLoyalty(); + startingDefense = card.getStartingDefense(); copyFromCard(card, game); // if temporary added abilities to the spell/card exist, you need to add it to the permanent derived from that card Abilities otherAbilities = game.getState().getAllOtherAbilities(card.getId()); diff --git a/Mage/src/main/java/mage/game/permanent/PermanentImpl.java b/Mage/src/main/java/mage/game/permanent/PermanentImpl.java index 5f8cf24ba1a..045c0c12cbf 100644 --- a/Mage/src/main/java/mage/game/permanent/PermanentImpl.java +++ b/Mage/src/main/java/mage/game/permanent/PermanentImpl.java @@ -21,6 +21,7 @@ import mage.constants.*; import mage.counters.Counter; import mage.counters.CounterType; import mage.counters.Counters; +import mage.filter.FilterOpponent; import mage.game.Game; import mage.game.GameState; import mage.game.ZoneChangeInfo; @@ -33,9 +34,10 @@ import mage.game.permanent.token.SquirrelToken; import mage.game.stack.Spell; import mage.game.stack.StackObject; import mage.players.Player; -import mage.target.TargetCard; +import mage.target.TargetPlayer; import mage.util.CardUtil; import mage.util.GameLog; +import mage.util.RandomUtil; import mage.util.ThreadLocalStringBuilder; import org.apache.log4j.Logger; @@ -75,6 +77,7 @@ public abstract class PermanentImpl extends CardImpl implements Permanent { protected final Set goadingPlayers = new HashSet<>(); protected UUID originalControllerId; protected UUID controllerId; + protected UUID protectorId = null; protected UUID beforeResetControllerId; protected int damage; protected boolean controlledFromStartOfControllerTurn; @@ -174,6 +177,7 @@ public abstract class PermanentImpl extends CardImpl implements Permanent { this.loyaltyActivationsAvailable = permanent.loyaltyActivationsAvailable; this.legendRuleApplies = permanent.legendRuleApplies; this.transformCount = permanent.transformCount; + this.protectorId = permanent.protectorId; this.morphed = permanent.morphed; this.manifested = permanent.manifested; @@ -482,7 +486,7 @@ public abstract class PermanentImpl extends CardImpl implements Permanent { @Override public void setLoyaltyActivationsAvailable(int setActivations) { - if(this.loyaltyActivationsAvailable < setActivations) { + if (this.loyaltyActivationsAvailable < setActivations) { this.loyaltyActivationsAvailable = setActivations; } } @@ -991,6 +995,15 @@ public abstract class PermanentImpl extends CardImpl implements Permanent { removeCounters(CounterType.LOYALTY.getName(), countersToRemove, source, game); } } + if (this.isBattle(game)) { + int defense = getCounters(game).getCount(CounterType.DEFENSE); + int countersToRemove = Math.min(actualDamage, defense); + if (attacker != null && markDamage) { + markDamage(CounterType.DEFENSE.createInstance(countersToRemove), attacker, false); + } else { + removeCounters(CounterType.DEFENSE.getName(), countersToRemove, source, game); + } + } DamagedEvent damagedEvent = new DamagedPermanentEvent(this.getId(), attackerId, this.getControllerId(), actualDamage, combat); damagedEvent.setExcess(actualDamage - lethal); game.fireEvent(damagedEvent); @@ -1134,6 +1147,9 @@ public abstract class PermanentImpl extends CardImpl implements Permanent { if (this.isPlaneswalker(game)) { lethal = Math.min(lethal, this.getCounters(game).getCount(CounterType.LOYALTY)); } + if (this.isBattle(game)) { + lethal = Math.min(lethal, this.getCounters(game).getCount(CounterType.DEFENSE)); + } return lethal; } @@ -1196,6 +1212,18 @@ public abstract class PermanentImpl extends CardImpl implements Permanent { this.addCounters(CounterType.LOYALTY.createInstance(countersToAdd), source, game); } } + if (this.isBattle(game)) { + int defense; + if (this.getStartingDefense() == -2) { + defense = source.getManaCostsToPay().getX(); + } else { + defense = this.getStartingDefense(); + } + if (defense > 0) { + this.addCounters(CounterType.DEFENSE.createInstance(defense), source, game); + } + this.chooseProtector(game, source); + } if (!fireEvent) { return false; } @@ -1381,8 +1409,23 @@ public abstract class PermanentImpl extends CardImpl implements Permanent { return canAttackInPrinciple(defenderId, game); } + @Override + public boolean canBeAttacked(UUID attackerId, UUID playerToAttack, Game game) { + if (isPlaneswalker(game)) { + return isControlledBy(playerToAttack); + } + if (isBattle(game)) { + return isProtectedBy(playerToAttack); + } + return false; + } + @Override public boolean canAttackInPrinciple(UUID defenderId, Game game) { + if (isBattle(game)) { + // battles can never attack + return false; + } ApprovingObject approvingObject = game.getContinuousEffects().asThough( this.objectId, AsThoughEffectType.ATTACK_AS_HASTE, null, defenderId, game ); @@ -1422,7 +1465,7 @@ public abstract class PermanentImpl extends CardImpl implements Permanent { @Override public boolean canBlock(UUID attackerId, Game game) { - if (tapped && null == game.getState().getContinuousEffects().asThough(this.getId(), AsThoughEffectType.BLOCK_TAPPED, null, this.getControllerId(), game)) { + if (tapped && game.getState().getContinuousEffects().asThough(this.getId(), AsThoughEffectType.BLOCK_TAPPED, null, this.getControllerId(), game) == null || isBattle(game)) { return false; } Permanent attacker = game.getPermanent(attackerId); @@ -1528,7 +1571,7 @@ public abstract class PermanentImpl extends CardImpl implements Permanent { return game.getCombat().removeFromCombat(objectId, game, withEvent); } else if (this.isPlaneswalker(game)) { if (game.getCombat().getDefenders().contains(getId())) { - game.getCombat().removePlaneswalkerFromCombat(objectId, game); + game.getCombat().removeDefendingPermanentFromCombat(objectId, game); } } return false; @@ -1629,6 +1672,40 @@ public abstract class PermanentImpl extends CardImpl implements Permanent { return goadingPlayers; } + @Override + public void chooseProtector(Game game, Ability source) { + Set opponents = game.getOpponents(this.getControllerId()); + UUID protectorId; + if (opponents.size() > 1) { + TargetPlayer target = new TargetPlayer(new FilterOpponent("protector for " + getName())); + target.setNotTarget(true); + target.setRequired(true); + game.getPlayer(getControllerId()).choose(Outcome.Neutral, target, source, game); + protectorId = target.getFirstTarget(); + } else { + protectorId = RandomUtil.randomFromCollection(opponents); + } + String protectorName = game.getPlayer(protectorId).getLogName(); + game.informPlayers(protectorName + " has been chosen to protect " + this.getLogName()); + this.addInfo("protector", "Protected by " + protectorName, game); + this.setProtectorId(protectorId); + } + + @Override + public void setProtectorId(UUID protectorId) { + this.protectorId = protectorId; + } + + @Override + public UUID getProtectorId() { + return protectorId; + } + + @Override + public boolean isProtectedBy(UUID playerId) { + return protectorId != null && protectorId.equals(playerId); + } + @Override public void setPairedCard(MageObjectReference pairedCard) { this.pairedPermanent = pairedCard; diff --git a/Mage/src/main/java/mage/game/permanent/PermanentToken.java b/Mage/src/main/java/mage/game/permanent/PermanentToken.java index e71a6e293a8..8e9a0f754fc 100644 --- a/Mage/src/main/java/mage/game/permanent/PermanentToken.java +++ b/Mage/src/main/java/mage/game/permanent/PermanentToken.java @@ -86,6 +86,7 @@ public class PermanentToken extends PermanentImpl { this.supertype.addAll(token.getSuperType()); this.subtype.copyFrom(token.getSubtype(game)); this.startingLoyalty = token.getStartingLoyalty(); + this.startingDefense = token.getStartingDefense(); // workaround for entersTheBattlefield replacement effects if (this.abilities.containsClass(ChangelingAbility.class)) { this.subtype.setIsAllCreatureTypes(true); diff --git a/Mage/src/main/java/mage/game/stack/Spell.java b/Mage/src/main/java/mage/game/stack/Spell.java index 6c210beed2b..4aa1858d5e8 100644 --- a/Mage/src/main/java/mage/game/stack/Spell.java +++ b/Mage/src/main/java/mage/game/stack/Spell.java @@ -62,6 +62,7 @@ public class Spell extends StackObjectImpl implements Card { private boolean resolving = false; private UUID commandedBy = null; // for Word of Command private int startingLoyalty; + private int startingDefense; private ActivationManaAbilityStep currentActivatingManaAbilitiesStep = ActivationManaAbilityStep.BEFORE; @@ -73,7 +74,7 @@ public class Spell extends StackObjectImpl implements Card { Card affectedCard = card; // TODO: must be removed after transform cards (one side) migrated to MDF engine (multiple sides) - if (ability.getSpellAbilityCastMode() == SpellAbilityCastMode.DISTURB && affectedCard.getSecondCardFace() != null) { + if (ability.getSpellAbilityCastMode() == SpellAbilityCastMode.TRANSFORMED && affectedCard.getSecondCardFace() != null) { // simulate another side as new card (another code part in continues effect from disturb ability) affectedCard = TransformAbility.transformCardSpellStatic(card, card.getSecondCardFace(), game); } @@ -83,6 +84,7 @@ public class Spell extends StackObjectImpl implements Card { this.frameColor = affectedCard.getFrameColor(null).copy(); this.frameStyle = affectedCard.getFrameStyle(); this.startingLoyalty = affectedCard.getStartingLoyalty(); + this.startingDefense = affectedCard.getStartingDefense(); this.id = ability.getId(); this.zoneChangeCounter = affectedCard.getZoneChangeCounter(game); // sync card's ZCC with spell (copy spell settings) this.ability = ability; @@ -134,6 +136,7 @@ public class Spell extends StackObjectImpl implements Card { this.currentActivatingManaAbilitiesStep = spell.currentActivatingManaAbilitiesStep; this.targetChanged = spell.targetChanged; this.startingLoyalty = spell.startingLoyalty; + this.startingDefense = spell.startingDefense; } public boolean activate(Game game, boolean noMana) { @@ -641,6 +644,16 @@ public class Spell extends StackObjectImpl implements Card { this.startingLoyalty = startingLoyalty; } + @Override + public int getStartingDefense() { + return startingDefense; + } + + @Override + public void setStartingDefense(int startingDefense) { + this.startingDefense = startingDefense; + } + @Override public UUID getId() { return id; diff --git a/Mage/src/main/java/mage/game/stack/StackAbility.java b/Mage/src/main/java/mage/game/stack/StackAbility.java index 9381716f60a..0840f4105ba 100644 --- a/Mage/src/main/java/mage/game/stack/StackAbility.java +++ b/Mage/src/main/java/mage/game/stack/StackAbility.java @@ -16,7 +16,6 @@ import mage.abilities.effects.Effect; import mage.abilities.effects.Effects; import mage.abilities.hint.Hint; import mage.abilities.icon.CardIcon; -import mage.cards.Card; import mage.cards.FrameStyle; import mage.constants.*; import mage.filter.predicate.mageobject.MageObjectReferencePredicate; @@ -243,6 +242,15 @@ public class StackAbility extends StackObjectImpl implements Ability { public void setStartingLoyalty(int startingLoyalty) { } + @Override + public int getStartingDefense() { + return 0; + } + + @Override + public void setStartingDefense(int startingDefense) { + } + @Override public Zone getZone() { return this.ability.getZone(); diff --git a/Mage/src/main/java/mage/target/common/TargetAnyTarget.java b/Mage/src/main/java/mage/target/common/TargetAnyTarget.java index 8a76cf96a1c..cabe6310758 100644 --- a/Mage/src/main/java/mage/target/common/TargetAnyTarget.java +++ b/Mage/src/main/java/mage/target/common/TargetAnyTarget.java @@ -1,232 +1,32 @@ package mage.target.common; -import mage.MageObject; -import mage.abilities.Ability; -import mage.constants.Zone; -import mage.filter.Filter; -import mage.filter.common.FilterCreaturePlayerOrPlaneswalker; -import mage.game.Game; -import mage.game.permanent.Permanent; -import mage.players.Player; -import mage.target.TargetImpl; - -import java.util.HashSet; -import java.util.Set; -import java.util.UUID; +import mage.filter.common.FilterAnyTarget; /** * @author JRHerlehy Created on 4/8/18. */ -public class TargetAnyTarget extends TargetImpl { +public class TargetAnyTarget extends TargetPermanentOrPlayer { - protected FilterCreaturePlayerOrPlaneswalker filter; + private static final FilterAnyTarget filter = new FilterAnyTarget(); public TargetAnyTarget() { - this(1, 1, new FilterCreaturePlayerOrPlaneswalker()); + this(1); } public TargetAnyTarget(int numTargets) { - this(numTargets, numTargets, new FilterCreaturePlayerOrPlaneswalker()); + this(numTargets, numTargets); } - public TargetAnyTarget(FilterCreaturePlayerOrPlaneswalker filter) { - this(1, 1, filter); + public TargetAnyTarget(int minNumTargets, int maxNumTargets) { + super(minNumTargets, maxNumTargets, filter, false); } - public TargetAnyTarget(int numTargets, int maxNumTargets) { - this(numTargets, maxNumTargets, new FilterCreaturePlayerOrPlaneswalker()); - } - - public TargetAnyTarget(int minNumTargets, int maxNumTargets, FilterCreaturePlayerOrPlaneswalker filter) { - this.minNumberOfTargets = minNumTargets; - this.maxNumberOfTargets = maxNumTargets; - this.zone = Zone.ALL; - this.filter = filter; - this.targetName = filter.getMessage(); - } - - public TargetAnyTarget(final TargetAnyTarget target) { + protected TargetAnyTarget(final TargetAnyTarget target) { super(target); - this.filter = target.filter.copy(); - } - - @Override - public Filter getFilter() { - return this.filter; - } - - @Override - public boolean canTarget(UUID id, Game game) { - Permanent permanent = game.getPermanent(id); - if (permanent != null) { - return filter.match(permanent, game); - } - Player player = game.getPlayer(id); - return filter.match(player, game); - } - - @Override - public boolean canTarget(UUID id, Ability source, Game game) { - return canTarget(source.getControllerId(), id, source, game); - } - - @Override - public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { - Permanent permanent = game.getPermanent(id); - Player player = game.getPlayer(id); - - if (source != null) { - MageObject targetSource = game.getObject(source); - if (permanent != null) { - return permanent.canBeTargetedBy(targetSource, source.getControllerId(), game) && filter.match(permanent, source.getControllerId(), source, game); - } - if (player != null) { - return player.canBeTargetedBy(targetSource, source.getControllerId(), game) && filter.match(player, game); - } - } - - if (permanent != null) { - return filter.match(permanent, game); - } - return filter.match(player, game); - } - - /** - * Checks if there are enough {@link Permanent} or {@link Player} that can - * be chosen. Should only be used for Ability targets since this checks for - * protection, shroud etc. - * - * @param sourceControllerId - controller of the target event source - * @param source - * @param game - * @return - true if enough valid {@link Permanent} or {@link Player} exist - */ - @Override - public boolean canChoose(UUID sourceControllerId, Ability source, Game game) { - int count = 0; - - MageObject targetSource = game.getObject(source); - for (UUID playerId : game.getState().getPlayersInRange(sourceControllerId, game)) { - Player player = game.getPlayer(playerId); - if (player != null && player.canBeTargetedBy(targetSource, sourceControllerId, game) && filter.match(player, game)) { - count++; - if (count >= this.minNumberOfTargets) { - return true; - } - } - } - - for (Permanent permanent : game.getBattlefield().getActivePermanents(filter.getPermanentFilter(), sourceControllerId, game)) { - if (permanent.canBeTargetedBy(targetSource, sourceControllerId, game) && filter.match(permanent, sourceControllerId, source, game)) { - count++; - if (count >= this.minNumberOfTargets) { - return true; - } - } - } - - return false; - } - - /** - * Checks if there are enough {@link Permanent} or {@link Player} that can - * be selected. Should not be used for Ability targets since this does not - * check for protection, shroud etc. - * - * @param sourceControllerId - controller of the select event - * @param game - * @return - true if enough valid {@link Permanent} or {@link Player} exist - */ - @Override - public boolean canChoose(UUID sourceControllerId, Game game) { - int count = 0; - - for (UUID playerId : game.getState().getPlayersInRange(sourceControllerId, game)) { - Player player = game.getPlayer(playerId); - if (filter.match(player, game)) { - count++; - if (count >= this.minNumberOfTargets) { - return true; - } - } - } - - for (Permanent permanent : game.getBattlefield().getActivePermanents(filter.getPermanentFilter(), sourceControllerId, game)) { - if (filter.match(permanent, sourceControllerId, null, game)) { - count++; - if (count >= this.minNumberOfTargets) { - return true; - } - } - } - - return false; - } - - @Override - public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { - Set possibleTargets = new HashSet<>(); - MageObject targetSource = game.getObject(source); - - for (UUID playerId : game.getState().getPlayersInRange(sourceControllerId, game)) { - Player player = game.getPlayer(playerId); - if (player != null - && player.canBeTargetedBy(targetSource, sourceControllerId, game) - && filter.match(player, sourceControllerId, source, game)) { - possibleTargets.add(playerId); - } - } - - for (Permanent permanent : game.getBattlefield().getActivePermanents(filter.getPermanentFilter(), sourceControllerId, game)) { - if (permanent.canBeTargetedBy(targetSource, sourceControllerId, game) - && filter.match(permanent, sourceControllerId, source, game)) { - possibleTargets.add(permanent.getId()); - } - } - - return possibleTargets; - } - - @Override - public Set possibleTargets(UUID sourceControllerId, Game game) { - Set possibleTargets = new HashSet<>(); - - for (UUID playerId : game.getState().getPlayersInRange(sourceControllerId, game)) { - Player player = game.getPlayer(playerId); - if (filter.match(player, game)) { - possibleTargets.add(playerId); - } - } - - for (Permanent permanent : game.getBattlefield().getActivePermanents(filter.getPermanentFilter(), sourceControllerId, game)) { - if (filter.getPermanentFilter().match(permanent, sourceControllerId, null, game)) { - possibleTargets.add(permanent.getId()); - } - } - - return possibleTargets; - } - - @Override - public String getTargetedName(Game game) { - StringBuilder sb = new StringBuilder(); - for (UUID targetId : getTargets()) { - Permanent permanent = game.getPermanent(targetId); - if (permanent != null) { - sb.append(permanent.getLogName()).append(' '); - } else { - Player player = game.getPlayer(targetId); - if (player != null) { - sb.append(player.getLogName()).append(' '); - } - } - } - return sb.toString().trim(); } @Override public TargetAnyTarget copy() { return new TargetAnyTarget(this); } - } diff --git a/Mage/src/main/java/mage/target/common/TargetAnyTargetAmount.java b/Mage/src/main/java/mage/target/common/TargetAnyTargetAmount.java index 62270edd244..8132871fd93 100644 --- a/Mage/src/main/java/mage/target/common/TargetAnyTargetAmount.java +++ b/Mage/src/main/java/mage/target/common/TargetAnyTargetAmount.java @@ -3,15 +3,16 @@ package mage.target.common; import mage.abilities.dynamicvalue.DynamicValue; import mage.abilities.dynamicvalue.common.StaticValue; import mage.constants.Zone; -import mage.filter.common.FilterCreaturePlayerOrPlaneswalker; +import mage.filter.common.FilterAnyTarget; +import mage.filter.common.FilterPermanentOrPlayer; /** * @author BetaSteward_at_googlemail.com */ public class TargetAnyTargetAmount extends TargetPermanentOrPlayerAmount { - private static final FilterCreaturePlayerOrPlaneswalker defaultFilter - = new FilterCreaturePlayerOrPlaneswalker("targets"); + private static final FilterPermanentOrPlayer defaultFilter + = new FilterAnyTarget("targets"); public TargetAnyTargetAmount(int amount) { this(amount, 0); diff --git a/Mage/src/main/java/mage/target/common/TargetDefender.java b/Mage/src/main/java/mage/target/common/TargetDefender.java index bbd75386101..86c71f77a7a 100644 --- a/Mage/src/main/java/mage/target/common/TargetDefender.java +++ b/Mage/src/main/java/mage/target/common/TargetDefender.java @@ -1,210 +1,25 @@ package mage.target.common; -import mage.MageObject; -import mage.abilities.Ability; -import mage.constants.Zone; -import mage.filter.Filter; -import mage.filter.StaticFilters; -import mage.filter.common.FilterPlaneswalkerOrPlayer; -import mage.game.Game; -import mage.game.permanent.Permanent; -import mage.players.Player; -import mage.target.TargetImpl; +import mage.filter.common.FilterDefender; -import java.util.HashSet; import java.util.Set; import java.util.UUID; /** * @author BetaSteward_at_googlemail.com */ -public class TargetDefender extends TargetImpl { +public class TargetDefender extends TargetPermanentOrPlayer { - protected final FilterPlaneswalkerOrPlayer filter; - protected final UUID attackerId; - - public TargetDefender(Set defenders, UUID attackerId) { - this(1, 1, defenders, attackerId); + public TargetDefender(Set defenders) { + super(1, 1, new FilterDefender(defenders), true); } - public TargetDefender(int numTargets, Set defenders, UUID attackerId) { - this(numTargets, numTargets, defenders, attackerId); - } - - public TargetDefender(int minNumTargets, int maxNumTargets, Set defenders, UUID attackerId) { - this.minNumberOfTargets = minNumTargets; - this.maxNumberOfTargets = maxNumTargets; - this.zone = Zone.ALL; - this.filter = new FilterPlaneswalkerOrPlayer(defenders); - this.targetName = filter.getMessage(); - this.attackerId = attackerId; - this.notTarget = true; - } - - public TargetDefender(final TargetDefender target) { + private TargetDefender(final TargetDefender target) { super(target); - this.filter = target.filter.copy(); - this.attackerId = target.attackerId; - } - - @Override - public Filter getFilter() { - return this.filter; - } - - @Override - public boolean canChoose(UUID sourceControllerId, Ability source, Game game) { - int count = 0; - MageObject targetSource = game.getObject(source); - for (UUID playerId : game.getState().getPlayersInRange(sourceControllerId, game)) { - Player player = game.getPlayer(playerId); - if (player != null - && (notTarget || player.canBeTargetedBy(targetSource, sourceControllerId, game)) - && filter.match(player, game)) { - count++; - if (count >= this.minNumberOfTargets) { - return true; - } - } - } - for (Permanent permanent : game.getBattlefield().getActivePermanents(StaticFilters.FILTER_PERMANENT_PLANESWALKER, sourceControllerId, game)) { - if ((notTarget - || permanent.canBeTargetedBy(targetSource, sourceControllerId, game)) - && filter.match(permanent, game)) { - count++; - if (count >= this.minNumberOfTargets) { - return true; - } - } - } - return false; - } - - @Override - public boolean canChoose(UUID sourceControllerId, Game game) { - int count = 0; - for (UUID playerId : game.getState().getPlayersInRange(sourceControllerId, game)) { - Player player = game.getPlayer(playerId); - if (player != null - && filter.match(player, game)) { - count++; - if (count >= this.minNumberOfTargets) { - return true; - } - } - } - for (Permanent permanent : game.getBattlefield().getActivePermanents(StaticFilters.FILTER_PERMANENT_PLANESWALKER, sourceControllerId, game)) { - if (filter.match(permanent, game)) { - count++; - if (count >= this.minNumberOfTargets) { - return true; - } - } - } - return false; - } - - @Override - public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { - if (source == null) { - return possibleTargets(sourceControllerId, game); - } - Set possibleTargets = new HashSet<>(); - MageObject targetSource = game.getObject(source); - for (UUID playerId : game.getState().getPlayersInRange(sourceControllerId, game)) { - Player player = game.getPlayer(playerId); - if (player != null - && (notTarget - || player.canBeTargetedBy(targetSource, sourceControllerId, game)) - && filter.match(player, game)) { - possibleTargets.add(playerId); - } - } - for (Permanent permanent : game.getBattlefield().getActivePermanents(StaticFilters.FILTER_PERMANENT_PLANESWALKER, sourceControllerId, game)) { - if ((notTarget - || permanent.canBeTargetedBy(targetSource, sourceControllerId, game)) - && filter.match(permanent, game)) { - possibleTargets.add(permanent.getId()); - } - } - return possibleTargets; - } - - @Override - public Set possibleTargets(UUID sourceControllerId, Game game) { - Set possibleTargets = new HashSet<>(); - for (UUID playerId : game.getState().getPlayersInRange(sourceControllerId, game)) { - Player player = game.getPlayer(playerId); - if (player != null - && filter.match(player, game)) { - possibleTargets.add(playerId); - } - } - for (Permanent permanent : game.getBattlefield().getActivePermanents(StaticFilters.FILTER_PERMANENT_PLANESWALKER, sourceControllerId, game)) { - if (filter.match(permanent, game)) { - possibleTargets.add(permanent.getId()); - } - } - return possibleTargets; - } - - @Override - public String getTargetedName(Game game) { - StringBuilder sb = new StringBuilder(); - for (UUID targetId : getTargets()) { - Permanent permanent = game.getPermanent(targetId); - if (permanent != null) { - sb.append(permanent.getName()).append(' '); - } else { - Player player = game.getPlayer(targetId); - sb.append(player.getLogName()).append(' '); - } - } - return sb.toString().trim(); - } - - @Override - public boolean canTarget(UUID id, Game game) { - Player player = game.getPlayer(id); - if (player != null) { - return filter.match(player, game); - } - Permanent permanent = game.getPermanent(id); - return permanent != null - && filter.match(permanent, game); - } - - @Override - public boolean canTarget(UUID id, Ability source, Game game) { - Player player = game.getPlayer(id); - MageObject targetSource = game.getObject(attackerId); - if (player != null) { - return (notTarget - || player.canBeTargetedBy(targetSource, (source == null ? null : source.getControllerId()), game)) - && filter.match(player, game); - } - Permanent permanent = game.getPermanent(id); // planeswalker - if (permanent != null) { - //Could be targeting due to combat decision to attack a player or planeswalker. - UUID controllerId = null; - if (source != null) { - controllerId = source.getControllerId(); - } - return (notTarget - || permanent.canBeTargetedBy(targetSource, controllerId, game)) - && filter.match(permanent, game); - } - return false; - } - - @Override - public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) { - return canTarget(id, source, game); } @Override public TargetDefender copy() { return new TargetDefender(this); } - } diff --git a/Mage/src/main/java/mage/util/CardUtil.java b/Mage/src/main/java/mage/util/CardUtil.java index 588cc75153c..807a8972439 100644 --- a/Mage/src/main/java/mage/util/CardUtil.java +++ b/Mage/src/main/java/mage/util/CardUtil.java @@ -1726,14 +1726,14 @@ public final class CardUtil { return i == null ? 1 : Integer.sum(i, 1); } - public static String convertStartingLoyalty(int startingLoyalty) { - switch (startingLoyalty) { + public static String convertLoyaltyOrDefense(int value) { + switch (value) { case -2: return "X"; case -1: return ""; default: - return "" + startingLoyalty; + return "" + value; } } diff --git a/Mage/src/main/java/mage/util/functions/CopyTokenFunction.java b/Mage/src/main/java/mage/util/functions/CopyTokenFunction.java index b2035478f0b..f80c3af248c 100644 --- a/Mage/src/main/java/mage/util/functions/CopyTokenFunction.java +++ b/Mage/src/main/java/mage/util/functions/CopyTokenFunction.java @@ -96,6 +96,7 @@ public class CopyTokenFunction implements Function { target.setPower(sourceObj.getPower().getBaseValue()); target.setToughness(sourceObj.getToughness().getBaseValue()); target.setStartingLoyalty(sourceObj.getStartingLoyalty()); + target.setStartingDefense(sourceObj.getStartingDefense()); return target; } @@ -114,6 +115,7 @@ public class CopyTokenFunction implements Function { target.setZoneChangeCounter(spell.getZoneChangeCounter(game), game); // Copy starting loyalty from spell (Ob Nixilis, the Adversary) target.setStartingLoyalty(spell.getStartingLoyalty()); + target.setStartingDefense(spell.getStartingDefense()); } else { target.setZoneChangeCounter(source.getZoneChangeCounter(game), game); } diff --git a/Utils/cardClass.tmpl b/Utils/cardClass.tmpl index da22cf54057..6488f45320e 100644 --- a/Utils/cardClass.tmpl +++ b/Utils/cardClass.tmpl @@ -2,7 +2,7 @@ package mage.cards.[=$cardNameFirstLetter=]; import java.util.UUID;[= if ($power || $power eq 0) { - if ($planeswalker eq 'true') { + if ($planeswalker eq 'true' || $battle eq 'true') { }else { $OUT .= "\nimport mage.MageInt;" } @@ -30,6 +30,8 @@ public final class [=$className=] extends CardImpl { if ($power || $power eq 0) { if ($planeswalker eq 'true') { $OUT .= "\n this.setStartingLoyalty($power);"; + } elsif ($battle eq 'true') { + $OUT .= "\n this.setStartingDefense($power);"; } else { $OUT .= "\n this.power = new MageInt($power);"; $OUT .= "\n this.toughness = new MageInt($toughness);"; diff --git a/Utils/gen-card.pl b/Utils/gen-card.pl index 7b6c3dbeb99..1b48cef242a 100755 --- a/Utils/gen-card.pl +++ b/Utils/gen-card.pl @@ -79,6 +79,7 @@ $cardTypes{'Land'} = 'CardType.LAND'; $cardTypes{'Sorcery'} = 'CardType.SORCERY'; $cardTypes{'Planeswalker'} = 'CardType.PLANESWALKER'; $cardTypes{'Tribal'} = 'CardType.TRIBAL'; +$cardTypes{'Battle'} = 'CardType.BATTLE'; my %raritiesConversion; $raritiesConversion{'C'} = 'COMMON'; @@ -185,6 +186,7 @@ $vars{'toughness'} = $card[7]; my @types; $vars{'planeswalker'} = 'false'; +$vars{'battle'} = 'false'; $vars{'subType'} = ''; $vars{'hasSubTypes'} = 'false'; $vars{'hasSuperTypes'} = 'false'; @@ -196,6 +198,9 @@ while ($type =~ m/([a-zA-Z]+)( )*/g) { if ($cardTypes{$1} eq $cardTypes{'Planeswalker'}) { $vars{'planeswalker'} = 'true'; $cardAbilities = $card[7]; + } elsif ($cardTypes{$1} eq $cardTypes{'Battle'}) { + $vars{'battle'} = 'true'; + $cardAbilities = $card[7]; } } else { if (@types) {