diff --git a/Mage.Client/pom.xml b/Mage.Client/pom.xml index b01196abd89..cc198add2a8 100644 --- a/Mage.Client/pom.xml +++ b/Mage.Client/pom.xml @@ -6,7 +6,7 @@ org.mage mage-root - 1.4.54 + 1.4.55 mage-client diff --git a/Mage.Client/src/main/java/mage/client/chat/ChatPanelBasic.java b/Mage.Client/src/main/java/mage/client/chat/ChatPanelBasic.java index acf12357f10..3e9158d6ebc 100644 --- a/Mage.Client/src/main/java/mage/client/chat/ChatPanelBasic.java +++ b/Mage.Client/src/main/java/mage/client/chat/ChatPanelBasic.java @@ -265,7 +265,7 @@ public class ChatPanelBasic extends javax.swing.JPanel { } String cachedProfanityFilterValue = PreferencesDialog.getCachedValue(PreferencesDialog.KEY_GAME_USE_PROFANITY_FILTER, "0"); - boolean isContainsSwearing = !containsSwearing(messageToTest, cachedProfanityFilterValue); + boolean isContainsSwearing = containsSwearing(messageToTest, cachedProfanityFilterValue); boolean isUserInfoOrGameOrStatus = messageType == MessageType.USER_INFO || messageType == MessageType.GAME || messageType == MessageType.STATUS; if (isUserInfoOrGameOrStatus || cachedProfanityFilterValue.equals("0") || (!cachedProfanityFilterValue.equals("0") && !isContainsSwearing)) { if (username != null && !username.isEmpty()) { diff --git a/Mage.Common/pom.xml b/Mage.Common/pom.xml index eccddd0df10..e9248c50e18 100644 --- a/Mage.Common/pom.xml +++ b/Mage.Common/pom.xml @@ -7,7 +7,7 @@ org.mage mage-root - 1.4.54 + 1.4.55 mage-common diff --git a/Mage.Common/src/main/java/mage/utils/MageVersion.java b/Mage.Common/src/main/java/mage/utils/MageVersion.java index 8402770ab52..0f7197155c1 100644 --- a/Mage.Common/src/main/java/mage/utils/MageVersion.java +++ b/Mage.Common/src/main/java/mage/utils/MageVersion.java @@ -17,8 +17,8 @@ public class MageVersion implements Serializable, Comparable { // * launcher gives priority to 1.4.48 instead 1.4.48-any-text, so don't use empty release info public static final int MAGE_VERSION_MAJOR = 1; public static final int MAGE_VERSION_MINOR = 4; - public static final int MAGE_VERSION_RELEASE = 54; - public static final String MAGE_VERSION_RELEASE_INFO = "V3"; // V1, V1a, V1b for releases; V1-beta3, V1-beta4 for betas + public static final int MAGE_VERSION_RELEASE = 55; + public static final String MAGE_VERSION_RELEASE_INFO = "V1"; // V1, V1a, V1b for releases; V1-beta3, V1-beta4 for betas // strict mode // Each update requires a strict version diff --git a/Mage.Plugins/Mage.Counter.Plugin/pom.xml b/Mage.Plugins/Mage.Counter.Plugin/pom.xml index a53c82f0ee7..0ebcfdd8bc3 100644 --- a/Mage.Plugins/Mage.Counter.Plugin/pom.xml +++ b/Mage.Plugins/Mage.Counter.Plugin/pom.xml @@ -7,7 +7,7 @@ org.mage mage-plugins - 1.4.54 + 1.4.55 mage-counter-plugin diff --git a/Mage.Plugins/pom.xml b/Mage.Plugins/pom.xml index 2efdd9c8dcb..0e2b4f6138f 100644 --- a/Mage.Plugins/pom.xml +++ b/Mage.Plugins/pom.xml @@ -7,7 +7,7 @@ org.mage mage-root - 1.4.54 + 1.4.55 mage-plugins diff --git a/Mage.Reports/pom.xml b/Mage.Reports/pom.xml index d939f1f985a..f251298eced 100644 --- a/Mage.Reports/pom.xml +++ b/Mage.Reports/pom.xml @@ -6,7 +6,7 @@ org.mage mage-root - 1.4.54 + 1.4.55 mage-reports diff --git a/Mage.Server.Console/pom.xml b/Mage.Server.Console/pom.xml index df940eb51db..f7407a80156 100644 --- a/Mage.Server.Console/pom.xml +++ b/Mage.Server.Console/pom.xml @@ -6,7 +6,7 @@ org.mage mage-root - 1.4.54 + 1.4.55 mage-server-console diff --git a/Mage.Server.Plugins/Mage.Deck.Constructed/pom.xml b/Mage.Server.Plugins/Mage.Deck.Constructed/pom.xml index c23c4b4b692..6b0ba470c9e 100644 --- a/Mage.Server.Plugins/Mage.Deck.Constructed/pom.xml +++ b/Mage.Server.Plugins/Mage.Deck.Constructed/pom.xml @@ -7,7 +7,7 @@ org.mage mage-server-plugins - 1.4.54 + 1.4.55 mage-deck-constructed diff --git a/Mage.Server.Plugins/Mage.Deck.Limited/pom.xml b/Mage.Server.Plugins/Mage.Deck.Limited/pom.xml index 89bc3e956d7..6093f196f59 100644 --- a/Mage.Server.Plugins/Mage.Deck.Limited/pom.xml +++ b/Mage.Server.Plugins/Mage.Deck.Limited/pom.xml @@ -7,7 +7,7 @@ org.mage mage-server-plugins - 1.4.54 + 1.4.55 mage-deck-limited diff --git a/Mage.Server.Plugins/Mage.Game.BrawlDuel/pom.xml b/Mage.Server.Plugins/Mage.Game.BrawlDuel/pom.xml index eeb6b522630..de808e4f9c2 100644 --- a/Mage.Server.Plugins/Mage.Game.BrawlDuel/pom.xml +++ b/Mage.Server.Plugins/Mage.Game.BrawlDuel/pom.xml @@ -7,7 +7,7 @@ org.mage mage-server-plugins - 1.4.54 + 1.4.55 mage-game-brawlduel diff --git a/Mage.Server.Plugins/Mage.Game.BrawlFreeForAll/pom.xml b/Mage.Server.Plugins/Mage.Game.BrawlFreeForAll/pom.xml index a1b009c1c2a..23594520bd6 100644 --- a/Mage.Server.Plugins/Mage.Game.BrawlFreeForAll/pom.xml +++ b/Mage.Server.Plugins/Mage.Game.BrawlFreeForAll/pom.xml @@ -6,7 +6,7 @@ org.mage mage-server-plugins - 1.4.54 + 1.4.55 mage-game-brawlfreeforall diff --git a/Mage.Server.Plugins/Mage.Game.CanadianHighlanderDuel/pom.xml b/Mage.Server.Plugins/Mage.Game.CanadianHighlanderDuel/pom.xml index 8212fec501b..eb7288a4ef4 100644 --- a/Mage.Server.Plugins/Mage.Game.CanadianHighlanderDuel/pom.xml +++ b/Mage.Server.Plugins/Mage.Game.CanadianHighlanderDuel/pom.xml @@ -7,7 +7,7 @@ org.mage mage-server-plugins - 1.4.54 + 1.4.55 mage-game-canadianhighlanderduel diff --git a/Mage.Server.Plugins/Mage.Game.CommanderDuel/pom.xml b/Mage.Server.Plugins/Mage.Game.CommanderDuel/pom.xml index f10d29fec70..7d6919218ed 100644 --- a/Mage.Server.Plugins/Mage.Game.CommanderDuel/pom.xml +++ b/Mage.Server.Plugins/Mage.Game.CommanderDuel/pom.xml @@ -7,7 +7,7 @@ org.mage mage-server-plugins - 1.4.54 + 1.4.55 mage-game-commanderduel diff --git a/Mage.Server.Plugins/Mage.Game.CommanderFreeForAll/pom.xml b/Mage.Server.Plugins/Mage.Game.CommanderFreeForAll/pom.xml index 2aeba5eb94d..5fd2deec0af 100644 --- a/Mage.Server.Plugins/Mage.Game.CommanderFreeForAll/pom.xml +++ b/Mage.Server.Plugins/Mage.Game.CommanderFreeForAll/pom.xml @@ -6,7 +6,7 @@ org.mage mage-server-plugins - 1.4.54 + 1.4.55 mage-game-commanderfreeforall diff --git a/Mage.Server.Plugins/Mage.Game.CustomPillarOfTheParunsDuel/pom.xml b/Mage.Server.Plugins/Mage.Game.CustomPillarOfTheParunsDuel/pom.xml index fc4d8742002..190112dc919 100644 --- a/Mage.Server.Plugins/Mage.Game.CustomPillarOfTheParunsDuel/pom.xml +++ b/Mage.Server.Plugins/Mage.Game.CustomPillarOfTheParunsDuel/pom.xml @@ -7,7 +7,7 @@ org.mage mage-server-plugins - 1.4.54 + 1.4.55 mage-game-custompillaroftheparunsduel diff --git a/Mage.Server.Plugins/Mage.Game.FreeForAll/pom.xml b/Mage.Server.Plugins/Mage.Game.FreeForAll/pom.xml index d3f37d4524e..de8f60e544e 100644 --- a/Mage.Server.Plugins/Mage.Game.FreeForAll/pom.xml +++ b/Mage.Server.Plugins/Mage.Game.FreeForAll/pom.xml @@ -7,7 +7,7 @@ org.mage mage-server-plugins - 1.4.54 + 1.4.55 mage-game-freeforall diff --git a/Mage.Server.Plugins/Mage.Game.FreeformCommanderDuel/pom.xml b/Mage.Server.Plugins/Mage.Game.FreeformCommanderDuel/pom.xml index 9625ef7b338..a9dec0c425e 100644 --- a/Mage.Server.Plugins/Mage.Game.FreeformCommanderDuel/pom.xml +++ b/Mage.Server.Plugins/Mage.Game.FreeformCommanderDuel/pom.xml @@ -7,7 +7,7 @@ org.mage mage-server-plugins - 1.4.54 + 1.4.55 mage-game-freeformcommanderduel diff --git a/Mage.Server.Plugins/Mage.Game.FreeformCommanderFreeForAll/pom.xml b/Mage.Server.Plugins/Mage.Game.FreeformCommanderFreeForAll/pom.xml index ab43f71efbe..0045b6ea026 100644 --- a/Mage.Server.Plugins/Mage.Game.FreeformCommanderFreeForAll/pom.xml +++ b/Mage.Server.Plugins/Mage.Game.FreeformCommanderFreeForAll/pom.xml @@ -6,7 +6,7 @@ org.mage mage-server-plugins - 1.4.54 + 1.4.55 mage-game-freeformcommanderfreeforall diff --git a/Mage.Server.Plugins/Mage.Game.FreeformUnlimitedCommander/pom.xml b/Mage.Server.Plugins/Mage.Game.FreeformUnlimitedCommander/pom.xml index c533ce926dc..9d091c25637 100644 --- a/Mage.Server.Plugins/Mage.Game.FreeformUnlimitedCommander/pom.xml +++ b/Mage.Server.Plugins/Mage.Game.FreeformUnlimitedCommander/pom.xml @@ -7,7 +7,7 @@ org.mage mage-server-plugins - 1.4.54 + 1.4.55 mage-game-freeformunlimitedcommander @@ -23,7 +23,7 @@ org.mage mage-game-freeformcommanderfreeforall - 1.4.54 + 1.4.55 compile diff --git a/Mage.Server.Plugins/Mage.Game.MomirDuel/pom.xml b/Mage.Server.Plugins/Mage.Game.MomirDuel/pom.xml index eeb2c4a8b0d..23aaef8f534 100644 --- a/Mage.Server.Plugins/Mage.Game.MomirDuel/pom.xml +++ b/Mage.Server.Plugins/Mage.Game.MomirDuel/pom.xml @@ -7,7 +7,7 @@ org.mage mage-server-plugins - 1.4.54 + 1.4.55 mage-game-momirduel diff --git a/Mage.Server.Plugins/Mage.Game.MomirGame/pom.xml b/Mage.Server.Plugins/Mage.Game.MomirGame/pom.xml index fcdf035555b..4e403e3f4cd 100644 --- a/Mage.Server.Plugins/Mage.Game.MomirGame/pom.xml +++ b/Mage.Server.Plugins/Mage.Game.MomirGame/pom.xml @@ -7,7 +7,7 @@ org.mage mage-server-plugins - 1.4.54 + 1.4.55 mage-game-momirfreeforall diff --git a/Mage.Server.Plugins/Mage.Game.OathbreakerDuel/pom.xml b/Mage.Server.Plugins/Mage.Game.OathbreakerDuel/pom.xml index 0948973c6cc..8a79934e501 100644 --- a/Mage.Server.Plugins/Mage.Game.OathbreakerDuel/pom.xml +++ b/Mage.Server.Plugins/Mage.Game.OathbreakerDuel/pom.xml @@ -6,7 +6,7 @@ org.mage mage-server-plugins - 1.4.54 + 1.4.55 mage-game-oathbreakerduel @@ -22,7 +22,7 @@ org.mage mage-game-oathbreakerfreeforall - 1.4.54 + 1.4.55 compile diff --git a/Mage.Server.Plugins/Mage.Game.OathbreakerFreeForAll/pom.xml b/Mage.Server.Plugins/Mage.Game.OathbreakerFreeForAll/pom.xml index 9e35b646af0..6a906431afd 100644 --- a/Mage.Server.Plugins/Mage.Game.OathbreakerFreeForAll/pom.xml +++ b/Mage.Server.Plugins/Mage.Game.OathbreakerFreeForAll/pom.xml @@ -7,7 +7,7 @@ org.mage mage-server-plugins - 1.4.54 + 1.4.55 mage-game-oathbreakerfreeforall diff --git a/Mage.Server.Plugins/Mage.Game.PennyDreadfulCommanderFreeForAll/pom.xml b/Mage.Server.Plugins/Mage.Game.PennyDreadfulCommanderFreeForAll/pom.xml index fa7df6db7ee..bc221cf10e3 100644 --- a/Mage.Server.Plugins/Mage.Game.PennyDreadfulCommanderFreeForAll/pom.xml +++ b/Mage.Server.Plugins/Mage.Game.PennyDreadfulCommanderFreeForAll/pom.xml @@ -6,7 +6,7 @@ org.mage mage-server-plugins - 1.4.54 + 1.4.55 mage-game-pennydreadfulcommanderfreeforall diff --git a/Mage.Server.Plugins/Mage.Game.TinyLeadersDuel/pom.xml b/Mage.Server.Plugins/Mage.Game.TinyLeadersDuel/pom.xml index 53a85916626..ef4ea9bea06 100644 --- a/Mage.Server.Plugins/Mage.Game.TinyLeadersDuel/pom.xml +++ b/Mage.Server.Plugins/Mage.Game.TinyLeadersDuel/pom.xml @@ -7,7 +7,7 @@ org.mage mage-server-plugins - 1.4.54 + 1.4.55 mage-game-tinyleadersduel diff --git a/Mage.Server.Plugins/Mage.Game.TwoPlayerDuel/pom.xml b/Mage.Server.Plugins/Mage.Game.TwoPlayerDuel/pom.xml index 876be9e6ac2..35664806139 100644 --- a/Mage.Server.Plugins/Mage.Game.TwoPlayerDuel/pom.xml +++ b/Mage.Server.Plugins/Mage.Game.TwoPlayerDuel/pom.xml @@ -7,7 +7,7 @@ org.mage mage-server-plugins - 1.4.54 + 1.4.55 mage-game-twoplayerduel diff --git a/Mage.Server.Plugins/Mage.Player.AI.DraftBot/pom.xml b/Mage.Server.Plugins/Mage.Player.AI.DraftBot/pom.xml index 29e7e6e7bd0..8afec9cdd1c 100644 --- a/Mage.Server.Plugins/Mage.Player.AI.DraftBot/pom.xml +++ b/Mage.Server.Plugins/Mage.Player.AI.DraftBot/pom.xml @@ -7,7 +7,7 @@ org.mage mage-server-plugins - 1.4.54 + 1.4.55 mage-player-ai-draftbot diff --git a/Mage.Server.Plugins/Mage.Player.AI.MA/pom.xml b/Mage.Server.Plugins/Mage.Player.AI.MA/pom.xml index d18b3d20610..9ae6ceb0bd5 100644 --- a/Mage.Server.Plugins/Mage.Player.AI.MA/pom.xml +++ b/Mage.Server.Plugins/Mage.Player.AI.MA/pom.xml @@ -7,7 +7,7 @@ org.mage mage-server-plugins - 1.4.54 + 1.4.55 mage-player-ai-ma diff --git a/Mage.Server.Plugins/Mage.Player.AI/pom.xml b/Mage.Server.Plugins/Mage.Player.AI/pom.xml index 0003de8c6fa..4be51267f30 100644 --- a/Mage.Server.Plugins/Mage.Player.AI/pom.xml +++ b/Mage.Server.Plugins/Mage.Player.AI/pom.xml @@ -7,7 +7,7 @@ org.mage mage-server-plugins - 1.4.54 + 1.4.55 mage-player-ai diff --git a/Mage.Server.Plugins/Mage.Player.AIMCTS/pom.xml b/Mage.Server.Plugins/Mage.Player.AIMCTS/pom.xml index d8871a0cf3c..f2fa92296ab 100644 --- a/Mage.Server.Plugins/Mage.Player.AIMCTS/pom.xml +++ b/Mage.Server.Plugins/Mage.Player.AIMCTS/pom.xml @@ -7,7 +7,7 @@ org.mage mage-server-plugins - 1.4.54 + 1.4.55 mage-player-ai-mcts diff --git a/Mage.Server.Plugins/Mage.Player.Human/pom.xml b/Mage.Server.Plugins/Mage.Player.Human/pom.xml index 334acab1a06..de5ddc29d61 100644 --- a/Mage.Server.Plugins/Mage.Player.Human/pom.xml +++ b/Mage.Server.Plugins/Mage.Player.Human/pom.xml @@ -7,7 +7,7 @@ org.mage mage-server-plugins - 1.4.54 + 1.4.55 mage-player-human diff --git a/Mage.Server.Plugins/Mage.Tournament.BoosterDraft/pom.xml b/Mage.Server.Plugins/Mage.Tournament.BoosterDraft/pom.xml index 1b1d7e3f554..39150f8e260 100644 --- a/Mage.Server.Plugins/Mage.Tournament.BoosterDraft/pom.xml +++ b/Mage.Server.Plugins/Mage.Tournament.BoosterDraft/pom.xml @@ -7,7 +7,7 @@ org.mage mage-server-plugins - 1.4.54 + 1.4.55 mage-tournament-boosterdraft diff --git a/Mage.Server.Plugins/Mage.Tournament.Constructed/pom.xml b/Mage.Server.Plugins/Mage.Tournament.Constructed/pom.xml index 1926e65f038..2760714e8d2 100644 --- a/Mage.Server.Plugins/Mage.Tournament.Constructed/pom.xml +++ b/Mage.Server.Plugins/Mage.Tournament.Constructed/pom.xml @@ -7,7 +7,7 @@ org.mage mage-server-plugins - 1.4.54 + 1.4.55 mage-tournament-constructed diff --git a/Mage.Server.Plugins/Mage.Tournament.Sealed/pom.xml b/Mage.Server.Plugins/Mage.Tournament.Sealed/pom.xml index f8c8721dc0b..9b494faa29b 100644 --- a/Mage.Server.Plugins/Mage.Tournament.Sealed/pom.xml +++ b/Mage.Server.Plugins/Mage.Tournament.Sealed/pom.xml @@ -7,7 +7,7 @@ org.mage mage-server-plugins - 1.4.54 + 1.4.55 mage-tournament-sealed diff --git a/Mage.Server.Plugins/pom.xml b/Mage.Server.Plugins/pom.xml index b1a63a49142..5ba184819b3 100644 --- a/Mage.Server.Plugins/pom.xml +++ b/Mage.Server.Plugins/pom.xml @@ -7,7 +7,7 @@ org.mage mage-root - 1.4.54 + 1.4.55 mage-server-plugins diff --git a/Mage.Server/pom.xml b/Mage.Server/pom.xml index 76fc2f09373..9ee564862c7 100644 --- a/Mage.Server/pom.xml +++ b/Mage.Server/pom.xml @@ -6,7 +6,7 @@ org.mage mage-root - 1.4.54 + 1.4.55 mage-server diff --git a/Mage.Sets/pom.xml b/Mage.Sets/pom.xml index 6afd2a5cdb7..a33ae70434b 100644 --- a/Mage.Sets/pom.xml +++ b/Mage.Sets/pom.xml @@ -7,7 +7,7 @@ org.mage mage-root - 1.4.54 + 1.4.55 mage-sets diff --git a/Mage.Sets/src/mage/cards/a/AbzanCharm.java b/Mage.Sets/src/mage/cards/a/AbzanCharm.java index f34b01dd664..0de055f435a 100644 --- a/Mage.Sets/src/mage/cards/a/AbzanCharm.java +++ b/Mage.Sets/src/mage/cards/a/AbzanCharm.java @@ -43,7 +43,7 @@ public final class AbzanCharm extends CardImpl { this.getSpellAbility().addMode(mode); // *Distribute two +1/+1 counters among one or two target creatures. - mode = new Mode(new DistributeCountersEffect(CounterType.P1P1, 2, false, "one or two target creatures")); + mode = new Mode(new DistributeCountersEffect(2, "one or two target creatures")); mode.addTarget(new TargetCreaturePermanentAmount(2)); this.getSpellAbility().addMode(mode); diff --git a/Mage.Sets/src/mage/cards/a/AetherGust.java b/Mage.Sets/src/mage/cards/a/AetherGust.java index a12817aa909..fc0254699f6 100644 --- a/Mage.Sets/src/mage/cards/a/AetherGust.java +++ b/Mage.Sets/src/mage/cards/a/AetherGust.java @@ -37,7 +37,7 @@ public final class AetherGust extends CardImpl { // Choose target spell or permanent that's red or green. Its owner puts it on the top or bottom of their library. this.getSpellAbility().addEffect(new PutOnTopOrBottomLibraryTargetEffect(true).setText( "choose target spell or permanent that's red or green. " + - "Its owner puts it on the top or bottom of their library" + "Its owner puts it on their choice of the top or bottom of their library" )); this.getSpellAbility().addTarget(new TargetSpellOrPermanent(filter)); } diff --git a/Mage.Sets/src/mage/cards/a/AirtightAlibi.java b/Mage.Sets/src/mage/cards/a/AirtightAlibi.java index b534379c2fc..a6621508eb8 100644 --- a/Mage.Sets/src/mage/cards/a/AirtightAlibi.java +++ b/Mage.Sets/src/mage/cards/a/AirtightAlibi.java @@ -125,7 +125,6 @@ class AirtightAlibiReplacementEffect extends ReplacementEffectImpl { public boolean applies(GameEvent event, Ability source, Game game) { return Optional .ofNullable(source.getSourcePermanentIfItStillExists(game)) - .filter(Objects::nonNull) .map(Permanent::getAttachedTo) .map(event.getTargetId()::equals) .orElse(false); diff --git a/Mage.Sets/src/mage/cards/a/AjaniMentorOfHeroes.java b/Mage.Sets/src/mage/cards/a/AjaniMentorOfHeroes.java index 7015d62397e..02796fb08c5 100644 --- a/Mage.Sets/src/mage/cards/a/AjaniMentorOfHeroes.java +++ b/Mage.Sets/src/mage/cards/a/AjaniMentorOfHeroes.java @@ -38,7 +38,7 @@ public final class AjaniMentorOfHeroes extends CardImpl { this.setStartingLoyalty(4); // +1: Distribute three +1/+1 counters among one, two, or three target creatures you control - Ability ability = new LoyaltyAbility(new DistributeCountersEffect(CounterType.P1P1, 3, false, "one, two, or three target creatures you control"), 1); + Ability ability = new LoyaltyAbility(new DistributeCountersEffect(3, "one, two, or three target creatures you control"), 1); ability.addTarget(new TargetCreaturePermanentAmount(3, StaticFilters.FILTER_CONTROLLED_CREATURES)); this.addAbility(ability); diff --git a/Mage.Sets/src/mage/cards/a/AjaniSleeperAgent.java b/Mage.Sets/src/mage/cards/a/AjaniSleeperAgent.java index 1307aed6550..091d653edea 100644 --- a/Mage.Sets/src/mage/cards/a/AjaniSleeperAgent.java +++ b/Mage.Sets/src/mage/cards/a/AjaniSleeperAgent.java @@ -42,7 +42,7 @@ public final class AjaniSleeperAgent extends CardImpl { this.addAbility(new LoyaltyAbility(new AjaniSleeperAgentEffect(), 1)); // −3: Distribute three +1/+1 counters among up to three target creatures. They gain vigilance until end of turn. - Ability ability = new LoyaltyAbility(new DistributeCountersEffect(CounterType.P1P1, 3, false, "up to three target creatures"), -3); + Ability ability = new LoyaltyAbility(new DistributeCountersEffect(3, "up to three target creatures"), -3); ability.addEffect(new GainAbilityTargetEffect(VigilanceAbility.getInstance()).setText("They gain vigilance until end of turn")); Target target = new TargetCreaturePermanentAmount(3); target.setMinNumberOfTargets(0); diff --git a/Mage.Sets/src/mage/cards/a/AjanisLastStand.java b/Mage.Sets/src/mage/cards/a/AjanisLastStand.java index d0288164c7e..a2697f89c90 100644 --- a/Mage.Sets/src/mage/cards/a/AjanisLastStand.java +++ b/Mage.Sets/src/mage/cards/a/AjanisLastStand.java @@ -1,6 +1,8 @@ package mage.cards.a; import java.util.UUID; + +import mage.MageObject; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.common.DiscardedByOpponentTriggeredAbility; import mage.abilities.condition.common.PermanentsOnTheBattlefieldCondition; @@ -63,6 +65,7 @@ class AjanisLastStandTriggeredAbility extends TriggeredAbilityImpl { new CreateTokenEffect(new AvatarToken2()), new SacrificeSourceCost() ), false); + setLeavesTheBattlefieldTrigger(true); } private AjanisLastStandTriggeredAbility(final AjanisLastStandTriggeredAbility ability) { @@ -98,4 +101,9 @@ class AjanisLastStandTriggeredAbility extends TriggeredAbilityImpl { + "you may sacrifice {this}. " + "If you do, create a 4/4 white Avatar creature token with flying."; } + + @Override + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); + } } diff --git a/Mage.Sets/src/mage/cards/a/AleshaWhoLaughsAtFate.java b/Mage.Sets/src/mage/cards/a/AleshaWhoLaughsAtFate.java index 8a75217b72f..5371fcbf667 100644 --- a/Mage.Sets/src/mage/cards/a/AleshaWhoLaughsAtFate.java +++ b/Mage.Sets/src/mage/cards/a/AleshaWhoLaughsAtFate.java @@ -82,8 +82,8 @@ enum AleshaWhoLaughsAtFatePredicate implements ObjectSourcePlayerPredicate public boolean apply(ObjectSourcePlayer input, Game game) { return Optional .ofNullable(input.getSource().getSourcePermanentOrLKI(game)) - .map(MageObject::getManaValue) - .map(p -> input.getObject().getManaValue() <= p) + .map(MageObject::getPower) + .map(p -> input.getObject().getManaValue() <= p.getValue()) .orElse(false); } } diff --git a/Mage.Sets/src/mage/cards/a/AnaraWolvidFamiliar.java b/Mage.Sets/src/mage/cards/a/AnaraWolvidFamiliar.java index 8a079e6b1db..70858cee6cb 100644 --- a/Mage.Sets/src/mage/cards/a/AnaraWolvidFamiliar.java +++ b/Mage.Sets/src/mage/cards/a/AnaraWolvidFamiliar.java @@ -44,7 +44,7 @@ public final class AnaraWolvidFamiliar extends CardImpl { new GainAbilityControlledEffect( IndestructibleAbility.getInstance(), Duration.WhileOnBattlefield, filter - ), MyTurnCondition.instance, "as long as it's your turn, " + + ), MyTurnCondition.instance, "during your turn, " + "commanders you control have indestructible" ))); diff --git a/Mage.Sets/src/mage/cards/a/ArmamentCorps.java b/Mage.Sets/src/mage/cards/a/ArmamentCorps.java index dd6f95e5014..454359f62e7 100644 --- a/Mage.Sets/src/mage/cards/a/ArmamentCorps.java +++ b/Mage.Sets/src/mage/cards/a/ArmamentCorps.java @@ -28,7 +28,7 @@ public final class ArmamentCorps extends CardImpl { this.toughness = new MageInt(4); // When Armament Corps enters the battlefield, distribute two +1/+1 counters among one or two target creatures you control. - Ability ability = new EntersBattlefieldTriggeredAbility(new DistributeCountersEffect(CounterType.P1P1, 2, false, "one or two target creatures you control"), false); + Ability ability = new EntersBattlefieldTriggeredAbility(new DistributeCountersEffect(2, "one or two target creatures you control"), false); ability.addTarget(new TargetCreaturePermanentAmount(2, StaticFilters.FILTER_CONTROLLED_CREATURES)); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/a/AssaultIntercessor.java b/Mage.Sets/src/mage/cards/a/AssaultIntercessor.java index 8a12da72ce2..245cec70910 100644 --- a/Mage.Sets/src/mage/cards/a/AssaultIntercessor.java +++ b/Mage.Sets/src/mage/cards/a/AssaultIntercessor.java @@ -76,11 +76,9 @@ class AssaultIntercessorEffect extends OneShotEffect { public boolean apply(Game game, Ability source) { return Optional .ofNullable(getValue("creatureDied")) - .filter(Objects::nonNull) .map(Permanent.class::cast) .map(Controllable::getControllerId) .map(game::getPlayer) - .filter(Objects::nonNull) .map(player -> player.loseLife(2, game, source, false) > 0) .orElse(false); } diff --git a/Mage.Sets/src/mage/cards/a/AtKnifepoint.java b/Mage.Sets/src/mage/cards/a/AtKnifepoint.java index df7ab794ce0..8b0055caf08 100644 --- a/Mage.Sets/src/mage/cards/a/AtKnifepoint.java +++ b/Mage.Sets/src/mage/cards/a/AtKnifepoint.java @@ -34,7 +34,7 @@ public final class AtKnifepoint extends CardImpl { // As long as it's your turn, outlaws you control have first strike. this.addAbility(new SimpleStaticAbility(new ConditionalContinuousEffect( new GainAbilityControlledEffect(FirstStrikeAbility.getInstance(), Duration.WhileOnBattlefield, filter), - MyTurnCondition.instance, "as long as it's your turn, outlaws you control have first strike" + MyTurnCondition.instance, "during your turn, outlaws you control have first strike" ))); // Whenever you commit a crime, create a 1/1 red Mercenary creature token with "{T}: Target creature you control gets +1/+0 until end of turn. Activate only as a sorcery." This ability triggers only once each turn. diff --git a/Mage.Sets/src/mage/cards/a/AthreosGodOfPassage.java b/Mage.Sets/src/mage/cards/a/AthreosGodOfPassage.java index 16948746ae7..33c317738ce 100644 --- a/Mage.Sets/src/mage/cards/a/AthreosGodOfPassage.java +++ b/Mage.Sets/src/mage/cards/a/AthreosGodOfPassage.java @@ -1,6 +1,7 @@ package mage.cards.a; import mage.MageInt; +import mage.MageObject; import mage.abilities.Ability; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.common.SimpleStaticAbility; @@ -122,6 +123,7 @@ class AthreosDiesCreatureTriggeredAbility extends TriggeredAbilityImpl { super(Zone.BATTLEFIELD, effect, optional); this.filter = filter; setTriggerPhrase("Whenever " + filter.getMessage() + " dies, "); + setLeavesTheBattlefieldTrigger(true); } private AthreosDiesCreatureTriggeredAbility(AthreosDiesCreatureTriggeredAbility ability) { @@ -153,4 +155,9 @@ class AthreosDiesCreatureTriggeredAbility extends TriggeredAbilityImpl { } return true; } + + @Override + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); + } } diff --git a/Mage.Sets/src/mage/cards/a/AthreosShroudVeiled.java b/Mage.Sets/src/mage/cards/a/AthreosShroudVeiled.java index f0f2df5cc95..8debc0e7bcb 100644 --- a/Mage.Sets/src/mage/cards/a/AthreosShroudVeiled.java +++ b/Mage.Sets/src/mage/cards/a/AthreosShroudVeiled.java @@ -80,6 +80,7 @@ class AthreosShroudVeiledTriggeredAbility extends TriggeredAbilityImpl { AthreosShroudVeiledTriggeredAbility() { super(Zone.BATTLEFIELD, null, false); + setLeavesTheBattlefieldTrigger(true); } private AthreosShroudVeiledTriggeredAbility(final AthreosShroudVeiledTriggeredAbility ability) { diff --git a/Mage.Sets/src/mage/cards/a/AvacynsCollar.java b/Mage.Sets/src/mage/cards/a/AvacynsCollar.java index dac578fcc71..1c6baec7753 100644 --- a/Mage.Sets/src/mage/cards/a/AvacynsCollar.java +++ b/Mage.Sets/src/mage/cards/a/AvacynsCollar.java @@ -1,6 +1,7 @@ package mage.cards.a; +import mage.MageObject; import mage.abilities.Ability; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.common.SimpleStaticAbility; @@ -60,6 +61,7 @@ class AvacynsCollarTriggeredAbility extends TriggeredAbilityImpl { public AvacynsCollarTriggeredAbility() { super(Zone.BATTLEFIELD, new CreateTokenEffect(new SpiritWhiteToken())); + setLeavesTheBattlefieldTrigger(true); } private AvacynsCollarTriggeredAbility(final AvacynsCollarTriggeredAbility ability) { @@ -91,4 +93,9 @@ class AvacynsCollarTriggeredAbility extends TriggeredAbilityImpl { public String getRule() { return "Whenever equipped creature dies, if it was a Human, create a 1/1 white Spirit creature token with flying."; } + + @Override + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); + } } diff --git a/Mage.Sets/src/mage/cards/b/BayekOfSiwa.java b/Mage.Sets/src/mage/cards/b/BayekOfSiwa.java index 62b45254497..563dd830ce6 100644 --- a/Mage.Sets/src/mage/cards/b/BayekOfSiwa.java +++ b/Mage.Sets/src/mage/cards/b/BayekOfSiwa.java @@ -47,7 +47,7 @@ public final class BayekOfSiwa extends CardImpl { this.addAbility(new SimpleStaticAbility(new ConditionalContinuousEffect( new GainAbilityControlledEffect( DoubleStrikeAbility.getInstance(), Duration.WhileOnBattlefield, filter, true - ), MyTurnCondition.instance, "as long as it's your turn, " + + ), MyTurnCondition.instance, "during your turn, " + "other historic creatures you control have double strike" ))); diff --git a/Mage.Sets/src/mage/cards/b/Bereavement.java b/Mage.Sets/src/mage/cards/b/Bereavement.java index 9f90fc52865..05fb4ff62c1 100644 --- a/Mage.Sets/src/mage/cards/b/Bereavement.java +++ b/Mage.Sets/src/mage/cards/b/Bereavement.java @@ -1,6 +1,8 @@ package mage.cards.b; import java.util.UUID; + +import mage.MageObject; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.effects.common.discard.DiscardTargetEffect; import mage.cards.CardImpl; @@ -41,6 +43,7 @@ class BereavementTriggeredAbility extends TriggeredAbilityImpl { BereavementTriggeredAbility() { super(Zone.BATTLEFIELD, new DiscardTargetEffect(1)); + setLeavesTheBattlefieldTrigger(true); } private BereavementTriggeredAbility(final BereavementTriggeredAbility ability) { @@ -73,4 +76,9 @@ class BereavementTriggeredAbility extends TriggeredAbilityImpl { public String getRule() { return "Whenever a green creature dies, its controller discards a card."; } + + @Override + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); + } } diff --git a/Mage.Sets/src/mage/cards/b/BilbosRing.java b/Mage.Sets/src/mage/cards/b/BilbosRing.java index 98cfcdf00c9..eba03249b05 100644 --- a/Mage.Sets/src/mage/cards/b/BilbosRing.java +++ b/Mage.Sets/src/mage/cards/b/BilbosRing.java @@ -39,7 +39,7 @@ public final class BilbosRing extends CardImpl { // As long as it's your turn, equipped creature has hexproof and can't be blocked. Ability ability = new SimpleStaticAbility(new ConditionalContinuousEffect( new GainAbilityAttachedEffect(HexproofAbility.getInstance(), AttachmentType.EQUIPMENT), - MyTurnCondition.instance, "as long as it's your turn, equipped creature has hexproof" + MyTurnCondition.instance, "during your turn, equipped creature has hexproof" )); ability.addEffect(new ConditionalRestrictionEffect( new CantBeBlockedAttachedEffect(AttachmentType.EQUIPMENT), diff --git a/Mage.Sets/src/mage/cards/b/BiogenicUpgrade.java b/Mage.Sets/src/mage/cards/b/BiogenicUpgrade.java index 8d0622f6e4d..af5253deaee 100644 --- a/Mage.Sets/src/mage/cards/b/BiogenicUpgrade.java +++ b/Mage.Sets/src/mage/cards/b/BiogenicUpgrade.java @@ -27,7 +27,7 @@ public final class BiogenicUpgrade extends CardImpl { // Distribute three +1/+1 counters among one, two, or three target creatures, then double the number of +1/+1 counters on each of those creatures. this.getSpellAbility().addEffect(new DistributeCountersEffect( - CounterType.P1P1, 3, false, + 3, "one, two, or three target creatures" )); this.getSpellAbility().addTarget(new TargetCreaturePermanentAmount(3)); @@ -75,4 +75,4 @@ class BiogenicUpgradeEffect extends OneShotEffect { } return true; } -} \ No newline at end of file +} diff --git a/Mage.Sets/src/mage/cards/b/BlessingsOfNature.java b/Mage.Sets/src/mage/cards/b/BlessingsOfNature.java index 0dd2185b863..b6f13ca0c49 100644 --- a/Mage.Sets/src/mage/cards/b/BlessingsOfNature.java +++ b/Mage.Sets/src/mage/cards/b/BlessingsOfNature.java @@ -19,7 +19,7 @@ public final class BlessingsOfNature extends CardImpl { super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{4}{G}"); // Distribute four +1/+1 counters among any number of target creatures. - this.getSpellAbility().addEffect(new DistributeCountersEffect(CounterType.P1P1, 4, false, "any number of target creatures")); + this.getSpellAbility().addEffect(new DistributeCountersEffect(4, "any number of target creatures")); this.getSpellAbility().addTarget(new TargetCreaturePermanentAmount(4)); this.addAbility(new MiracleAbility("{G}")); diff --git a/Mage.Sets/src/mage/cards/b/BountyOfTheHunt.java b/Mage.Sets/src/mage/cards/b/BountyOfTheHunt.java index 6def70de482..082ba4df231 100644 --- a/Mage.Sets/src/mage/cards/b/BountyOfTheHunt.java +++ b/Mage.Sets/src/mage/cards/b/BountyOfTheHunt.java @@ -35,9 +35,9 @@ public final class BountyOfTheHunt extends CardImpl { // Distribute three +1/+1 counters among one, two, or three target creatures. For each +1/+1 counter you put on a creature this way, remove a +1/+1 counter from that creature at the beginning of the next cleanup step. this.getSpellAbility().addEffect(new DistributeCountersEffect( - CounterType.P1P1, 3, true, + 3, "one, two, or three target creatures" - )); + ).withRemoveAtEndOfTurn()); this.getSpellAbility().addTarget(new TargetCreaturePermanentAmount(3)); } diff --git a/Mage.Sets/src/mage/cards/b/BrassKnuckles.java b/Mage.Sets/src/mage/cards/b/BrassKnuckles.java index cc5a5b226b0..830e18e7cf1 100644 --- a/Mage.Sets/src/mage/cards/b/BrassKnuckles.java +++ b/Mage.Sets/src/mage/cards/b/BrassKnuckles.java @@ -67,7 +67,6 @@ enum BrassKnucklesCondition implements Condition { .ofNullable(source.getSourcePermanentIfItStillExists(game)) .map(Permanent::getAttachedTo) .map(game::getPermanent) - .filter(Objects::nonNull) .map(Permanent::getAttachments) .map(Collection::stream) .map(stream -> stream.map(game::getPermanent)) diff --git a/Mage.Sets/src/mage/cards/b/BridgeFromBelow.java b/Mage.Sets/src/mage/cards/b/BridgeFromBelow.java index a89d8103604..0be4df74c26 100644 --- a/Mage.Sets/src/mage/cards/b/BridgeFromBelow.java +++ b/Mage.Sets/src/mage/cards/b/BridgeFromBelow.java @@ -65,6 +65,7 @@ class BridgeFromBelowAbility extends TriggeredAbilityImpl { this.filter = filter; this.withInterveningIf(SourceInGraveyardCondition.instance); setTriggerPhrase(filter.getMessage()); + setLeavesTheBattlefieldTrigger(true); // it's not required for Bridge from Below, but better to keep same code style and verify pass } private BridgeFromBelowAbility(final BridgeFromBelowAbility ability) { diff --git a/Mage.Sets/src/mage/cards/c/CallerOfTheClaw.java b/Mage.Sets/src/mage/cards/c/CallerOfTheClaw.java index 6c9e7cf4a97..2a514af68ca 100644 --- a/Mage.Sets/src/mage/cards/c/CallerOfTheClaw.java +++ b/Mage.Sets/src/mage/cards/c/CallerOfTheClaw.java @@ -1,4 +1,3 @@ - package mage.cards.c; import java.util.UUID; @@ -39,6 +38,7 @@ public final class CallerOfTheClaw extends CardImpl { // Flash this.addAbility(FlashAbility.getInstance()); + // When Caller of the Claw enters the battlefield, create a 2/2 green Bear creature token for each nontoken creature put into your graveyard from the battlefield this turn. this.getSpellAbility().addWatcher(new CallerOfTheClawWatcher()); Effect effect = new CreateTokenEffect(new BearToken(), new CallerOfTheClawDynamicValue()); diff --git a/Mage.Sets/src/mage/cards/c/CarthTheLion.java b/Mage.Sets/src/mage/cards/c/CarthTheLion.java index 5d42400bf1d..af484ad7a79 100644 --- a/Mage.Sets/src/mage/cards/c/CarthTheLion.java +++ b/Mage.Sets/src/mage/cards/c/CarthTheLion.java @@ -58,6 +58,7 @@ class CarthTheLionTriggeredAbility extends TriggeredAbilityImpl { super(Zone.BATTLEFIELD, new LookLibraryAndPickControllerEffect( 7, 1, filter, PutCards.HAND, PutCards.BOTTOM_RANDOM)); setTriggerPhrase("Whenever {this} enters or a planeswalker you control dies, "); + setLeavesTheBattlefieldTrigger(true); } private CarthTheLionTriggeredAbility(final CarthTheLionTriggeredAbility ability) { diff --git a/Mage.Sets/src/mage/cards/c/CaseOfTheTrampledGarden.java b/Mage.Sets/src/mage/cards/c/CaseOfTheTrampledGarden.java index fefa7177521..bc7153a94ce 100644 --- a/Mage.Sets/src/mage/cards/c/CaseOfTheTrampledGarden.java +++ b/Mage.Sets/src/mage/cards/c/CaseOfTheTrampledGarden.java @@ -40,7 +40,7 @@ public final class CaseOfTheTrampledGarden extends CardImpl { this.subtype.add(SubType.CASE); // When this Case enters the battlefield, distribute two +1/+1 counters among one or two target creatures you control. - Ability initialAbility = new EntersBattlefieldTriggeredAbility(new DistributeCountersEffect(CounterType.P1P1, 2, + Ability initialAbility = new EntersBattlefieldTriggeredAbility(new DistributeCountersEffect(2, "one or two target creatures you control")); TargetPermanentAmount target = new TargetPermanentAmount(2, StaticFilters.FILTER_CONTROLLED_CREATURES); target.setMinNumberOfTargets(1); diff --git a/Mage.Sets/src/mage/cards/c/ChainerNightmareAdept.java b/Mage.Sets/src/mage/cards/c/ChainerNightmareAdept.java index 5b527f865a0..95111180226 100644 --- a/Mage.Sets/src/mage/cards/c/ChainerNightmareAdept.java +++ b/Mage.Sets/src/mage/cards/c/ChainerNightmareAdept.java @@ -122,10 +122,10 @@ class ChainerNightmareAdeptWatcher extends Watcher { @Override public void watch(GameEvent event, Game game) { if (event.getType() == GameEvent.EventType.SPELL_CAST) { - if (event.getAdditionalReference() == null) { + if (event.getApprovingObject() == null) { return; } - morMap.computeIfAbsent(event.getAdditionalReference().getApprovingMageObjectReference(), m -> new HashMap<>()) + morMap.computeIfAbsent(event.getApprovingObject().getApprovingMageObjectReference(), m -> new HashMap<>()) .compute(event.getPlayerId(), (u, i) -> i == null ? 0 : Integer.sum(i, -1)); } } diff --git a/Mage.Sets/src/mage/cards/c/ChandraHopesBeacon.java b/Mage.Sets/src/mage/cards/c/ChandraHopesBeacon.java index 9142b8bcffb..1fb0d5e1482 100644 --- a/Mage.Sets/src/mage/cards/c/ChandraHopesBeacon.java +++ b/Mage.Sets/src/mage/cards/c/ChandraHopesBeacon.java @@ -150,10 +150,10 @@ class ChandraHopesBeaconWatcher extends Watcher { @Override public void watch(GameEvent event, Game game) { if (event.getType() != GameEvent.EventType.SPELL_CAST - || event.getAdditionalReference() == null) { + || event.getApprovingObject() == null) { return; } - MageObjectReference mor = event.getAdditionalReference().getApprovingMageObjectReference(); + MageObjectReference mor = event.getApprovingObject().getApprovingMageObjectReference(); Spell spell = game.getSpell(event.getTargetId()); if (mor == null || spell == null) { return; diff --git a/Mage.Sets/src/mage/cards/c/ChissGoriaForgeTyrant.java b/Mage.Sets/src/mage/cards/c/ChissGoriaForgeTyrant.java index 014bd5b749e..6eca6368d9e 100644 --- a/Mage.Sets/src/mage/cards/c/ChissGoriaForgeTyrant.java +++ b/Mage.Sets/src/mage/cards/c/ChissGoriaForgeTyrant.java @@ -194,10 +194,10 @@ class ChissGoriaForgeTyrantWatcher extends Watcher { @Override public void watch(GameEvent event, Game game) { - if (event.getType() != GameEvent.EventType.SPELL_CAST || event.getAdditionalReference() == null) { + if (event.getType() != GameEvent.EventType.SPELL_CAST || event.getApprovingObject() == null) { return; } - MageObjectReference mor = event.getAdditionalReference().getApprovingMageObjectReference(); + MageObjectReference mor = event.getApprovingObject().getApprovingMageObjectReference(); Spell spell = game.getSpell(event.getTargetId()); if (mor == null || spell == null) { return; diff --git a/Mage.Sets/src/mage/cards/c/Chronozoa.java b/Mage.Sets/src/mage/cards/c/Chronozoa.java index 39edd855a93..97f3248396b 100644 --- a/Mage.Sets/src/mage/cards/c/Chronozoa.java +++ b/Mage.Sets/src/mage/cards/c/Chronozoa.java @@ -33,7 +33,7 @@ public final class Chronozoa extends CardImpl { // Vanishing 3 (This permanent enters the battlefield with three time counters on it. At the beginning of your upkeep, remove a time counter from it. When the last is removed, sacrifice it.) this.addAbility(new VanishingAbility(3)); - // When Chronozoa is put into a graveyard from play, if it had no time counters on it, create two tokens that are copies of it. + // When Chronozoa dies, if it had no time counters on it, create two tokens that are copies of it. Effect effect = new CreateTokenCopySourceEffect(2); effect.setText("create two tokens that are copies of it"); this.addAbility(new ConditionalInterveningIfTriggeredAbility(new DiesSourceTriggeredAbility(effect, false), diff --git a/Mage.Sets/src/mage/cards/c/CircleOfTheMoonDruid.java b/Mage.Sets/src/mage/cards/c/CircleOfTheMoonDruid.java index 7b2792ab071..11b3632e532 100644 --- a/Mage.Sets/src/mage/cards/c/CircleOfTheMoonDruid.java +++ b/Mage.Sets/src/mage/cards/c/CircleOfTheMoonDruid.java @@ -44,7 +44,7 @@ class CircleOfTheMoonDruidBearEffect extends ContinuousEffectImpl { CircleOfTheMoonDruidBearEffect() { super(Duration.WhileOnBattlefield, Outcome.Benefit); - staticText = "as long as it's your turn, {this} is a Bear with base power and toughness 4/2"; + staticText = "during your turn, {this} is a Bear with base power and toughness 4/2"; } private CircleOfTheMoonDruidBearEffect(final CircleOfTheMoonDruidBearEffect effect) { diff --git a/Mage.Sets/src/mage/cards/c/ClipWings.java b/Mage.Sets/src/mage/cards/c/ClipWings.java index b6a3db21273..40e5ea7c351 100644 --- a/Mage.Sets/src/mage/cards/c/ClipWings.java +++ b/Mage.Sets/src/mage/cards/c/ClipWings.java @@ -1,4 +1,3 @@ - package mage.cards.c; import java.util.UUID; @@ -26,7 +25,8 @@ public final class ClipWings extends CardImpl { super(ownerId,setInfo,new CardType[]{CardType.INSTANT},"{1}{G}"); // Each opponent sacrifices a creature with flying. - this.getSpellAbility().addEffect(new SacrificeOpponentsEffect(filter)); + this.getSpellAbility().addEffect(new SacrificeOpponentsEffect(filter) + .setText("each opponent sacrifices a creature of their choice with flying")); } private ClipWings(final ClipWings card) { diff --git a/Mage.Sets/src/mage/cards/c/ConquerorsFlail.java b/Mage.Sets/src/mage/cards/c/ConquerorsFlail.java index 280501b12d1..32539d56033 100644 --- a/Mage.Sets/src/mage/cards/c/ConquerorsFlail.java +++ b/Mage.Sets/src/mage/cards/c/ConquerorsFlail.java @@ -114,7 +114,6 @@ class ConquerorsFlailEffect extends ContinuousRuleModifyingEffectImpl { .ofNullable(source.getSourcePermanentIfItStillExists(game)) .map(Permanent::getAttachedTo) .map(game::getPermanent) - .filter(Objects::nonNull) .map(permanent -> permanent.isCreature(game)) .orElse(false); } diff --git a/Mage.Sets/src/mage/cards/c/Contagion.java b/Mage.Sets/src/mage/cards/c/Contagion.java index c1e223c9079..0e27f3f1b90 100644 --- a/Mage.Sets/src/mage/cards/c/Contagion.java +++ b/Mage.Sets/src/mage/cards/c/Contagion.java @@ -39,7 +39,7 @@ public final class Contagion extends CardImpl { // Distribute two -2/-1 counters among one or two target creatures. this.getSpellAbility().addTarget(new TargetCreaturePermanentAmount(2)); this.getSpellAbility().addEffect(new DistributeCountersEffect( - CounterType.M2M1, 2, false, + CounterType.M2M1, 2, "one or two target creatures" )); } diff --git a/Mage.Sets/src/mage/cards/c/CoramTheUndertaker.java b/Mage.Sets/src/mage/cards/c/CoramTheUndertaker.java index b8f3a117abf..40b9f884318 100644 --- a/Mage.Sets/src/mage/cards/c/CoramTheUndertaker.java +++ b/Mage.Sets/src/mage/cards/c/CoramTheUndertaker.java @@ -237,15 +237,15 @@ class CoramTheUndertakerWatcher extends Watcher { cardsAllowedToBePlayedOrCast.add(new MageObjectReference(mainCard, game)); return; } - if (event.getAdditionalReference() == null - || !MageIdentifier.CoramTheUndertakerWatcher.equals(event.getAdditionalReference().getApprovingAbility().getIdentifier())) { + if (event.getApprovingObject() == null + || !MageIdentifier.CoramTheUndertakerWatcher.equals(event.getApprovingObject().getApprovingAbility().getIdentifier())) { return; } if (event.getType() == GameEvent.EventType.LAND_PLAYED) { - landPlayedForSource.add(event.getAdditionalReference().getApprovingMageObjectReference()); + landPlayedForSource.add(event.getApprovingObject().getApprovingMageObjectReference()); } if (event.getType() == GameEvent.EventType.SPELL_CAST) { - spellCastForSource.add(event.getAdditionalReference().getApprovingMageObjectReference()); + spellCastForSource.add(event.getApprovingObject().getApprovingMageObjectReference()); } } diff --git a/Mage.Sets/src/mage/cards/c/CourtOfGarenbrig.java b/Mage.Sets/src/mage/cards/c/CourtOfGarenbrig.java index f932580a229..bb9072ea778 100644 --- a/Mage.Sets/src/mage/cards/c/CourtOfGarenbrig.java +++ b/Mage.Sets/src/mage/cards/c/CourtOfGarenbrig.java @@ -33,7 +33,7 @@ public final class CourtOfGarenbrig extends CardImpl { // At the beginning of your upkeep, distribute two +1/+1 counters among up to two target creatures. Then if you're the monarch, double the number of +1/+1 counters on each creature you control. Ability ability = new BeginningOfUpkeepTriggeredAbility( new DistributeCountersEffect( - CounterType.P1P1, 2, false, "up to two target creatures" + 2, "up to two target creatures" ) ); TargetCreaturePermanentAmount target = new TargetCreaturePermanentAmount(2); diff --git a/Mage.Sets/src/mage/cards/c/CourtOfLocthwain.java b/Mage.Sets/src/mage/cards/c/CourtOfLocthwain.java index 8cf9f4510b2..adce8898a57 100644 --- a/Mage.Sets/src/mage/cards/c/CourtOfLocthwain.java +++ b/Mage.Sets/src/mage/cards/c/CourtOfLocthwain.java @@ -226,7 +226,7 @@ class CourtOfLocthwainWatcher extends Watcher { && event.getPlayerId() != null) { decrementCastAvailable( event.getPlayerId(), - event.getAdditionalReference().getApprovingMageObjectReference() + event.getApprovingObject().getApprovingMageObjectReference() ); } } diff --git a/Mage.Sets/src/mage/cards/d/DapperShieldmate.java b/Mage.Sets/src/mage/cards/d/DapperShieldmate.java index 259a8365775..399434b3768 100644 --- a/Mage.Sets/src/mage/cards/d/DapperShieldmate.java +++ b/Mage.Sets/src/mage/cards/d/DapperShieldmate.java @@ -39,7 +39,7 @@ public final class DapperShieldmate extends CardImpl { // As long as it's your turn, Dapper Shieldmate gets +2/+0. this.addAbility(new SimpleStaticAbility(new ConditionalContinuousEffect( new BoostSourceEffect(2, 0, Duration.WhileOnBattlefield), - MyTurnCondition.instance, "as long as it's your turn, {this} gets +2/+0" + MyTurnCondition.instance, "during your turn, {this} gets +2/+0" ))); } diff --git a/Mage.Sets/src/mage/cards/d/DaxosBlessedByTheSun.java b/Mage.Sets/src/mage/cards/d/DaxosBlessedByTheSun.java index 8b7b30d757e..774f1cbf73f 100644 --- a/Mage.Sets/src/mage/cards/d/DaxosBlessedByTheSun.java +++ b/Mage.Sets/src/mage/cards/d/DaxosBlessedByTheSun.java @@ -54,6 +54,7 @@ class DaxosBlessedByTheSunAbility extends TriggeredAbilityImpl { DaxosBlessedByTheSunAbility() { super(Zone.BATTLEFIELD, new GainLifeEffect(1)); + setLeavesTheBattlefieldTrigger(true); } private DaxosBlessedByTheSunAbility(DaxosBlessedByTheSunAbility ability) { diff --git a/Mage.Sets/src/mage/cards/d/DeathTyrant.java b/Mage.Sets/src/mage/cards/d/DeathTyrant.java index 5c2826e3451..18d0feed6a4 100644 --- a/Mage.Sets/src/mage/cards/d/DeathTyrant.java +++ b/Mage.Sets/src/mage/cards/d/DeathTyrant.java @@ -62,6 +62,7 @@ class DeathTyrantTriggeredAbility extends TriggeredAbilityImpl { DeathTyrantTriggeredAbility() { super(Zone.BATTLEFIELD, new CreateTokenEffect(new ZombieToken())); setTriggerPhrase("Whenever an attacking creature you control or a blocking creature an opponent controls dies, "); + setLeavesTheBattlefieldTrigger(true); } private DeathTyrantTriggeredAbility(final DeathTyrantTriggeredAbility ability) { @@ -94,7 +95,7 @@ class DeathTyrantTriggeredAbility extends TriggeredAbilityImpl { } @Override - public boolean isInUseableZone(Game game, MageObject source, GameEvent event) { - return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, event, game); + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); } } diff --git a/Mage.Sets/src/mage/cards/d/DeathsPresence.java b/Mage.Sets/src/mage/cards/d/DeathsPresence.java index 619f967e0b1..8ccfce29cac 100644 --- a/Mage.Sets/src/mage/cards/d/DeathsPresence.java +++ b/Mage.Sets/src/mage/cards/d/DeathsPresence.java @@ -1,8 +1,8 @@ - - package mage.cards.d; import java.util.UUID; + +import mage.MageObject; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.effects.common.counter.AddCountersTargetEffect; import mage.cards.CardImpl; @@ -25,7 +25,6 @@ public final class DeathsPresence extends CardImpl { public DeathsPresence(UUID ownerId, CardSetInfo setInfo) { super(ownerId,setInfo,new CardType[]{CardType.ENCHANTMENT},"{5}{G}"); - // Whenever a creature you control dies, put X +1/+1 counters on target creature you control, where X is the power of the creature that died. this.addAbility(new DeathsPresenceTriggeredAbility()); } @@ -44,6 +43,7 @@ class DeathsPresenceTriggeredAbility extends TriggeredAbilityImpl { public DeathsPresenceTriggeredAbility() { super(Zone.BATTLEFIELD, null); + setLeavesTheBattlefieldTrigger(true); } private DeathsPresenceTriggeredAbility(final DeathsPresenceTriggeredAbility ability) { @@ -80,4 +80,9 @@ class DeathsPresenceTriggeredAbility extends TriggeredAbilityImpl { public String getRule() { return "Whenever a creature you control dies, put X +1/+1 counters on target creature you control, where X is the power of the creature that died."; } + + @Override + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); + } } diff --git a/Mage.Sets/src/mage/cards/d/DefendTheCelestus.java b/Mage.Sets/src/mage/cards/d/DefendTheCelestus.java index 6aaf9f372af..cf520b7eb23 100644 --- a/Mage.Sets/src/mage/cards/d/DefendTheCelestus.java +++ b/Mage.Sets/src/mage/cards/d/DefendTheCelestus.java @@ -20,7 +20,7 @@ public final class DefendTheCelestus extends CardImpl { // Distribute three +1/+1 counters among one, two, or three target creatures you control. this.getSpellAbility().addEffect(new DistributeCountersEffect( - CounterType.P1P1, 3, false, + 3, "one, two, or three target creatures you control" )); this.getSpellAbility().addTarget(new TargetCreaturePermanentAmount( diff --git a/Mage.Sets/src/mage/cards/d/DiabolicServitude.java b/Mage.Sets/src/mage/cards/d/DiabolicServitude.java index bc8f05ca387..8e932387792 100644 --- a/Mage.Sets/src/mage/cards/d/DiabolicServitude.java +++ b/Mage.Sets/src/mage/cards/d/DiabolicServitude.java @@ -1,6 +1,8 @@ package mage.cards.d; import java.util.UUID; + +import mage.MageObject; import mage.MageObjectReference; import mage.abilities.Ability; import mage.abilities.TriggeredAbilityImpl; @@ -91,6 +93,7 @@ class DiabolicServitudeCreatureDiesTriggeredAbility extends TriggeredAbilityImpl public DiabolicServitudeCreatureDiesTriggeredAbility() { super(Zone.BATTLEFIELD, new DiabolicServitudeExileCreatureEffect(), false); + setLeavesTheBattlefieldTrigger(true); } private DiabolicServitudeCreatureDiesTriggeredAbility(final DiabolicServitudeCreatureDiesTriggeredAbility ability) { @@ -123,6 +126,11 @@ class DiabolicServitudeCreatureDiesTriggeredAbility extends TriggeredAbilityImpl public String getRule() { return "When the creature put onto the battlefield with {this} dies, exile it and return {this} to its owner's hand."; } + + @Override + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); + } } class DiabolicServitudeExileCreatureEffect extends OneShotEffect { diff --git a/Mage.Sets/src/mage/cards/d/DiregrafCaptain.java b/Mage.Sets/src/mage/cards/d/DiregrafCaptain.java index 0e35ac6c7bb..9db8a67e7ee 100644 --- a/Mage.Sets/src/mage/cards/d/DiregrafCaptain.java +++ b/Mage.Sets/src/mage/cards/d/DiregrafCaptain.java @@ -1,6 +1,7 @@ package mage.cards.d; import mage.MageInt; +import mage.MageObject; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.effects.common.LoseLifeTargetEffect; @@ -67,6 +68,7 @@ class DiregrafCaptainTriggeredAbility extends TriggeredAbilityImpl { public DiregrafCaptainTriggeredAbility() { super(Zone.BATTLEFIELD, new LoseLifeTargetEffect(1), false); this.addTarget(new TargetOpponent()); + this.setLeavesTheBattlefieldTrigger(true); } private DiregrafCaptainTriggeredAbility(final DiregrafCaptainTriggeredAbility ability) { @@ -99,4 +101,9 @@ class DiregrafCaptainTriggeredAbility extends TriggeredAbilityImpl { public String getRule() { return "Whenever another Zombie you control dies, target opponent loses 1 life."; } -} + + @Override + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/d/Discontinuity.java b/Mage.Sets/src/mage/cards/d/Discontinuity.java index 23fcadd095b..1df0bfe7fad 100644 --- a/Mage.Sets/src/mage/cards/d/Discontinuity.java +++ b/Mage.Sets/src/mage/cards/d/Discontinuity.java @@ -24,7 +24,7 @@ public final class Discontinuity extends CardImpl { // As long as it's your turn, this spell costs {2}{U}{U} less to cast. this.addAbility(new SimpleStaticAbility(Zone.ALL, new SpellCostReductionSourceEffect( new ManaCostsImpl<>("{2}{U}{U}"), MyTurnCondition.instance - ).setText("as long as it's your turn, this spell costs {2}{U}{U} less to cast")) + ).setText("during your turn, this spell costs {2}{U}{U} less to cast")) .setRuleAtTheTop(true) .addHint(MyTurnHint.instance)); diff --git a/Mage.Sets/src/mage/cards/d/Dreadhound.java b/Mage.Sets/src/mage/cards/d/Dreadhound.java index be6fe64a348..3df8fc02676 100644 --- a/Mage.Sets/src/mage/cards/d/Dreadhound.java +++ b/Mage.Sets/src/mage/cards/d/Dreadhound.java @@ -53,6 +53,7 @@ class DreadhoundTriggeredAbility extends TriggeredAbilityImpl { public DreadhoundTriggeredAbility() { super(Zone.BATTLEFIELD, new LoseLifeOpponentsEffect(1)); setTriggerPhrase("Whenever a creature dies or a creature card is put into a graveyard from a library, "); + setLeavesTheBattlefieldTrigger(true); } private DreadhoundTriggeredAbility(final DreadhoundTriggeredAbility ability) { diff --git a/Mage.Sets/src/mage/cards/d/DuelistOfDeepFaith.java b/Mage.Sets/src/mage/cards/d/DuelistOfDeepFaith.java index 7bf47090cad..37f43cfc5ac 100644 --- a/Mage.Sets/src/mage/cards/d/DuelistOfDeepFaith.java +++ b/Mage.Sets/src/mage/cards/d/DuelistOfDeepFaith.java @@ -34,7 +34,7 @@ public final class DuelistOfDeepFaith extends CardImpl { // As long as it's your turn, Duelist of Deep Faith has first strike. this.addAbility(new SimpleStaticAbility(new ConditionalContinuousEffect( new GainAbilitySourceEffect(FirstStrikeAbility.getInstance()), - MyTurnCondition.instance, "as long as it's your turn, {this} has first strike" + MyTurnCondition.instance, "during your turn, {this} has first strike" )).addHint(MyTurnHint.instance)); } diff --git a/Mage.Sets/src/mage/cards/e/ElspethsTalent.java b/Mage.Sets/src/mage/cards/e/ElspethsTalent.java index 0bf855505d6..04f1c3a2123 100644 --- a/Mage.Sets/src/mage/cards/e/ElspethsTalent.java +++ b/Mage.Sets/src/mage/cards/e/ElspethsTalent.java @@ -90,7 +90,6 @@ class ElspethsTalentTriggeredAbility extends TriggeredAbilityImpl { && event.getSourceId().equals(permanent.getAttachedTo()) && isControlledBy(event.getPlayerId()) && Optional.ofNullable(game.getStack().getStackObject(event.getSourceId())) - .filter(Objects::nonNull) .map(StackObject::getStackAbility) .map(LoyaltyAbility.class::isInstance) .orElse(false); diff --git a/Mage.Sets/src/mage/cards/e/ElusiveOtter.java b/Mage.Sets/src/mage/cards/e/ElusiveOtter.java index bd427e0393a..fdc288de3bd 100644 --- a/Mage.Sets/src/mage/cards/e/ElusiveOtter.java +++ b/Mage.Sets/src/mage/cards/e/ElusiveOtter.java @@ -38,7 +38,7 @@ public final class ElusiveOtter extends AdventureCard { // Grove's Bounty // Distribute X +1/+1 counters among any number of target creatures you control. this.getSpellCard().getSpellAbility().addEffect(new DistributeCountersEffect( - CounterType.P1P1, GetXValue.instance, false, + CounterType.P1P1, GetXValue.instance, "any number of target creatures you control" )); Target target = new TargetCreaturePermanentAmount(GetXValue.instance, StaticFilters.FILTER_CONTROLLED_CREATURES); diff --git a/Mage.Sets/src/mage/cards/e/ElvenRite.java b/Mage.Sets/src/mage/cards/e/ElvenRite.java index 9acd8a00d2c..030277f47db 100644 --- a/Mage.Sets/src/mage/cards/e/ElvenRite.java +++ b/Mage.Sets/src/mage/cards/e/ElvenRite.java @@ -19,7 +19,7 @@ public final class ElvenRite extends CardImpl { super(ownerId,setInfo,new CardType[]{CardType.SORCERY},"{1}{G}"); // Distribute two +1/+1 counters among one or two target creatures. - this.getSpellAbility().addEffect(new DistributeCountersEffect(CounterType.P1P1, 2, false, "one or two target creatures")); + this.getSpellAbility().addEffect(new DistributeCountersEffect(2, "one or two target creatures")); this.getSpellAbility().addTarget(new TargetCreaturePermanentAmount(2)); } diff --git a/Mage.Sets/src/mage/cards/e/EmergentWoodwurm.java b/Mage.Sets/src/mage/cards/e/EmergentWoodwurm.java index fb1b81d6d81..d9472c83b64 100644 --- a/Mage.Sets/src/mage/cards/e/EmergentWoodwurm.java +++ b/Mage.Sets/src/mage/cards/e/EmergentWoodwurm.java @@ -69,7 +69,6 @@ enum EmergentWoodwurmPredicate implements ObjectSourcePlayerPredicate { .of(input) .map(ObjectSourcePlayer::getSource) .map(ability -> ability.getSourcePermanentOrLKI(game)) - .filter(Objects::nonNull) .map(MageObject::getPower) .map(MageInt::getValue) .map(i -> input.getObject().getManaValue() <= i) diff --git a/Mage.Sets/src/mage/cards/e/EndlessEvil.java b/Mage.Sets/src/mage/cards/e/EndlessEvil.java index e259defedc2..d1cf80be8c0 100644 --- a/Mage.Sets/src/mage/cards/e/EndlessEvil.java +++ b/Mage.Sets/src/mage/cards/e/EndlessEvil.java @@ -1,5 +1,6 @@ package mage.cards.e; +import mage.MageObject; import mage.abilities.Ability; import mage.abilities.TriggeredAbility; import mage.abilities.TriggeredAbilityImpl; @@ -97,6 +98,7 @@ class EndlessEvilBounceAbility extends TriggeredAbilityImpl { public EndlessEvilBounceAbility() { super(Zone.BATTLEFIELD, new ReturnToHandSourceEffect(false, true)); + setLeavesTheBattlefieldTrigger(true); } private EndlessEvilBounceAbility(final EndlessEvilBounceAbility effect) { @@ -126,4 +128,9 @@ class EndlessEvilBounceAbility extends TriggeredAbilityImpl { public String getRule() { return "When enchanted creature dies, if that creature was a Horror, return {this} to its owner's hand."; } + + @Override + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); + } } diff --git a/Mage.Sets/src/mage/cards/e/EnigmaSphinx.java b/Mage.Sets/src/mage/cards/e/EnigmaSphinx.java index 797e6e6f567..a1f0488b0cc 100644 --- a/Mage.Sets/src/mage/cards/e/EnigmaSphinx.java +++ b/Mage.Sets/src/mage/cards/e/EnigmaSphinx.java @@ -3,6 +3,7 @@ package mage.cards.e; import java.util.UUID; import mage.MageInt; +import mage.MageObject; import mage.abilities.Ability; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.effects.Effect; @@ -65,6 +66,7 @@ class EnigmaSphinxTriggeredAbility extends TriggeredAbilityImpl { public EnigmaSphinxTriggeredAbility(Effect effect, boolean optional) { super(Zone.ALL, effect, optional); setTriggerPhrase("When {this} is put into your graveyard from the battlefield, "); + setLeavesTheBattlefieldTrigger(true); } private EnigmaSphinxTriggeredAbility(final EnigmaSphinxTriggeredAbility ability) { @@ -94,6 +96,11 @@ class EnigmaSphinxTriggeredAbility extends TriggeredAbilityImpl { } return false; } + + @Override + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); + } } class EnigmaSphinxEffect extends OneShotEffect { diff --git a/Mage.Sets/src/mage/cards/e/EvelynTheCovetous.java b/Mage.Sets/src/mage/cards/e/EvelynTheCovetous.java index d42736a2ec9..5f098d88de5 100644 --- a/Mage.Sets/src/mage/cards/e/EvelynTheCovetous.java +++ b/Mage.Sets/src/mage/cards/e/EvelynTheCovetous.java @@ -201,9 +201,9 @@ class EvelynTheCovetousWatcher extends Watcher { @Override public void watch(GameEvent event, Game game) { if ((event.getType() == GameEvent.EventType.SPELL_CAST || event.getType() == GameEvent.EventType.LAND_PLAYED) - && event.getAdditionalReference() != null) { + && event.getApprovingObject() != null) { usedMap.computeIfAbsent( - event.getAdditionalReference() + event.getApprovingObject() .getApprovingMageObjectReference(), x -> new HashSet<>() ).add(event.getPlayerId()); diff --git a/Mage.Sets/src/mage/cards/f/FaithfulPikemaster.java b/Mage.Sets/src/mage/cards/f/FaithfulPikemaster.java index 7ec554a3515..a990fe5b854 100644 --- a/Mage.Sets/src/mage/cards/f/FaithfulPikemaster.java +++ b/Mage.Sets/src/mage/cards/f/FaithfulPikemaster.java @@ -37,7 +37,7 @@ public final class FaithfulPikemaster extends CardImpl { // As long as it's your turn, Faithful Pikemaster has first strike. this.addAbility(new SimpleStaticAbility(new ConditionalContinuousEffect( new GainAbilitySourceEffect(FirstStrikeAbility.getInstance(), Duration.WhileOnBattlefield), - MyTurnCondition.instance, "as long as it's your turn, {this} has first strike." + MyTurnCondition.instance, "during your turn, {this} has first strike." )).addHint(MyTurnHint.instance)); } diff --git a/Mage.Sets/src/mage/cards/f/FalkenrathNoble.java b/Mage.Sets/src/mage/cards/f/FalkenrathNoble.java index ed0621fd7e1..01cae39b36b 100644 --- a/Mage.Sets/src/mage/cards/f/FalkenrathNoble.java +++ b/Mage.Sets/src/mage/cards/f/FalkenrathNoble.java @@ -1,6 +1,8 @@ package mage.cards.f; import mage.MageInt; +import mage.MageObject; +import mage.abilities.AbilityImpl; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.effects.common.GainLifeEffect; import mage.abilities.effects.common.LoseLifeTargetEffect; @@ -51,6 +53,7 @@ class FalkenrathNobleTriggeredAbility extends TriggeredAbilityImpl { super(Zone.BATTLEFIELD, new LoseLifeTargetEffect(1), false); this.addEffect(new GainLifeEffect(1)); this.addTarget(new TargetPlayer()); + setLeavesTheBattlefieldTrigger(true); } private FalkenrathNobleTriggeredAbility(final FalkenrathNobleTriggeredAbility ability) { @@ -87,4 +90,9 @@ class FalkenrathNobleTriggeredAbility extends TriggeredAbilityImpl { public String getRule() { return "Whenever {this} or another creature dies, target player loses 1 life and you gain 1 life."; } + + @Override + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); + } } diff --git a/Mage.Sets/src/mage/cards/f/FearOfAbduction.java b/Mage.Sets/src/mage/cards/f/FearOfAbduction.java new file mode 100644 index 00000000000..72792029d78 --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/FearOfAbduction.java @@ -0,0 +1,125 @@ +package mage.cards.f; + +import java.util.UUID; + +import mage.MageInt; +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.LeavesBattlefieldTriggeredAbility; +import mage.abilities.costs.common.ExileTargetCost; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.ExileTargetEffect; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Zone; +import mage.filter.StaticFilters; +import mage.game.ExileZone; +import mage.game.Game; +import mage.players.Player; +import mage.target.common.TargetControlledPermanent; +import mage.target.common.TargetCreaturePermanent; +import mage.util.CardUtil; + +/** + * @author Cguy7777 + */ +public final class FearOfAbduction extends CardImpl { + + public FearOfAbduction(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT, CardType.CREATURE}, "{4}{W}{W}"); + + this.subtype.add(SubType.NIGHTMARE); + this.power = new MageInt(5); + this.toughness = new MageInt(5); + + // As an additional cost to cast this spell, exile a creature you control. + this.getSpellAbility().addCost( + new ExileTargetCost(new TargetControlledPermanent(StaticFilters.FILTER_CONTROLLED_A_CREATURE))); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // When Fear of Abduction enters, exile target creature an opponent controls. + Ability ability = new EntersBattlefieldTriggeredAbility(new FearOfAbductionExileEffect()); + ability.addTarget(new TargetCreaturePermanent(StaticFilters.FILTER_OPPONENTS_PERMANENT_CREATURE)); + this.addAbility(ability); + + // When Fear of Abduction leaves the battlefield, put each card exiled with it into its owner's hand. + this.addAbility(new LeavesBattlefieldTriggeredAbility(new FearOfAbductionReturnEffect())); + } + + private FearOfAbduction(final FearOfAbduction card) { + super(card); + } + + @Override + public FearOfAbduction copy() { + return new FearOfAbduction(this); + } +} + +class FearOfAbductionExileEffect extends OneShotEffect { + + FearOfAbductionExileEffect() { + super(Outcome.Benefit); + this.staticText = "exile target creature an opponent controls"; + } + + private FearOfAbductionExileEffect(final FearOfAbductionExileEffect effect) { + super(effect); + } + + @Override + public FearOfAbductionExileEffect copy() { + return new FearOfAbductionExileEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + MageObject sourceObject = source.getSourceObject(game); + if (sourceObject != null) { + return new ExileTargetEffect( + // Offset -1 so that opponent's creature is exiled to the same exile zone as your creature + CardUtil.getExileZoneId(game, source, -1), + CardUtil.getSourceName(game, source)) + .apply(game, source); + } + return false; + } +} + +class FearOfAbductionReturnEffect extends OneShotEffect { + + FearOfAbductionReturnEffect() { + super(Outcome.Neutral); + this.staticText = "put each card exiled with it into its owner's hand"; + } + + private FearOfAbductionReturnEffect(final FearOfAbductionReturnEffect effect) { + super(effect); + } + + @Override + public FearOfAbductionReturnEffect copy() { + return new FearOfAbductionReturnEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller != null) { + // Offset -2 since Fear of Abduction has left the battlefield since last time + ExileZone exileZone = game.getExile().getExileZone(CardUtil.getExileZoneId(game, source, -2)); + if (exileZone != null) { + controller.moveCards(exileZone, Zone.HAND, source, game); + } + return true; + } + return false; + } +} diff --git a/Mage.Sets/src/mage/cards/f/FearOfSleepParalysis.java b/Mage.Sets/src/mage/cards/f/FearOfSleepParalysis.java new file mode 100644 index 00000000000..db977eb225d --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/FearOfSleepParalysis.java @@ -0,0 +1,92 @@ +package mage.cards.f; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.abilityword.EerieAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.ReplacementEffectImpl; +import mage.abilities.effects.common.TapTargetEffect; +import mage.abilities.effects.common.counter.AddCountersTargetEffect; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.counters.CounterType; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; +import mage.target.common.TargetCreaturePermanent; + +/** + * + * @author Grath + */ +public final class FearOfSleepParalysis extends CardImpl { + + public FearOfSleepParalysis(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT, CardType.CREATURE}, "{5}{U}"); + + this.subtype.add(SubType.NIGHTMARE); + this.power = new MageInt(6); + this.toughness = new MageInt(6); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Eerie -- Whenever Fear of Sleep Paralysis or another enchantment you control enters and whenever you fully unlock a Room, tap up to one target creature and put a stun counter on it. + Ability ability = new EerieAbility(new TapTargetEffect()); + ability.addEffect(new AddCountersTargetEffect(CounterType.STUN.createInstance()).setText("and put a stun counter on it")); + ability.addTarget(new TargetCreaturePermanent(0, 1)); + this.addAbility(ability); + + // Stun counters can't be removed from permanents your opponents control. + this.addAbility(new SimpleStaticAbility(new FearOfSleepParalysisEffect())); + } + + private FearOfSleepParalysis(final FearOfSleepParalysis card) { + super(card); + } + + @Override + public FearOfSleepParalysis copy() { + return new FearOfSleepParalysis(this); + } +} + +class FearOfSleepParalysisEffect extends ReplacementEffectImpl { + + FearOfSleepParalysisEffect() { + super(Duration.WhileOnBattlefield, Outcome.PreventDamage); + staticText = "Stun counters can't be removed from permanents your opponents control"; + } + + private FearOfSleepParalysisEffect(final FearOfSleepParalysisEffect effect) { + super(effect); + } + + @Override + public FearOfSleepParalysisEffect copy() { + return new FearOfSleepParalysisEffect(this); + } + + @Override + public boolean replaceEvent(GameEvent event, Ability source, Game game) { + return true; + } + + @Override + public boolean checksEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.REMOVE_COUNTERS; + } + + @Override + public boolean applies(GameEvent event, Ability source, Game game) { + Permanent target = game.getPermanent(event.getTargetId()); + return target != null && event.getData().equals(CounterType.STUN.getName()) && !target.getControllerId().equals(source.getControllerId()); + } + +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/f/FirestormPhoenix.java b/Mage.Sets/src/mage/cards/f/FirestormPhoenix.java new file mode 100644 index 00000000000..18608d7ade1 --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/FirestormPhoenix.java @@ -0,0 +1,201 @@ +package mage.cards.f; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.SpellAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.ContinuousEffectImpl; +import mage.abilities.effects.ContinuousRuleModifyingEffectImpl; +import mage.abilities.effects.ReplacementEffectImpl; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.cards.CardsImpl; +import mage.constants.*; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.events.ZoneChangeEvent; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.targetpointer.FixedTarget; + +import java.util.UUID; + +/** + * @author RobertFrosty, xenohedron + */ + +public final class FirestormPhoenix extends CardImpl { + + public FirestormPhoenix(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{R}{R}"); + + this.subtype.add(SubType.PHOENIX); + this.power = new MageInt(3); + this.toughness = new MageInt(2); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // If Firestorm Phoenix would die, return Firestorm Phoenix to its owner's hand instead. + // Until that player's next turn, that player plays with that card revealed in their hand and can't play it. + this.addAbility(new SimpleStaticAbility(Zone.ALL, new FirestormPhoenixEffect())); + } + + private FirestormPhoenix(final FirestormPhoenix card) { + super(card); + } + + @Override + public FirestormPhoenix copy() { + return new FirestormPhoenix(this); + } +} + +class FirestormPhoenixEffect extends ReplacementEffectImpl { + + + FirestormPhoenixEffect() { + super(Duration.Custom, Outcome.ReturnToHand); + staticText = "If {this} would die, return {this} to its owner's hand instead. " + + "Until that player's next turn, that player plays with that card revealed in their hand and can't play it"; + } + + private FirestormPhoenixEffect(final FirestormPhoenixEffect effect) { + super(effect); + } + + @Override + public FirestormPhoenixEffect copy() { + return new FirestormPhoenixEffect(this); + } + + @Override + public boolean replaceEvent(GameEvent event, Ability source, Game game) { + Player controller = game.getPlayer(source.getControllerId()); + Permanent permanent = ((ZoneChangeEvent) event).getTarget(); + if (controller == null || permanent == null) { + return false; + } + controller.moveCards(permanent, Zone.HAND, source, game); + Card card = game.getCard(permanent.getId()); + if (card == null) { + return true; + } + game.addEffect(new FirestormPhoenixRevealEffect().setTargetPointer(new FixedTarget(card, game)), source); + game.addEffect(new FirestormPhoenixRestrictEffect().setTargetPointer(new FixedTarget(card, game)), source); + return true; + + } + + @Override + public boolean checksEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.ZONE_CHANGE; + } + + @Override + public boolean applies(GameEvent event, Ability source, Game game) { + return source.getSourceId().equals(event.getTargetId()) + && ((ZoneChangeEvent) event).isDiesEvent(); + } +} + +class FirestormPhoenixRevealEffect extends ContinuousEffectImpl { + + private int startingTurnNum; + + FirestormPhoenixRevealEffect() { + super(Duration.Custom, Layer.PlayerEffects, SubLayer.NA, Outcome.Detriment); + } + + private FirestormPhoenixRevealEffect(final FirestormPhoenixRevealEffect effect) { + super(effect); + this.startingTurnNum = effect.startingTurnNum; + } + + @Override + public FirestormPhoenixRevealEffect copy() { + return new FirestormPhoenixRevealEffect(this); + } + + + @Override + public void init(Ability source, Game game) { + super.init(source, game); + startingTurnNum = game.getTurnNum(); + } + + @Override + public boolean isInactive(Ability source, Game game) { + if (game.getCard(getTargetPointer().getFirst(game, source)) == null) { + return true; + } + return game.isActivePlayer(game.getOwnerId(getTargetPointer().getFirst(game, source))) + && game.getTurnNum() > startingTurnNum; + } + + @Override + public boolean apply(Game game, Ability source) { + Card card = game.getCard(getTargetPointer().getFirst(game, source)); + if (card == null) { + discard(); + return false; + } + Player player = game.getPlayer(card.getOwnerId()); + if (player != null) { + player.revealCards(card.getIdName(), new CardsImpl(card), game, false); + } + return true; + } +} + +class FirestormPhoenixRestrictEffect extends ContinuousRuleModifyingEffectImpl { + + private int startingTurnNum; + + FirestormPhoenixRestrictEffect() { + super(Duration.Custom, Outcome.Detriment); + } + + private FirestormPhoenixRestrictEffect(final FirestormPhoenixRestrictEffect effect) { + super(effect); + this.startingTurnNum = effect.startingTurnNum; + } + + @Override + public FirestormPhoenixRestrictEffect copy() { + return new FirestormPhoenixRestrictEffect(this); + } + + @Override + public void init(Ability source, Game game) { + super.init(source, game); + startingTurnNum = game.getTurnNum(); + } + + @Override + public boolean isInactive(Ability source, Game game) { + if (game.getCard(getTargetPointer().getFirst(game, source)) == null) { + return true; + } + return game.isActivePlayer(game.getOwnerId(getTargetPointer().getFirst(game, source))) + && game.getTurnNum() > startingTurnNum; + } + + @Override + public boolean checksEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.CAST_SPELL_LATE; + } + + @Override + public boolean applies(GameEvent event, Ability source, Game game) { + Card cardInHand = game.getCard(getTargetPointer().getFirst(game, source)); + SpellAbility spellAbility = SpellAbility.getSpellAbilityFromEvent(event, game); + if (cardInHand == null || spellAbility == null) { + return false; + } + Card cardToCast = spellAbility.getCharacteristics(game); + return cardToCast != null && cardToCast.getId().equals(cardInHand.getId()); + } +} diff --git a/Mage.Sets/src/mage/cards/f/Flameskull.java b/Mage.Sets/src/mage/cards/f/Flameskull.java index 8e7d5446332..d1101dfc643 100644 --- a/Mage.Sets/src/mage/cards/f/Flameskull.java +++ b/Mage.Sets/src/mage/cards/f/Flameskull.java @@ -1,7 +1,6 @@ package mage.cards.f; import mage.MageInt; -import mage.MageObject; import mage.MageObjectReference; import mage.abilities.Ability; import mage.abilities.common.CantBlockAbility; @@ -132,10 +131,10 @@ class FlameskullWatcher extends Watcher { @Override public void watch(GameEvent event, Game game) { if (event.getType() != GameEvent.EventType.SPELL_CAST - || event.getAdditionalReference() == null) { + || event.getApprovingObject() == null) { return; } - MageObjectReference mor = event.getAdditionalReference().getApprovingMageObjectReference(); + MageObjectReference mor = event.getApprovingObject().getApprovingMageObjectReference(); Spell spell = game.getSpell(event.getTargetId()); if (mor == null || spell == null) { return; diff --git a/Mage.Sets/src/mage/cards/f/ForgeAnew.java b/Mage.Sets/src/mage/cards/f/ForgeAnew.java index 6feaac3ce53..2cbcab53136 100644 --- a/Mage.Sets/src/mage/cards/f/ForgeAnew.java +++ b/Mage.Sets/src/mage/cards/f/ForgeAnew.java @@ -47,7 +47,7 @@ public class ForgeAnew extends CardImpl { //As long as it’s your turn, you may activate equip abilities any time you could cast an instant. this.addAbility(new SimpleStaticAbility(new ConditionalAsThoughEffect( new ActivateAbilitiesAnyTimeYouCouldCastInstantEffect(EquipAbility.class, "equip abilities"), MyTurnCondition.instance - ).setText("as long as it's your turn, you may activate equip abilities any time you could cast an instant.")) + ).setText("during your turn, you may activate equip abilities any time you could cast an instant.")) ); //You may pay {0} rather than pay the equip cost of the first equip ability you activate during each of your turns. @@ -144,4 +144,4 @@ class ForgeAnewWatcher extends Watcher { ForgeAnewWatcher watcher = game.getState().getWatcher(ForgeAnewWatcher.class); return watcher != null && watcher.equippedThisTurn.contains(playerId); } -} \ No newline at end of file +} diff --git a/Mage.Sets/src/mage/cards/f/FrodoDeterminedHero.java b/Mage.Sets/src/mage/cards/f/FrodoDeterminedHero.java index 65e834023de..efb3a17d37b 100644 --- a/Mage.Sets/src/mage/cards/f/FrodoDeterminedHero.java +++ b/Mage.Sets/src/mage/cards/f/FrodoDeterminedHero.java @@ -50,7 +50,7 @@ public final class FrodoDeterminedHero extends CardImpl { // As long as it's your turn, prevent all damage that would be dealt to Frodo. this.addAbility(new SimpleStaticAbility(new ConditionalPreventionEffect( new PreventAllDamageToSourceEffect(Duration.WhileOnBattlefield), MyTurnCondition.instance, - "as long as it's your turn, prevent all damage that would be dealt to {this}" + "during your turn, prevent all damage that would be dealt to {this}" ))); } diff --git a/Mage.Sets/src/mage/cards/g/GaleaKindlerOfHope.java b/Mage.Sets/src/mage/cards/g/GaleaKindlerOfHope.java index 2436daa2dab..011cd01ca5a 100644 --- a/Mage.Sets/src/mage/cards/g/GaleaKindlerOfHope.java +++ b/Mage.Sets/src/mage/cards/g/GaleaKindlerOfHope.java @@ -91,7 +91,7 @@ class GaleaKindlerOfHopeTriggeredAbility extends TriggeredAbilityImpl { if (!isControlledBy(event.getPlayerId()) || event.getZone() != Zone.LIBRARY || !event - .getAdditionalReference() + .getApprovingObject() .getApprovingMageObjectReference() .refersTo(this.getSourceObject(game), game)) { return false; diff --git a/Mage.Sets/src/mage/cards/g/GeyserDrake.java b/Mage.Sets/src/mage/cards/g/GeyserDrake.java index dcc23648826..4d17f62214c 100644 --- a/Mage.Sets/src/mage/cards/g/GeyserDrake.java +++ b/Mage.Sets/src/mage/cards/g/GeyserDrake.java @@ -32,7 +32,7 @@ public final class GeyserDrake extends CardImpl { // As long as it's not your turn, spells you cast cost {1} less to cast. this.addAbility(new SimpleStaticAbility(new ConditionalCostModificationEffect( new SpellsCostReductionControllerEffect(StaticFilters.FILTER_CARD, 1), - NotMyTurnCondition.instance, "as long as it's not your turn, " + + NotMyTurnCondition.instance, "during turns other than yours, " + "spells you cast cost {1} less to cast" ))); } diff --git a/Mage.Sets/src/mage/cards/g/GhostVacuum.java b/Mage.Sets/src/mage/cards/g/GhostVacuum.java new file mode 100644 index 00000000000..b76bff02e39 --- /dev/null +++ b/Mage.Sets/src/mage/cards/g/GhostVacuum.java @@ -0,0 +1,114 @@ +package mage.cards.g; + +import java.util.Objects; +import java.util.UUID; + +import mage.abilities.Ability; +import mage.abilities.common.ActivateAsSorceryActivatedAbility; +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.ContinuousEffect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.ExileTargetEffect; +import mage.abilities.effects.common.continuous.AddCreatureTypeAdditionEffect; +import mage.abilities.effects.common.continuous.SetBasePowerToughnessTargetEffect; +import mage.cards.*; +import mage.constants.*; +import mage.counters.CounterType; +import mage.counters.Counters; +import mage.filter.StaticFilters; +import mage.game.ExileZone; +import mage.game.Game; +import mage.players.Player; +import mage.target.common.TargetCardInGraveyard; +import mage.target.targetpointer.FixedTarget; +import mage.util.CardUtil; + +/** + * @author Cguy7777 + */ +public final class GhostVacuum extends CardImpl { + + public GhostVacuum(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{1}"); + + // {T}: Exile target card from a graveyard. + Ability exileAbility + = new SimpleActivatedAbility(new ExileTargetEffect().setToSourceExileZone(true), new TapSourceCost()); + exileAbility.addTarget(new TargetCardInGraveyard()); + this.addAbility(exileAbility); + + // {6}, {T}, Sacrifice Ghost Vacuum: + // Put each creature card exiled with Ghost Vacuum onto the battlefield under your control with a flying counter on it. + // Each of them is a 1/1 Spirit in addition to its other types. Activate only as a sorcery. + Ability putOntoBattlefieldAbility + = new ActivateAsSorceryActivatedAbility(new GhostVacuumEffect(), new GenericManaCost(6)); + putOntoBattlefieldAbility.addCost(new TapSourceCost()); + putOntoBattlefieldAbility.addCost(new SacrificeSourceCost()); + this.addAbility(putOntoBattlefieldAbility); + } + + private GhostVacuum(final GhostVacuum card) { + super(card); + } + + @Override + public GhostVacuum copy() { + return new GhostVacuum(this); + } +} + +class GhostVacuumEffect extends OneShotEffect { + + GhostVacuumEffect() { + super(Outcome.Benefit); + staticText = "Put each creature card exiled with {this} onto the battlefield under your control " + + "with a flying counter on it. Each of them is a 1/1 Spirit in addition to its other types."; + } + + private GhostVacuumEffect(final GhostVacuumEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + ExileZone exileZone = game.getExile().getExileZone(CardUtil.getExileZoneId(game, source)); + + if (player == null || exileZone == null || exileZone.isEmpty()) { + return false; + } + + // Put each creature card exiled with Ghost Vacuum + // onto the battlefield under your control with a flying counter on it. + Cards creatureCards = new CardsImpl(exileZone.getCards(StaticFilters.FILTER_CARD_CREATURE, game)); + Counters countersToAdd = new Counters(); + countersToAdd.addCounter(CounterType.FLYING.createInstance()); + for (Card card : creatureCards.getCards(game)) { + game.setEnterWithCounters(card.getId(), countersToAdd.copy()); + } + player.moveCards(creatureCards, Zone.BATTLEFIELD, source, game); + + creatureCards.stream() + .map(game::getPermanent) + .filter(Objects::nonNull) + .forEach(permanent -> { + // Each of them is a 1/1 Spirit in addition to its other types. + ContinuousEffect effect = new SetBasePowerToughnessTargetEffect(1, 1, Duration.EndOfGame); + effect.setTargetPointer(new FixedTarget(permanent, game)); + game.addEffect(effect, source); + + effect = new AddCreatureTypeAdditionEffect(SubType.SPIRIT, false); + effect.setTargetPointer(new FixedTarget(permanent, game)); + game.addEffect(effect, source); + }); + return true; + } + + @Override + public GhostVacuumEffect copy() { + return new GhostVacuumEffect(this); + } +} diff --git a/Mage.Sets/src/mage/cards/g/GlimpseTheCosmos.java b/Mage.Sets/src/mage/cards/g/GlimpseTheCosmos.java index a8ffb3d46b9..6466b2cbcad 100644 --- a/Mage.Sets/src/mage/cards/g/GlimpseTheCosmos.java +++ b/Mage.Sets/src/mage/cards/g/GlimpseTheCosmos.java @@ -162,7 +162,7 @@ class GlimpseTheCosmosWatcher extends Watcher { public void watch(GameEvent event, Game game) { if (event.getType() == GameEvent.EventType.CAST_SPELL && event.hasApprovingIdentifier(MageIdentifier.GlimpseTheCosmosWatcher)) { - Ability approvingAbility = event.getAdditionalReference().getApprovingAbility(); + Ability approvingAbility = event.getApprovingObject().getApprovingAbility(); if (approvingAbility != null && approvingAbility.getSourceId().equals(event.getSourceId())) { sourceCards.add(game.getCard(event.getSourceId())); diff --git a/Mage.Sets/src/mage/cards/g/GlintWeaver.java b/Mage.Sets/src/mage/cards/g/GlintWeaver.java index 1a9ab59f620..2d8f0e0d398 100644 --- a/Mage.Sets/src/mage/cards/g/GlintWeaver.java +++ b/Mage.Sets/src/mage/cards/g/GlintWeaver.java @@ -33,7 +33,7 @@ public final class GlintWeaver extends CardImpl { // When Glint Weaver enters the battlefield, distribute three +1/+1 counters among one, two, or three target creatures, then you gain life equal to the greatest toughness among creatures you control. Ability ability = new EntersBattlefieldTriggeredAbility(new DistributeCountersEffect( - CounterType.P1P1, 3, "one, two, or three target creatures" + 3, "one, two, or three target creatures" )); ability.addEffect(new GainLifeEffect(GreatestToughnessAmongControlledCreaturesValue.instance) .setText(", then you gain life equal to the greatest toughness among creatures you control")); diff --git a/Mage.Sets/src/mage/cards/g/Gobland.java b/Mage.Sets/src/mage/cards/g/Gobland.java new file mode 100644 index 00000000000..b4a572b2f87 --- /dev/null +++ b/Mage.Sets/src/mage/cards/g/Gobland.java @@ -0,0 +1,48 @@ +package mage.cards.g; + +import mage.MageInt; +import mage.abilities.common.CantBlockAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.common.InfoEffect; +import mage.abilities.mana.RedManaAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; + +import java.util.UUID; + +/** + * @author PurpleCrowbar + */ +public final class Gobland extends CardImpl { + + public Gobland(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.LAND, CardType.CREATURE}, ""); + this.subtype.add(SubType.MOUNTAIN, SubType.GOBLIN); + this.power = new MageInt(2); + this.toughness = new MageInt(1); + + this.color.setRed(true); + + // (Gobland isn't a spell, it's affected by summoning sickness, and it has "{T}: Add {R}.") + this.addAbility(new SimpleStaticAbility(new InfoEffect( + "({this} isn't a spell, it's affected by summoning sickness, and it has \"{T}: Add {R}.\")" + ))); + + // Add {R} + this.addAbility(new RedManaAbility()); + + // Gobland can't block. + this.addAbility(new CantBlockAbility()); + } + + private Gobland(final Gobland card) { + super(card); + } + + @Override + public Gobland copy() { + return new Gobland(this); + } +} diff --git a/Mage.Sets/src/mage/cards/g/GraveBetrayal.java b/Mage.Sets/src/mage/cards/g/GraveBetrayal.java index af4674f4632..4d70006e4cc 100644 --- a/Mage.Sets/src/mage/cards/g/GraveBetrayal.java +++ b/Mage.Sets/src/mage/cards/g/GraveBetrayal.java @@ -2,6 +2,8 @@ package mage.cards.g; import java.util.UUID; + +import mage.MageObject; import mage.abilities.Ability; import mage.abilities.DelayedTriggeredAbility; import mage.abilities.TriggeredAbilityImpl; @@ -53,6 +55,7 @@ class GraveBetrayalTriggeredAbility extends TriggeredAbilityImpl { public GraveBetrayalTriggeredAbility() { super(Zone.BATTLEFIELD, null); + setLeavesTheBattlefieldTrigger(true); } private GraveBetrayalTriggeredAbility(final GraveBetrayalTriggeredAbility ability) { @@ -92,6 +95,11 @@ class GraveBetrayalTriggeredAbility extends TriggeredAbilityImpl { public String getRule() { return "Whenever a creature you don't control dies, return it to the battlefield under your control with an additional +1/+1 counter on it at the beginning of the next end step. That creature is a black Zombie in addition to its other colors and types."; } + + @Override + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); + } } class GraveBetrayalEffect extends OneShotEffect { @@ -125,7 +133,6 @@ class GraveBetrayalEffect extends OneShotEffect { } return false; } - } class GraveBetrayalReplacementEffect extends ReplacementEffectImpl { diff --git a/Mage.Sets/src/mage/cards/g/GravePact.java b/Mage.Sets/src/mage/cards/g/GravePact.java index c58990753c0..ccfd32d1d99 100644 --- a/Mage.Sets/src/mage/cards/g/GravePact.java +++ b/Mage.Sets/src/mage/cards/g/GravePact.java @@ -1,20 +1,16 @@ package mage.cards.g; import mage.abilities.Ability; -import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.common.DiesCreatureTriggeredAbility; import mage.abilities.effects.OneShotEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Outcome; -import mage.constants.Zone; import mage.filter.StaticFilters; import mage.game.Game; -import mage.game.events.GameEvent; -import mage.game.events.ZoneChangeEvent; import mage.game.permanent.Permanent; import mage.players.Player; -import mage.target.common.TargetControlledCreaturePermanent; import mage.target.common.TargetSacrifice; import java.util.ArrayList; @@ -31,7 +27,9 @@ public final class GravePact extends CardImpl { // Whenever a creature you control dies, each other player sacrifices a creature. - this.addAbility(new GravePactTriggeredAbility()); + this.addAbility(new DiesCreatureTriggeredAbility( + new GravePactEffect(), false, StaticFilters.FILTER_CONTROLLED_A_CREATURE + )); } private GravePact(final GravePact card) { @@ -44,43 +42,11 @@ public final class GravePact extends CardImpl { } } -class GravePactTriggeredAbility extends TriggeredAbilityImpl { - - public GravePactTriggeredAbility() { - super(Zone.BATTLEFIELD, new GravePactEffect()); - setTriggerPhrase("Whenever a creature you control dies, "); - } - - private GravePactTriggeredAbility(final GravePactTriggeredAbility ability) { - super(ability); - } - - @Override - public GravePactTriggeredAbility copy() { - return new GravePactTriggeredAbility(this); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.ZONE_CHANGE; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - ZoneChangeEvent zoneChangeEvent = (ZoneChangeEvent) event; - if (zoneChangeEvent.isDiesEvent()) { - Permanent permanent = (Permanent) game.getLastKnownInformation(event.getTargetId(), Zone.BATTLEFIELD); - return permanent != null && permanent.isControlledBy(this.getControllerId()) && permanent.isCreature(game); - } - return false; - } -} - class GravePactEffect extends OneShotEffect { GravePactEffect() { super(Outcome.Sacrifice); - this.staticText = "each other player sacrifices a creature"; + this.staticText = "each other player sacrifices a creature of their choice"; } private GravePactEffect(final GravePactEffect effect) { diff --git a/Mage.Sets/src/mage/cards/g/GuardianNaga.java b/Mage.Sets/src/mage/cards/g/GuardianNaga.java index b035af53982..23ee9288db3 100644 --- a/Mage.Sets/src/mage/cards/g/GuardianNaga.java +++ b/Mage.Sets/src/mage/cards/g/GuardianNaga.java @@ -35,7 +35,7 @@ public final class GuardianNaga extends AdventureCard { // As long as it's your turn, prevent all damage that would be dealt to Guardian Naga. this.addAbility(new SimpleStaticAbility(new ConditionalPreventionEffect( new PreventAllDamageToSourceEffect(Duration.WhileOnBattlefield), MyTurnCondition.instance, - "as long as it's your turn, prevent all damage that would be dealt to {this}" + "during your turn, prevent all damage that would be dealt to {this}" ))); // Banishing Coils diff --git a/Mage.Sets/src/mage/cards/g/GuardianScalelord.java b/Mage.Sets/src/mage/cards/g/GuardianScalelord.java index af29bf49010..17454291a1b 100644 --- a/Mage.Sets/src/mage/cards/g/GuardianScalelord.java +++ b/Mage.Sets/src/mage/cards/g/GuardianScalelord.java @@ -77,7 +77,6 @@ enum GuardianScalelordPredicate implements ObjectSourcePlayerPredicate { public boolean apply(ObjectSourcePlayer input, Game game) { return Optional .ofNullable(input.getSource().getSourcePermanentOrLKI(game)) - .filter(Objects::nonNull) .map(MageObject::getPower) .map(MageInt::getValue) .map(p -> input.getObject().getManaValue() <= p) diff --git a/Mage.Sets/src/mage/cards/g/GutterGrime.java b/Mage.Sets/src/mage/cards/g/GutterGrime.java index ca06176f03e..956f177edff 100644 --- a/Mage.Sets/src/mage/cards/g/GutterGrime.java +++ b/Mage.Sets/src/mage/cards/g/GutterGrime.java @@ -48,6 +48,7 @@ class GutterGrimeTriggeredAbility extends TriggeredAbilityImpl { public GutterGrimeTriggeredAbility() { super(Zone.BATTLEFIELD, new AddCountersSourceEffect(CounterType.SLIME.createInstance()), false); this.addEffect(new GutterGrimeEffect()); + setLeavesTheBattlefieldTrigger(true); } private GutterGrimeTriggeredAbility(final GutterGrimeTriggeredAbility ability) { @@ -83,6 +84,11 @@ class GutterGrimeTriggeredAbility extends TriggeredAbilityImpl { public String getRule() { return "Whenever a nontoken creature you control dies, put a slime counter on {this}, then create a green Ooze creature token with \"This creature's power and toughness are each equal to the number of slime counters on {this}.\""; } + + @Override + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); + } } class GutterGrimeEffect extends OneShotEffect { diff --git a/Mage.Sets/src/mage/cards/h/HatefulEidolon.java b/Mage.Sets/src/mage/cards/h/HatefulEidolon.java index a615cf25fc9..3a0e4298251 100644 --- a/Mage.Sets/src/mage/cards/h/HatefulEidolon.java +++ b/Mage.Sets/src/mage/cards/h/HatefulEidolon.java @@ -1,6 +1,7 @@ package mage.cards.h; import mage.MageInt; +import mage.MageObject; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.keyword.LifelinkAbility; import mage.cards.CardImpl; @@ -51,6 +52,7 @@ class HatefulEidolonTriggeredAbility extends TriggeredAbilityImpl { HatefulEidolonTriggeredAbility() { super(Zone.BATTLEFIELD, null, false); + setLeavesTheBattlefieldTrigger(true); } private HatefulEidolonTriggeredAbility(final HatefulEidolonTriggeredAbility ability) { @@ -105,4 +107,9 @@ class HatefulEidolonTriggeredAbility extends TriggeredAbilityImpl { return "Whenever an enchanted creature dies, draw a card for each " + "Aura you controlled that was attached to it."; } + + @Override + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); + } } diff --git a/Mage.Sets/src/mage/cards/h/HaukensInsight.java b/Mage.Sets/src/mage/cards/h/HaukensInsight.java index ad5a6cd5dee..19b896ecdd0 100644 --- a/Mage.Sets/src/mage/cards/h/HaukensInsight.java +++ b/Mage.Sets/src/mage/cards/h/HaukensInsight.java @@ -185,7 +185,7 @@ class HaukensInsightWatcher extends Watcher { public void watch(GameEvent event, Game game) { if (event.getType() == GameEvent.EventType.SPELL_CAST || event.getType() == GameEvent.EventType.LAND_PLAYED) { if (event.hasApprovingIdentifier(MageIdentifier.HaukensInsightWatcher)) { - usedFrom.add(event.getAdditionalReference().getApprovingMageObjectReference()); + usedFrom.add(event.getApprovingObject().getApprovingMageObjectReference()); } } } diff --git a/Mage.Sets/src/mage/cards/h/HedonistsTrove.java b/Mage.Sets/src/mage/cards/h/HedonistsTrove.java index 650fbff8f08..04d061f1302 100644 --- a/Mage.Sets/src/mage/cards/h/HedonistsTrove.java +++ b/Mage.Sets/src/mage/cards/h/HedonistsTrove.java @@ -173,12 +173,12 @@ class HedonistsTroveWatcher extends Watcher { @Override public void watch(GameEvent event, Game game) { if (event.getType() != GameEvent.EventType.SPELL_CAST - || event.getAdditionalReference() == null) { + || event.getApprovingObject() == null) { return; } playerMap .computeIfAbsent(event.getPlayerId(), x -> new HashSet<>()) - .add(event.getAdditionalReference().getApprovingMageObjectReference()); + .add(event.getApprovingObject().getApprovingMageObjectReference()); playerMap.get(event.getPlayerId()).removeIf(Objects::isNull); } diff --git a/Mage.Sets/src/mage/cards/h/HexgoldHalberd.java b/Mage.Sets/src/mage/cards/h/HexgoldHalberd.java index 64c50c3941e..c3b5c746bfb 100644 --- a/Mage.Sets/src/mage/cards/h/HexgoldHalberd.java +++ b/Mage.Sets/src/mage/cards/h/HexgoldHalberd.java @@ -36,7 +36,7 @@ public final class HexgoldHalberd extends CardImpl { // As long as it's your turn, equipped creature has first strike and trample. Ability ability = new SimpleStaticAbility(new ConditionalContinuousEffect( new GainAbilityAttachedEffect(FirstStrikeAbility.getInstance(), AttachmentType.EQUIPMENT), - MyTurnCondition.instance, "as long as it's your turn, equipped creature has first strike" + MyTurnCondition.instance, "during your turn, equipped creature has first strike" )); ability.addEffect(new ConditionalContinuousEffect( new GainAbilityAttachedEffect(TrampleAbility.getInstance(), AttachmentType.EQUIPMENT), diff --git a/Mage.Sets/src/mage/cards/h/Hookblade.java b/Mage.Sets/src/mage/cards/h/Hookblade.java index 6d41497fa40..dccff7bdb2e 100644 --- a/Mage.Sets/src/mage/cards/h/Hookblade.java +++ b/Mage.Sets/src/mage/cards/h/Hookblade.java @@ -36,7 +36,7 @@ public final class Hookblade extends CardImpl { // As long as it's your turn, equipped creature has flying. this.addAbility(new SimpleStaticAbility(new ConditionalContinuousEffect( new GainAbilityAttachedEffect(FlyingAbility.getInstance().getInstance(), AttachmentType.EQUIPMENT), - MyTurnCondition.instance, "as long as it's your turn, equipped creature has flying" + MyTurnCondition.instance, "during your turn, equipped creature has flying" )).addHint(MyTurnHint.instance)); // Equip {2} diff --git a/Mage.Sets/src/mage/cards/h/HookbladeVeteran.java b/Mage.Sets/src/mage/cards/h/HookbladeVeteran.java index af280695d3c..7e280f5f9e8 100644 --- a/Mage.Sets/src/mage/cards/h/HookbladeVeteran.java +++ b/Mage.Sets/src/mage/cards/h/HookbladeVeteran.java @@ -31,7 +31,7 @@ public final class HookbladeVeteran extends CardImpl { // As long as it's your turn, Hookblade Veteran has flying. this.addAbility(new SimpleStaticAbility(new ConditionalContinuousEffect( new GainAbilitySourceEffect(FlyingAbility.getInstance(), Duration.WhileOnBattlefield), - MyTurnCondition.instance, "as long as it's your turn, {this} has flying" + MyTurnCondition.instance, "during your turn, {this} has flying" )).addHint(MyTurnHint.instance)); } diff --git a/Mage.Sets/src/mage/cards/i/IanMalcolmChaotician.java b/Mage.Sets/src/mage/cards/i/IanMalcolmChaotician.java index 20494b03b74..2ec090a52d5 100644 --- a/Mage.Sets/src/mage/cards/i/IanMalcolmChaotician.java +++ b/Mage.Sets/src/mage/cards/i/IanMalcolmChaotician.java @@ -224,7 +224,7 @@ class IanMalcolmChaoticianWatcher extends Watcher { if (event.getType() == GameEvent.EventType.SPELL_CAST && event.hasApprovingIdentifier(MageIdentifier.IanMalcolmChaoticianWatcher)) { usedMap.computeIfAbsent( - event.getAdditionalReference() + event.getApprovingObject() .getApprovingMageObjectReference(), x -> new HashSet<>() ).add(event.getPlayerId()); diff --git a/Mage.Sets/src/mage/cards/i/IdolOfEndurance.java b/Mage.Sets/src/mage/cards/i/IdolOfEndurance.java index ce789ae7194..f57fbfb3197 100644 --- a/Mage.Sets/src/mage/cards/i/IdolOfEndurance.java +++ b/Mage.Sets/src/mage/cards/i/IdolOfEndurance.java @@ -211,10 +211,10 @@ class IdolOfEnduranceWatcher extends Watcher { @Override public void watch(GameEvent event, Game game) { if (event.getType() == GameEvent.EventType.SPELL_CAST) { - if (event.getAdditionalReference() == null) { + if (event.getApprovingObject() == null) { return; } - morMap.computeIfAbsent(event.getAdditionalReference().getApprovingMageObjectReference(), m -> new HashMap<>()) + morMap.computeIfAbsent(event.getApprovingObject().getApprovingMageObjectReference(), m -> new HashMap<>()) .compute(event.getPlayerId(), (u, i) -> i == null ? 0 : Integer.sum(i, -1)); } } diff --git a/Mage.Sets/src/mage/cards/i/Indicate.java b/Mage.Sets/src/mage/cards/i/Indicate.java new file mode 100644 index 00000000000..40da8d0ec0c --- /dev/null +++ b/Mage.Sets/src/mage/cards/i/Indicate.java @@ -0,0 +1,32 @@ +package mage.cards.i; + +import mage.abilities.effects.common.InfoEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author PurpleCrowbar + */ +public final class Indicate extends CardImpl { + + public Indicate(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{0}"); + + // Target permanent. + this.getSpellAbility().addEffect(new InfoEffect("Target permanent.")); + this.getSpellAbility().addTarget(new TargetPermanent()); + } + + private Indicate(final Indicate card) { + super(card); + } + + @Override + public Indicate copy() { + return new Indicate(this); + } +} diff --git a/Mage.Sets/src/mage/cards/i/InfestedThrinax.java b/Mage.Sets/src/mage/cards/i/InfestedThrinax.java index 6aaf3d51784..a4c24be192a 100644 --- a/Mage.Sets/src/mage/cards/i/InfestedThrinax.java +++ b/Mage.Sets/src/mage/cards/i/InfestedThrinax.java @@ -1,7 +1,9 @@ package mage.cards.i; import mage.MageInt; +import mage.MageObject; import mage.abilities.DelayedTriggeredAbility; +import mage.abilities.TriggeredAbilityImpl; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.dynamicvalue.common.SavedDamageValue; import mage.abilities.effects.common.CreateDelayedTriggeredAbilityEffect; @@ -55,6 +57,7 @@ class InfestedThrinaxTriggeredAbility extends DelayedTriggeredAbility { InfestedThrinaxTriggeredAbility() { super(new CreateTokenEffect(new SaprolingToken(), SavedDamageValue.MUCH), Duration.EndOfTurn, false, false); + setLeavesTheBattlefieldTrigger(true); } private InfestedThrinaxTriggeredAbility(final InfestedThrinaxTriggeredAbility ability) { @@ -89,4 +92,9 @@ class InfestedThrinaxTriggeredAbility extends DelayedTriggeredAbility { return "Whenever a nontoken creature you control dies, " + "create a number of 1/1 green Saproling creature tokens equal to that creature's power."; } + + @Override + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); + } } diff --git a/Mage.Sets/src/mage/cards/j/JadeSeedstones.java b/Mage.Sets/src/mage/cards/j/JadeSeedstones.java index 837679c29fa..149827d2016 100644 --- a/Mage.Sets/src/mage/cards/j/JadeSeedstones.java +++ b/Mage.Sets/src/mage/cards/j/JadeSeedstones.java @@ -24,7 +24,7 @@ public final class JadeSeedstones extends CardImpl { // When Jade Seedstones enters the battlefield, distribute three +1/+1 counters among one, two, or three target creatures you control. Ability ability = new EntersBattlefieldTriggeredAbility(new DistributeCountersEffect( - CounterType.P1P1, 3, "one, two, or three target creatures you control" + 3, "one, two, or three target creatures you control" )); TargetPermanentAmount target = new TargetPermanentAmount(3, StaticFilters.FILTER_CONTROLLED_CREATURES); target.setMinNumberOfTargets(1); diff --git a/Mage.Sets/src/mage/cards/j/JavelinOfLightning.java b/Mage.Sets/src/mage/cards/j/JavelinOfLightning.java index c939738da92..51e89db9cb6 100644 --- a/Mage.Sets/src/mage/cards/j/JavelinOfLightning.java +++ b/Mage.Sets/src/mage/cards/j/JavelinOfLightning.java @@ -38,7 +38,7 @@ public final class JavelinOfLightning extends CardImpl { // As long as it's your turn, equipped creature gets +2/+0 and has first strike. Ability ability = new SimpleStaticAbility(new ConditionalContinuousEffect( new BoostEquippedEffect(2, 0), MyTurnCondition.instance, - "as long as it's your turn, equipped creature gets +2/+0")); + "during your turn, equipped creature gets +2/+0")); ability.addEffect(new ConditionalContinuousEffect(new GainAbilityAttachedEffect( FirstStrikeAbility.getInstance(), AttachmentType.EQUIPMENT ), MyTurnCondition.instance, "and has first strike")); diff --git a/Mage.Sets/src/mage/cards/j/JerrenCorruptedBishop.java b/Mage.Sets/src/mage/cards/j/JerrenCorruptedBishop.java index 0b514ae90af..606bec7fe1e 100644 --- a/Mage.Sets/src/mage/cards/j/JerrenCorruptedBishop.java +++ b/Mage.Sets/src/mage/cards/j/JerrenCorruptedBishop.java @@ -94,6 +94,7 @@ class JerrenCorruptedBishopTriggeredAbility extends TriggeredAbilityImpl { JerrenCorruptedBishopTriggeredAbility() { super(Zone.BATTLEFIELD, new LoseLifeSourceControllerEffect(1)); this.addEffect(new CreateTokenEffect(new HumanToken())); + setLeavesTheBattlefieldTrigger(true); } private JerrenCorruptedBishopTriggeredAbility(final JerrenCorruptedBishopTriggeredAbility ability) { diff --git a/Mage.Sets/src/mage/cards/j/JuganTheRisingStar.java b/Mage.Sets/src/mage/cards/j/JuganTheRisingStar.java index 192819795c9..effc7db6288 100644 --- a/Mage.Sets/src/mage/cards/j/JuganTheRisingStar.java +++ b/Mage.Sets/src/mage/cards/j/JuganTheRisingStar.java @@ -33,7 +33,7 @@ public final class JuganTheRisingStar extends CardImpl { // Flying this.addAbility(FlyingAbility.getInstance()); // When Jugan, the Rising Star dies, you may distribute five +1/+1 counters among any number of target creatures. - Ability ability = new DiesSourceTriggeredAbility(new DistributeCountersEffect(CounterType.P1P1, 5, false, "any number of target creatures"), true); + Ability ability = new DiesSourceTriggeredAbility(new DistributeCountersEffect(5, "any number of target creatures"), true); ability.addTarget(new TargetCreaturePermanentAmount(5)); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/k/KaghaShadowArchdruid.java b/Mage.Sets/src/mage/cards/k/KaghaShadowArchdruid.java index 5830ad878e6..9f4b47eaaa3 100644 --- a/Mage.Sets/src/mage/cards/k/KaghaShadowArchdruid.java +++ b/Mage.Sets/src/mage/cards/k/KaghaShadowArchdruid.java @@ -123,7 +123,7 @@ class KaghaShadowArchdruidWatcher extends Watcher { public void watch(GameEvent event, Game game) { if ((GameEvent.EventType.SPELL_CAST.equals(event.getType()) || GameEvent.EventType.LAND_PLAYED.equals(event.getType())) && event.hasApprovingIdentifier(MageIdentifier.KaghaShadowArchdruidWatcher)) { - usedFrom.add(event.getAdditionalReference().getApprovingMageObjectReference()); + usedFrom.add(event.getApprovingObject().getApprovingMageObjectReference()); } if (event.getType() == GameEvent.EventType.ZONE_CHANGE) { ZoneChangeEvent zEvent = (ZoneChangeEvent) event; diff --git a/Mage.Sets/src/mage/cards/k/KarmicJustice.java b/Mage.Sets/src/mage/cards/k/KarmicJustice.java index 09dcd7ea130..68bb6406d27 100644 --- a/Mage.Sets/src/mage/cards/k/KarmicJustice.java +++ b/Mage.Sets/src/mage/cards/k/KarmicJustice.java @@ -44,6 +44,7 @@ class KarmicJusticeTriggeredAbility extends TriggeredAbilityImpl { KarmicJusticeTriggeredAbility() { super(Zone.BATTLEFIELD, new DestroyTargetEffect(), true); + this.setLeavesTheBattlefieldTrigger(true); } private KarmicJusticeTriggeredAbility(final KarmicJusticeTriggeredAbility ability) { diff --git a/Mage.Sets/src/mage/cards/k/KayaTheInexorable.java b/Mage.Sets/src/mage/cards/k/KayaTheInexorable.java index e52b32c15e9..cc1ab89fcaf 100644 --- a/Mage.Sets/src/mage/cards/k/KayaTheInexorable.java +++ b/Mage.Sets/src/mage/cards/k/KayaTheInexorable.java @@ -78,6 +78,7 @@ class KayaTheInexorableTriggeredAbility extends TriggeredAbilityImpl { public KayaTheInexorableTriggeredAbility() { super(Zone.ALL, null, false); + this.setLeavesTheBattlefieldTrigger(true); } private KayaTheInexorableTriggeredAbility(KayaTheInexorableTriggeredAbility ability) { @@ -107,22 +108,6 @@ class KayaTheInexorableTriggeredAbility extends TriggeredAbilityImpl { return false; } - @Override - public boolean isInUseableZone(Game game, MageObject source, GameEvent event) { - Permanent sourcePermanent = null; - if (game.getState().getZone(getSourceId()) == Zone.BATTLEFIELD) { - sourcePermanent = game.getPermanent(getSourceId()); - } else { - if (game.checkShortLivingLKI(getSourceId(), Zone.BATTLEFIELD)) { - sourcePermanent = (Permanent) game.getLastKnownInformation(getSourceId(), Zone.BATTLEFIELD); - } - } - if (sourcePermanent == null) { - return false; - } - return hasSourceObjectAbility(game, sourcePermanent, event); - } - @Override public KayaTheInexorableTriggeredAbility copy() { return new KayaTheInexorableTriggeredAbility(this); diff --git a/Mage.Sets/src/mage/cards/k/KayasGhostform.java b/Mage.Sets/src/mage/cards/k/KayasGhostform.java index 0cd641eb58c..de7433f4804 100644 --- a/Mage.Sets/src/mage/cards/k/KayasGhostform.java +++ b/Mage.Sets/src/mage/cards/k/KayasGhostform.java @@ -60,6 +60,7 @@ class KayasGhostformTriggeredAbility extends TriggeredAbilityImpl { KayasGhostformTriggeredAbility() { super(Zone.ALL, new ReturnToBattlefieldUnderYourControlAttachedEffect(), false); + setLeavesTheBattlefieldTrigger(true); } private KayasGhostformTriggeredAbility(final KayasGhostformTriggeredAbility ability) { diff --git a/Mage.Sets/src/mage/cards/k/KessDissidentMage.java b/Mage.Sets/src/mage/cards/k/KessDissidentMage.java index 64f349b7699..7deead6e67f 100644 --- a/Mage.Sets/src/mage/cards/k/KessDissidentMage.java +++ b/Mage.Sets/src/mage/cards/k/KessDissidentMage.java @@ -178,9 +178,9 @@ class KessDissidentMageWatcher extends Watcher { && event.hasApprovingIdentifier(MageIdentifier.KessDissidentMageWatcher)) { Spell spell = (Spell) game.getObject(event.getTargetId()); if (spell != null) { - allowingObjects.add(event.getAdditionalReference().getApprovingMageObjectReference()); + allowingObjects.add(event.getApprovingObject().getApprovingMageObjectReference()); castSpells.put(new MageObjectReference(spell.getMainCard().getId(), game), - event.getAdditionalReference().getApprovingAbility().getSourceId()); + event.getApprovingObject().getApprovingAbility().getSourceId()); } } } diff --git a/Mage.Sets/src/mage/cards/k/Knife.java b/Mage.Sets/src/mage/cards/k/Knife.java index b425fa67570..2d8a4965b5b 100644 --- a/Mage.Sets/src/mage/cards/k/Knife.java +++ b/Mage.Sets/src/mage/cards/k/Knife.java @@ -31,7 +31,7 @@ public final class Knife extends CardImpl { // As long as it's your turn, equipped creature gets +1/+0 and has first strike. Ability ability = new SimpleStaticAbility(new ConditionalContinuousEffect( new BoostEquippedEffect(1, 0), MyTurnCondition.instance, - "as long as it's your turn, equipped creature gets +1/+0" + "during your turn, equipped creature gets +1/+0" )); ability.addEffect(new ConditionalContinuousEffect(new GainAbilityAttachedEffect( FirstStrikeAbility.getInstance(), AttachmentType.EQUIPMENT diff --git a/Mage.Sets/src/mage/cards/k/KotoseTheSilentSpider.java b/Mage.Sets/src/mage/cards/k/KotoseTheSilentSpider.java index b02bc89141e..4e6aa44c4b6 100644 --- a/Mage.Sets/src/mage/cards/k/KotoseTheSilentSpider.java +++ b/Mage.Sets/src/mage/cards/k/KotoseTheSilentSpider.java @@ -170,7 +170,7 @@ class KotoseTheSilentSpiderWatcher extends Watcher { morMap.values().removeIf(Set::isEmpty); return; } - if (event.getType() != GameEvent.EventType.SPELL_CAST || event.getAdditionalReference() == null) { + if (event.getType() != GameEvent.EventType.SPELL_CAST || event.getApprovingObject() == null) { return; } Spell spell = game.getSpell(event.getTargetId()); @@ -178,7 +178,7 @@ class KotoseTheSilentSpiderWatcher extends Watcher { return; } morMap.getOrDefault( - event.getAdditionalReference().getApprovingMageObjectReference(), Collections.emptySet() + event.getApprovingObject().getApprovingMageObjectReference(), Collections.emptySet() ).removeIf(set -> set .stream() .anyMatch(mor -> mor.getSourceId().equals(spell.getMainCard().getId()) diff --git a/Mage.Sets/src/mage/cards/l/LathielTheBounteousDawn.java b/Mage.Sets/src/mage/cards/l/LathielTheBounteousDawn.java index f78d7464be6..47bf24fade8 100644 --- a/Mage.Sets/src/mage/cards/l/LathielTheBounteousDawn.java +++ b/Mage.Sets/src/mage/cards/l/LathielTheBounteousDawn.java @@ -45,7 +45,7 @@ public final class LathielTheBounteousDawn extends CardImpl { // At the beginning of each end step, if you gained life this turn, distribute up to that many +1/+1 counters among any number of other target creatures. Ability ability = new ConditionalInterveningIfTriggeredAbility( new BeginningOfEndStepTriggeredAbility(TargetController.ANY, new DistributeCountersEffect( - CounterType.P1P1, 1, false, "" + 1, "" ), false), condition, "At the beginning of each end step, if you gained life this turn, " + "distribute up to that many +1/+1 counters among any number of other target creatures." diff --git a/Mage.Sets/src/mage/cards/l/LeechFanatic.java b/Mage.Sets/src/mage/cards/l/LeechFanatic.java index dd4f73ffbd0..70c1eb1097a 100644 --- a/Mage.Sets/src/mage/cards/l/LeechFanatic.java +++ b/Mage.Sets/src/mage/cards/l/LeechFanatic.java @@ -31,7 +31,7 @@ public final class LeechFanatic extends CardImpl { // As long as it's your turn, Leech Fanatic has lifelink. this.addAbility(new SimpleStaticAbility(new ConditionalContinuousEffect( new GainAbilitySourceEffect(LifelinkAbility.getInstance(), Duration.WhileOnBattlefield), - MyTurnCondition.instance, "as long as it's your turn, {this} has lifelink" + MyTurnCondition.instance, "during your turn, {this} has lifelink" )).addHint(MyTurnHint.instance)); } diff --git a/Mage.Sets/src/mage/cards/l/LobeliaDefenderOfBagEnd.java b/Mage.Sets/src/mage/cards/l/LobeliaDefenderOfBagEnd.java index fd540af7c71..866c5237622 100644 --- a/Mage.Sets/src/mage/cards/l/LobeliaDefenderOfBagEnd.java +++ b/Mage.Sets/src/mage/cards/l/LobeliaDefenderOfBagEnd.java @@ -222,10 +222,10 @@ class LobeliaDefenderOfBagEndWatcher extends Watcher { @Override public void watch(GameEvent event, Game game) { if (event.getType() == GameEvent.EventType.SPELL_CAST || event.getType() == GameEvent.EventType.PLAY_LAND) { - if (event.getAdditionalReference() == null) { + if (event.getApprovingObject() == null) { return; } - morMap.computeIfAbsent(event.getAdditionalReference().getApprovingMageObjectReference(), m -> new HashMap<>()) + morMap.computeIfAbsent(event.getApprovingObject().getApprovingMageObjectReference(), m -> new HashMap<>()) .compute(event.getPlayerId(), (u, i) -> i == null ? 0 : Integer.sum(i, -1)); } } diff --git a/Mage.Sets/src/mage/cards/l/LuminousBroodmoth.java b/Mage.Sets/src/mage/cards/l/LuminousBroodmoth.java index 8da7d9ba86d..916f03d6777 100644 --- a/Mage.Sets/src/mage/cards/l/LuminousBroodmoth.java +++ b/Mage.Sets/src/mage/cards/l/LuminousBroodmoth.java @@ -1,6 +1,7 @@ package mage.cards.l; import mage.MageInt; +import mage.MageObject; import mage.abilities.Ability; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.effects.OneShotEffect; @@ -56,6 +57,7 @@ class LuminousBroodmothTriggeredAbility extends TriggeredAbilityImpl { LuminousBroodmothTriggeredAbility() { super(Zone.BATTLEFIELD, new LuminousBroodmothEffect(), false); + setLeavesTheBattlefieldTrigger(true); } private LuminousBroodmothTriggeredAbility(final LuminousBroodmothTriggeredAbility ability) { @@ -97,6 +99,11 @@ class LuminousBroodmothTriggeredAbility extends TriggeredAbilityImpl { return "Whenever a creature you control without flying dies, " + "return it to the battlefield under its owner's control with a flying counter on it."; } + + @Override + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); + } } class LuminousBroodmothEffect extends OneShotEffect { diff --git a/Mage.Sets/src/mage/cards/l/LyndeCheerfulTormentor.java b/Mage.Sets/src/mage/cards/l/LyndeCheerfulTormentor.java index 3366ed15c9b..4bb11c18f64 100644 --- a/Mage.Sets/src/mage/cards/l/LyndeCheerfulTormentor.java +++ b/Mage.Sets/src/mage/cards/l/LyndeCheerfulTormentor.java @@ -70,6 +70,7 @@ class LyndeCheerfulTormentorCurseDiesTriggeredAbility extends TriggeredAbilityIm new LyndeCheerfulTormentorReturnCurseEffect() ) )); + setLeavesTheBattlefieldTrigger(true); } private LyndeCheerfulTormentorCurseDiesTriggeredAbility(final LyndeCheerfulTormentorCurseDiesTriggeredAbility ability) { @@ -102,8 +103,8 @@ class LyndeCheerfulTormentorCurseDiesTriggeredAbility extends TriggeredAbilityIm } @Override - public boolean isInUseableZone(Game game, MageObject source, GameEvent event) { - return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, event, game); + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); } @Override diff --git a/Mage.Sets/src/mage/cards/m/MaestrosAscendancy.java b/Mage.Sets/src/mage/cards/m/MaestrosAscendancy.java index 06518125704..a4e8a5a4ab1 100644 --- a/Mage.Sets/src/mage/cards/m/MaestrosAscendancy.java +++ b/Mage.Sets/src/mage/cards/m/MaestrosAscendancy.java @@ -148,14 +148,14 @@ class MaestrosAscendancyWatcher extends Watcher { public void watch(GameEvent event, Game game) { if (event.getType() == GameEvent.EventType.SPELL_CAST && event.hasApprovingIdentifier(MageIdentifier.MaestrosAscendencyAlternateCast) - && event.getAdditionalReference() != null) { + && event.getApprovingObject() != null) { playerMap.computeIfAbsent( - event.getAdditionalReference() + event.getApprovingObject() .getApprovingMageObjectReference(), x -> new HashSet<>() ).add(event.getPlayerId()); spellMap.computeIfAbsent( - event.getAdditionalReference() + event.getApprovingObject() .getApprovingMageObjectReference(), x -> new HashSet<>() ).add(new MageObjectReference(event.getTargetId(), game)); diff --git a/Mage.Sets/src/mage/cards/m/MagusOfTheBridge.java b/Mage.Sets/src/mage/cards/m/MagusOfTheBridge.java index 9f28c451fd0..81fc10fb684 100644 --- a/Mage.Sets/src/mage/cards/m/MagusOfTheBridge.java +++ b/Mage.Sets/src/mage/cards/m/MagusOfTheBridge.java @@ -2,6 +2,7 @@ package mage.cards.m; import java.util.UUID; import mage.MageInt; +import mage.MageObject; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.common.PutIntoGraveFromBattlefieldAllTriggeredAbility; import mage.abilities.effects.common.CreateTokenEffect; @@ -59,6 +60,7 @@ class MagusOfTheBridgeTriggeredAbility extends TriggeredAbilityImpl { public MagusOfTheBridgeTriggeredAbility() { super(Zone.BATTLEFIELD, new ExileSourceEffect()); setTriggerPhrase("When a creature is put into an opponent's graveyard from the battlefield, "); + setLeavesTheBattlefieldTrigger(true); } private MagusOfTheBridgeTriggeredAbility(final MagusOfTheBridgeTriggeredAbility ability) { @@ -86,4 +88,9 @@ class MagusOfTheBridgeTriggeredAbility extends TriggeredAbilityImpl { } return false; } + + @Override + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); + } } diff --git a/Mage.Sets/src/mage/cards/m/MarchOfRecklessJoy.java b/Mage.Sets/src/mage/cards/m/MarchOfRecklessJoy.java index 78d3dd1e562..f93dee68cf2 100644 --- a/Mage.Sets/src/mage/cards/m/MarchOfRecklessJoy.java +++ b/Mage.Sets/src/mage/cards/m/MarchOfRecklessJoy.java @@ -117,11 +117,11 @@ class MarchOfRecklessJoyWatcher extends Watcher { @Override public void watch(GameEvent event, Game game) { - if (event.getType() != GameEvent.EventType.SPELL_CAST || event.getAdditionalReference() == null) { + if (event.getType() != GameEvent.EventType.SPELL_CAST || event.getApprovingObject() == null) { return; } morMap.compute(event - .getAdditionalReference() + .getApprovingObject() .getApprovingMageObjectReference(), CardUtil::setOrIncrementValue ); diff --git a/Mage.Sets/src/mage/cards/m/MarchesaTheBlackRose.java b/Mage.Sets/src/mage/cards/m/MarchesaTheBlackRose.java index e4358d79f2b..a2593ee86a9 100644 --- a/Mage.Sets/src/mage/cards/m/MarchesaTheBlackRose.java +++ b/Mage.Sets/src/mage/cards/m/MarchesaTheBlackRose.java @@ -3,6 +3,7 @@ package mage.cards.m; import java.util.UUID; import mage.MageInt; +import mage.MageObject; import mage.abilities.Ability; import mage.abilities.DelayedTriggeredAbility; import mage.abilities.TriggeredAbilityImpl; @@ -67,6 +68,7 @@ class MarchesaTheBlackRoseTriggeredAbility extends TriggeredAbilityImpl { public MarchesaTheBlackRoseTriggeredAbility() { super(Zone.BATTLEFIELD, new MarchesaTheBlackRoseEffect()); setTriggerPhrase("Whenever a creature you control with a +1/+1 counter on it dies, "); + setLeavesTheBattlefieldTrigger(true); } private MarchesaTheBlackRoseTriggeredAbility(final MarchesaTheBlackRoseTriggeredAbility ability) { @@ -100,6 +102,11 @@ class MarchesaTheBlackRoseTriggeredAbility extends TriggeredAbilityImpl { } return false; } + + @Override + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); + } } class MarchesaTheBlackRoseEffect extends OneShotEffect { diff --git a/Mage.Sets/src/mage/cards/m/MariTheKillingQuill.java b/Mage.Sets/src/mage/cards/m/MariTheKillingQuill.java index 70f9e008ffc..be800e48089 100644 --- a/Mage.Sets/src/mage/cards/m/MariTheKillingQuill.java +++ b/Mage.Sets/src/mage/cards/m/MariTheKillingQuill.java @@ -1,6 +1,7 @@ package mage.cards.m; import mage.MageInt; +import mage.MageObject; import mage.abilities.Ability; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.common.DealsCombatDamageToAPlayerTriggeredAbility; @@ -165,6 +166,7 @@ class MariTheKillingQuillCreatureDiesAbility extends TriggeredAbilityImpl { public MariTheKillingQuillCreatureDiesAbility() { super(Zone.BATTLEFIELD, new MariTheKillingQuillExileCreatureEffect(), false); + setLeavesTheBattlefieldTrigger(true); } private MariTheKillingQuillCreatureDiesAbility(final MariTheKillingQuillCreatureDiesAbility ability) { @@ -202,6 +204,11 @@ class MariTheKillingQuillCreatureDiesAbility extends TriggeredAbilityImpl { public String getRule() { return "Whenever a creature an opponent controls dies, exile it with a hit counter on it."; } + + @Override + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); + } } class MariTheKillingQuillExileCreatureEffect extends OneShotEffect { diff --git a/Mage.Sets/src/mage/cards/m/MartyrsBond.java b/Mage.Sets/src/mage/cards/m/MartyrsBond.java index 0eb4d9d7ad0..7183de40072 100644 --- a/Mage.Sets/src/mage/cards/m/MartyrsBond.java +++ b/Mage.Sets/src/mage/cards/m/MartyrsBond.java @@ -1,5 +1,6 @@ package mage.cards.m; +import mage.MageObject; import mage.abilities.Ability; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.effects.Effect; @@ -50,6 +51,7 @@ class MartyrsBondTriggeredAbility extends TriggeredAbilityImpl { public MartyrsBondTriggeredAbility() { super(Zone.BATTLEFIELD, new MartyrsBondEffect()); + setLeavesTheBattlefieldTrigger(true); } private MartyrsBondTriggeredAbility(final MartyrsBondTriggeredAbility ability) { @@ -88,6 +90,10 @@ class MartyrsBondTriggeredAbility extends TriggeredAbilityImpl { return "Whenever {this} or another nonland permanent you control is put into a graveyard from the battlefield, each opponent sacrifices a permanent that shares a card type with it."; } + @Override + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); + } } class MartyrsBondEffect extends OneShotEffect { diff --git a/Mage.Sets/src/mage/cards/m/MassacreGirl.java b/Mage.Sets/src/mage/cards/m/MassacreGirl.java index 219837f3e6a..fa5de8db62e 100644 --- a/Mage.Sets/src/mage/cards/m/MassacreGirl.java +++ b/Mage.Sets/src/mage/cards/m/MassacreGirl.java @@ -1,8 +1,10 @@ package mage.cards.m; import mage.MageInt; +import mage.MageObject; import mage.abilities.Ability; import mage.abilities.DelayedTriggeredAbility; +import mage.abilities.TriggeredAbilityImpl; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.continuous.BoostAllEffect; @@ -77,6 +79,7 @@ class MassacreGirlDelayedTriggeredAbility extends DelayedTriggeredAbility { MassacreGirlDelayedTriggeredAbility() { super(new BoostAllEffect(-1, -1, Duration.EndOfTurn, true), Duration.EndOfTurn, false); + setLeavesTheBattlefieldTrigger(true); } private MassacreGirlDelayedTriggeredAbility(final MassacreGirlDelayedTriggeredAbility ability) { @@ -103,4 +106,9 @@ class MassacreGirlDelayedTriggeredAbility extends DelayedTriggeredAbility { public String getRule() { return "Whenever a creature dies this turn, each creature other than {this} gets -1/-1 until end of turn"; } + + @Override + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); + } } \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/m/MassacreWurm.java b/Mage.Sets/src/mage/cards/m/MassacreWurm.java index a90806c0799..ef0fde681c5 100644 --- a/Mage.Sets/src/mage/cards/m/MassacreWurm.java +++ b/Mage.Sets/src/mage/cards/m/MassacreWurm.java @@ -2,6 +2,7 @@ package mage.cards.m; import java.util.UUID; import mage.MageInt; +import mage.MageObject; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.effects.Effect; @@ -52,6 +53,7 @@ class MassacreWurmTriggeredAbility extends TriggeredAbilityImpl { MassacreWurmTriggeredAbility() { super(Zone.BATTLEFIELD, new LoseLifeTargetEffect(2)); setTriggerPhrase("Whenever a creature an opponent controls dies, "); + setLeavesTheBattlefieldTrigger(true); } private MassacreWurmTriggeredAbility(final MassacreWurmTriggeredAbility ability) { @@ -81,4 +83,9 @@ class MassacreWurmTriggeredAbility extends TriggeredAbilityImpl { } return false; } + + @Override + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); + } } diff --git a/Mage.Sets/src/mage/cards/m/MeathookMassacreII.java b/Mage.Sets/src/mage/cards/m/MeathookMassacreII.java new file mode 100644 index 00000000000..c26dab23580 --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/MeathookMassacreII.java @@ -0,0 +1,114 @@ +package mage.cards.m; + +import mage.abilities.Ability; +import mage.abilities.common.DiesCreatureTriggeredAbility; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.costs.Cost; +import mage.abilities.costs.common.PayLifeCost; +import mage.abilities.dynamicvalue.common.GetXValue; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.DoIfCostPaid; +import mage.abilities.effects.common.ReturnFromGraveyardToBattlefieldWithCounterTargetEffect; +import mage.abilities.effects.common.SacrificeAllEffect; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SuperType; +import mage.counters.CounterType; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.players.Player; +import mage.target.targetpointer.FixedTarget; + +import java.util.UUID; + +/** + * @author Cguy7777 + */ +public final class MeathookMassacreII extends CardImpl { + + public MeathookMassacreII(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{X}{X}{B}{B}{B}{B}"); + + this.supertype.add(SuperType.LEGENDARY); + + // When Meathook Massacre II enters, each player sacrifices X creatures of their choice. + this.addAbility(new EntersBattlefieldTriggeredAbility( + new SacrificeAllEffect(GetXValue.instance, StaticFilters.FILTER_PERMANENT_CREATURES))); + + // Whenever a creature you control dies, you may pay 3 life. + // If you do, return that card under your control with a finality counter on it. + this.addAbility(new DiesCreatureTriggeredAbility( + new DoIfCostPaid( + new ReturnFromGraveyardToBattlefieldWithCounterTargetEffect( + CounterType.FINALITY.createInstance()) + .setText("return that card under your control with a finality counter on it"), + new PayLifeCost(3)), + false, + StaticFilters.FILTER_CONTROLLED_A_CREATURE, + true)); + + // Whenever a creature an opponent controls dies, they may pay 3 life. + // If they don't, return that card under your control with a finality counter on it. + this.addAbility(new DiesCreatureTriggeredAbility( + new MeathookMassacreIIEffect(), + false, + StaticFilters.FILTER_OPPONENTS_PERMANENT_A_CREATURE, + true)); + } + + private MeathookMassacreII(final MeathookMassacreII card) { + super(card); + } + + @Override + public MeathookMassacreII copy() { + return new MeathookMassacreII(this); + } +} + +class MeathookMassacreIIEffect extends OneShotEffect { + + MeathookMassacreIIEffect() { + super(Outcome.PutCreatureInPlay); + staticText = "they may pay 3 life. " + + "If they don't, return that card under your control with a finality counter on it"; + } + + private MeathookMassacreIIEffect(final MeathookMassacreIIEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = getTargetPointer().getControllerOfFirstTargetOrLKI(game, source); + if (player == null) { + return false; + } + + // Whenever a creature an opponent controls dies, they may pay 3 life. + // If they don't, return that card under your control with a finality counter on it. + Cost cost = new PayLifeCost(3); + if (player.chooseUse(Outcome.Detriment, "Pay 3 life to prevent this effect?", source, game) + && cost.pay(source, game, source, player.getId(), true)) { + return true; + } + + // If a token died, card will be null and nothing will be returned to the battlefield + Card card = game.getCard(getTargetPointer().getFirst(game, source)); + if (card == null) { + return true; + } + new ReturnFromGraveyardToBattlefieldWithCounterTargetEffect(CounterType.FINALITY.createInstance()) + .setTargetPointer(new FixedTarget(card, game)) + .apply(game, source); + return true; + } + + @Override + public MeathookMassacreIIEffect copy() { + return new MeathookMassacreIIEffect(this); + } +} diff --git a/Mage.Sets/src/mage/cards/m/MillicentRestlessRevenant.java b/Mage.Sets/src/mage/cards/m/MillicentRestlessRevenant.java index 25571adba98..a69602dea49 100644 --- a/Mage.Sets/src/mage/cards/m/MillicentRestlessRevenant.java +++ b/Mage.Sets/src/mage/cards/m/MillicentRestlessRevenant.java @@ -79,6 +79,7 @@ class MillicentRestlessRevenantTriggeredAbility extends TriggeredAbilityImpl { MillicentRestlessRevenantTriggeredAbility() { super(Zone.BATTLEFIELD, new CreateTokenEffect(new SpiritWhiteToken())); + setLeavesTheBattlefieldTrigger(true); } private MillicentRestlessRevenantTriggeredAbility(final MillicentRestlessRevenantTriggeredAbility ability) { @@ -124,8 +125,12 @@ class MillicentRestlessRevenantTriggeredAbility extends TriggeredAbilityImpl { } @Override - public boolean isInUseableZone(Game game, MageObject source, GameEvent event) { - return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, event, game); + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + if (event.getType() == GameEvent.EventType.ZONE_CHANGE) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); + } else { + return super.isInUseableZone(game, sourceObject, event); + } } @Override diff --git a/Mage.Sets/src/mage/cards/m/MimicVat.java b/Mage.Sets/src/mage/cards/m/MimicVat.java index 984c13b86c0..8d7a377ec93 100644 --- a/Mage.Sets/src/mage/cards/m/MimicVat.java +++ b/Mage.Sets/src/mage/cards/m/MimicVat.java @@ -4,6 +4,8 @@ package mage.cards.m; import java.util.HashSet; import java.util.Set; import java.util.UUID; + +import mage.MageObject; import mage.abilities.Ability; import mage.abilities.DelayedTriggeredAbility; import mage.abilities.TriggeredAbilityImpl; @@ -60,6 +62,7 @@ class MimicVatTriggeredAbility extends TriggeredAbilityImpl { MimicVatTriggeredAbility() { super(Zone.BATTLEFIELD, new MimicVatEffect(), true); + setLeavesTheBattlefieldTrigger(true); } private MimicVatTriggeredAbility(final MimicVatTriggeredAbility ability) { @@ -105,6 +108,11 @@ class MimicVatTriggeredAbility extends TriggeredAbilityImpl { public String getRule() { return AbilityWord.IMPRINT.formatWord() + "Whenever a nontoken creature dies, you may exile that card. If you do, return each other card exiled with {this} to its owner's graveyard."; } + + @Override + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); + } } class MimicVatEffect extends OneShotEffect { diff --git a/Mage.Sets/src/mage/cards/m/MiriamHerdWhisperer.java b/Mage.Sets/src/mage/cards/m/MiriamHerdWhisperer.java index 55a388ae9f8..bae4567b8ed 100644 --- a/Mage.Sets/src/mage/cards/m/MiriamHerdWhisperer.java +++ b/Mage.Sets/src/mage/cards/m/MiriamHerdWhisperer.java @@ -47,7 +47,7 @@ public final class MiriamHerdWhisperer extends CardImpl { // As long as it's your turn, Mounts and Vehicles you control have hexproof. this.addAbility(new SimpleStaticAbility(new ConditionalContinuousEffect( new GainAbilityAllEffect(HexproofAbility.getInstance(), Duration.WhileOnBattlefield, filter), - MyTurnCondition.instance, "as long as it's your turn, Mounts and Vehicles you control have hexproof" + MyTurnCondition.instance, "during your turn, Mounts and Vehicles you control have hexproof" ))); // Whenever a Mount or Vehicle you control attacks, put a +1/+1 counter on it. diff --git a/Mage.Sets/src/mage/cards/m/MiteOverseer.java b/Mage.Sets/src/mage/cards/m/MiteOverseer.java index b1897aa6e67..23daae87013 100644 --- a/Mage.Sets/src/mage/cards/m/MiteOverseer.java +++ b/Mage.Sets/src/mage/cards/m/MiteOverseer.java @@ -41,7 +41,7 @@ public final class MiteOverseer extends CardImpl { // As long as it's your turn, creature tokens you control get +1/+0 and have first strike. Ability ability = new SimpleStaticAbility(new ConditionalContinuousEffect(new BoostControlledEffect( 1, 0, Duration.WhileOnBattlefield, StaticFilters.FILTER_CREATURE_TOKEN - ), MyTurnCondition.instance, "as long as it's your turn, creature tokens you control get +1/+0")); + ), MyTurnCondition.instance, "during your turn, creature tokens you control get +1/+0")); ability.addEffect(new ConditionalContinuousEffect(new GainAbilityControlledEffect( FirstStrikeAbility.getInstance(), Duration.WhileOnBattlefield, StaticFilters.FILTER_CREATURE_TOKEN ), MyTurnCondition.instance, "and have first strike")); diff --git a/Mage.Sets/src/mage/cards/m/MolderBeast.java b/Mage.Sets/src/mage/cards/m/MolderBeast.java index 1346b665656..696f0ad2c06 100644 --- a/Mage.Sets/src/mage/cards/m/MolderBeast.java +++ b/Mage.Sets/src/mage/cards/m/MolderBeast.java @@ -3,6 +3,7 @@ package mage.cards.m; import java.util.UUID; import mage.MageInt; +import mage.MageObject; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.effects.common.continuous.BoostSourceEffect; import mage.abilities.keyword.TrampleAbility; @@ -47,6 +48,7 @@ class MolderBeastTriggeredAbility extends TriggeredAbilityImpl { public MolderBeastTriggeredAbility() { super(Zone.BATTLEFIELD, new BoostSourceEffect(2, 0, Duration.EndOfTurn), false); + setLeavesTheBattlefieldTrigger(true); } private MolderBeastTriggeredAbility(final MolderBeastTriggeredAbility ability) { @@ -74,4 +76,9 @@ class MolderBeastTriggeredAbility extends TriggeredAbilityImpl { public MolderBeastTriggeredAbility copy() { return new MolderBeastTriggeredAbility(this); } + + @Override + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); + } } \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/m/MoxPoison.java b/Mage.Sets/src/mage/cards/m/MoxPoison.java new file mode 100644 index 00000000000..8955f0120a8 --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/MoxPoison.java @@ -0,0 +1,38 @@ +package mage.cards.m; + +import mage.abilities.Ability; +import mage.abilities.effects.common.counter.AddCountersPlayersEffect; +import mage.abilities.mana.AnyColorManaAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.TargetController; +import mage.counters.CounterType; + +import java.util.UUID; + +/** + * @author PurpleCrowbar + */ +public final class MoxPoison extends CardImpl { + + public MoxPoison(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{0}"); + + // {T}: Add one mana of any color. You get two poison counters. + Ability ability = new AnyColorManaAbility(); + ability.addEffect(new AddCountersPlayersEffect( + CounterType.POISON.createInstance(2), TargetController.YOU + )); + this.addAbility(ability); + } + + private MoxPoison(final MoxPoison card) { + super(card); + } + + @Override + public MoxPoison copy() { + return new MoxPoison(this); + } +} diff --git a/Mage.Sets/src/mage/cards/m/MuldrothaTheGravetide.java b/Mage.Sets/src/mage/cards/m/MuldrothaTheGravetide.java index 25a974f5b09..f9ec0bc9b19 100644 --- a/Mage.Sets/src/mage/cards/m/MuldrothaTheGravetide.java +++ b/Mage.Sets/src/mage/cards/m/MuldrothaTheGravetide.java @@ -146,8 +146,8 @@ class MuldrothaTheGravetideWatcher extends Watcher { private void addPermanentTypes(GameEvent event, Card mageObject, Game game) { if (mageObject != null - && event.getAdditionalReference() != null - && MageIdentifier.MuldrothaTheGravetideWatcher.equals(event.getAdditionalReference().getApprovingAbility().getIdentifier())) { + && event.getApprovingObject() != null + && MageIdentifier.MuldrothaTheGravetideWatcher.equals(event.getApprovingObject().getApprovingAbility().getIdentifier())) { UUID playerId = null; if (mageObject instanceof Spell) { playerId = ((Spell) mageObject).getControllerId(); @@ -155,10 +155,10 @@ class MuldrothaTheGravetideWatcher extends Watcher { playerId = ((Permanent) mageObject).getControllerId(); } if (playerId != null) { - Set permanentTypes = sourcePlayedPermanentTypes.get(event.getAdditionalReference().getApprovingMageObjectReference()); + Set permanentTypes = sourcePlayedPermanentTypes.get(event.getApprovingObject().getApprovingMageObjectReference()); if (permanentTypes == null) { permanentTypes = EnumSet.noneOf(CardType.class); - sourcePlayedPermanentTypes.put(event.getAdditionalReference().getApprovingMageObjectReference(), permanentTypes); + sourcePlayedPermanentTypes.put(event.getApprovingObject().getApprovingMageObjectReference(), permanentTypes); } Set typesNotCast = EnumSet.noneOf(CardType.class); for (CardType cardType : mageObject.getCardType(game)) { diff --git a/Mage.Sets/src/mage/cards/m/MycoidShepherd.java b/Mage.Sets/src/mage/cards/m/MycoidShepherd.java index 9afb6ae5ce9..51f3d1f815b 100644 --- a/Mage.Sets/src/mage/cards/m/MycoidShepherd.java +++ b/Mage.Sets/src/mage/cards/m/MycoidShepherd.java @@ -51,6 +51,7 @@ class MycoidShepherdTriggeredAbility extends TriggeredAbilityImpl { public MycoidShepherdTriggeredAbility() { super(Zone.BATTLEFIELD, new GainLifeEffect(5), true); + setLeavesTheBattlefieldTrigger(true); } private MycoidShepherdTriggeredAbility(final MycoidShepherdTriggeredAbility ability) { @@ -91,4 +92,9 @@ class MycoidShepherdTriggeredAbility extends TriggeredAbilityImpl { public MycoidShepherdTriggeredAbility copy() { return new MycoidShepherdTriggeredAbility(this); } + + @Override + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); + } } \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/m/MyojinOfToweringMight.java b/Mage.Sets/src/mage/cards/m/MyojinOfToweringMight.java index 22ebbcd5ea3..7191e76fad9 100644 --- a/Mage.Sets/src/mage/cards/m/MyojinOfToweringMight.java +++ b/Mage.Sets/src/mage/cards/m/MyojinOfToweringMight.java @@ -45,7 +45,7 @@ public final class MyojinOfToweringMight extends CardImpl { // Remove an indestructible counter from Myojin of Towering Might: Distribute eight +1/+1 counters among any number of target creatures you control. They gain trample until end of turn. Ability ability = new SimpleActivatedAbility(new DistributeCountersEffect( - CounterType.P1P1, 8, false, + 8, "any number of target creatures you control" ), new RemoveCountersSourceCost(CounterType.INDESTRUCTIBLE.createInstance())); ability.addEffect(new GainAbilityTargetEffect( diff --git a/Mage.Sets/src/mage/cards/m/MyrkulsEdict.java b/Mage.Sets/src/mage/cards/m/MyrkulsEdict.java index 6ed2743b0b7..7bc83dd5b7b 100644 --- a/Mage.Sets/src/mage/cards/m/MyrkulsEdict.java +++ b/Mage.Sets/src/mage/cards/m/MyrkulsEdict.java @@ -66,7 +66,7 @@ class MyrkulsEdictEffect extends OneShotEffect { MyrkulsEdictEffect() { super(Outcome.Benefit); - staticText = "choose an opponent. That player sacrifices a creature"; + staticText = "choose an opponent. That player sacrifices a creature of their choice"; } private MyrkulsEdictEffect(final MyrkulsEdictEffect effect) { diff --git a/Mage.Sets/src/mage/cards/n/NarsetEnlightenedExile.java b/Mage.Sets/src/mage/cards/n/NarsetEnlightenedExile.java index c9f1c13b644..bee904dc0dc 100644 --- a/Mage.Sets/src/mage/cards/n/NarsetEnlightenedExile.java +++ b/Mage.Sets/src/mage/cards/n/NarsetEnlightenedExile.java @@ -78,10 +78,9 @@ enum NarsetEnlightenedExilePredicate implements ObjectSourcePlayerPredicate input, Game game) { return Optional .ofNullable(input.getSource().getSourcePermanentOrLKI(game)) - .filter(Objects::nonNull) .map(MageObject::getPower) .map(MageInt::getValue) .map(p -> input.getObject().getManaValue() < p) .orElse(false); } -} \ No newline at end of file +} diff --git a/Mage.Sets/src/mage/cards/n/NashiMoonSagesScion.java b/Mage.Sets/src/mage/cards/n/NashiMoonSagesScion.java index 1c78fd0ffde..95eebdf01f9 100644 --- a/Mage.Sets/src/mage/cards/n/NashiMoonSagesScion.java +++ b/Mage.Sets/src/mage/cards/n/NashiMoonSagesScion.java @@ -121,7 +121,7 @@ class NashiMoonSagesScionWatcher extends Watcher { morMap.values().removeIf(Set::isEmpty); return; } - if (event.getType() != GameEvent.EventType.SPELL_CAST || event.getAdditionalReference() == null) { + if (event.getType() != GameEvent.EventType.SPELL_CAST || event.getApprovingObject() == null) { return; } Spell spell = game.getSpell(event.getTargetId()); @@ -129,7 +129,7 @@ class NashiMoonSagesScionWatcher extends Watcher { return; } morMap.getOrDefault( - event.getAdditionalReference().getApprovingMageObjectReference(), Collections.emptySet() + event.getApprovingObject().getApprovingMageObjectReference(), Collections.emptySet() ).removeIf(set -> set .stream() .anyMatch(mor -> mor.getSourceId().equals(spell.getMainCard().getId()) diff --git a/Mage.Sets/src/mage/cards/n/Necroskitter.java b/Mage.Sets/src/mage/cards/n/Necroskitter.java index c68159e3503..dc9742b15c4 100644 --- a/Mage.Sets/src/mage/cards/n/Necroskitter.java +++ b/Mage.Sets/src/mage/cards/n/Necroskitter.java @@ -3,6 +3,7 @@ package mage.cards.n; import java.util.UUID; import mage.MageInt; +import mage.MageObject; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.effects.Effect; import mage.abilities.effects.common.ReturnToBattlefieldUnderYourControlTargetEffect; @@ -55,6 +56,7 @@ class NecroskitterTriggeredAbility extends TriggeredAbilityImpl { public NecroskitterTriggeredAbility() { super(Zone.BATTLEFIELD, new ReturnToBattlefieldUnderYourControlTargetEffect(), true); + setLeavesTheBattlefieldTrigger(true); } private NecroskitterTriggeredAbility(final NecroskitterTriggeredAbility ability) { @@ -92,4 +94,9 @@ class NecroskitterTriggeredAbility extends TriggeredAbilityImpl { public String getRule() { return "Whenever a creature an opponent controls with a -1/-1 counter on it dies, you may return that card to the battlefield under your control."; } + + @Override + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); + } } diff --git a/Mage.Sets/src/mage/cards/n/NetherTraitor.java b/Mage.Sets/src/mage/cards/n/NetherTraitor.java index a4d9f339b6c..3b4f04d605d 100644 --- a/Mage.Sets/src/mage/cards/n/NetherTraitor.java +++ b/Mage.Sets/src/mage/cards/n/NetherTraitor.java @@ -3,6 +3,7 @@ package mage.cards.n; import java.util.UUID; import mage.MageInt; +import mage.MageObject; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.costs.mana.ColoredManaCost; import mage.abilities.effects.common.DoIfCostPaid; @@ -55,6 +56,7 @@ class NetherTraitorTriggeredAbility extends TriggeredAbilityImpl { NetherTraitorTriggeredAbility(){ super(Zone.GRAVEYARD, new DoIfCostPaid(new ReturnSourceFromGraveyardToBattlefieldEffect(), new ColoredManaCost(ColoredManaSymbol.B))); + setLeavesTheBattlefieldTrigger(true); } private NetherTraitorTriggeredAbility(final NetherTraitorTriggeredAbility ability) { @@ -94,4 +96,9 @@ class NetherTraitorTriggeredAbility extends TriggeredAbilityImpl { public String getRule() { return "Whenever another creature is put into your graveyard from the battlefield, you may pay {B}. If you do, return {this} from your graveyard to the battlefield."; } + + @Override + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); + } } diff --git a/Mage.Sets/src/mage/cards/n/NimDeathmantle.java b/Mage.Sets/src/mage/cards/n/NimDeathmantle.java index 3dcefa47b45..3dca6eb6eeb 100644 --- a/Mage.Sets/src/mage/cards/n/NimDeathmantle.java +++ b/Mage.Sets/src/mage/cards/n/NimDeathmantle.java @@ -1,5 +1,6 @@ package mage.cards.n; +import mage.MageObject; import mage.ObjectColor; import mage.abilities.Ability; import mage.abilities.TriggeredAbilityImpl; @@ -72,6 +73,7 @@ class NimDeathmantleTriggeredAbility extends TriggeredAbilityImpl { NimDeathmantleTriggeredAbility() { super(Zone.BATTLEFIELD, new NimDeathmantleEffect(), false); + setLeavesTheBattlefieldTrigger(true); } private NimDeathmantleTriggeredAbility(final NimDeathmantleTriggeredAbility ability) { @@ -108,6 +110,11 @@ class NimDeathmantleTriggeredAbility extends TriggeredAbilityImpl { public String getRule() { return "Whenever a nontoken creature is put into your graveyard from the battlefield, you may pay {4}. If you do, return that card to the battlefield and attach {this} to it."; } + + @Override + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); + } } class NimDeathmantleEffect extends OneShotEffect { diff --git a/Mage.Sets/src/mage/cards/n/NumaJoragaChieftain.java b/Mage.Sets/src/mage/cards/n/NumaJoragaChieftain.java index 8f128de6df0..16dc5790cdd 100644 --- a/Mage.Sets/src/mage/cards/n/NumaJoragaChieftain.java +++ b/Mage.Sets/src/mage/cards/n/NumaJoragaChieftain.java @@ -88,7 +88,7 @@ class NumaJoragaChieftainEffect extends OneShotEffect { return false; } ReflexiveTriggeredAbility ability = new ReflexiveTriggeredAbility( - new DistributeCountersEffect(CounterType.P1P1, costX, false, ""), + new DistributeCountersEffect(costX, ""), false, "distribute " + costX + " +1/+1 counters among any number of target Elves" ); ability.addTarget(new TargetCreaturePermanentAmount(costX, filter)); diff --git a/Mage.Sets/src/mage/cards/o/OmnivorousFlytrap.java b/Mage.Sets/src/mage/cards/o/OmnivorousFlytrap.java new file mode 100644 index 00000000000..7a5c2f998f8 --- /dev/null +++ b/Mage.Sets/src/mage/cards/o/OmnivorousFlytrap.java @@ -0,0 +1,110 @@ +package mage.cards.o; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldOrAttacksSourceTriggeredAbility; +import mage.abilities.condition.IntCompareCondition; +import mage.abilities.condition.common.DeliriumCondition; +import mage.abilities.decorator.ConditionalOneShotEffect; +import mage.abilities.dynamicvalue.common.CardTypesInGraveyardCount; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.counter.DistributeCountersEffect; +import mage.abilities.hint.common.CardTypesInGraveyardHint; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.counters.CounterType; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.common.TargetCreaturePermanentAmount; + +import java.util.UUID; + +/** + * @author Cguy7777 + */ +public final class OmnivorousFlytrap extends CardImpl { + + public OmnivorousFlytrap(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{G}"); + + this.subtype.add(SubType.PLANT); + this.power = new MageInt(2); + this.toughness = new MageInt(4); + + // Delirium -- Whenever Omnivorous Flytrap enters or attacks, if there are four or more card types among cards in your graveyard, distribute two +1/+1 counters among one or two target creatures. Then if there are six or more card types among cards in your graveyard, double the number of +1/+1 counters on those creatures. + Ability ability = new EntersBattlefieldOrAttacksSourceTriggeredAbility( + new DistributeCountersEffect(CounterType.P1P1, 2, "one or two target creatures")) + .withInterveningIf(DeliriumCondition.instance); + ability.addEffect(new ConditionalOneShotEffect( + new OmnivorousFlytrapEffect(), + new OmnivorousFlytrapCondition()) + .concatBy("Then")); + ability.addTarget(new TargetCreaturePermanentAmount(2)); + ability.addHint(CardTypesInGraveyardHint.YOU); + this.addAbility(ability.setAbilityWord(AbilityWord.DELIRIUM)); + } + + private OmnivorousFlytrap(final OmnivorousFlytrap card) { + super(card); + } + + @Override + public OmnivorousFlytrap copy() { + return new OmnivorousFlytrap(this); + } +} + +class OmnivorousFlytrapEffect extends OneShotEffect { + + OmnivorousFlytrapEffect() { + super(Outcome.Benefit); + staticText = "double the number of +1/+1 counters on those creatures"; + } + + private OmnivorousFlytrapEffect(final OmnivorousFlytrapEffect effect) { + super(effect); + } + + @Override + public OmnivorousFlytrapEffect copy() { + return new OmnivorousFlytrapEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller == null) { + return false; + } + + for (UUID targetId : getTargetPointer().getTargets(game, source)) { + Permanent permanent = game.getPermanent(targetId); + if (permanent != null) { + int existingCounters = permanent.getCounters(game).getCount(CounterType.P1P1); + if (existingCounters > 0) { + permanent.addCounters(CounterType.P1P1.createInstance(existingCounters), source, game); + } + } + } + return true; + } +} + +class OmnivorousFlytrapCondition extends IntCompareCondition { + + OmnivorousFlytrapCondition() { + super(ComparisonType.OR_GREATER, 6); + } + + @Override + protected int getInputValue(Game game, Ability source) { + return CardTypesInGraveyardCount.YOU.calculate(game, source, null); + } + + @Override + public String toString() { + return "if there are six or more card types among cards in your graveyard"; + } +} diff --git a/Mage.Sets/src/mage/cards/o/OnduKnotmaster.java b/Mage.Sets/src/mage/cards/o/OnduKnotmaster.java index 3fadb3e5c83..421ac8229eb 100644 --- a/Mage.Sets/src/mage/cards/o/OnduKnotmaster.java +++ b/Mage.Sets/src/mage/cards/o/OnduKnotmaster.java @@ -52,7 +52,7 @@ public final class OnduKnotmaster extends AdventureCard { // Distribute two +1/+1 counters among one or two target creatures. this.getSpellCard().getSpellAbility().addEffect( new DistributeCountersEffect( - CounterType.P1P1, 2, false, + 2, "one or two target creatures" ) ); diff --git a/Mage.Sets/src/mage/cards/o/OneWithTheMultiverse.java b/Mage.Sets/src/mage/cards/o/OneWithTheMultiverse.java index dd2780ae3d3..26719137046 100644 --- a/Mage.Sets/src/mage/cards/o/OneWithTheMultiverse.java +++ b/Mage.Sets/src/mage/cards/o/OneWithTheMultiverse.java @@ -136,7 +136,7 @@ class OneWithTheMultiverseWatcher extends Watcher { public void watch(GameEvent event, Game game) { if (event.getType() == GameEvent.EventType.SPELL_CAST && event.hasApprovingIdentifier(MageIdentifier.OneWithTheMultiverseWatcher)) { - usedFrom.add(event.getAdditionalReference().getApprovingMageObjectReference()); + usedFrom.add(event.getApprovingObject().getApprovingMageObjectReference()); } } diff --git a/Mage.Sets/src/mage/cards/o/OrahSkyclaveHierophant.java b/Mage.Sets/src/mage/cards/o/OrahSkyclaveHierophant.java index 4180c056097..96a82a5c3e8 100644 --- a/Mage.Sets/src/mage/cards/o/OrahSkyclaveHierophant.java +++ b/Mage.Sets/src/mage/cards/o/OrahSkyclaveHierophant.java @@ -1,6 +1,7 @@ package mage.cards.o; import mage.MageInt; +import mage.MageObject; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.effects.common.ReturnFromGraveyardToBattlefieldTargetEffect; import mage.abilities.keyword.LifelinkAbility; @@ -51,6 +52,7 @@ class OrahSkyclaveHierophantTriggeredAbility extends TriggeredAbilityImpl { OrahSkyclaveHierophantTriggeredAbility() { super(Zone.BATTLEFIELD, new ReturnFromGraveyardToBattlefieldTargetEffect()); + setLeavesTheBattlefieldTrigger(true); } private OrahSkyclaveHierophantTriggeredAbility(final OrahSkyclaveHierophantTriggeredAbility ability) { @@ -94,4 +96,9 @@ class OrahSkyclaveHierophantTriggeredAbility extends TriggeredAbilityImpl { return "Whenever {this} or another Cleric you control dies, return target Cleric card " + "with lesser mana value from your graveyard to the battlefield."; } + + @Override + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); + } } diff --git a/Mage.Sets/src/mage/cards/p/PainDistributor.java b/Mage.Sets/src/mage/cards/p/PainDistributor.java index cb50f2654e1..85595a0f134 100644 --- a/Mage.Sets/src/mage/cards/p/PainDistributor.java +++ b/Mage.Sets/src/mage/cards/p/PainDistributor.java @@ -103,11 +103,9 @@ class PainDistributorEffect extends OneShotEffect { public boolean apply(Game game, Ability source) { return Optional .ofNullable(getValue("permanentDied")) - .filter(Objects::nonNull) .map(Permanent.class::cast) .map(Controllable::getControllerId) .map(game::getPlayer) - .filter(Objects::nonNull) .map(player -> player.damage(1, source, game) > 0) .orElse(false); } diff --git a/Mage.Sets/src/mage/cards/p/PatronOfTheVein.java b/Mage.Sets/src/mage/cards/p/PatronOfTheVein.java index b4b8c86adb4..7460ec51914 100644 --- a/Mage.Sets/src/mage/cards/p/PatronOfTheVein.java +++ b/Mage.Sets/src/mage/cards/p/PatronOfTheVein.java @@ -70,6 +70,7 @@ class PatronOfTheVeinCreatureDiesTriggeredAbility extends TriggeredAbilityImpl { public PatronOfTheVeinCreatureDiesTriggeredAbility() { super(Zone.BATTLEFIELD, new PatronOfTheVeinExileCreatureEffect(), false); + setLeavesTheBattlefieldTrigger(true); } private PatronOfTheVeinCreatureDiesTriggeredAbility(final PatronOfTheVeinCreatureDiesTriggeredAbility ability) { @@ -107,6 +108,11 @@ class PatronOfTheVeinCreatureDiesTriggeredAbility extends TriggeredAbilityImpl { public String getRule() { return "Whenever a creature an opponent controls dies, exile it and put a +1/+1 counter on each Vampire you control."; } + + @Override + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); + } } class PatronOfTheVeinExileCreatureEffect extends OneShotEffect { diff --git a/Mage.Sets/src/mage/cards/p/PeltCollector.java b/Mage.Sets/src/mage/cards/p/PeltCollector.java index 95b6af1183c..848dd821458 100644 --- a/Mage.Sets/src/mage/cards/p/PeltCollector.java +++ b/Mage.Sets/src/mage/cards/p/PeltCollector.java @@ -64,6 +64,7 @@ class PeltCollectorTriggeredAbility extends TriggeredAbilityImpl { PeltCollectorTriggeredAbility() { super(Zone.BATTLEFIELD, new AddCountersSourceEffect(CounterType.P1P1.createInstance())); + setLeavesTheBattlefieldTrigger(true); } private PeltCollectorTriggeredAbility(PeltCollectorTriggeredAbility ability) { diff --git a/Mage.Sets/src/mage/cards/p/PhantasmalImage.java b/Mage.Sets/src/mage/cards/p/PhantasmalImage.java index 9466bd0b5c3..ba73a50278f 100644 --- a/Mage.Sets/src/mage/cards/p/PhantasmalImage.java +++ b/Mage.Sets/src/mage/cards/p/PhantasmalImage.java @@ -44,9 +44,7 @@ public final class PhantasmalImage extends CardImpl { this.power = new MageInt(0); this.toughness = new MageInt(0); - // You may have Phantasmal Image enter the battlefield as a copy of any creature - // on the battlefield, except it's an Illusion in addition to its other types and - // it has "When this creature becomes the target of a spell or ability, sacrifice it." + // You may have Phantasmal Image enter the battlefield as a copy of any creature on the battlefield, except it's an Illusion in addition to its other types and it has "When this creature becomes the target of a spell or ability, sacrifice it." Effect effect = new CopyPermanentEffect(StaticFilters.FILTER_PERMANENT_CREATURE, phantasmalImageApplier); effect.setText(effectText); this.addAbility(new EntersBattlefieldAbility(effect, true)); diff --git a/Mage.Sets/src/mage/cards/p/PiasRevolution.java b/Mage.Sets/src/mage/cards/p/PiasRevolution.java index 402bc1e6ff5..cf9897d2572 100644 --- a/Mage.Sets/src/mage/cards/p/PiasRevolution.java +++ b/Mage.Sets/src/mage/cards/p/PiasRevolution.java @@ -1,5 +1,6 @@ package mage.cards.p; +import mage.MageObject; import mage.abilities.Ability; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.effects.Effect; @@ -97,6 +98,7 @@ class PiasRevolutionTriggeredAbility extends TriggeredAbilityImpl { public PiasRevolutionTriggeredAbility() { super(Zone.BATTLEFIELD, new PiasRevolutionReturnEffect(), false); setTriggerPhrase("Whenever a nontoken artifact is put into your graveyard from the battlefield, "); + setLeavesTheBattlefieldTrigger(true); } private PiasRevolutionTriggeredAbility(final PiasRevolutionTriggeredAbility ability) { @@ -127,4 +129,9 @@ class PiasRevolutionTriggeredAbility extends TriggeredAbilityImpl { } return false; } + + @Override + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); + } } diff --git a/Mage.Sets/src/mage/cards/p/PickYourPoison.java b/Mage.Sets/src/mage/cards/p/PickYourPoison.java index b7ec2909e29..fa624d5439e 100644 --- a/Mage.Sets/src/mage/cards/p/PickYourPoison.java +++ b/Mage.Sets/src/mage/cards/p/PickYourPoison.java @@ -35,7 +35,8 @@ public final class PickYourPoison extends CardImpl { this.getSpellAbility().addMode(new Mode(new SacrificeOpponentsEffect(StaticFilters.FILTER_PERMANENT_ENCHANTMENT))); // * Each opponent sacrifices a creature with flying. - this.getSpellAbility().addMode(new Mode(new SacrificeOpponentsEffect(filter))); + this.getSpellAbility().addMode(new Mode(new SacrificeOpponentsEffect(filter) + .setText("each opponent sacrifices a creature of their choice with flying"))); } private PickYourPoison(final PickYourPoison card) { diff --git a/Mage.Sets/src/mage/cards/p/PicnicRuiner.java b/Mage.Sets/src/mage/cards/p/PicnicRuiner.java index d7eeb6c0ab7..e8ef4ec3cd9 100644 --- a/Mage.Sets/src/mage/cards/p/PicnicRuiner.java +++ b/Mage.Sets/src/mage/cards/p/PicnicRuiner.java @@ -42,7 +42,7 @@ public final class PicnicRuiner extends AdventureCard { // Distribute three +1/+1 counters among any number of target creatures you control. this.getSpellCard().getSpellAbility().addEffect( new DistributeCountersEffect( - CounterType.P1P1, 3, false, + 3, "any number of target creatures you control" ) ); diff --git a/Mage.Sets/src/mage/cards/p/PompousGadabout.java b/Mage.Sets/src/mage/cards/p/PompousGadabout.java index fbb62bc9219..d62ccf93994 100644 --- a/Mage.Sets/src/mage/cards/p/PompousGadabout.java +++ b/Mage.Sets/src/mage/cards/p/PompousGadabout.java @@ -43,7 +43,7 @@ public final class PompousGadabout extends CardImpl { // Pompous Gadabout has hexproof as long as it's your turn. this.addAbility(new SimpleStaticAbility(new ConditionalContinuousEffect( new GainAbilitySourceEffect(HexproofAbility.getInstance(), Duration.WhileOnBattlefield), - MyTurnCondition.instance, "{this} has hexproof as long as it's your turn" + MyTurnCondition.instance, "during your turn, {this} has hexproof" )).addHint(MyTurnHint.instance)); // Pompous Gadabout can't be blocked by creatures that don't have a name. @@ -67,4 +67,4 @@ enum PompousGadaboutPredicate implements Predicate { public boolean apply(Permanent input, Game game) { return input.getName() == null || input.getName().isEmpty(); } -} \ No newline at end of file +} diff --git a/Mage.Sets/src/mage/cards/p/PravaOfTheSteelLegion.java b/Mage.Sets/src/mage/cards/p/PravaOfTheSteelLegion.java index ddd95014043..20e305dba0e 100644 --- a/Mage.Sets/src/mage/cards/p/PravaOfTheSteelLegion.java +++ b/Mage.Sets/src/mage/cards/p/PravaOfTheSteelLegion.java @@ -44,7 +44,7 @@ public final class PravaOfTheSteelLegion extends CardImpl { // As long as it's your turn, creature tokens you control get +1/+4. this.addAbility(new SimpleStaticAbility(new ConditionalContinuousEffect(new BoostControlledEffect( 1, 4, Duration.WhileOnBattlefield, filter - ), MyTurnCondition.instance, "as long as it's your turn, creature tokens you control get +1/+4"))); + ), MyTurnCondition.instance, "during your turn, creature tokens you control get +1/+4"))); // {3}{W}: Create a 1/1 white Soldier creature token. this.addAbility(new SimpleActivatedAbility( diff --git a/Mage.Sets/src/mage/cards/p/ProperBurial.java b/Mage.Sets/src/mage/cards/p/ProperBurial.java index 4f2eac1f9da..0e46e0e563a 100644 --- a/Mage.Sets/src/mage/cards/p/ProperBurial.java +++ b/Mage.Sets/src/mage/cards/p/ProperBurial.java @@ -1,5 +1,6 @@ package mage.cards.p; +import mage.MageObject; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.effects.common.GainLifeEffect; import mage.cards.CardImpl; @@ -40,6 +41,7 @@ class ProperBurialTriggeredAbility extends TriggeredAbilityImpl { public ProperBurialTriggeredAbility() { super(Zone.BATTLEFIELD, null); + setLeavesTheBattlefieldTrigger(true); } private ProperBurialTriggeredAbility(final ProperBurialTriggeredAbility ability) { @@ -76,4 +78,9 @@ class ProperBurialTriggeredAbility extends TriggeredAbilityImpl { public String getRule() { return "Whenever a creature you control dies, you gain life equal to that creature's toughness."; } + + @Override + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); + } } diff --git a/Mage.Sets/src/mage/cards/p/Psychomancer.java b/Mage.Sets/src/mage/cards/p/Psychomancer.java index e04b189242c..ee633d13cb7 100644 --- a/Mage.Sets/src/mage/cards/p/Psychomancer.java +++ b/Mage.Sets/src/mage/cards/p/Psychomancer.java @@ -57,6 +57,7 @@ class PsychomancerTriggeredAbility extends TriggeredAbilityImpl { this.setTriggerPhrase("Whenever {this} or another nontoken artifact you control is put " + "into a graveyard from the battlefield or is put into exile from the battlefield, "); this.withFlavorWord("Harbinger of Despair"); + this.setLeavesTheBattlefieldTrigger(true); } private PsychomancerTriggeredAbility(final PsychomancerTriggeredAbility ability) { diff --git a/Mage.Sets/src/mage/cards/p/Purgatory.java b/Mage.Sets/src/mage/cards/p/Purgatory.java index 05c5dc84327..295053af5f3 100644 --- a/Mage.Sets/src/mage/cards/p/Purgatory.java +++ b/Mage.Sets/src/mage/cards/p/Purgatory.java @@ -62,6 +62,7 @@ class PurgatoryTriggeredAbility extends TriggeredAbilityImpl { PurgatoryTriggeredAbility() { super(Zone.BATTLEFIELD, new PurgatoryExileEffect(), false); + setLeavesTheBattlefieldTrigger(true); } private PurgatoryTriggeredAbility(final PurgatoryTriggeredAbility ability) { @@ -103,6 +104,11 @@ class PurgatoryTriggeredAbility extends TriggeredAbilityImpl { public String getRule() { return "Whenever a nontoken creature is put into your graveyard from the battlefield, exile that card."; } + + @Override + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); + } } class PurgatoryExileEffect extends OneShotEffect { diff --git a/Mage.Sets/src/mage/cards/q/QuintoriusLoremaster.java b/Mage.Sets/src/mage/cards/q/QuintoriusLoremaster.java index ca664488b90..d4c3db7f3f2 100644 --- a/Mage.Sets/src/mage/cards/q/QuintoriusLoremaster.java +++ b/Mage.Sets/src/mage/cards/q/QuintoriusLoremaster.java @@ -110,7 +110,6 @@ enum QuintoriusLoremasterPredicate implements ObjectSourcePlayerPredicate UUID exileZoneId = CardUtil.getExileZoneId(game, quintorius.getId(), quintorius.getZoneChangeCounter(game)); return exile.getExileZone(exileZoneId); }) - .filter(Objects::nonNull) .map(exile -> exile.contains(input.getObject().getId())) .orElse(false); } diff --git a/Mage.Sets/src/mage/cards/q/QuirionBeastcaller.java b/Mage.Sets/src/mage/cards/q/QuirionBeastcaller.java index f246f6aaf5a..3cb8f79460c 100644 --- a/Mage.Sets/src/mage/cards/q/QuirionBeastcaller.java +++ b/Mage.Sets/src/mage/cards/q/QuirionBeastcaller.java @@ -40,7 +40,7 @@ public final class QuirionBeastcaller extends CardImpl { // When Quirion Beastcaller dies, distribute X +1/+1 counters among any number of target creatures you control, where X is the number of +1/+1 counters on Quirion Beastcaller. Ability ability = new DiesSourceTriggeredAbility(new DistributeCountersEffect( // Amount here is only used for text generation. Real amount is set in target. - CounterType.P1P1, 1, false, "any number of target creatures you control" + 1, "any number of target creatures you control" ).setText("distribute X +1/+1 counters among any number of target creatures you control, where X is the number of +1/+1 counters on {this}")); ability.addTarget(new TargetPermanentAmount(new CountersSourceCount(CounterType.P1P1), StaticFilters.FILTER_CONTROLLED_CREATURES)); this.addAbility(ability); diff --git a/Mage.Sets/src/mage/cards/r/RadiantScrollwielder.java b/Mage.Sets/src/mage/cards/r/RadiantScrollwielder.java index edbd306c6e1..42d0d8dab13 100644 --- a/Mage.Sets/src/mage/cards/r/RadiantScrollwielder.java +++ b/Mage.Sets/src/mage/cards/r/RadiantScrollwielder.java @@ -159,12 +159,12 @@ class RadiantScrollwielderWatcher extends Watcher { @Override public void watch(GameEvent event, Game game) { - if (event.getType() != GameEvent.EventType.SPELL_CAST || event.getAdditionalReference() == null) { + if (event.getType() != GameEvent.EventType.SPELL_CAST || event.getApprovingObject() == null) { return; } morMap.put( new MageObjectReference(event.getSourceId(), game), - event.getAdditionalReference().getApprovingMageObjectReference() + event.getApprovingObject().getApprovingMageObjectReference() ); } diff --git a/Mage.Sets/src/mage/cards/r/RampageOfTheValkyries.java b/Mage.Sets/src/mage/cards/r/RampageOfTheValkyries.java index 3e4088341c1..c7a83bedf83 100644 --- a/Mage.Sets/src/mage/cards/r/RampageOfTheValkyries.java +++ b/Mage.Sets/src/mage/cards/r/RampageOfTheValkyries.java @@ -55,7 +55,7 @@ class RampageOfTheValkyriesEffect extends OneShotEffect { RampageOfTheValkyriesEffect() { super(Outcome.Benefit); - staticText = "each other player sacrifices a creature"; + staticText = "each other player sacrifices a creature of their choice"; } private RampageOfTheValkyriesEffect(final RampageOfTheValkyriesEffect effect) { diff --git a/Mage.Sets/src/mage/cards/r/RangersOfIthilien.java b/Mage.Sets/src/mage/cards/r/RangersOfIthilien.java index bbce9a324ff..e0ef3234804 100644 --- a/Mage.Sets/src/mage/cards/r/RangersOfIthilien.java +++ b/Mage.Sets/src/mage/cards/r/RangersOfIthilien.java @@ -70,7 +70,6 @@ enum RangersOfIthilienPredicate implements ObjectSourcePlayerPredicate input, Game game) { return Optional .ofNullable(input.getSource().getSourcePermanentIfItStillExists(game)) - .filter(Objects::nonNull) .map(MageObject::getPower) .map(MageInt::getValue) .map(i -> i > input.getObject().getPower().getValue()) diff --git a/Mage.Sets/src/mage/cards/r/Remembrance.java b/Mage.Sets/src/mage/cards/r/Remembrance.java index 347bf2c4b58..a9fe28c4235 100644 --- a/Mage.Sets/src/mage/cards/r/Remembrance.java +++ b/Mage.Sets/src/mage/cards/r/Remembrance.java @@ -1,6 +1,7 @@ package mage.cards.r; +import mage.MageObject; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.effects.common.search.SearchLibraryPutInHandEffect; import mage.cards.CardImpl; @@ -52,6 +53,7 @@ class RemembranceTriggeredAbility extends TriggeredAbilityImpl { RemembranceTriggeredAbility() { super(Zone.BATTLEFIELD, null, true); + setLeavesTheBattlefieldTrigger(true); } private RemembranceTriggeredAbility(final RemembranceTriggeredAbility ability) { @@ -92,4 +94,9 @@ class RemembranceTriggeredAbility extends TriggeredAbilityImpl { "you may search your library for a card with the same name as that creature, " + "reveal it, put it into your hand, then shuffle."; } + + @Override + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); + } } diff --git a/Mage.Sets/src/mage/cards/r/ReyhanLastOfTheAbzan.java b/Mage.Sets/src/mage/cards/r/ReyhanLastOfTheAbzan.java index c957bce8e86..8223b5f7c3c 100644 --- a/Mage.Sets/src/mage/cards/r/ReyhanLastOfTheAbzan.java +++ b/Mage.Sets/src/mage/cards/r/ReyhanLastOfTheAbzan.java @@ -63,6 +63,7 @@ class ReyhanLastOfTheAbzanTriggeredAbility extends TriggeredAbilityImpl { public ReyhanLastOfTheAbzanTriggeredAbility() { super(Zone.BATTLEFIELD, null, true); + setLeavesTheBattlefieldTrigger(true); } private ReyhanLastOfTheAbzanTriggeredAbility(final ReyhanLastOfTheAbzanTriggeredAbility ability) { diff --git a/Mage.Sets/src/mage/cards/r/RhukHexgoldNabber.java b/Mage.Sets/src/mage/cards/r/RhukHexgoldNabber.java index b7a81dcb1a6..3bce95e4211 100644 --- a/Mage.Sets/src/mage/cards/r/RhukHexgoldNabber.java +++ b/Mage.Sets/src/mage/cards/r/RhukHexgoldNabber.java @@ -1,6 +1,7 @@ package mage.cards.r; import mage.MageInt; +import mage.MageObject; import mage.abilities.Ability; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.effects.OneShotEffect; @@ -65,6 +66,7 @@ class RhukHexgoldNabberTriggeredAbility extends TriggeredAbilityImpl { RhukHexgoldNabberTriggeredAbility() { super(Zone.BATTLEFIELD, new RhukHexgoldNabberEffect(), true); + setLeavesTheBattlefieldTrigger(true); } private RhukHexgoldNabberTriggeredAbility(final RhukHexgoldNabberTriggeredAbility ability) { @@ -110,6 +112,15 @@ class RhukHexgoldNabberTriggeredAbility extends TriggeredAbilityImpl { return "Whenever an equipped creature you control other than {this} attacks or dies, " + "you may attach all Equipment attached to that creature to {this}."; } + + @Override + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + if (event.getType() == GameEvent.EventType.ZONE_CHANGE) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); + } else { + return super.isInUseableZone(game, sourceObject, event); + } + } } class RhukHexgoldNabberEffect extends OneShotEffect { diff --git a/Mage.Sets/src/mage/cards/r/RienneAngelOfRebirth.java b/Mage.Sets/src/mage/cards/r/RienneAngelOfRebirth.java index d69857b0063..850fb69a03f 100644 --- a/Mage.Sets/src/mage/cards/r/RienneAngelOfRebirth.java +++ b/Mage.Sets/src/mage/cards/r/RienneAngelOfRebirth.java @@ -1,6 +1,7 @@ package mage.cards.r; import mage.MageInt; +import mage.MageObject; import mage.abilities.Ability; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.common.SimpleStaticAbility; @@ -70,6 +71,7 @@ class RienneAngelOfRebirthTriggeredAbility extends TriggeredAbilityImpl { RienneAngelOfRebirthTriggeredAbility() { super(Zone.BATTLEFIELD, new RienneAngelOfRebirthEffect(), false); + setLeavesTheBattlefieldTrigger(true); } private RienneAngelOfRebirthTriggeredAbility(final RienneAngelOfRebirthTriggeredAbility ability) { @@ -109,6 +111,11 @@ class RienneAngelOfRebirthTriggeredAbility extends TriggeredAbilityImpl { return "Whenever another multicolored creature you control dies, " + "return it to its owner's hand at the beginning of the next end step."; } + + @Override + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); + } } class RienneAngelOfRebirthEffect extends OneShotEffect { diff --git a/Mage.Sets/src/mage/cards/r/Ringwraiths.java b/Mage.Sets/src/mage/cards/r/Ringwraiths.java index 0919b94bb08..10dbec9e613 100644 --- a/Mage.Sets/src/mage/cards/r/Ringwraiths.java +++ b/Mage.Sets/src/mage/cards/r/Ringwraiths.java @@ -77,11 +77,9 @@ class RingwraithsEffect extends OneShotEffect { return Optional .ofNullable(getTargetPointer().getFirst(game, source)) .map(game::getPermanent) - .filter(Objects::nonNull) .filter(permanent -> permanent.isLegendary(game)) .map(Controllable::getControllerId) .map(game::getPlayer) - .filter(Objects::nonNull) .map(player -> player.loseLife(3, game, source, false) > 0) .orElse(false); } diff --git a/Mage.Sets/src/mage/cards/r/RunAfoul.java b/Mage.Sets/src/mage/cards/r/RunAfoul.java index 00d2fb3e2a3..e2d0d55f990 100644 --- a/Mage.Sets/src/mage/cards/r/RunAfoul.java +++ b/Mage.Sets/src/mage/cards/r/RunAfoul.java @@ -26,7 +26,8 @@ public final class RunAfoul extends CardImpl { super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{G}"); // Target opponent sacrifices a creature with flying. - this.getSpellAbility().addEffect(new SacrificeEffect(filter, 1, "Target opponent")); + this.getSpellAbility().addEffect(new SacrificeEffect(filter, 1, "Target opponent") + .setText("target opponent sacrifices a creature of their choice with flying")); this.getSpellAbility().addTarget(new TargetOpponent()); } diff --git a/Mage.Sets/src/mage/cards/s/SacredGround.java b/Mage.Sets/src/mage/cards/s/SacredGround.java index baea68dc46d..7fa043d7480 100644 --- a/Mage.Sets/src/mage/cards/s/SacredGround.java +++ b/Mage.Sets/src/mage/cards/s/SacredGround.java @@ -1,5 +1,6 @@ package mage.cards.s; +import mage.MageObject; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.effects.common.ReturnFromGraveyardToBattlefieldTargetEffect; import mage.cards.CardImpl; @@ -40,6 +41,7 @@ class SacredGroundTriggeredAbility extends TriggeredAbilityImpl { SacredGroundTriggeredAbility() { super(Zone.BATTLEFIELD, new ReturnFromGraveyardToBattlefieldTargetEffect()); + setLeavesTheBattlefieldTrigger(true); } private SacredGroundTriggeredAbility(final SacredGroundTriggeredAbility ability) { @@ -75,4 +77,9 @@ class SacredGroundTriggeredAbility extends TriggeredAbilityImpl { public String getRule() { return "Whenever a spell or ability an opponent controls causes a land to be put into your graveyard from the battlefield, return that card to the battlefield."; } + + @Override + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); + } } diff --git a/Mage.Sets/src/mage/cards/s/Sangromancer.java b/Mage.Sets/src/mage/cards/s/Sangromancer.java index fb7e27358f2..90f62fec037 100644 --- a/Mage.Sets/src/mage/cards/s/Sangromancer.java +++ b/Mage.Sets/src/mage/cards/s/Sangromancer.java @@ -4,6 +4,7 @@ package mage.cards.s; import java.util.UUID; import mage.MageInt; +import mage.MageObject; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.effects.common.GainLifeEffect; import mage.abilities.keyword.FlyingAbility; @@ -46,9 +47,11 @@ public final class Sangromancer extends CardImpl { } class SangromancerFirstTriggeredAbility extends TriggeredAbilityImpl { + SangromancerFirstTriggeredAbility() { super(Zone.BATTLEFIELD, new GainLifeEffect(3), true); setTriggerPhrase("Whenever a creature an opponent controls dies, "); + setLeavesTheBattlefieldTrigger(true); } private SangromancerFirstTriggeredAbility(final SangromancerFirstTriggeredAbility ability) { @@ -75,6 +78,11 @@ class SangromancerFirstTriggeredAbility extends TriggeredAbilityImpl { } return false; } + + @Override + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); + } } class SangromancerSecondTriggeredAbility extends TriggeredAbilityImpl { diff --git a/Mage.Sets/src/mage/cards/s/SavraQueenOfTheGolgari.java b/Mage.Sets/src/mage/cards/s/SavraQueenOfTheGolgari.java index 83c226df1b6..872a3a3bbaa 100644 --- a/Mage.Sets/src/mage/cards/s/SavraQueenOfTheGolgari.java +++ b/Mage.Sets/src/mage/cards/s/SavraQueenOfTheGolgari.java @@ -70,7 +70,7 @@ class SavraSacrificeEffect extends OneShotEffect { SavraSacrificeEffect() { super(Outcome.Sacrifice); - this.staticText = "each other player sacrifices a creature"; + this.staticText = "each other player sacrifices a creature of their choice"; } private SavraSacrificeEffect(final SavraSacrificeEffect effect) { diff --git a/Mage.Sets/src/mage/cards/s/ScrapTrawler.java b/Mage.Sets/src/mage/cards/s/ScrapTrawler.java index ed51d7e4994..80078d64cbe 100644 --- a/Mage.Sets/src/mage/cards/s/ScrapTrawler.java +++ b/Mage.Sets/src/mage/cards/s/ScrapTrawler.java @@ -1,6 +1,7 @@ package mage.cards.s; import mage.MageInt; +import mage.MageObject; import mage.abilities.Ability; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.effects.common.ReturnToHandTargetEffect; @@ -56,6 +57,7 @@ class ScrapTrawlerTriggeredAbility extends TriggeredAbilityImpl { super(Zone.BATTLEFIELD, new ReturnToHandTargetEffect()); getEffects().get(0).setText("return to your hand target artifact card in your graveyard with lesser mana value"); setTriggerPhrase("Whenever {this} or another artifact you control is put into a graveyard from the battlefield, "); + setLeavesTheBattlefieldTrigger(true); } private ScrapTrawlerTriggeredAbility(final ScrapTrawlerTriggeredAbility ability) { @@ -90,4 +92,9 @@ class ScrapTrawlerTriggeredAbility extends TriggeredAbilityImpl { } return false; } + + @Override + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); + } } diff --git a/Mage.Sets/src/mage/cards/s/Scrapheap.java b/Mage.Sets/src/mage/cards/s/Scrapheap.java index a8274337aea..013e4aa5aaf 100644 --- a/Mage.Sets/src/mage/cards/s/Scrapheap.java +++ b/Mage.Sets/src/mage/cards/s/Scrapheap.java @@ -1,6 +1,7 @@ package mage.cards.s; +import mage.MageObject; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.effects.common.GainLifeEffect; import mage.cards.CardImpl; @@ -45,12 +46,13 @@ class ScrapheapTriggeredAbility extends TriggeredAbilityImpl { return new ScrapheapTriggeredAbility(this); } - private ScrapheapTriggeredAbility(final ScrapheapTriggeredAbility ability){ - super(ability); - } - public ScrapheapTriggeredAbility(){ super(Zone.BATTLEFIELD, new GainLifeEffect(1)); + setLeavesTheBattlefieldTrigger(true); + } + + private ScrapheapTriggeredAbility(final ScrapheapTriggeredAbility ability){ + super(ability); } @Override @@ -76,4 +78,9 @@ class ScrapheapTriggeredAbility extends TriggeredAbilityImpl { public String getRule() { return "Whenever an artifact or enchantment is put into your graveyard from the battlefield, you gain 1 life."; } + + @Override + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); + } } diff --git a/Mage.Sets/src/mage/cards/s/SeasonOfLoss.java b/Mage.Sets/src/mage/cards/s/SeasonOfLoss.java index 581db0f199d..0d9a6b36393 100644 --- a/Mage.Sets/src/mage/cards/s/SeasonOfLoss.java +++ b/Mage.Sets/src/mage/cards/s/SeasonOfLoss.java @@ -74,7 +74,7 @@ enum SeasonOfLossValue implements DynamicValue { @Override public String getMessage() { - return "creature you controlled that died this turn"; + return "creature that died under your control this turn"; } @Override diff --git a/Mage.Sets/src/mage/cards/s/SeerOfStolenSight.java b/Mage.Sets/src/mage/cards/s/SeerOfStolenSight.java index 435b71fe139..86ec5fb253f 100644 --- a/Mage.Sets/src/mage/cards/s/SeerOfStolenSight.java +++ b/Mage.Sets/src/mage/cards/s/SeerOfStolenSight.java @@ -2,6 +2,7 @@ package mage.cards.s; import mage.MageInt; import mage.MageItem; +import mage.MageObject; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.effects.keyword.SurveilEffect; import mage.abilities.keyword.MenaceAbility; @@ -53,6 +54,7 @@ class SeerOfStolenSightTriggeredAbility extends TriggeredAbilityImpl { SeerOfStolenSightTriggeredAbility() { super(Zone.BATTLEFIELD, new SurveilEffect(1)); setTriggerPhrase("Whenever one or more artifacts and/or creatures you control are put into a graveyard from the battlefield, "); + setLeavesTheBattlefieldTrigger(true); } private SeerOfStolenSightTriggeredAbility(final SeerOfStolenSightTriggeredAbility ability) { @@ -85,4 +87,9 @@ class SeerOfStolenSightTriggeredAbility extends TriggeredAbilityImpl { .map(Controllable::getControllerId) .anyMatch(this::isControlledBy); } + + @Override + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); + } } diff --git a/Mage.Sets/src/mage/cards/s/SerpentsSoulJar.java b/Mage.Sets/src/mage/cards/s/SerpentsSoulJar.java index a9b21caa7b5..cb450af8b94 100644 --- a/Mage.Sets/src/mage/cards/s/SerpentsSoulJar.java +++ b/Mage.Sets/src/mage/cards/s/SerpentsSoulJar.java @@ -142,10 +142,10 @@ class SerpentsSoulJarWatcher extends Watcher { @Override public void watch(GameEvent event, Game game) { if (event.getType() == GameEvent.EventType.SPELL_CAST) { - if (event.getAdditionalReference() == null) { + if (event.getApprovingObject() == null) { return; } - morMap.computeIfAbsent(event.getAdditionalReference().getApprovingMageObjectReference(), m -> new HashMap<>()) + morMap.computeIfAbsent(event.getApprovingObject().getApprovingMageObjectReference(), m -> new HashMap<>()) .compute(event.getPlayerId(), (u, i) -> i == null ? 0 : Integer.sum(i, -1)); return; } diff --git a/Mage.Sets/src/mage/cards/s/SerraParagon.java b/Mage.Sets/src/mage/cards/s/SerraParagon.java index 5c188cb07b4..908d79747fb 100644 --- a/Mage.Sets/src/mage/cards/s/SerraParagon.java +++ b/Mage.Sets/src/mage/cards/s/SerraParagon.java @@ -193,7 +193,7 @@ class SerraParagonWatcher extends Watcher { || event.getType() == GameEvent.EventType.LAND_PLAYED) && event.hasApprovingIdentifier(MageIdentifier.SerraParagonWatcher)) { map.computeIfAbsent( - event.getAdditionalReference() + event.getApprovingObject() .getApprovingMageObjectReference(), x -> new HashSet<>() ).add(event.getPlayerId()); diff --git a/Mage.Sets/src/mage/cards/s/ShamblingSwarm.java b/Mage.Sets/src/mage/cards/s/ShamblingSwarm.java index 759c1ee3f0c..d3f9dc9c8f9 100644 --- a/Mage.Sets/src/mage/cards/s/ShamblingSwarm.java +++ b/Mage.Sets/src/mage/cards/s/ShamblingSwarm.java @@ -26,7 +26,9 @@ public final class ShamblingSwarm extends CardImpl { this.toughness = new MageInt(3); // When Shambling Swarm dies, distribute three -1/-1 counters among one, two, or three target creatures. For each -1/-1 counter you put on a creature this way, remove a -1/-1 counter from that creature at the beginning of the next end step. - Ability ability = new DiesSourceTriggeredAbility(new DistributeCountersEffect(CounterType.M1M1, 3, true, "one, two, or three target creatures"), false); + Ability ability = new DiesSourceTriggeredAbility(new DistributeCountersEffect( + CounterType.M1M1, 3, "one, two, or three target creatures" + ).withRemoveAtEndOfTurn(), false); ability.addTarget(new TargetCreaturePermanentAmount(3)); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/s/ShaoJun.java b/Mage.Sets/src/mage/cards/s/ShaoJun.java index 29b708e7f21..f01493cd8d1 100644 --- a/Mage.Sets/src/mage/cards/s/ShaoJun.java +++ b/Mage.Sets/src/mage/cards/s/ShaoJun.java @@ -44,7 +44,7 @@ public final class ShaoJun extends CardImpl { // Leap Strike -- As long as it's your turn, Shao Jun has flying and first strike. Ability ability = new SimpleStaticAbility(new ConditionalContinuousEffect( new GainAbilitySourceEffect(FlyingAbility.getInstance(), Duration.WhileOnBattlefield), - MyTurnCondition.instance, "as long as it's your turn, {this} has flying" + MyTurnCondition.instance, "during your turn, {this} has flying" )); ability.addEffect(new ConditionalContinuousEffect( new GainAbilitySourceEffect(FirstStrikeAbility.getInstance(), Duration.WhileOnBattlefield), diff --git a/Mage.Sets/src/mage/cards/s/ShardOfTheVoidDragon.java b/Mage.Sets/src/mage/cards/s/ShardOfTheVoidDragon.java index e82071a39ad..9ffd88a72c9 100644 --- a/Mage.Sets/src/mage/cards/s/ShardOfTheVoidDragon.java +++ b/Mage.Sets/src/mage/cards/s/ShardOfTheVoidDragon.java @@ -59,6 +59,7 @@ class ShardOfTheVoidDragonTriggeredAbility extends TriggeredAbilityImpl { super(Zone.BATTLEFIELD, new AddCountersSourceEffect(CounterType.P1P1.createInstance(2))); setTriggerPhrase("Whenever an artifact is put into a graveyard from the battlefield or is put into exile from the battlefield, "); withFlavorWord("Matter Absorption"); + setLeavesTheBattlefieldTrigger(true); } private ShardOfTheVoidDragonTriggeredAbility(final ShardOfTheVoidDragonTriggeredAbility ability) { diff --git a/Mage.Sets/src/mage/cards/s/ShelobChildOfUngoliant.java b/Mage.Sets/src/mage/cards/s/ShelobChildOfUngoliant.java index 4d32904e1e6..51cd8a005ce 100644 --- a/Mage.Sets/src/mage/cards/s/ShelobChildOfUngoliant.java +++ b/Mage.Sets/src/mage/cards/s/ShelobChildOfUngoliant.java @@ -1,6 +1,7 @@ package mage.cards.s; import mage.MageInt; +import mage.MageObject; import mage.MageObjectReference; import mage.abilities.Ability; import mage.abilities.TriggeredAbilityImpl; @@ -145,6 +146,7 @@ class ShelobChildOfUngoliantTriggeredAbility extends TriggeredAbilityImpl { super(Zone.BATTLEFIELD, effect); this.addWatcher(new ShelobChildOfUngoliantWatcher()); this.setTriggerPhrase("Whenever another creature dealt damage this turn by a Spider you controlled dies, "); + setLeavesTheBattlefieldTrigger(true); } private ShelobChildOfUngoliantTriggeredAbility(final ShelobChildOfUngoliantTriggeredAbility ability) { @@ -185,6 +187,11 @@ class ShelobChildOfUngoliantTriggeredAbility extends TriggeredAbilityImpl { } return false; } + + @Override + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); + } } class ShelobChildOfUngoliantEffect extends OneShotEffect { diff --git a/Mage.Sets/src/mage/cards/s/SkophosReaver.java b/Mage.Sets/src/mage/cards/s/SkophosReaver.java index 88bc8571346..4b3af0f4fce 100644 --- a/Mage.Sets/src/mage/cards/s/SkophosReaver.java +++ b/Mage.Sets/src/mage/cards/s/SkophosReaver.java @@ -31,7 +31,7 @@ public final class SkophosReaver extends CardImpl { // As long as it's your turn, Skophos Reaver gets +2/+0. this.addAbility(new SimpleStaticAbility(new ConditionalContinuousEffect( new BoostSourceEffect(2, 0, Duration.WhileOnBattlefield), - MyTurnCondition.instance, "as long as it's your turn, {this} gets +2/+0" + MyTurnCondition.instance, "during your turn, {this} gets +2/+0" ))); // Madness {1}{R} diff --git a/Mage.Sets/src/mage/cards/s/SlagstoneRefinery.java b/Mage.Sets/src/mage/cards/s/SlagstoneRefinery.java index 635d5590fc4..53cee240701 100644 --- a/Mage.Sets/src/mage/cards/s/SlagstoneRefinery.java +++ b/Mage.Sets/src/mage/cards/s/SlagstoneRefinery.java @@ -1,5 +1,6 @@ package mage.cards.s; +import mage.MageObject; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.effects.common.CreateTokenEffect; import mage.cards.CardImpl; @@ -40,6 +41,7 @@ class SlagstoneRefineryTriggeredAbility extends TriggeredAbilityImpl { SlagstoneRefineryTriggeredAbility() { super(Zone.BATTLEFIELD, new CreateTokenEffect(new PowerstoneToken(), 1, true)); + setLeavesTheBattlefieldTrigger(true); } private SlagstoneRefineryTriggeredAbility(final SlagstoneRefineryTriggeredAbility ability) { @@ -79,4 +81,9 @@ class SlagstoneRefineryTriggeredAbility extends TriggeredAbilityImpl { return "Whenever {this} or another nontoken artifact you control is put into a graveyard from the battlefield " + "or is put into exile from the battlefield, create a tapped Powerstone token."; } + + @Override + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); + } } diff --git a/Mage.Sets/src/mage/cards/s/SlayersPlate.java b/Mage.Sets/src/mage/cards/s/SlayersPlate.java index 0eba2e6116d..56e9742fb9e 100644 --- a/Mage.Sets/src/mage/cards/s/SlayersPlate.java +++ b/Mage.Sets/src/mage/cards/s/SlayersPlate.java @@ -2,6 +2,8 @@ package mage.cards.s; import java.util.UUID; + +import mage.MageObject; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.costs.mana.GenericManaCost; @@ -56,6 +58,7 @@ class SlayersPlateTriggeredAbility extends TriggeredAbilityImpl { public SlayersPlateTriggeredAbility() { super(Zone.BATTLEFIELD, new CreateTokenEffect(new SpiritWhiteToken())); + setLeavesTheBattlefieldTrigger(true); } private SlayersPlateTriggeredAbility(final SlayersPlateTriggeredAbility ability) { @@ -87,4 +90,9 @@ class SlayersPlateTriggeredAbility extends TriggeredAbilityImpl { public String getRule() { return "Whenever equipped creature dies, if it was a Human, create a 1/1 white Spirit creature token with flying."; } + + @Override + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); + } } diff --git a/Mage.Sets/src/mage/cards/s/SplendidAgony.java b/Mage.Sets/src/mage/cards/s/SplendidAgony.java index 0ed6e347ff0..b553a39d4a7 100644 --- a/Mage.Sets/src/mage/cards/s/SplendidAgony.java +++ b/Mage.Sets/src/mage/cards/s/SplendidAgony.java @@ -19,7 +19,7 @@ public final class SplendidAgony extends CardImpl { super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{2}{B}"); // Distribute two -1/-1 counters among one or two target creatures. - getSpellAbility().addEffect(new DistributeCountersEffect(CounterType.M1M1, 2, false, "one or two target creatures")); + getSpellAbility().addEffect(new DistributeCountersEffect(CounterType.M1M1, 2, "one or two target creatures")); getSpellAbility().addTarget(new TargetCreaturePermanentAmount(2)); } diff --git a/Mage.Sets/src/mage/cards/s/SporebackWolf.java b/Mage.Sets/src/mage/cards/s/SporebackWolf.java index 4829b90571e..827640ebb0b 100644 --- a/Mage.Sets/src/mage/cards/s/SporebackWolf.java +++ b/Mage.Sets/src/mage/cards/s/SporebackWolf.java @@ -29,7 +29,7 @@ public final class SporebackWolf extends CardImpl { // As long as it's your turn, Sporeback Wolf gets +0/+2. this.addAbility(new SimpleStaticAbility(new ConditionalContinuousEffect( new BoostSourceEffect(0, 2, Duration.WhileOnBattlefield), - MyTurnCondition.instance, "as long as it's your turn, {this} gets +0/+2." + MyTurnCondition.instance, "during your turn, {this} gets +0/+2." )).addHint(MyTurnHint.instance)); } diff --git a/Mage.Sets/src/mage/cards/s/Sporogenesis.java b/Mage.Sets/src/mage/cards/s/Sporogenesis.java index 2ff18d9e74d..61960d3cf7c 100644 --- a/Mage.Sets/src/mage/cards/s/Sporogenesis.java +++ b/Mage.Sets/src/mage/cards/s/Sporogenesis.java @@ -1,5 +1,6 @@ package mage.cards.s; +import mage.MageObject; import mage.abilities.Ability; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility; @@ -68,6 +69,7 @@ class SporogenesisTriggeredAbility extends TriggeredAbilityImpl { SporogenesisTriggeredAbility() { super(Zone.BATTLEFIELD, new CreateTokenEffect(new SaprolingToken(), new SporogenesisCount()), false); + setLeavesTheBattlefieldTrigger(true); } private SporogenesisTriggeredAbility(final SporogenesisTriggeredAbility ability) { @@ -104,6 +106,11 @@ class SporogenesisTriggeredAbility extends TriggeredAbilityImpl { public String getRule() { return "Whenever a creature with a fungus counter on it dies, create a 1/1 green Saproling creature token for each fungus counter on that creature."; } + + @Override + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); + } } class SporogenesisCount implements DynamicValue { diff --git a/Mage.Sets/src/mage/cards/s/SporogenicInfection.java b/Mage.Sets/src/mage/cards/s/SporogenicInfection.java index b1002ed7f2a..75c17441582 100644 --- a/Mage.Sets/src/mage/cards/s/SporogenicInfection.java +++ b/Mage.Sets/src/mage/cards/s/SporogenicInfection.java @@ -46,7 +46,8 @@ public final class SporogenicInfection extends CardImpl { this.addAbility(new EnchantAbility(auraTarget)); // When Sporogenic Infection enters, target player sacrifices a creature other than enchanted creature. - Ability ability = new EntersBattlefieldTriggeredAbility(new SacrificeEffect(filter, 1, "target player")); + Ability ability = new EntersBattlefieldTriggeredAbility(new SacrificeEffect(filter, 1, "target player") + .setText("target player sacrifices a creature of their choice other than enchanted creature")); ability.addTarget(new TargetPlayer()); this.addAbility(ability); @@ -75,6 +76,7 @@ enum SporogenicInfectionPredicate implements ObjectSourcePlayerPredicate !permanent.hasAbility(DefenderAbility.getInstance(), game)) .orElse(false); } @@ -94,7 +93,6 @@ class SurgeEngineAbility extends ActivatedAbilityImpl { private static final Condition staticCondition = (game, source) -> Optional .ofNullable(source.getSourcePermanentIfItStillExists(game)) - .filter(Objects::nonNull) .map(permanent -> permanent.getColor(game)) .map(ObjectColor::isBlue) .orElse(false); diff --git a/Mage.Sets/src/mage/cards/s/SyrKonradTheGrim.java b/Mage.Sets/src/mage/cards/s/SyrKonradTheGrim.java index 91eeb02bc3c..fad8498bc0b 100644 --- a/Mage.Sets/src/mage/cards/s/SyrKonradTheGrim.java +++ b/Mage.Sets/src/mage/cards/s/SyrKonradTheGrim.java @@ -54,6 +54,7 @@ class SyrKonradTheGrimTriggeredAbility extends TriggeredAbilityImpl { SyrKonradTheGrimTriggeredAbility() { super(Zone.BATTLEFIELD, new DamagePlayersEffect(1, TargetController.OPPONENT)); + setLeavesTheBattlefieldTrigger(true); } private SyrKonradTheGrimTriggeredAbility(final SyrKonradTheGrimTriggeredAbility ability) { @@ -95,8 +96,8 @@ class SyrKonradTheGrimTriggeredAbility extends TriggeredAbilityImpl { } @Override - public boolean isInUseableZone(Game game, MageObject source, GameEvent event) { - return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, event, game); + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); } @Override diff --git a/Mage.Sets/src/mage/cards/t/TaekoThePatientAvalanche.java b/Mage.Sets/src/mage/cards/t/TaekoThePatientAvalanche.java index 9637aaff982..7412aeb4948 100644 --- a/Mage.Sets/src/mage/cards/t/TaekoThePatientAvalanche.java +++ b/Mage.Sets/src/mage/cards/t/TaekoThePatientAvalanche.java @@ -67,6 +67,7 @@ class TaekoThePatientAvalancheTriggeredAbility extends TriggeredAbilityImpl { super(Zone.BATTLEFIELD, new ScryEffect(1, false)); this.addEffect(new AddCountersSourceEffect(CounterType.P1P1.createInstance()).concatBy("and")); this.setTriggerPhrase("Whenever another creature you control leaves the battlefield, if it didn't die, "); + setLeavesTheBattlefieldTrigger(true); } private TaekoThePatientAvalancheTriggeredAbility(final TaekoThePatientAvalancheTriggeredAbility ability) { diff --git a/Mage.Sets/src/mage/cards/t/TheGrandEvolution.java b/Mage.Sets/src/mage/cards/t/TheGrandEvolution.java index 9f79f79efca..49ae80faade 100644 --- a/Mage.Sets/src/mage/cards/t/TheGrandEvolution.java +++ b/Mage.Sets/src/mage/cards/t/TheGrandEvolution.java @@ -46,7 +46,7 @@ public final class TheGrandEvolution extends CardImpl { sagaAbility.addChapterEffect( this, SagaChapter.CHAPTER_II, SagaChapter.CHAPTER_II, new DistributeCountersEffect( - CounterType.P1P1, 7, + 7, "any number of target creatures you control" ), new TargetPermanentAmount(7, StaticFilters.FILTER_CONTROLLED_CREATURES) ); diff --git a/Mage.Sets/src/mage/cards/t/TheRollercrusherRide.java b/Mage.Sets/src/mage/cards/t/TheRollercrusherRide.java new file mode 100644 index 00000000000..1dbe303bd32 --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TheRollercrusherRide.java @@ -0,0 +1,93 @@ +package mage.cards.t; + +import java.util.UUID; + +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.common.DeliriumCondition; +import mage.abilities.dynamicvalue.common.GetXValue; +import mage.abilities.effects.ReplacementEffectImpl; +import mage.abilities.effects.common.DamageTargetEffect; +import mage.abilities.hint.common.CardTypesInGraveyardHint; +import mage.constants.*; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.game.Game; +import mage.game.events.DamageEvent; +import mage.game.events.GameEvent; +import mage.target.common.TargetCreaturePermanent; +import mage.target.targetadjustment.TargetsCountAdjuster; +import mage.util.CardUtil; + +/** + * @author Cguy7777 + */ +public final class TheRollercrusherRide extends CardImpl { + + public TheRollercrusherRide(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{X}{2}{R}"); + + this.supertype.add(SuperType.LEGENDARY); + + // Delirium -- If a source you control would deal noncombat damage to a permanent or player while there are four or more card types among cards in your graveyard, + // it deals double that damage instead. + this.addAbility(new SimpleStaticAbility(new TheRollercrusherRideEffect()) + .setAbilityWord(AbilityWord.DELIRIUM) + .addHint(CardTypesInGraveyardHint.YOU)); + + // When The Rollercrusher Ride enters, it deals X damage to each of up to X target creatures. + Ability ability = new EntersBattlefieldTriggeredAbility(new DamageTargetEffect(GetXValue.instance) + .setText("it deals X damage to each of up to X target creatures")); + ability.addTarget(new TargetCreaturePermanent(0, 0)); + ability.setTargetAdjuster(new TargetsCountAdjuster(GetXValue.instance)); + this.addAbility(ability); + } + + private TheRollercrusherRide(final TheRollercrusherRide card) { + super(card); + } + + @Override + public TheRollercrusherRide copy() { + return new TheRollercrusherRide(this); + } +} + +class TheRollercrusherRideEffect extends ReplacementEffectImpl { + + TheRollercrusherRideEffect() { + super(Duration.WhileOnBattlefield, Outcome.Benefit); + staticText = "if a source you control would deal noncombat damage to a permanent or player " + + "while there are four or more card types among cards in your graveyard, " + + "it deals double that damage instead"; + } + + private TheRollercrusherRideEffect(final TheRollercrusherRideEffect effect) { + super(effect); + } + + @Override + public boolean checksEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.DAMAGE_PERMANENT + || event.getType() == GameEvent.EventType.DAMAGE_PLAYER; + } + + @Override + public boolean applies(GameEvent event, Ability source, Game game) { + return source.isControlledBy(game.getControllerId(event.getSourceId())) + && !((DamageEvent) event).isCombatDamage() + && DeliriumCondition.instance.apply(game, source); + } + + @Override + public boolean replaceEvent(GameEvent event, Ability source, Game game) { + event.setAmount(CardUtil.overflowMultiply(event.getAmount(), 2)); + return false; + } + + @Override + public TheRollercrusherRideEffect copy() { + return new TheRollercrusherRideEffect(this); + } +} diff --git a/Mage.Sets/src/mage/cards/t/TheScorpionGod.java b/Mage.Sets/src/mage/cards/t/TheScorpionGod.java index 632c9a2b0cb..8a03884f9e0 100644 --- a/Mage.Sets/src/mage/cards/t/TheScorpionGod.java +++ b/Mage.Sets/src/mage/cards/t/TheScorpionGod.java @@ -3,6 +3,7 @@ package mage.cards.t; import java.util.UUID; import mage.MageInt; +import mage.MageObject; import mage.abilities.Ability; import mage.abilities.DelayedTriggeredAbility; import mage.abilities.TriggeredAbilityImpl; @@ -71,6 +72,7 @@ class TheScorpionGodTriggeredAbility extends TriggeredAbilityImpl { public TheScorpionGodTriggeredAbility() { super(Zone.BATTLEFIELD, new DrawCardSourceControllerEffect(1), false); + setLeavesTheBattlefieldTrigger(true); } private TheScorpionGodTriggeredAbility(final TheScorpionGodTriggeredAbility ability) { @@ -105,6 +107,11 @@ class TheScorpionGodTriggeredAbility extends TriggeredAbilityImpl { public String getRule() { return "Whenever a creature with a -1/-1 counter on it dies, draw a card."; } + + @Override + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); + } } class TheScorpionGodEffect extends OneShotEffect { diff --git a/Mage.Sets/src/mage/cards/t/TheSkullsporeNexus.java b/Mage.Sets/src/mage/cards/t/TheSkullsporeNexus.java index 530092dd1e5..ff220b1df5a 100644 --- a/Mage.Sets/src/mage/cards/t/TheSkullsporeNexus.java +++ b/Mage.Sets/src/mage/cards/t/TheSkullsporeNexus.java @@ -112,6 +112,7 @@ class TheSkullsporeNexusTrigger extends TriggeredAbilityImpl { TheSkullsporeNexusTrigger() { super(Zone.BATTLEFIELD, null, false); + setLeavesTheBattlefieldTrigger(true); } private TheSkullsporeNexusTrigger(final TheSkullsporeNexusTrigger ability) { @@ -170,8 +171,8 @@ class TheSkullsporeNexusTrigger extends TriggeredAbilityImpl { } @Override - public boolean isInUseableZone(Game game, MageObject source, GameEvent event) { - return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, event, game); + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); } } diff --git a/Mage.Sets/src/mage/cards/t/TheTaleOfTamiyo.java b/Mage.Sets/src/mage/cards/t/TheTaleOfTamiyo.java new file mode 100644 index 00000000000..af41a7f1be3 --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TheTaleOfTamiyo.java @@ -0,0 +1,186 @@ +package mage.cards.t; + +import mage.ApprovingObject; +import mage.abilities.Ability; +import mage.abilities.common.SagaAbility; +import mage.abilities.effects.OneShotEffect; +import mage.cards.*; +import mage.constants.*; +import mage.filter.FilterCard; +import mage.filter.predicate.Predicates; +import mage.game.Game; +import mage.players.Player; +import mage.target.common.TargetCardInYourGraveyard; + +import java.util.*; + +/** + * @author frafen, xenohedron + */ +public final class TheTaleOfTamiyo extends CardImpl { + + private static final FilterCard filter = new FilterCard("instant, sorcery, and/or Tamiyo planeswalker cards from your graveyard"); + + static { + filter.add(Predicates.or( + CardType.INSTANT.getPredicate(), + CardType.SORCERY.getPredicate(), + SubType.TAMIYO.getPredicate() + )); + } + + public TheTaleOfTamiyo(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{U}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.SAGA); + + // (As this Saga enters and after your draw step, add a lore counter. Sacrifice after IV.) + SagaAbility sagaAbility = new SagaAbility(this, SagaChapter.CHAPTER_IV); + + // I, II, III -- Mill two cards. If two cards that share a card type were milled this way, draw a card and repeat this process. + sagaAbility.addChapterEffect( + this, SagaChapter.CHAPTER_I, SagaChapter.CHAPTER_III, + new TheTaleOfTamiyoEffect1() + ); + + // IV -- Exile any number of target instant, sorcery, and/or Tamiyo planeswalker cards from your graveyard. Copy them. You may cast any number of the copies. + sagaAbility.addChapterEffect( + this, SagaChapter.CHAPTER_IV, + new TheTaleOfTamiyoEffect2(), + new TargetCardInYourGraveyard(0, Integer.MAX_VALUE, filter) + ); + + this.addAbility(sagaAbility); + } + + private TheTaleOfTamiyo(final TheTaleOfTamiyo card) { + super(card); + } + + @Override + public TheTaleOfTamiyo copy() { + return new TheTaleOfTamiyo(this); + } +} + +// Based on Grindstone +class TheTaleOfTamiyoEffect1 extends OneShotEffect { + + TheTaleOfTamiyoEffect1() { + super(Outcome.DrawCard); + this.staticText = "Mill two cards. If two cards that share a card type were milled this way, draw a card and repeat this process."; + } + + private TheTaleOfTamiyoEffect1(final TheTaleOfTamiyoEffect1 effect) { + super(effect); + } + + @Override + public TheTaleOfTamiyoEffect1 copy() { + return new TheTaleOfTamiyoEffect1(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller == null) { + return false; + } + + // In case of a replacement effect that would cause an infinite loop on repeating the process + int possibleIterations = controller.getLibrary().size() / 2; + int iteration = 0; + + do { + iteration++; + if (iteration > possibleIterations + 20) { + // Protection against infinity loops + game.setDraw(source.getControllerId()); + return true; + } + + // Mill 2 cards + List cards = new ArrayList<>(controller + .millCards(2, source, game) + .getCards(game)); + + // Stop if less than 2 cards were milled + if (cards.size() < 2) { + break; + } + + // Set to hold types of the first card + Set firstCardTypes = new HashSet<>(cards.get(0).getCardType()); + + // Check if there's at least one type in common + boolean typesMatch = false; + + // Iterate over the second card's types to see if there is any overlap with the first card's types + for (CardType type : cards.get(1).getCardType()) { + if (firstCardTypes.contains(type)) { + typesMatch = true; + break; + } + } + + // If there is a match, draw a card and continue; otherwise, break + if (typesMatch) { + controller.drawCards(1, source, game); + } else { + break; + } + + } while (controller.canRespond()); + + return true; + } + + +} + +class TheTaleOfTamiyoEffect2 extends OneShotEffect { + + TheTaleOfTamiyoEffect2() { + super(Outcome.PlayForFree); + this.staticText = "Exile any number of target instant, sorcery, and/or Tamiyo planeswalker cards " + + "from your graveyard. Copy them. You may cast any number of the copies."; + } + + private TheTaleOfTamiyoEffect2(final TheTaleOfTamiyoEffect2 effect) { + super(effect); + } + + @Override + public TheTaleOfTamiyoEffect2 copy() { + return new TheTaleOfTamiyoEffect2(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + Cards toExile = new CardsImpl(); + getTargetPointer().getTargets(game, source) + .stream() + .map(game::getCard) + .filter(Objects::nonNull) + .forEach(toExile::add); + if (controller == null || toExile.isEmpty()) { + return false; + } + controller.moveCards(toExile, Zone.EXILED, source, game); + toExile.retainZone(Zone.EXILED, game); + for (Card card : toExile.getCards(game)) { + if (controller.chooseUse(outcome, "Cast copy of " + card.getName() + "?", source, game)) { + Card cardCopy = game.copyCard(card, source, source.getControllerId()); + game.getState().setValue("PlayFromNotOwnHandZone" + cardCopy.getId(), Boolean.TRUE); + controller.cast( + controller.chooseAbilityForCast(cardCopy, game, false), + game, false, new ApprovingObject(source, game) + ); + game.getState().setValue("PlayFromNotOwnHandZone" + cardCopy.getId(), null); + } + } + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/t/ThrashingFrontliner.java b/Mage.Sets/src/mage/cards/t/ThrashingFrontliner.java index bdfb2ce261b..ef963b2b662 100644 --- a/Mage.Sets/src/mage/cards/t/ThrashingFrontliner.java +++ b/Mage.Sets/src/mage/cards/t/ThrashingFrontliner.java @@ -74,7 +74,6 @@ class ThrashingFrontlinerTriggeredAbility extends TriggeredAbilityImpl { .ofNullable(this.getSourceId()) .map(game.getCombat()::getDefenderId) .map(game::getPermanent) - .filter(Objects::nonNull) .map(permanent -> permanent.isBattle(game)) .orElse(false); } diff --git a/Mage.Sets/src/mage/cards/t/TianaShipsCaretaker.java b/Mage.Sets/src/mage/cards/t/TianaShipsCaretaker.java index a3bdcbb7a44..658f2bfb8c2 100644 --- a/Mage.Sets/src/mage/cards/t/TianaShipsCaretaker.java +++ b/Mage.Sets/src/mage/cards/t/TianaShipsCaretaker.java @@ -3,6 +3,7 @@ package mage.cards.t; import java.util.UUID; import mage.MageInt; +import mage.MageObject; import mage.abilities.Ability; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.common.delayed.AtTheBeginOfNextEndStepDelayedTriggeredAbility; @@ -65,6 +66,7 @@ class TianaShipsCaretakerTriggeredAbility extends TriggeredAbilityImpl { TianaShipsCaretakerTriggeredAbility() { super(Zone.BATTLEFIELD, new TianaShipsCaretakerEffect(), true); setTriggerPhrase("Whenever an Aura or Equipment you control is put into a graveyard from the battlefield, "); + setLeavesTheBattlefieldTrigger(true); } private TianaShipsCaretakerTriggeredAbility(final TianaShipsCaretakerTriggeredAbility ability) { @@ -98,6 +100,11 @@ class TianaShipsCaretakerTriggeredAbility extends TriggeredAbilityImpl { } return false; } + + @Override + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); + } } class TianaShipsCaretakerEffect extends OneShotEffect { diff --git a/Mage.Sets/src/mage/cards/u/UnchartedVoyage.java b/Mage.Sets/src/mage/cards/u/UnchartedVoyage.java index c6d5ca882f0..f3e6c98c98d 100644 --- a/Mage.Sets/src/mage/cards/u/UnchartedVoyage.java +++ b/Mage.Sets/src/mage/cards/u/UnchartedVoyage.java @@ -18,7 +18,7 @@ public final class UnchartedVoyage extends CardImpl { super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{3}{U}"); // Target creature's owner puts it on their choice of the top or bottom of their library. - this.getSpellAbility().addEffect(new PutOnTopOrBottomLibraryTargetEffect(false, true)); + this.getSpellAbility().addEffect(new PutOnTopOrBottomLibraryTargetEffect(false)); this.getSpellAbility().addTarget(new TargetCreaturePermanent()); // Surveil 1. diff --git a/Mage.Sets/src/mage/cards/u/UndercityUpheaval.java b/Mage.Sets/src/mage/cards/u/UndercityUpheaval.java index 9129b3df8a7..ea27f64da27 100644 --- a/Mage.Sets/src/mage/cards/u/UndercityUpheaval.java +++ b/Mage.Sets/src/mage/cards/u/UndercityUpheaval.java @@ -30,7 +30,7 @@ public final class UndercityUpheaval extends CardImpl { super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{1}{G}{G}"); // Undergrowth -- Distribute X +1/+1 counters among any number of target creatures you control, where X is the number of creature cards in your graveyard as you cast this spell. Creatures you control gain vigilance until end of turn. - this.getSpellAbility().addEffect(new DistributeCountersEffect(CounterType.P1P1, 1, "") + this.getSpellAbility().addEffect(new DistributeCountersEffect(1, "") .setText("distribute X +1/+1 counters among any number of target creatures you control, " + "where X is the number of creature cards in your graveyard as you cast this spell")); this.getSpellAbility().addEffect(new GainAbilityControlledEffect( diff --git a/Mage.Sets/src/mage/cards/u/UnluckyWitness.java b/Mage.Sets/src/mage/cards/u/UnluckyWitness.java index 5e984e60aa5..8181f4d3f12 100644 --- a/Mage.Sets/src/mage/cards/u/UnluckyWitness.java +++ b/Mage.Sets/src/mage/cards/u/UnluckyWitness.java @@ -124,10 +124,10 @@ class UnluckyWitnessWatcher extends Watcher { @Override public void watch(GameEvent event, Game game) { if (event.getType() != GameEvent.EventType.SPELL_CAST - || event.getAdditionalReference() == null) { + || event.getApprovingObject() == null) { return; } - MageObjectReference mor = event.getAdditionalReference().getApprovingMageObjectReference(); + MageObjectReference mor = event.getApprovingObject().getApprovingMageObjectReference(); Spell spell = game.getSpell(event.getTargetId()); if (mor == null || spell == null) { return; diff --git a/Mage.Sets/src/mage/cards/u/UrzaAcademyHeadmaster.java b/Mage.Sets/src/mage/cards/u/UrzaAcademyHeadmaster.java index 0d05ae0f168..6032b4ee8c1 100644 --- a/Mage.Sets/src/mage/cards/u/UrzaAcademyHeadmaster.java +++ b/Mage.Sets/src/mage/cards/u/UrzaAcademyHeadmaster.java @@ -144,7 +144,7 @@ class UrzaAcademyHeadmasterRandomEffect extends OneShotEffect { break; case 2: // AJANI MENTOR OF HEROES 1 sb.append("Distribute three +1/+1 counters among one, two, or three target creatures you control."); - effects.add(new DistributeCountersEffect(CounterType.P1P1, 3, false, "one, two, or three target creatures you control")); + effects.add(new DistributeCountersEffect(3, "one, two, or three target creatures you control")); target = new TargetCreaturePermanentAmount(3, filter1); break; case 3: // NICOL BOLAS PLANESWALKER 1 diff --git a/Mage.Sets/src/mage/cards/v/VerdantSuccession.java b/Mage.Sets/src/mage/cards/v/VerdantSuccession.java index b435f3b8a62..3e0b602b9e2 100644 --- a/Mage.Sets/src/mage/cards/v/VerdantSuccession.java +++ b/Mage.Sets/src/mage/cards/v/VerdantSuccession.java @@ -62,6 +62,7 @@ class VerdantSuccessionTriggeredAbility extends TriggeredAbilityImpl { VerdantSuccessionTriggeredAbility() { super(Zone.BATTLEFIELD, null, true); + setLeavesTheBattlefieldTrigger(true); } private VerdantSuccessionTriggeredAbility(final VerdantSuccessionTriggeredAbility ability) { @@ -98,6 +99,11 @@ class VerdantSuccessionTriggeredAbility extends TriggeredAbilityImpl { public String getRule() { return "Whenever a green nontoken creature dies, that creature's controller may search their library for a card with the same name as that creature, put it onto the battlefield, then shuffle."; } + + @Override + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); + } } class VerdantSuccessionEffect extends OneShotEffect { diff --git a/Mage.Sets/src/mage/cards/v/VerdurousGearhulk.java b/Mage.Sets/src/mage/cards/v/VerdurousGearhulk.java index f0b0a04f93c..b17752c6191 100644 --- a/Mage.Sets/src/mage/cards/v/VerdurousGearhulk.java +++ b/Mage.Sets/src/mage/cards/v/VerdurousGearhulk.java @@ -31,7 +31,7 @@ public final class VerdurousGearhulk extends CardImpl { this.addAbility(TrampleAbility.getInstance()); // When Verdurous Gearhulk enters the battlefield, distribute four +1/+1 counters among any number of target creatures you control. - Ability ability = new EntersBattlefieldTriggeredAbility(new DistributeCountersEffect(CounterType.P1P1, 4, false, "any number of target creatures you control"), false); + Ability ability = new EntersBattlefieldTriggeredAbility(new DistributeCountersEffect(4, "any number of target creatures you control"), false); ability.addTarget(new TargetCreaturePermanentAmount(4, StaticFilters.FILTER_CONTROLLED_CREATURES)); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/v/VesuvanShapeshifter.java b/Mage.Sets/src/mage/cards/v/VesuvanShapeshifter.java index fc7bd04d0cf..40f766221b7 100644 --- a/Mage.Sets/src/mage/cards/v/VesuvanShapeshifter.java +++ b/Mage.Sets/src/mage/cards/v/VesuvanShapeshifter.java @@ -114,7 +114,7 @@ class VesuvanShapeshifterEffect extends OneShotEffect { if (copyFromCreature != null) { game.copyPermanent(Duration.Custom, copyFromCreature, copyToCreature.getId(), source, new VesuvanShapeShifterFaceUpCopyApplier()); source.getTargets().clear(); - game.processAction(); // needed to get effects ready if copy happens in replacment and the copied abilities react of the same event (e.g. turn face up) + game.processAction(); // needed to get effects ready if copy happens in replacement and the copied abilities react of the same event (e.g. turn face up) return true; } } diff --git a/Mage.Sets/src/mage/cards/v/VileMutilator.java b/Mage.Sets/src/mage/cards/v/VileMutilator.java index a1cb99a7ef0..3c6752f4ea6 100644 --- a/Mage.Sets/src/mage/cards/v/VileMutilator.java +++ b/Mage.Sets/src/mage/cards/v/VileMutilator.java @@ -50,7 +50,7 @@ public final class VileMutilator extends CardImpl { // When Vile Mutilator enters, each opponent sacrifices a nontoken enchantment, then sacrifices a nontoken creature. Ability ability = new EntersBattlefieldTriggeredAbility(new SacrificeOpponentsEffect(filter)); - ability.addEffect(new SacrificeOpponentsEffect(filter2).setText(", then sacrifices a nontoken creature")); + ability.addEffect(new SacrificeOpponentsEffect(filter2).setText(", then sacrifices a nontoken creature of their choice")); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/v/VillageCannibals.java b/Mage.Sets/src/mage/cards/v/VillageCannibals.java index f3d9aaec997..9fb2dab04a7 100644 --- a/Mage.Sets/src/mage/cards/v/VillageCannibals.java +++ b/Mage.Sets/src/mage/cards/v/VillageCannibals.java @@ -3,6 +3,7 @@ package mage.cards.v; import java.util.UUID; import mage.MageInt; +import mage.MageObject; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.effects.common.counter.AddCountersSourceEffect; import mage.cards.CardImpl; @@ -48,6 +49,7 @@ class VillageCannibalsTriggeredAbility extends TriggeredAbilityImpl { public VillageCannibalsTriggeredAbility() { super(Zone.BATTLEFIELD, new AddCountersSourceEffect(CounterType.P1P1.createInstance())); setTriggerPhrase("Whenever another Human creature dies, "); + setLeavesTheBattlefieldTrigger(true); } private VillageCannibalsTriggeredAbility(final VillageCannibalsTriggeredAbility ability) { @@ -74,4 +76,9 @@ class VillageCannibalsTriggeredAbility extends TriggeredAbilityImpl { return permanent != null && permanent.isCreature(game) && permanent.hasSubtype(SubType.HUMAN, game) && !permanent.getId().equals(this.getSourceId()); } + + @Override + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); + } } diff --git a/Mage.Sets/src/mage/cards/v/VindictiveVampire.java b/Mage.Sets/src/mage/cards/v/VindictiveVampire.java index 89c82ab21cf..34af2943e23 100644 --- a/Mage.Sets/src/mage/cards/v/VindictiveVampire.java +++ b/Mage.Sets/src/mage/cards/v/VindictiveVampire.java @@ -57,6 +57,7 @@ class VindictiveVampireTriggeredAbility extends TriggeredAbilityImpl { public VindictiveVampireTriggeredAbility(Zone zone, Effect effect) { super(zone, effect, false); setTriggerPhrase("Whenever another creature you control dies, "); + setLeavesTheBattlefieldTrigger(true); } private VindictiveVampireTriggeredAbility(final VindictiveVampireTriggeredAbility ability) { @@ -69,17 +70,8 @@ class VindictiveVampireTriggeredAbility extends TriggeredAbilityImpl { } @Override - public boolean isInUseableZone(Game game, MageObject source, GameEvent event) { - Permanent sourcePermanent; - if (game.getState().getZone(getSourceId()) == Zone.BATTLEFIELD) { - sourcePermanent = game.getPermanent(getSourceId()); - } else { - sourcePermanent = (Permanent) game.getPermanentOrLKIBattlefield(getSourceId()); - } - if (sourcePermanent == null) { - return false; - } - return hasSourceObjectAbility(game, sourcePermanent, event); + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); } @Override diff --git a/Mage.Sets/src/mage/cards/v/ViridianRevel.java b/Mage.Sets/src/mage/cards/v/ViridianRevel.java index 79212a3654d..9606f89b43e 100644 --- a/Mage.Sets/src/mage/cards/v/ViridianRevel.java +++ b/Mage.Sets/src/mage/cards/v/ViridianRevel.java @@ -3,6 +3,8 @@ package mage.cards.v; import java.util.UUID; + +import mage.MageObject; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.effects.common.DrawCardSourceControllerEffect; import mage.cards.Card; @@ -40,8 +42,10 @@ public final class ViridianRevel extends CardImpl { } class ViridianRevelTriggeredAbility extends TriggeredAbilityImpl { + ViridianRevelTriggeredAbility() { super(Zone.BATTLEFIELD, new DrawCardSourceControllerEffect(1), true); + setLeavesTheBattlefieldTrigger(true); } private ViridianRevelTriggeredAbility(final ViridianRevelTriggeredAbility ability) { @@ -75,4 +79,9 @@ class ViridianRevelTriggeredAbility extends TriggeredAbilityImpl { public String getRule() { return "Whenever an artifact is put into an opponent's graveyard from the battlefield, you may draw a card."; } + + @Override + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); + } } \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/v/VivienArkbowRanger.java b/Mage.Sets/src/mage/cards/v/VivienArkbowRanger.java index 699ff98981f..44b54206a5a 100644 --- a/Mage.Sets/src/mage/cards/v/VivienArkbowRanger.java +++ b/Mage.Sets/src/mage/cards/v/VivienArkbowRanger.java @@ -36,7 +36,7 @@ public final class VivienArkbowRanger extends CardImpl { // +1: Distribute two +1/+1 counters among up to two target creatures. They gain trample until end of turn. Ability ability = new LoyaltyAbility(new DistributeCountersEffect( - CounterType.P1P1, 2, false, "up to two target creatures"), 1); + 2, "up to two target creatures"), 1); ability.addEffect(new GainAbilityTargetEffect( TrampleAbility.getInstance(), Duration.EndOfTurn, "They gain trample until end of turn" diff --git a/Mage.Sets/src/mage/cards/w/WarTrainedSlasher.java b/Mage.Sets/src/mage/cards/w/WarTrainedSlasher.java index 6f2e4e1aee4..b77283e273e 100644 --- a/Mage.Sets/src/mage/cards/w/WarTrainedSlasher.java +++ b/Mage.Sets/src/mage/cards/w/WarTrainedSlasher.java @@ -73,7 +73,6 @@ class WarTrainedSlasherTriggeredAbility extends TriggeredAbilityImpl { .ofNullable(this.getSourceId()) .map(game.getCombat()::getDefenderId) .map(game::getPermanent) - .filter(Objects::nonNull) .map(permanent -> permanent.isBattle(game)) .orElse(false); } diff --git a/Mage.Sets/src/mage/cards/w/WhispersteelDagger.java b/Mage.Sets/src/mage/cards/w/WhispersteelDagger.java index be011c45707..74c8976c803 100644 --- a/Mage.Sets/src/mage/cards/w/WhispersteelDagger.java +++ b/Mage.Sets/src/mage/cards/w/WhispersteelDagger.java @@ -155,10 +155,10 @@ class WhispersteelDaggerWatcher extends Watcher { @Override public void watch(GameEvent event, Game game) { if (event.getType() == GameEvent.EventType.SPELL_CAST) { - if (event.getAdditionalReference() == null) { + if (event.getApprovingObject() == null) { return; } - morMap.computeIfAbsent(event.getAdditionalReference().getApprovingMageObjectReference(), m -> new HashMap<>()) + morMap.computeIfAbsent(event.getApprovingObject().getApprovingMageObjectReference(), m -> new HashMap<>()) .computeIfAbsent(game.getOwnerId(event.getSourceId()), m -> new HashMap<>()) .compute(event.getPlayerId(), (u, i) -> i == null ? 0 : Integer.sum(i, -1)); return; diff --git a/Mage.Sets/src/mage/cards/w/WildwoodGeist.java b/Mage.Sets/src/mage/cards/w/WildwoodGeist.java index 2b8aece07af..48bac927307 100644 --- a/Mage.Sets/src/mage/cards/w/WildwoodGeist.java +++ b/Mage.Sets/src/mage/cards/w/WildwoodGeist.java @@ -11,7 +11,6 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Duration; import mage.constants.SubType; -import mage.constants.Zone; import java.util.UUID; @@ -31,7 +30,7 @@ public final class WildwoodGeist extends CardImpl { this.addAbility(new SimpleStaticAbility(new ConditionalContinuousEffect( new BoostSourceEffect(2, 2, Duration.WhileOnBattlefield), MyTurnCondition.instance, - "{this} gets +2/+2 as long as it's your turn")) + "during your turn, {this} gets +2/+2")) .addHint(MyTurnHint.instance)); } diff --git a/Mage.Sets/src/mage/cards/w/WisedraftersWill.java b/Mage.Sets/src/mage/cards/w/WisedraftersWill.java new file mode 100644 index 00000000000..6286cc9f575 --- /dev/null +++ b/Mage.Sets/src/mage/cards/w/WisedraftersWill.java @@ -0,0 +1,50 @@ +package mage.cards.w; + +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.costs.common.SacrificeSourceCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.CounterTargetEffect; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.continuous.PlayWithHandRevealedEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.TargetController; +import mage.target.TargetSpell; + +import java.util.UUID; + +/** + * @author PurpleCrowbar + */ +public final class WisedraftersWill extends CardImpl { + + public WisedraftersWill(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{U}"); + + // Your opponents play with their hands revealed. + this.addAbility(new SimpleStaticAbility(new PlayWithHandRevealedEffect(TargetController.OPPONENT))); + + // {U}, Sacrifice Wisedrafter's Will: Draw a card. + Ability ability = new SimpleActivatedAbility(new DrawCardSourceControllerEffect(1), new ManaCostsImpl<>("{U}")); + ability.addCost(new SacrificeSourceCost()); + this.addAbility(ability); + + // {U}{U}, Sacrifice Wisedrafter's Will: Counter target spell. + ability = new SimpleActivatedAbility(new CounterTargetEffect(), new ManaCostsImpl<>("{U}{U}")); + ability.addCost(new SacrificeSourceCost()); + ability.addTarget(new TargetSpell()); + this.addAbility(ability); + } + + private WisedraftersWill(final WisedraftersWill card) { + super(card); + } + + @Override + public WisedraftersWill copy() { + return new WisedraftersWill(this); + } +} diff --git a/Mage.Sets/src/mage/cards/w/Wish.java b/Mage.Sets/src/mage/cards/w/Wish.java index 2c467c3c5f4..dc430487ce8 100644 --- a/Mage.Sets/src/mage/cards/w/Wish.java +++ b/Mage.Sets/src/mage/cards/w/Wish.java @@ -121,7 +121,7 @@ class WishWatcher extends Watcher { public void watch(GameEvent event, Game game) { if ((GameEvent.EventType.SPELL_CAST.equals(event.getType()) || GameEvent.EventType.LAND_PLAYED.equals(event.getType())) && event.hasApprovingIdentifier(MageIdentifier.WishWatcher)) { - usedFrom.add(event.getAdditionalReference().getApprovingMageObjectReference()); + usedFrom.add(event.getApprovingObject().getApprovingMageObjectReference()); } } diff --git a/Mage.Sets/src/mage/cards/w/WrathOfLeknif.java b/Mage.Sets/src/mage/cards/w/WrathOfLeknif.java new file mode 100644 index 00000000000..f91fb8161bf --- /dev/null +++ b/Mage.Sets/src/mage/cards/w/WrathOfLeknif.java @@ -0,0 +1,33 @@ +package mage.cards.w; + +import mage.abilities.effects.common.DestroyAllEffect; +import mage.abilities.effects.common.UntapLandsEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.filter.StaticFilters; + +import java.util.UUID; + +/** + * @author PurpleCrowbar + */ +public final class WrathOfLeknif extends CardImpl { + + public WrathOfLeknif(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{1}{W}{W}{U}"); + + // Destroy all creatures. They can't be regenerated. Untap up to four lands you control. + this.getSpellAbility().addEffect(new DestroyAllEffect(StaticFilters.FILTER_PERMANENT_CREATURES, true)); + this.getSpellAbility().addEffect(new UntapLandsEffect(4, true, true)); + } + + private WrathOfLeknif(final WrathOfLeknif card) { + super(card); + } + + @Override + public WrathOfLeknif copy() { + return new WrathOfLeknif(this); + } +} diff --git a/Mage.Sets/src/mage/cards/w/WurmskinForger.java b/Mage.Sets/src/mage/cards/w/WurmskinForger.java index a76efa49449..88a1b282c0d 100644 --- a/Mage.Sets/src/mage/cards/w/WurmskinForger.java +++ b/Mage.Sets/src/mage/cards/w/WurmskinForger.java @@ -27,7 +27,7 @@ public final class WurmskinForger extends CardImpl { this.toughness = new MageInt(2); // When Wurmskin Forger enters the battlefield, distribute three +1/+1 counters among one, two, or three target creatures. - Ability ability = new EntersBattlefieldTriggeredAbility(new DistributeCountersEffect(CounterType.P1P1, 3, false, "one, two, or three target creatures"), false); + Ability ability = new EntersBattlefieldTriggeredAbility(new DistributeCountersEffect(3, "one, two, or three target creatures"), false); ability.addTarget(new TargetCreaturePermanentAmount(3)); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/x/XiraTheGoldenSting.java b/Mage.Sets/src/mage/cards/x/XiraTheGoldenSting.java index e892d64e088..e94568dec23 100644 --- a/Mage.Sets/src/mage/cards/x/XiraTheGoldenSting.java +++ b/Mage.Sets/src/mage/cards/x/XiraTheGoldenSting.java @@ -1,7 +1,9 @@ package mage.cards.x; import mage.MageInt; +import mage.MageObject; import mage.abilities.Ability; +import mage.abilities.TriggeredAbilityImpl; import mage.abilities.common.AttacksTriggeredAbility; import mage.abilities.common.delayed.WhenTargetDiesDelayedTriggeredAbility; import mage.abilities.effects.common.CreateDelayedTriggeredAbilityEffect; @@ -78,6 +80,7 @@ class XiraTheGoldenStingTriggeredAbility extends WhenTargetDiesDelayedTriggeredA super(new DrawCardSourceControllerEffect(1), Duration.Custom, SetTargetPointer.NONE); this.addEffect(new CreateTokenEffect(new XiraBlackInsectToken()).concatBy("and")); setTriggerPhrase("When that creature dies, if it has an egg counter on it, "); + setLeavesTheBattlefieldTrigger(true); } private XiraTheGoldenStingTriggeredAbility(final XiraTheGoldenStingTriggeredAbility ability) { @@ -100,4 +103,9 @@ class XiraTheGoldenStingTriggeredAbility extends WhenTargetDiesDelayedTriggeredA int zccdiff = game.getState().getZoneChangeCounter(mor.getSourceId()) - mor.getZoneChangeCounter(); return zccdiff > 1 || zccdiff > 0 && game.getState().getZone(mor.getSourceId()) != Zone.GRAVEYARD; } + + @Override + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); + } } diff --git a/Mage.Sets/src/mage/cards/y/YannikScavengingSentinel.java b/Mage.Sets/src/mage/cards/y/YannikScavengingSentinel.java index 1bcfeadfce7..648aa7cecc1 100644 --- a/Mage.Sets/src/mage/cards/y/YannikScavengingSentinel.java +++ b/Mage.Sets/src/mage/cards/y/YannikScavengingSentinel.java @@ -99,9 +99,8 @@ class YannikScavengingSentinelEffect extends OneShotEffect { game.addDelayedTriggeredAbility(new OnLeaveReturnExiledAbility(), source); if (game.getState().getZone(permanent.getId()) != Zone.BATTLEFIELD) { ReflexiveTriggeredAbility ability = new ReflexiveTriggeredAbility( - new DistributeCountersEffect( - CounterType.P1P1, power, false, "" - ), false, "distribute X +1/+1 counters among any number of target creatures, " + + new DistributeCountersEffect(power, ""), false, + "distribute X +1/+1 counters among any number of target creatures, " + "where X is the exiled creature's power" ); ability.addTarget(new TargetCreaturePermanentAmount(power)); diff --git a/Mage.Sets/src/mage/cards/z/ZamrielSeraphOfSteel.java b/Mage.Sets/src/mage/cards/z/ZamrielSeraphOfSteel.java index 06bfb82eccc..74d3ba631d0 100644 --- a/Mage.Sets/src/mage/cards/z/ZamrielSeraphOfSteel.java +++ b/Mage.Sets/src/mage/cards/z/ZamrielSeraphOfSteel.java @@ -47,7 +47,7 @@ public final class ZamrielSeraphOfSteel extends CardImpl { new GainAbilityControlledEffect( IndestructibleAbility.getInstance(), Duration.WhileOnBattlefield, filter - ), MyTurnCondition.instance, "as long as it's your turn, " + + ), MyTurnCondition.instance, "during your turn, " + "equipped creatures you control have indestructible" )).addHint(MyTurnHint.instance)); } diff --git a/Mage.Sets/src/mage/cards/z/ZurgoHelmsmasher.java b/Mage.Sets/src/mage/cards/z/ZurgoHelmsmasher.java index 9ca7ced514d..1768be75d1c 100644 --- a/Mage.Sets/src/mage/cards/z/ZurgoHelmsmasher.java +++ b/Mage.Sets/src/mage/cards/z/ZurgoHelmsmasher.java @@ -41,7 +41,7 @@ public final class ZurgoHelmsmasher extends CardImpl { this.addAbility(new SimpleStaticAbility( new ConditionalContinuousEffect(new GainAbilitySourceEffect(IndestructibleAbility.getInstance(), Duration.WhileOnBattlefield), MyTurnCondition.instance, - "{this} has indestructible as long as it's your turn")) + "during your turn, {this} has indestructible")) .addHint(MyTurnHint.instance)); // Whenever a creature dealt damage by Zurgo Helmsmasher this turn dies, put a +1/+1 counter on Zurgo Helmsmasher. diff --git a/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java b/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java index 81fe839e138..28e8282694e 100644 --- a/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java +++ b/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java @@ -80,6 +80,7 @@ public final class DuskmournHouseOfHorror extends ExpansionSet { cards.add(new SetCardInfo("Ethereal Armor", 7, Rarity.UNCOMMON, mage.cards.e.EtherealArmor.class)); cards.add(new SetCardInfo("Exorcise", 8, Rarity.UNCOMMON, mage.cards.e.Exorcise.class)); cards.add(new SetCardInfo("Fanatic of the Harrowing", 96, Rarity.COMMON, mage.cards.f.FanaticOfTheHarrowing.class)); + cards.add(new SetCardInfo("Fear of Abduction", 9, Rarity.UNCOMMON, mage.cards.f.FearOfAbduction.class)); cards.add(new SetCardInfo("Fear of Being Hunted", 134, Rarity.UNCOMMON, mage.cards.f.FearOfBeingHunted.class)); cards.add(new SetCardInfo("Fear of Burning Alive", 135, Rarity.UNCOMMON, mage.cards.f.FearOfBurningAlive.class)); cards.add(new SetCardInfo("Fear of Exposure", 177, Rarity.UNCOMMON, mage.cards.f.FearOfExposure.class)); @@ -103,6 +104,8 @@ public final class DuskmournHouseOfHorror extends ExpansionSet { cards.add(new SetCardInfo("Friendly Ghost", 12, Rarity.COMMON, mage.cards.f.FriendlyGhost.class)); cards.add(new SetCardInfo("Friendly Teddy", 247, Rarity.COMMON, mage.cards.f.FriendlyTeddy.class)); cards.add(new SetCardInfo("Get Out", 60, Rarity.UNCOMMON, mage.cards.g.GetOut.class)); + cards.add(new SetCardInfo("Ghost Vacuum", 248, Rarity.RARE, mage.cards.g.GhostVacuum.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Ghost Vacuum", 326, Rarity.RARE, mage.cards.g.GhostVacuum.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Give In to Violence", 101, Rarity.COMMON, mage.cards.g.GiveInToViolence.class)); cards.add(new SetCardInfo("Glimmer Seeker", 14, Rarity.UNCOMMON, mage.cards.g.GlimmerSeeker.class)); cards.add(new SetCardInfo("Glimmerburst", 62, Rarity.COMMON, mage.cards.g.Glimmerburst.class)); @@ -145,6 +148,9 @@ public final class DuskmournHouseOfHorror extends ExpansionSet { cards.add(new SetCardInfo("Manifest Dread", 189, Rarity.COMMON, mage.cards.m.ManifestDread.class)); cards.add(new SetCardInfo("Marina Vendrell's Grimoire", 64, Rarity.RARE, mage.cards.m.MarinaVendrellsGrimoire.class)); cards.add(new SetCardInfo("Marvin, Murderous Mimic", 253, Rarity.RARE, mage.cards.m.MarvinMurderousMimic.class)); + cards.add(new SetCardInfo("Meathook Massacre II", 108, Rarity.MYTHIC, mage.cards.m.MeathookMassacreII.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Meathook Massacre II", 293, Rarity.MYTHIC, mage.cards.m.MeathookMassacreII.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Meathook Massacre II", 311, Rarity.MYTHIC, mage.cards.m.MeathookMassacreII.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Miasma Demon", 109, Rarity.UNCOMMON, mage.cards.m.MiasmaDemon.class)); cards.add(new SetCardInfo("Midnight Mayhem", 222, Rarity.UNCOMMON, mage.cards.m.MidnightMayhem.class)); cards.add(new SetCardInfo("Most Valuable Slayer", 144, Rarity.COMMON, mage.cards.m.MostValuableSlayer.class)); @@ -156,6 +162,8 @@ public final class DuskmournHouseOfHorror extends ExpansionSet { cards.add(new SetCardInfo("Niko, Light of Hope", 224, Rarity.MYTHIC, mage.cards.n.NikoLightOfHope.class)); cards.add(new SetCardInfo("Norin, Swift Survivalist", 145, Rarity.UNCOMMON, mage.cards.n.NorinSwiftSurvivalist.class)); cards.add(new SetCardInfo("Oblivious Bookworm", 225, Rarity.UNCOMMON, mage.cards.o.ObliviousBookworm.class)); + cards.add(new SetCardInfo("Omnivorous Flytrap", 192, Rarity.RARE, mage.cards.o.OmnivorousFlytrap.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Omnivorous Flytrap", 322, Rarity.RARE, mage.cards.o.OmnivorousFlytrap.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Optimistic Scavenger", 21, Rarity.UNCOMMON, mage.cards.o.OptimisticScavenger.class)); cards.add(new SetCardInfo("Orphans of the Wheat", 22, Rarity.UNCOMMON, mage.cards.o.OrphansOfTheWheat.class)); cards.add(new SetCardInfo("Overlord of the Balemurk", 113, Rarity.MYTHIC, mage.cards.o.OverlordOfTheBalemurk.class)); @@ -211,7 +219,11 @@ public final class DuskmournHouseOfHorror extends ExpansionSet { cards.add(new SetCardInfo("Terramorphic Expanse", 269, Rarity.COMMON, mage.cards.t.TerramorphicExpanse.class)); cards.add(new SetCardInfo("The Jolly Balloon Man", 219, Rarity.RARE, mage.cards.t.TheJollyBalloonMan.class)); cards.add(new SetCardInfo("The Mindskinner", 66, Rarity.RARE, mage.cards.t.TheMindskinner.class)); + cards.add(new SetCardInfo("The Rollercrusher Ride", 155, Rarity.MYTHIC, mage.cards.t.TheRollercrusherRide.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("The Rollercrusher Ride", 298, Rarity.MYTHIC, mage.cards.t.TheRollercrusherRide.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("The Rollercrusher Ride", 317, Rarity.MYTHIC, mage.cards.t.TheRollercrusherRide.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("The Swarmweaver", 236, Rarity.RARE, mage.cards.t.TheSwarmweaver.class)); + cards.add(new SetCardInfo("The Tale of Tamiyo", 75, Rarity.RARE, mage.cards.t.TheTaleOfTamiyo.class)); cards.add(new SetCardInfo("The Wandering Rescuer", 41, Rarity.MYTHIC, mage.cards.t.TheWanderingRescuer.class)); cards.add(new SetCardInfo("Thornspire Verge", 270, Rarity.RARE, mage.cards.t.ThornspireVerge.class)); cards.add(new SetCardInfo("Threats Around Every Corner", 200, Rarity.UNCOMMON, mage.cards.t.ThreatsAroundEveryCorner.class)); diff --git a/Mage.Sets/src/mage/sets/DuskmournHouseOfHorrorCommander.java b/Mage.Sets/src/mage/sets/DuskmournHouseOfHorrorCommander.java index 7353d9329a1..b8d5ef137f1 100644 --- a/Mage.Sets/src/mage/sets/DuskmournHouseOfHorrorCommander.java +++ b/Mage.Sets/src/mage/sets/DuskmournHouseOfHorrorCommander.java @@ -107,6 +107,7 @@ public final class DuskmournHouseOfHorrorCommander extends ExpansionSet { cards.add(new SetCardInfo("Ezuri's Predation", 178, Rarity.RARE, mage.cards.e.EzurisPredation.class)); cards.add(new SetCardInfo("Falkenrath Noble", 140, Rarity.UNCOMMON, mage.cards.f.FalkenrathNoble.class)); cards.add(new SetCardInfo("Fate Unraveler", 141, Rarity.RARE, mage.cards.f.FateUnraveler.class)); + cards.add(new SetCardInfo("Fear of Sleep Paralysis", 12, Rarity.RARE, mage.cards.f.FearOfSleepParalysis.class)); cards.add(new SetCardInfo("Feed the Swarm", 78, Rarity.COMMON, mage.cards.f.FeedTheSwarm.class)); cards.add(new SetCardInfo("Fellwar Stone", 245, Rarity.UNCOMMON, mage.cards.f.FellwarStone.class)); cards.add(new SetCardInfo("Flooded Grove", 276, Rarity.RARE, mage.cards.f.FloodedGrove.class)); diff --git a/Mage.Sets/src/mage/sets/Legends.java b/Mage.Sets/src/mage/sets/Legends.java index 2800ea8176c..e3a1b69ff3a 100644 --- a/Mage.Sets/src/mage/sets/Legends.java +++ b/Mage.Sets/src/mage/sets/Legends.java @@ -107,6 +107,7 @@ public final class Legends extends ExpansionSet { cards.add(new SetCardInfo("Feint", 146, Rarity.COMMON, mage.cards.f.Feint.class)); cards.add(new SetCardInfo("Field of Dreams", 55, Rarity.RARE, mage.cards.f.FieldOfDreams.class)); cards.add(new SetCardInfo("Fire Sprites", 186, Rarity.COMMON, mage.cards.f.FireSprites.class)); + cards.add(new SetCardInfo("Firestorm Phoenix", 145, Rarity.RARE, mage.cards.f.FirestormPhoenix.class)); cards.add(new SetCardInfo("Flash Counter", 56, Rarity.COMMON, mage.cards.f.FlashCounter.class)); cards.add(new SetCardInfo("Flash Flood", 57, Rarity.COMMON, mage.cards.f.FlashFlood.class)); cards.add(new SetCardInfo("Floral Spuzzem", 187, Rarity.UNCOMMON, mage.cards.f.FloralSpuzzem.class)); diff --git a/Mage.Sets/src/mage/sets/MastersEditionIII.java b/Mage.Sets/src/mage/sets/MastersEditionIII.java index 481db42e98c..d2cfe1f52b1 100644 --- a/Mage.Sets/src/mage/sets/MastersEditionIII.java +++ b/Mage.Sets/src/mage/sets/MastersEditionIII.java @@ -90,6 +90,7 @@ public final class MastersEditionIII extends ExpansionSet { cards.add(new SetCardInfo("Fire Ambush", 97, Rarity.COMMON, mage.cards.f.FireAmbush.class)); cards.add(new SetCardInfo("Fire Drake", 98, Rarity.COMMON, mage.cards.f.FireDrake.class)); cards.add(new SetCardInfo("Fire Sprites", 118, Rarity.COMMON, mage.cards.f.FireSprites.class)); + cards.add(new SetCardInfo("Firestorm Phoenix", 99, Rarity.RARE, mage.cards.f.FirestormPhoenix.class)); cards.add(new SetCardInfo("Flash Flood", 35, Rarity.UNCOMMON, mage.cards.f.FlashFlood.class)); cards.add(new SetCardInfo("Forced Retreat", 37, Rarity.COMMON, mage.cards.f.ForcedRetreat.class)); cards.add(new SetCardInfo("Force Spike", 36, Rarity.COMMON, mage.cards.f.ForceSpike.class)); @@ -254,4 +255,4 @@ public final class MastersEditionIII extends ExpansionSet { cards.add(new SetCardInfo("Zhang Fei, Fierce Warrior", 28, Rarity.UNCOMMON, mage.cards.z.ZhangFeiFierceWarrior.class)); cards.add(new SetCardInfo("Zodiac Dragon", 112, Rarity.RARE, mage.cards.z.ZodiacDragon.class)); } -} \ No newline at end of file +} diff --git a/Mage.Sets/src/mage/sets/MysteryBooster2.java b/Mage.Sets/src/mage/sets/MysteryBooster2.java index 74fcfd4e4e4..3a97c5f3c87 100644 --- a/Mage.Sets/src/mage/sets/MysteryBooster2.java +++ b/Mage.Sets/src/mage/sets/MysteryBooster2.java @@ -107,6 +107,7 @@ public class MysteryBooster2 extends ExpansionSet { cards.add(new SetCardInfo("Gitaxian Probe", 28, Rarity.COMMON, mage.cards.g.GitaxianProbe.class)); cards.add(new SetCardInfo("Giver of Runes", 147, Rarity.RARE, mage.cards.g.GiverOfRunes.class)); cards.add(new SetCardInfo("Gix, Yawgmoth Praetor", 245, Rarity.MYTHIC, mage.cards.g.GixYawgmothPraetor.class)); + cards.add(new SetCardInfo("Gobland", 374, Rarity.RARE, mage.cards.g.Gobland.class)); cards.add(new SetCardInfo("Goblin Charbelcher", 221, Rarity.RARE, mage.cards.g.GoblinCharbelcher.class)); cards.add(new SetCardInfo("Goblin Gang Leader", 144, Rarity.UNCOMMON, mage.cards.g.GoblinGangLeader.class)); cards.add(new SetCardInfo("Goblin Guide", 58, Rarity.RARE, mage.cards.g.GoblinGuide.class)); @@ -126,6 +127,7 @@ public class MysteryBooster2 extends ExpansionSet { cards.add(new SetCardInfo("Hunting Cheetah", 134, Rarity.COMMON, mage.cards.h.HuntingCheetah.class)); cards.add(new SetCardInfo("Hydroblast", 165, Rarity.COMMON, mage.cards.h.Hydroblast.class)); cards.add(new SetCardInfo("Ice-Fang Coatl", 83, Rarity.RARE, mage.cards.i.IceFangCoatl.class)); + cards.add(new SetCardInfo("Indicate", 265, Rarity.RARE, mage.cards.i.Indicate.class)); cards.add(new SetCardInfo("Iona, Shield of Emeria", 12, Rarity.MYTHIC, mage.cards.i.IonaShieldOfEmeria.class)); cards.add(new SetCardInfo("Isochron Scepter", 96, Rarity.UNCOMMON, mage.cards.i.IsochronScepter.class)); cards.add(new SetCardInfo("Jace Beleren", 29, Rarity.MYTHIC, mage.cards.j.JaceBeleren.class)); @@ -160,6 +162,7 @@ public class MysteryBooster2 extends ExpansionSet { cards.add(new SetCardInfo("Mirri's Guile", 209, Rarity.RARE, mage.cards.m.MirrisGuile.class)); cards.add(new SetCardInfo("Mirri, Weatherlight Duelist", 252, Rarity.MYTHIC, mage.cards.m.MirriWeatherlightDuelist.class)); cards.add(new SetCardInfo("Mishra's Bauble", 97, Rarity.UNCOMMON, mage.cards.m.MishrasBauble.class)); + cards.add(new SetCardInfo("Mox Poison", 369, Rarity.RARE, mage.cards.m.MoxPoison.class)); cards.add(new SetCardInfo("Multani, Yavimaya's Avatar", 248, Rarity.MYTHIC, mage.cards.m.MultaniYavimayasAvatar.class)); cards.add(new SetCardInfo("Mutilate", 45, Rarity.RARE, mage.cards.m.Mutilate.class)); cards.add(new SetCardInfo("Mystic Sanctuary", 110, Rarity.COMMON, mage.cards.m.MysticSanctuary.class)); @@ -277,10 +280,12 @@ public class MysteryBooster2 extends ExpansionSet { cards.add(new SetCardInfo("Wasteland", 115, Rarity.UNCOMMON, mage.cards.w.Wasteland.class)); cards.add(new SetCardInfo("Whiteout", 77, Rarity.COMMON, mage.cards.w.Whiteout.class)); cards.add(new SetCardInfo("Winds of Change", 201, Rarity.UNCOMMON, mage.cards.w.WindsOfChange.class)); + cards.add(new SetCardInfo("Wisedrafter's Will", 303, Rarity.RARE, mage.cards.w.WisedraftersWill.class)); cards.add(new SetCardInfo("Wish", 64, Rarity.RARE, mage.cards.w.Wish.class)); cards.add(new SetCardInfo("Wishclaw Talisman", 51, Rarity.RARE, mage.cards.w.WishclawTalisman.class)); cards.add(new SetCardInfo("Worst Fears", 52, Rarity.MYTHIC, mage.cards.w.WorstFears.class)); cards.add(new SetCardInfo("Wowzer, the Aspirational", 365, Rarity.RARE, mage.cards.w.WowzerTheAspirational.class)); + cards.add(new SetCardInfo("Wrath of Leknif", 366, Rarity.RARE, mage.cards.w.WrathOfLeknif.class)); cards.add(new SetCardInfo("Xantcha, Sleeper Agent", 253, Rarity.RARE, mage.cards.x.XantchaSleeperAgent.class)); cards.add(new SetCardInfo("Yorion, Sky Nomad", 94, Rarity.RARE, mage.cards.y.YorionSkyNomad.class)); cards.add(new SetCardInfo("Zombie Master", 188, Rarity.RARE, mage.cards.z.ZombieMaster.class)); diff --git a/Mage.Tests/pom.xml b/Mage.Tests/pom.xml index 86e99e326e8..76334a3fa9e 100644 --- a/Mage.Tests/pom.xml +++ b/Mage.Tests/pom.xml @@ -6,7 +6,7 @@ org.mage mage-root - 1.4.54 + 1.4.55 mage-tests diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/ConniveTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/ConniveTest.java index 540539aa1c9..da8b6749bd8 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/ConniveTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/ConniveTest.java @@ -239,7 +239,7 @@ public class ConniveTest extends CardTestPlayerBase { addCard(Zone.BATTLEFIELD, playerA, "Swamp", 1); addCard(Zone.HAND, playerA, "Grizzly Bears", 1); // - addCustomEffect_DestroyTarget(playerA); + addCustomEffect_TargetDestroy(playerA); addCard(Zone.BATTLEFIELD, playerA, "Razorclaw Bear", 1); addCard(Zone.BATTLEFIELD, playerA, "Silvercoat Lion", 1); @@ -267,7 +267,7 @@ public class ConniveTest extends CardTestPlayerBase { addCard(Zone.BATTLEFIELD, playerA, "Swamp", 1); addCard(Zone.HAND, playerA, "Grizzly Bears", 1); // - addCustomEffect_DestroyTarget(playerA); + addCustomEffect_TargetDestroy(playerA); addCard(Zone.BATTLEFIELD, playerA, "Razorclaw Bear", 1); addCard(Zone.BATTLEFIELD, playerA, "Silvercoat Lion", 1); @@ -323,7 +323,7 @@ public class ConniveTest extends CardTestPlayerBase { // addCard(Zone.BATTLEFIELD, playerA, "Silvercoat Lion", 1); // - addCustomEffect_DestroyTarget(playerA); + addCustomEffect_TargetDestroy(playerA); // connive lion castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Change of Plans"); @@ -350,7 +350,7 @@ public class ConniveTest extends CardTestPlayerBase { addCard(Zone.BATTLEFIELD, playerA, "Razorclaw Bear", 1); addCard(Zone.BATTLEFIELD, playerA, "Silvercoat Lion", 1); // - addCustomEffect_DestroyTarget(playerA); + addCustomEffect_TargetDestroy(playerA); // connive lion castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Change of Plans"); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/ExploitTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/ExploitTest.java index 941fcf4ab14..a9a210b6156 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/ExploitTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/ExploitTest.java @@ -61,15 +61,16 @@ public class ExploitTest extends CardTestPlayerBase { addCard(Zone.HAND, playerB, "Lightning Bolt", 1); addCard(Zone.BATTLEFIELD, playerB, "Thundering Giant"); // 4/3 - setStrictChooseMode(true); - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Silumgar Butcher"); waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, 1); setChoice(playerA, true); // Choose to exploit setChoice(playerA, "Silvercoat Lion"); // sacrifice to Exploit + // kill butcher before exploit trigger resolve, so no exploits trigger with target + // if you failed here then something wrong with isInUseableZone castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Lightning Bolt", "Silumgar Butcher"); + setStrictChooseMode(true); setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/IncubateTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/IncubateTest.java index 8bccbf9446b..f2e12c3e5e0 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/IncubateTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/IncubateTest.java @@ -46,7 +46,7 @@ public class IncubateTest extends CardTestPlayerBase { @Test public void test_Transform_Custom() { // target transform - addCustomEffect_TransformTarget(playerA); + addCustomEffect_TargetTransform(playerA); // Alluring Suitor, 2/3 // Deadly Dancer, 3/3 @@ -74,7 +74,7 @@ public class IncubateTest extends CardTestPlayerBase { @Test public void test_Transform_IncubatorToken() { // target transform - addCustomEffect_TransformTarget(playerA); + addCustomEffect_TargetTransform(playerA); // Incubate 3. (Create an Incubator token with three +1/+1 counters on it and “{2}: Transform this artifact.” // It transforms into a 0/0 Phyrexian artifact creature.) @@ -115,9 +115,9 @@ public class IncubateTest extends CardTestPlayerBase { // use case: copy one side, can't tranform // target transform - addCustomEffect_TransformTarget(playerA); + addCustomEffect_TargetTransform(playerA); // target destroy - addCustomEffect_DestroyTarget(playerA); + addCustomEffect_TargetDestroy(playerA); // Incubate 3. (Create an Incubator token with three +1/+1 counters on it and “{2}: Transform this artifact.” // It transforms into a 0/0 Phyrexian artifact creature.) @@ -169,9 +169,9 @@ public class IncubateTest extends CardTestPlayerBase { // use case: copy one side, can't tranform // target transform - addCustomEffect_TransformTarget(playerA); + addCustomEffect_TargetTransform(playerA); // target destroy - addCustomEffect_DestroyTarget(playerA); + addCustomEffect_TargetDestroy(playerA); // Incubate 3. (Create an Incubator token with three +1/+1 counters on it and “{2}: Transform this artifact.” // It transforms into a 0/0 Phyrexian artifact creature.) diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/RecoverTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/RecoverTest.java index 9cd0ae1aa3a..5984367ae39 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/RecoverTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/RecoverTest.java @@ -1,4 +1,3 @@ - package org.mage.test.cards.abilities.keywords; import mage.constants.PhaseStep; @@ -7,8 +6,7 @@ import org.junit.Test; import org.mage.test.serverside.base.CardTestPlayerBase; /** - * - * @author LevelX2 + * @author LevelX2, JayDi85 */ public class RecoverTest extends CardTestPlayerBase { @@ -20,7 +18,7 @@ public class RecoverTest extends CardTestPlayerBase { * Otherwise, exile this card.” */ @Test - public void testReturnToHand() { + public void test_Normal_ToHand() { addCard(Zone.BATTLEFIELD, playerA, "Plains", 4); // You gain 4 life. // Recover {1}{W} @@ -35,6 +33,7 @@ public class RecoverTest extends CardTestPlayerBase { castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerB, "Lightning Bolt", "Silvercoat Lion"); setChoice(playerA, true); + setStrictChooseMode(true); setStopAt(1, PhaseStep.END_TURN); execute(); @@ -49,7 +48,7 @@ public class RecoverTest extends CardTestPlayerBase { } @Test - public void testGoingToExile() { + public void test_Normal_ToExile() { addCard(Zone.BATTLEFIELD, playerA, "Plains", 4); // You gain 4 life. // Recover {1}{W} @@ -64,6 +63,7 @@ public class RecoverTest extends CardTestPlayerBase { castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerB, "Lightning Bolt", "Silvercoat Lion"); setChoice(playerA, false); + setStrictChooseMode(true); setStopAt(1, PhaseStep.END_TURN); execute(); @@ -74,6 +74,111 @@ public class RecoverTest extends CardTestPlayerBase { assertGraveyardCount(playerA, "Silvercoat Lion", 1); assertLife(playerA, 24); + } + @Test + public void test_DieOther_Single_CanRecover() { + addCustomEffect_TargetDestroy(playerA, 1); + + // Recover—Pay half your life, rounded up. + addCard(Zone.GRAVEYARD, playerA, "Garza's Assassin"); + addCard(Zone.BATTLEFIELD, playerA, "Silvercoat Lion", 1); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "target destroy"); + addTarget(playerA, "Silvercoat Lion"); + setChoice(playerA, true); // pay half life + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertHandCount(playerA, "Garza's Assassin", 1); // after recover + assertLife(playerA, 20 / 2); + } + + @Test + public void test_DieOther_Multiple_CanRecover() { + // ruling from wiki: + // If multiple creatures are put into your graveyard from the battlefield at the same time, the recover + // ability of a card already in your graveyard triggers that many times. Only the first one to resolve + // will cause the card to move somewhere. By the time any of the other triggers resolve, the card won't be + // in your graveyard anymore. You can still pay the recover cost, but nothing else will happen. + + addCustomEffect_TargetDestroy(playerA, 2); + + // Recover—Pay half your life, rounded up. + addCard(Zone.GRAVEYARD, playerA, "Garza's Assassin"); + addCard(Zone.BATTLEFIELD, playerA, "Silvercoat Lion", 1); + addCard(Zone.BATTLEFIELD, playerA, "Grizzly Bears", 1); + + // raise 2 recover triggers, pay second trigger - it will be fizzled + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "target destroy"); + addTarget(playerA, "Silvercoat Lion^Grizzly Bears"); + setChoice(playerA, "Recover—Pay half your life"); // x2 triggers order + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, 1); + checkStackObject("on recover triggers", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Recover—Pay half your life", 2); + setChoice(playerA, false); // first trigger resolve - do not pay and exile + setChoice(playerA, true); // second trigger resolve - pay and fizzle + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertExileCount(playerA, "Garza's Assassin", 1); // after first unpayed trigger + assertLife(playerA, 20 / 2); // after second unpayed trigger + } + + @Test + public void test_DieItself_MustNotWork() { + // ruling from wiki: + // If a creature with recover is put into your graveyard from the battlefield, it doesn't cause its + // own recover ability to trigger. Similarly, if another creature is put into your graveyard from + // the battlefield at the same time that a card with recover is put there, it won't cause that + // recover ability to trigger. + + addCustomEffect_TargetDestroy(playerA, 1); + + // Recover—Pay half your life, rounded up. + addCard(Zone.BATTLEFIELD, playerA, "Garza's Assassin"); + + // no recover + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "target destroy"); + addTarget(playerA, "Garza's Assassin"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertGraveyardCount(playerA, "Garza's Assassin", 1); + assertLife(playerA, 20); + } + + @Test + public void test_DieItselfAndMultiple_MustNotWork() { + // ruling from wiki: + // If a creature with recover is put into your graveyard from the battlefield, it doesn't cause its + // own recover ability to trigger. Similarly, if another creature is put into your graveyard from + // the battlefield at the same time that a card with recover is put there, it won't cause that + // recover ability to trigger. + + // reason: it's leaves-the-battlefield trigger and look back in time (source was on battlefield in that time, so no trigger) + + addCustomEffect_TargetDestroy(playerA, 2); + + // Recover—Pay half your life, rounded up. + addCard(Zone.BATTLEFIELD, playerA, "Garza's Assassin"); + addCard(Zone.BATTLEFIELD, playerA, "Silvercoat Lion", 1); + + // no recover (if you catch recover dialog then something wrong with isInUseableZone) + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "target destroy"); + addTarget(playerA, "Garza's Assassin^Silvercoat Lion"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertGraveyardCount(playerA, "Garza's Assassin", 1); + assertGraveyardCount(playerA, "Silvercoat Lion", 1); + assertLife(playerA, 20); } } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/copy/PhantasmalImageTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/copy/PhantasmalImageTest.java index 3b066bf2145..75ef87401b0 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/copy/PhantasmalImageTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/copy/PhantasmalImageTest.java @@ -668,4 +668,66 @@ public class PhantasmalImageTest extends CardTestPlayerBase { assertTrue("Cloak and Dagger should be a Rogue", cloakB.hasSubtype(SubType.ROGUE, currentGame)); assertTrue("Cloak and Dagger should be an Equipment", cloakB.hasSubtype(SubType.EQUIPMENT, currentGame)); } + + @Test + public void test_SelfExploit_SidisiUndeadVizier_Normal() { + // bug https://github.com/magefree/mage/issues/5925 + // You may have Phantasmal Image enter the battlefield as a copy of any creature on the battlefield, + // except it's an Illusion in addition to its other types and it has "When this creature becomes the + // target of a spell or ability, sacrifice it." + addCard(Zone.HAND, playerA, "Phantasmal Image"); // {1}{U} + addCard(Zone.BATTLEFIELD, playerA, "Island", 2); + // + // Exploit (When this creature enters the battlefield, you may sacrifice a creature.) + // When Sidisi, Undead Vizier exploits a creature, you may search your library for a card, put it into your hand, then shuffle your library. + addCard(Zone.BATTLEFIELD, playerA, "Sidisi, Undead Vizier"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Phantasmal Image"); + setChoice(playerA, true); // use copy + setChoice(playerA, "Sidisi, Undead Vizier"); // to copy + setChoice(playerA, "Sidisi, Undead Vizier[only copy]"); // legendary rule, keep copy + setChoice(playerA, true); // use exploit on etb + setChoice(playerA, "Sidisi, Undead Vizier[only copy]"); // sacrifice itself on exploit + setChoice(playerA, true); // use exploit trigger (search lib) + addTarget(playerA, "Mountain"); // tutor mountain + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertHandCount(playerA, "Mountain", 1); + } + + @Test + public void test_SelfExploit_SidisiUndeadVizier_Exile() { + // exploit look for sacrifice only, not a dies conditional - so it must work with exile replace + + // You may have Phantasmal Image enter the battlefield as a copy of any creature on the battlefield, + // except it's an Illusion in addition to its other types and it has "When this creature becomes the + // target of a spell or ability, sacrifice it." + addCard(Zone.HAND, playerA, "Phantasmal Image"); // {1}{U} + addCard(Zone.BATTLEFIELD, playerA, "Island", 2); + // + // Exploit (When this creature enters the battlefield, you may sacrifice a creature.) + // When Sidisi, Undead Vizier exploits a creature, you may search your library for a card, put it into your hand, then shuffle your library. + addCard(Zone.BATTLEFIELD, playerA, "Sidisi, Undead Vizier"); + // + // If a card or token would be put into a graveyard from anywhere, exile it instead. + addCard(Zone.BATTLEFIELD, playerA, "Rest in Peace"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Phantasmal Image"); + setChoice(playerA, true); // use copy + setChoice(playerA, "Sidisi, Undead Vizier"); // to copy + setChoice(playerA, "Sidisi, Undead Vizier[only copy]"); // legendary rule, keep copy + setChoice(playerA, true); // use exploit on etb + setChoice(playerA, "Sidisi, Undead Vizier[only copy]"); // sacrifice itself on exploit + setChoice(playerA, true); // use exploit trigger (search lib) + addTarget(playerA, "Mountain"); // tutor mountain + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertHandCount(playerA, "Mountain", 1); + } } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/replacement/ZoneChangeReplacementTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/replacement/ZoneChangeReplacementTest.java index fbc6887c764..cd1c006c3d1 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/replacement/ZoneChangeReplacementTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/replacement/ZoneChangeReplacementTest.java @@ -1,4 +1,3 @@ - package org.mage.test.cards.replacement; import mage.constants.PhaseStep; @@ -384,4 +383,4 @@ public class ZoneChangeReplacementTest extends CardTestPlayerBase { assertPermanentCount(playerA, "Anafenza, the Foremost", 0); assertGraveyardCount(playerA, "Anafenza, the Foremost", 1); } -} +} \ No newline at end of file diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/c18/GeodeGolemTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/c18/GeodeGolemTest.java index 555d9793b02..fe28717b08c 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/c18/GeodeGolemTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/c18/GeodeGolemTest.java @@ -136,7 +136,7 @@ public class GeodeGolemTest extends CardTestCommanderDuelBase { addCard(Zone.BATTLEFIELD, playerA, "Mountain", 10); // addCustomEffect_TargetDamage(playerA, 3); - addCustomEffect_DestroyTarget(playerA); + addCustomEffect_TargetDestroy(playerA); checkCommandCardCount("before 1", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Birgi, God of Storytelling", 1); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/dmc/VerrakWarpedSengirTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/dmc/VerrakWarpedSengirTest.java index 1df34463bf4..50823474d0f 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/dmc/VerrakWarpedSengirTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/dmc/VerrakWarpedSengirTest.java @@ -116,4 +116,25 @@ public class VerrakWarpedSengirTest extends CardTestPlayerBase { assertLife(playerA, 20 - 2 * 2 + 2 * 2); // x2 pays, x2 gains assertLife(playerB, 20 - 2 * 2); // x2 lose } + + @Test + public void test_MustNotTriggerOnDiscardCost() { + // bug: https://github.com/magefree/mage/issues/12089 + + // Whenever you activate an ability that isn’t a mana ability, if life was paid to activate it, + // you may pay that much life again. If you do, copy that ability. You may choose new targets for the copy. + addCard(Zone.BATTLEFIELD, playerA, "Verrak, Warped Sengir"); + // + // Pay 2 life, Sacrifice another creature: Search your library for a card, put that card into your hand, then shuffle. + addCard(Zone.BATTLEFIELD, playerA, "Razaketh, the Foulblooded"); + + // activate without copy trigger (discard cost pay will remove Verrak before activate the ability) + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Pay 2 life, Sacrifice"); + setChoice(playerA, "Verrak, Warped Sengir"); // sacrifice cost + addTarget(playerA, "Mountain"); // search lib + + setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); + setStrictChooseMode(true); + execute(); + } } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/m19/AjanisLastStandTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/m19/AjanisLastStandTest.java new file mode 100644 index 00000000000..8fa520e87af --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/m19/AjanisLastStandTest.java @@ -0,0 +1,57 @@ +package org.mage.test.cards.single.m19; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author JayDi85 + */ +public class AjanisLastStandTest extends CardTestPlayerBase { + + @Test + public void test_TriggerOnAlive() { + addCustomEffect_TargetDestroy(playerA); + + // Whenever a creature or planeswalker you control dies, you may sacrifice Ajani's Last Stand. + // If you do, create a 4/4 white Avatar creature token with flying. + addCard(Zone.BATTLEFIELD, playerA, "Ajani's Last Stand"); + // + addCard(Zone.BATTLEFIELD, playerA, "Grizzly Bears"); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "target destroy"); + addTarget(playerA, "Grizzly Bears"); // to destroy + setChoice(playerA, true); // yes to sacrifice + + setStopAt(1, PhaseStep.END_TURN); + setStrictChooseMode(true); + execute(); + + assertGraveyardCount(playerA, "Ajani's Last Stand", 1); + assertGraveyardCount(playerA, "Grizzly Bears", 1); + assertTokenCount(playerA, "Avatar Token", 1); + } + + @Test + public void test_NoTriggerOnSelfDies() { + addCustomEffect_AllDestroy(playerA); + + // Whenever a creature or planeswalker you control dies, you may sacrifice Ajani's Last Stand. + // If you do, create a 4/4 white Avatar creature token with flying. + addCard(Zone.BATTLEFIELD, playerA, "Ajani's Last Stand"); + // + addCard(Zone.BATTLEFIELD, playerA, "Grizzly Bears"); + + // destroy all without triggers (cause no sacrifice cost can be pay here) + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "all destroy"); + + setStopAt(1, PhaseStep.END_TURN); + setStrictChooseMode(true); + execute(); + + assertGraveyardCount(playerA, "Ajani's Last Stand", 1); + assertGraveyardCount(playerA, "Grizzly Bears", 1); + assertTokenCount(playerA, "Avatar Token", 0); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/mh2/DauthiVoidwalkerTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/mh2/DauthiVoidwalkerTest.java index 7c81f698159..4f7b3d4d225 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/mh2/DauthiVoidwalkerTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/mh2/DauthiVoidwalkerTest.java @@ -73,4 +73,28 @@ public class DauthiVoidwalkerTest extends CardTestPlayerBase { assertLife(playerA, 20); assertLife(playerB, 20 - 3); } + + @Test + public void test_MakeSureNoTriggerInWrongZones() { + // bug report: it triggered in library + // https://github.com/magefree/mage/issues/13089 + + // If a card would be put into an opponent's graveyard from anywhere, instead exile it with a void counter on it. + // {T}, Sacrifice Dauthi Voidwalker: Choose an exiled card an opponent owns with a void counter on it. You may play it this turn without paying its mana cost. + addCard(Zone.HAND, playerA, "Dauthi Voidwalker", 1); + // + addCard(Zone.BATTLEFIELD, playerB, "Balduvian Bears", 1); + // + addCard(Zone.HAND, playerA, "Lightning Bolt"); + addCard(Zone.BATTLEFIELD, playerA, "Mountain"); + + // kill B's creature without triggers + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", "Balduvian Bears"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + checkGraveyardCount("after kill", 1, PhaseStep.PRECOMBAT_MAIN, playerB, "Balduvian Bears", 1); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + } } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/mh3/GluttonousHellkiteTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/mh3/GluttonousHellkiteTest.java index 9558b74aecc..4c3b4dca6bb 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/mh3/GluttonousHellkiteTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/mh3/GluttonousHellkiteTest.java @@ -112,7 +112,7 @@ public class GluttonousHellkiteTest extends CardTestPlayerBase { @Test public void test_CastWithSac_SacFullAndBlink() { - addCustomEffect_BlinkTarget(playerA); + addCustomEffect_TargetBlink(playerA); // When you cast this spell, each player sacrifices X creatures. Gluttonous Hellkite enters the battlefield // with two +1/+1 counters on it for each creature sacrificed this way. diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/triggers/dies/ChronozoaTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/dies/ChronozoaTest.java index 4949f349a9c..19ebc89e380 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/triggers/dies/ChronozoaTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/dies/ChronozoaTest.java @@ -23,7 +23,7 @@ public class ChronozoaTest extends CardTestPlayerBase { addCard(Zone.BATTLEFIELD, playerA, "Island", 4); // Flying // Vanishing 3 (This permanent enters the battlefield with three time counters on it. At the beginning of your upkeep, remove a time counter from it. When the last is removed, sacrifice it.) - // When Chronozoa is put into a graveyard from play, if it had no time counters on it, create two tokens that are copies of it. + // When Chronozoa dies, if it had no time counters on it, create two tokens that are copies of it. addCard(Zone.HAND, playerA, "Chronozoa"); // {3}{U} addCard(Zone.GRAVEYARD, playerA, "Chronozoa"); // Sacrifice a creature: Scry 1. (To scry 1, look at the top card of your library, then you may put that card on the bottom of your library.) diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/triggers/dies/SacrificeDiesTriggerTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/dies/SacrificeDiesTriggerTest.java index dd6088f56ff..eec48b69efa 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/triggers/dies/SacrificeDiesTriggerTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/dies/SacrificeDiesTriggerTest.java @@ -314,7 +314,7 @@ public class SacrificeDiesTriggerTest extends CardTestPlayerBase { @Test public void test_MultiModesDiesTrigger_ByDamage() { - addCustomEffect_BlinkTarget(playerA); + addCustomEffect_TargetBlink(playerA); // When Junji, the Midnight Sky dies, choose one — // • Each opponent discards two cards and loses 2 life. @@ -346,7 +346,7 @@ public class SacrificeDiesTriggerTest extends CardTestPlayerBase { @Test public void test_MultiModesDiesTrigger_BySacrificeCost() { - addCustomEffect_BlinkTarget(playerA); + addCustomEffect_TargetBlink(playerA); // When Junji, the Midnight Sky dies, choose one — // • Each opponent discards two cards and loses 2 life. diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/triggers/dies/SeerOfStolenSightTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/dies/SeerOfStolenSightTest.java new file mode 100644 index 00000000000..8a0fee2735e --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/dies/SeerOfStolenSightTest.java @@ -0,0 +1,81 @@ +package org.mage.test.cards.triggers.dies; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author JayDi85 + */ + +public class SeerOfStolenSightTest extends CardTestPlayerBase { + + @Test + public void test_SingleDie() { + skipInitShuffling(); + addCustomEffect_TargetDestroy(playerA); + + // Whenever one or more artifacts and/or creatures you control are put into a graveyard from the battlefield, surveil 1. + addCard(Zone.BATTLEFIELD, playerA, "Seer of Stolen Sight"); + addCard(Zone.LIBRARY, playerA, "Swamp", 2); + // + addCard(Zone.BATTLEFIELD, playerA, "Grizzly Bears"); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "target destroy", "Grizzly Bears"); + addTarget(playerA, "Swamp"); // surveil + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertGraveyardCount(playerA, "Grizzly Bears", 1); + assertGraveyardCount(playerA, "Swamp", 1); + } + + @Test + public void test_MultipleDies() { + skipInitShuffling(); + addCustomEffect_TargetDestroy(playerA, 2); + + // Whenever one or more artifacts and/or creatures you control are put into a graveyard from the battlefield, surveil 1. + addCard(Zone.BATTLEFIELD, playerA, "Seer of Stolen Sight"); + addCard(Zone.LIBRARY, playerA, "Swamp", 2); + // + addCard(Zone.BATTLEFIELD, playerA, "Grizzly Bears", 2); + + // must catch only one time trigger + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "target destroy", "Grizzly Bears^Grizzly Bears"); + addTarget(playerA, "Swamp"); // surveil + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertGraveyardCount(playerA, "Grizzly Bears", 2); + assertGraveyardCount(playerA, "Swamp", 1); + } + + @Test + public void test_OwnDie() { + skipInitShuffling(); + addCustomEffect_TargetDestroy(playerA); + + // Whenever one or more artifacts and/or creatures you control are put into a graveyard from the battlefield, surveil 1. + addCard(Zone.BATTLEFIELD, playerA, "Seer of Stolen Sight"); + addCard(Zone.LIBRARY, playerA, "Swamp", 2); + // + addCard(Zone.BATTLEFIELD, playerA, "Grizzly Bears", 1); + + // must trigger on own die + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "target destroy", "Seer of Stolen Sight"); + addTarget(playerA, "Swamp"); // surveil + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertGraveyardCount(playerA, "Seer of Stolen Sight", 1); + assertGraveyardCount(playerA, "Swamp", 1); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/triggers/dies/VengefulTownsfolkTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/dies/VengefulTownsfolkTest.java index 0cb9e99549e..9f63da3acc7 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/triggers/dies/VengefulTownsfolkTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/dies/VengefulTownsfolkTest.java @@ -8,7 +8,7 @@ import org.mage.test.serverside.base.CardTestPlayerBase; /** * Tests the {@link mage.abilities.common.DiesOneOrMoreTriggeredAbility} batching. * - * @author Susucr + * @author Susucr, JayDi85 */ public class VengefulTownsfolkTest extends CardTestPlayerBase { @@ -145,4 +145,52 @@ public class VengefulTownsfolkTest extends CardTestPlayerBase { assertPermanentCount(playerA, townsfolk, 1); assertPowerToughness(playerA, townsfolk, 3 + 1, 3 + 1); } + + @Test + public void testReplacedDieEvent_Single() { + addCard(Zone.BATTLEFIELD, playerA, townsfolk); + // + // {T}, Sacrifice up to three permanents: If there were three or more card types among the sacrificed permanents, each opponent loses 3 life, you gain 3 life, and you draw three cards. + addCard(Zone.BATTLEFIELD, playerA, "Baba Lysaga, Night Witch"); + addCard(Zone.BATTLEFIELD, playerA, "Angel of the God-Pharaoh"); // creature with cycle (will be exiled instead die) + // + // If a card with cycling would be put into your graveyard from anywhere and it wasn't cycled, exile it instead. + addCard(Zone.BATTLEFIELD, playerA, "Abandoned Sarcophagus"); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}, Sacrifice up to three"); + setChoice(playerA, "Angel of the God-Pharaoh"); // sac cost + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertExileCount(playerA, "Angel of the God-Pharaoh", 1); // exiled instead die + assertPermanentCount(playerA, townsfolk, 1); + assertPowerToughness(playerA, townsfolk, 3, 3); // no triggers + } + + @Test + public void testReplacedDieEvent_Multiple() { + addCard(Zone.BATTLEFIELD, playerA, townsfolk); + // + // {T}, Sacrifice up to three permanents: If there were three or more card types among the sacrificed permanents, each opponent loses 3 life, you gain 3 life, and you draw three cards. + addCard(Zone.BATTLEFIELD, playerA, "Baba Lysaga, Night Witch"); + addCard(Zone.BATTLEFIELD, playerA, "Grizzly Bears", 10); // creature + addCard(Zone.BATTLEFIELD, playerA, "Angel of the God-Pharaoh"); // creature with cycle (will be exiled instead die) + // + // If a card with cycling would be put into your graveyard from anywhere and it wasn't cycled, exile it instead. + addCard(Zone.BATTLEFIELD, playerA, "Abandoned Sarcophagus"); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}, Sacrifice up to three"); + setChoice(playerA, "Grizzly Bears^Angel of the God-Pharaoh"); // sac cost + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertGraveyardCount(playerA, "Grizzly Bears", 1); + assertExileCount(playerA, "Angel of the God-Pharaoh", 1); // exiled instead die + assertPermanentCount(playerA, townsfolk, 1); + assertPowerToughness(playerA, townsfolk, 3 + 1, 3 + 1); + } } diff --git a/Mage.Tests/src/test/java/org/mage/test/commander/duel/CommanderManaReplacmentTest.java b/Mage.Tests/src/test/java/org/mage/test/commander/duel/CommanderManaReplacementTest.java similarity index 96% rename from Mage.Tests/src/test/java/org/mage/test/commander/duel/CommanderManaReplacmentTest.java rename to Mage.Tests/src/test/java/org/mage/test/commander/duel/CommanderManaReplacementTest.java index 490f33bb0a2..418673e9d12 100644 --- a/Mage.Tests/src/test/java/org/mage/test/commander/duel/CommanderManaReplacmentTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/commander/duel/CommanderManaReplacementTest.java @@ -13,7 +13,7 @@ import org.mage.test.serverside.base.CardTestCommanderDuelBase; * * @author LevelX2 */ -public class CommanderManaReplacmentTest extends CardTestCommanderDuelBase { +public class CommanderManaReplacementTest extends CardTestCommanderDuelBase { @Override protected Game createNewGameAndPlayers() throws GameException, FileNotFoundException { diff --git a/Mage.Tests/src/test/java/org/mage/test/serverside/base/MageTestPlayerBase.java b/Mage.Tests/src/test/java/org/mage/test/serverside/base/MageTestPlayerBase.java index ca7e6c859e8..4b9deb86cf9 100644 --- a/Mage.Tests/src/test/java/org/mage/test/serverside/base/MageTestPlayerBase.java +++ b/Mage.Tests/src/test/java/org/mage/test/serverside/base/MageTestPlayerBase.java @@ -318,9 +318,6 @@ public abstract class MageTestPlayerBase { /** * Add cost modification effect to the game (all cast cost will be increaded or decreased for controller) - * - * @param controller - * @param modificationAmount */ protected void addCustomEffect_SpellCostModification(TestPlayer controller, int modificationAmount) { Effect effect; @@ -339,9 +336,6 @@ public abstract class MageTestPlayerBase { /** * Add target damage ability that can be called by text: "target damage xxx" - * - * @param controller - * @param damageAmount */ protected void addCustomEffect_TargetDamage(TestPlayer controller, int damageAmount) { Ability ability = new SimpleActivatedAbility(new DamageTargetEffect(damageAmount).setText("target damage " + damageAmount), new ManaCostsImpl<>("")); @@ -355,12 +349,17 @@ public abstract class MageTestPlayerBase { /** * Add target destroy ability that can be called by text "target destroy" - * - * @param controller */ - protected void addCustomEffect_DestroyTarget(TestPlayer controller) { + protected void addCustomEffect_TargetDestroy(TestPlayer controller) { + addCustomEffect_TargetDestroy(controller, 1); + } + + /** + * Add target destroy ability that can be called by text "target destroy" + */ + protected void addCustomEffect_TargetDestroy(TestPlayer controller, int numberOfTargets) { Ability ability = new SimpleActivatedAbility(new DestroyTargetEffect().setText("target destroy"), new ManaCostsImpl<>("")); - ability.addTarget(new TargetPermanent()); + ability.addTarget(new TargetPermanent(numberOfTargets, StaticFilters.FILTER_PERMANENT)); addCustomCardWithAbility( "target destroy for " + controller.getName(), controller, @@ -369,11 +368,21 @@ public abstract class MageTestPlayerBase { } /** - * Add target transform ability that can be called by text "target transform" - * - * @param controller + * Add all destroy ability that can be called by text "all destroy" */ - protected void addCustomEffect_TransformTarget(TestPlayer controller) { + protected void addCustomEffect_AllDestroy(TestPlayer controller) { + Ability ability = new SimpleActivatedAbility(new DestroyAllEffect(StaticFilters.FILTER_PERMANENT).setText("all destroy"), new ManaCostsImpl<>("")); + addCustomCardWithAbility( + "all destroy for " + controller.getName(), + controller, + ability + ); + } + + /** + * Add target transform ability that can be called by text "target transform" + */ + protected void addCustomEffect_TargetTransform(TestPlayer controller) { Ability ability = new SimpleActivatedAbility(new TransformTargetEffect().setText("target transform"), new ManaCostsImpl<>("")); ability.addTarget(new TargetPermanent()); addCustomCardWithAbility( @@ -385,10 +394,8 @@ public abstract class MageTestPlayerBase { /** * Add target blink ability that can be called by text "target blink" - * - * @param controller */ - protected void addCustomEffect_BlinkTarget(TestPlayer controller) { + protected void addCustomEffect_TargetBlink(TestPlayer controller) { Ability ability = new SimpleActivatedAbility( new ExileThenReturnTargetEffect(true, true).setText("target blink"), new ManaCostsImpl<>("") @@ -403,8 +410,6 @@ public abstract class MageTestPlayerBase { /** * Return target card to hand that can be called by text "return from ..." - * - * @param controller */ protected void addCustomEffect_ReturnFromAnyToHand(TestPlayer controller) { // graveyard diff --git a/Mage.Verify/pom.xml b/Mage.Verify/pom.xml index 03474793408..ac62b70e9db 100644 --- a/Mage.Verify/pom.xml +++ b/Mage.Verify/pom.xml @@ -6,7 +6,7 @@ org.mage mage-root - 1.4.54 + 1.4.55 mage-verify diff --git a/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java b/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java index 064801a98cf..e018ec86f55 100644 --- a/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java +++ b/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java @@ -7,6 +7,7 @@ import mage.ObjectColor; import mage.abilities.Ability; import mage.abilities.AbilityImpl; import mage.abilities.Mode; +import mage.abilities.TriggeredAbility; import mage.abilities.common.*; import mage.abilities.condition.Condition; import mage.abilities.costs.Cost; @@ -1979,10 +1980,58 @@ public class VerifyCardDataTest { fail(card, "abilities", "legendary nonpermanent cards need to have LegendarySpellAbility"); } + // special check: mutate is not supported yet, so must be removed from sets if (card.getAbilities().containsClass(MutateAbility.class)) { fail(card, "abilities", "mutate cards aren't implemented and shouldn't be available"); } + // special check: wrong dies triggers (there are also a runtime check on wrong usage, see isInUseableZoneDiesTrigger) + Set ignoredCards = new HashSet<>(); + ignoredCards.add("Caller of the Claw"); + ignoredCards.add("Boneyard Scourge"); + ignoredCards.add("Fell Shepherd"); + ignoredCards.add("Massacre Girl"); + ignoredCards.add("Infested Thrinax"); + ignoredCards.add("Xira, the Golden Sting"); + ignoredCards.add("Mawloc"); + List ignoredAbilities = new ArrayList<>(); + ignoredAbilities.add("roll"); // roll die effects + ignoredAbilities.add("with \"When"); // token creating effects + ignoredAbilities.add("gains \"When"); // token creating effects + ignoredAbilities.add("and \"When"); // token creating effects + ignoredAbilities.add("it has \"When"); // token creating effects + ignoredAbilities.add("beginning of your end step"); // step triggers + ignoredAbilities.add("beginning of each end step"); // step triggers + ignoredAbilities.add("beginning of combat"); // step triggers + if (!ignoredCards.contains(card.getName())) { + for (Ability ability : card.getAbilities()) { + TriggeredAbility triggeredAbility = ability instanceof TriggeredAbility ? (TriggeredAbility) ability : null; + if (triggeredAbility == null) { + continue; + } + // search and check dies related abilities + String rules = triggeredAbility.getRule(); + if (ignoredAbilities.stream().anyMatch(rules::contains)) { + continue; + } + boolean isDiesAbility = rules.contains("die ") + || rules.contains("dies ") + || rules.contains("die,") + || rules.contains("dies,"); + boolean isPutToGraveAbility = rules.contains("put into") + && rules.contains("graveyard") + && rules.contains("from the battlefield"); + if (triggeredAbility.isLeavesTheBattlefieldTrigger()) { + // TODO: add check for wrongly enabled settings too? + } else { + if (isDiesAbility || isPutToGraveAbility) { + fail(card, "abilities", "dies related trigger must use setLeavesTheBattlefieldTrigger(true) and possibly override isInUseableZone - " + + triggeredAbility.getClass().getSimpleName()); + } + } + } + } + // special check: duplicated words in ability text (wrong target/filter usage) // example: You may exile __two two__ blue cards // possible fixes: @@ -2303,6 +2352,9 @@ public class VerifyCardDataTest { } private void checkWrongAbilitiesTextStart() { + if (FULL_ABILITIES_CHECK_SET_CODES.isEmpty()) { + return; + } System.out.println("Ability text checks started for " + FULL_ABILITIES_CHECK_SET_CODES); wrongAbilityStatsTotal = 0; wrongAbilityStatsGood = 0; @@ -2310,6 +2362,10 @@ public class VerifyCardDataTest { } private void checkWrongAbilitiesTextEnd() { + if (FULL_ABILITIES_CHECK_SET_CODES.isEmpty()) { + return; + } + // TODO: implement tests result/stats by github actions to show in check message compared to prev version System.out.println(); System.out.printf("Stats for %d cards checked for abilities text:%n", wrongAbilityStatsTotal); diff --git a/Mage/pom.xml b/Mage/pom.xml index a1d7ab2f646..e5c1184ee14 100644 --- a/Mage/pom.xml +++ b/Mage/pom.xml @@ -6,7 +6,7 @@ org.mage mage-root - 1.4.54 + 1.4.55 mage diff --git a/Mage/src/main/java/mage/abilities/Ability.java b/Mage/src/main/java/mage/abilities/Ability.java index ab458969a70..96f1a194195 100644 --- a/Mage/src/main/java/mage/abilities/Ability.java +++ b/Mage/src/main/java/mage/abilities/Ability.java @@ -86,9 +86,6 @@ public interface Ability extends Controllable, Serializable { * Gets the id of the object which put this ability in motion. *

* WARNING, MageSingleton abilities contains dirty data here, so you can't use sourceId with it - * - * @return The {@link java.util.UUID} of the object this ability is - * associated with. */ UUID getSourceId(); @@ -351,16 +348,24 @@ public interface Ability extends Controllable, Serializable { void addWatcher(Watcher watcher); /** - * Returns true if this abilities source is in the zone for the ability + * Allow to control ability/trigger's lifecycle + *

+ * How-to use: + * - for normal abilities and triggers - keep default + * - for leave battlefield triggers - keep default + set setLeavesTheBattlefieldTrigger(true) + * - for dies triggers - override and use TriggeredAbilityImpl.isInUseableZoneDiesTrigger inside + set setLeavesTheBattlefieldTrigger(true) + * + * @param sourceObject can be null for static continues effects checking like rules modification (example: Yixlid Jailer) + * @param event can be null for state base effects checking like "when you control seven or more" (example: Endrek Sahr, Master Breeder) */ - boolean isInUseableZone(Game game, MageObject source, GameEvent event); + boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event); /** * Returns true if the source object has currently the ability (e.g. The * object can have lost all or some abilities for some time (e.g. Turn to * Frog) */ - boolean hasSourceObjectAbility(Game game, MageObject source, GameEvent event); + boolean hasSourceObjectAbility(Game game, MageObject sourceObject, GameEvent event); /** * Returns true if the ability has a tap itself in their costs @@ -478,6 +483,7 @@ public interface Ability extends Controllable, Serializable { /** * Finds the source object regardless of its zcc. Can be LKI from battlefield in some cases. + * Warning, do not use with singleton abilities */ MageObject getSourceObject(Game game); diff --git a/Mage/src/main/java/mage/abilities/AbilityImpl.java b/Mage/src/main/java/mage/abilities/AbilityImpl.java index 0830467cbdb..242a25f863e 100644 --- a/Mage/src/main/java/mage/abilities/AbilityImpl.java +++ b/Mage/src/main/java/mage/abilities/AbilityImpl.java @@ -29,7 +29,9 @@ import mage.game.Game; import mage.game.command.Dungeon; import mage.game.command.Emblem; import mage.game.command.Plane; +import mage.game.events.BatchEvent; import mage.game.events.GameEvent; +import mage.game.events.ZoneChangeEvent; import mage.game.permanent.Permanent; import mage.game.stack.Spell; import mage.game.stack.StackAbility; @@ -1172,69 +1174,162 @@ public abstract class AbilityImpl implements Ability { return false; } - /** - * @param game - * @param source - * @return - */ @Override - public boolean isInUseableZone(Game game, MageObject source, GameEvent event) { - if (!this.hasSourceObjectAbility(game, source, event)) { + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + if (!this.hasSourceObjectAbility(game, sourceObject, event)) { return false; } + + // workaround for singleton abilities like Flying + UUID affectedSourceId = getRealSourceObjectId(this, sourceObject); + MageObject affectedSourceObject = game.getObject(affectedSourceId); + + // global game effects (works all the time and don't have sourceId, example: FinalityCounterEffect) + if (affectedSourceId == null) { + return true; + } + + // emblems/dungeons/planes effects (works all the time, store in command zone) if (zone == Zone.COMMAND) { - if (this.getSourceId() == null) { // commander effects - return true; - } - MageObject object = game.getObject(this.getSourceId()); - // emblem/planes are always actual - if (object instanceof Emblem || object instanceof Dungeon || object instanceof Plane) { + if (affectedSourceObject instanceof Emblem || affectedSourceObject instanceof Dungeon || affectedSourceObject instanceof Plane) { return true; } } - UUID parameterSourceId; - // for singleton abilities like Flying we can't rely on abilities' source because it's only once in continuous effects - // so will use the sourceId of the object itself that came as a parameter if it is not null - if (this instanceof MageSingleton && source != null) { - parameterSourceId = source.getId(); - } else { - parameterSourceId = getSourceId(); - } - // check against shortLKI for effects that move multiple object at the same time (e.g. destroy all) - if (game.checkShortLivingLKI(getSourceId(), getZone())) { + // on entering permanents must use static abilities like it already on battlefield + // example: Tatterkite enters without counters from Mikaeus, the Unhallowed + if (game.getPermanentEntering(affectedSourceId) != null && zone == Zone.BATTLEFIELD) { return true; } - // check against current state - Zone test = game.getState().getZone(parameterSourceId); - return zone.match(test); + + // 603.10. + // Normally, objects that exist immediately after an event are checked to see if the event matched + // any trigger conditions, and continuous effects that exist at that time are used to determine what the + // trigger conditions are and what the objects involved in the event look like. + // ... + Zone affectedObjectZone = game.getState().getZone(affectedSourceId); + + // 603.10. + // ... + // However, some triggered abilities are exceptions to this rule; the game “looks back in time” to determine + // if those abilities trigger, using the existence of those abilities and the appearance of objects + // immediately prior to the event. The list of exceptions is as follows: + + // 603.10a + // Some zone-change triggers look back in time. These are leaves-the-battlefield abilities, + // abilities that trigger when a card leaves a graveyard, and abilities that trigger when an object that all + // players can see is put into a hand or library. + + // TODO: research use cases and implement shared logic with "looking zone" instead LKI only + // in most use cases it's already supported by event (example: saved permanent object in event's target) + // [x] 603.10a leaves-the-battlefield abilities and other + // [ ] 603.10b Abilities that trigger when a permanent phases out look back in time. + // [ ] 603.10c Abilities that trigger specifically when an object becomes unattached look back in time. + // [ ] 603.10d Abilities that trigger when a player loses control of an object look back in time. + // [ ] 603.10e Abilities that trigger when a spell is countered look back in time. + // [ ] 603.10f Abilities that trigger when a player loses the game look back in time. + // [ ] 603.10g Abilities that trigger when a player planeswalks away from a plane look back in time. + + if (event == null) { + // state base triggers - use only actual state + } else { + // event triggers and continues effects - can look back in time + if (isAbilityCanLookBackInTime(this) && isEventCanLookBackInTime(event)) { + // 603.10a leaves-the-battlefield + if (game.checkShortLivingLKI(affectedSourceId, Zone.BATTLEFIELD)) { + affectedObjectZone = Zone.BATTLEFIELD; + } + // 603.10a leaves a graveyard + // TODO: need tests + if (game.checkShortLivingLKI(affectedSourceId, Zone.GRAVEYARD)) { + affectedObjectZone = Zone.GRAVEYARD; + } + // 603.10a put into a hand or library + // TODO: need tests and implementation? + } + } + + return zone.match(affectedObjectZone); + } + + public static boolean isAbilityCanLookBackInTime(Ability ability) { + if (ability instanceof StaticAbility) { + return true; + } + if (ability instanceof TriggeredAbility) { + return ((TriggeredAbility) ability).isLeavesTheBattlefieldTrigger(); + } + return false; + } + + public static boolean isEventCanLookBackInTime(GameEvent event) { + if (event == null) { + return false; + } + + List allEvents = new ArrayList<>(); + if (event instanceof BatchEvent) { + allEvents.addAll(((BatchEvent) event).getEvents()); + } else { + allEvents.add(event); + } + + return allEvents.stream().anyMatch(e -> { + // TODO: need sync code with TriggeredAbilityImpl.isInUseableZone + // TODO: add more events with zone change logic (or make it event's param)? + // need research: is it ability's or event's task? + // - ability's task: code like ability.setLookBackInTime + // - event's task: code like current switch + // TODO: alternative solution: replace check by source.isLeavesTheBattlefieldTrigger? + switch (e.getType()) { + case DESTROYED_PERMANENT: + case EXPLOITED_CREATURE: + return true; + case ZONE_CHANGE: + return ((ZoneChangeEvent) e).getFromZone() == Zone.BATTLEFIELD; + default: + return false; + } + }); + } + + /** + * Find real source object id from any ability (real and singleton) + */ + protected static UUID getRealSourceObjectId(Ability sourceAbility, MageObject sourceObject) { + // In singleton abilities like Flying we can't rely on ability's source because it's init only once in continuous effects + // so will use the sourceId of the object itself that came as a parameter if it is not null + if (sourceAbility instanceof MageSingleton && sourceObject != null) { + return sourceObject.getId(); + } else { + return sourceAbility.getSourceId(); + } } @Override - public boolean hasSourceObjectAbility(Game game, MageObject source, GameEvent event) { - // if source object have this ability - // uses for ability.isInUseableZone - // replacement and other continues effects can be without source, but active (must return true) - - MageObject object = source; - // for singleton abilities like Flying we can't rely on abilities' source because it's only once in continuous effects - // so will use the sourceId of the object itself that came as a parameter if it is not null + public final boolean hasSourceObjectAbility(Game game, MageObject sourceObject, GameEvent event) { + MageObject object = sourceObject; if (object == null) { object = game.getPermanentEntering(getSourceId()); if (object == null) { object = game.getObject(getSourceId()); } } - if (object != null) { - if (object instanceof Permanent) { - return object.hasAbility(this, game) && ( - ((Permanent) object).isPhasedIn() || this.getWorksPhasedOut() - ); - } else { - // cards and other objects - return object.hasAbility(this, game); - } + + if (object == null) { + // global replacement and other continues effects can be without source, but active (must return true all time) + return true; } + + if (!object.hasAbility(this, game)) { + return false; + } + + // phase in/out support + if (object instanceof Permanent) { + return ((Permanent) object).isPhasedIn() || this.getWorksPhasedOut(); + } + return true; } diff --git a/Mage/src/main/java/mage/abilities/StaticAbility.java b/Mage/src/main/java/mage/abilities/StaticAbility.java index f826eff405f..3e00c3ca6de 100644 --- a/Mage/src/main/java/mage/abilities/StaticAbility.java +++ b/Mage/src/main/java/mage/abilities/StaticAbility.java @@ -1,12 +1,8 @@ - package mage.abilities; -import mage.MageObject; import mage.abilities.effects.Effect; import mage.constants.AbilityType; import mage.constants.Zone; -import mage.game.Game; -import mage.game.events.GameEvent; /** * @@ -25,17 +21,6 @@ public abstract class StaticAbility extends AbilityImpl { } } - @Override - public boolean isInUseableZone(Game game, MageObject source, GameEvent event) { - if (game.checkShortLivingLKI(getSourceId(), zone)) { - return true; // maybe this can be a problem if effects removed the ability from the object - } - if (game.getPermanentEntering(getSourceId()) != null && zone == Zone.BATTLEFIELD) { - return true; // abilities of permanents entering battlefield are countes as on battlefield - } - return super.isInUseableZone(game, source, event); - } - protected StaticAbility(final StaticAbility ability) { super(ability); } diff --git a/Mage/src/main/java/mage/abilities/TriggeredAbilities.java b/Mage/src/main/java/mage/abilities/TriggeredAbilities.java index 9d8ab33cef6..3670dc461f6 100644 --- a/Mage/src/main/java/mage/abilities/TriggeredAbilities.java +++ b/Mage/src/main/java/mage/abilities/TriggeredAbilities.java @@ -209,7 +209,7 @@ public class TriggeredAbilities extends LinkedHashMap if (ability.isInUseableZone(game, object, event)) { if (event == null || !game.getContinuousEffects().preventedByRuleModification(event, ability, game, false)) { if (object != null) { - boolean controllerSet = false; + boolean controllerSet = false; // TODO: wtf?!?!? Need rework whole "set" logic here Set eventTargets = CardUtil.getEventTargets(event); if (ability.getZone() != Zone.COMMAND && event != null diff --git a/Mage/src/main/java/mage/abilities/TriggeredAbility.java b/Mage/src/main/java/mage/abilities/TriggeredAbility.java index 7d690c6a862..f47a10f76c6 100644 --- a/Mage/src/main/java/mage/abilities/TriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/TriggeredAbility.java @@ -62,8 +62,26 @@ public interface TriggeredAbility extends Ability { TriggeredAbility setOptional(); + /** + * Allow trigger to fire after source leave the battlefield (example: will use LKI on itself sacrifice) + */ boolean isLeavesTheBattlefieldTrigger(); + /** + * 603.6c,603.6d + * If true the game “looks back in time” to determine if those abilities trigger + * This has to be set, if the triggered ability has to check back in time if the permanent the ability is connected + * to had the ability on the battlefield while the trigger is checked + *

+ * 603.6c + * Leaves-the-battlefield abilities trigger when a permanent moves from the battlefield to another zone, + * or when a phased-in permanent leaves the game because its owner leaves the game. These are written as, + * but aren’t limited to, “When [this object] leaves the battlefield, . . .” or “Whenever [something] is put + * into a graveyard from the battlefield, . . . .” (See also rule 603.10.) An ability that attempts to do + * something to the card that left the battlefield checks for it only in the first zone that it went to. + * An ability that triggers when a card is put into a certain zone “from anywhere” is never treated as a + * leaves-the-battlefield ability, even if an object is put into that zone from the battlefield. + */ void setLeavesTheBattlefieldTrigger(boolean leavesTheBattlefieldTrigger); @Override diff --git a/Mage/src/main/java/mage/abilities/TriggeredAbilityImpl.java b/Mage/src/main/java/mage/abilities/TriggeredAbilityImpl.java index 29030e2bf3f..a119c31c1bb 100644 --- a/Mage/src/main/java/mage/abilities/TriggeredAbilityImpl.java +++ b/Mage/src/main/java/mage/abilities/TriggeredAbilityImpl.java @@ -8,7 +8,9 @@ import mage.constants.AbilityType; import mage.constants.AbilityWord; import mage.constants.Zone; import mage.game.Game; +import mage.game.events.BatchEvent; import mage.game.events.GameEvent; +import mage.game.events.ZoneChangeBatchEvent; import mage.game.events.ZoneChangeEvent; import mage.game.permanent.Permanent; import mage.game.permanent.PermanentToken; @@ -49,7 +51,6 @@ public abstract class TriggeredAbilityImpl extends AbilityImpl implements Trigge // verify check: DoIfCostPaid effect already asks about action (optional), so no needs to ask it again in triggered ability if (effect instanceof DoIfCostPaid && (this.optional && ((DoIfCostPaid) effect).isOptional())) { throw new IllegalArgumentException("DoIfCostPaid effect must have only one optional settings, but it have two (trigger + DoIfCostPaid): " + this.getClass().getSimpleName()); - } } @@ -347,84 +348,73 @@ public abstract class TriggeredAbilityImpl extends AbilityImpl implements Trigge } @Override - public boolean isInUseableZone(Game game, MageObject source, GameEvent event) { + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { - /** - * 603.6. Trigger events that involve objects changing zones are called - * “zone-change triggers.” Many abilities with zone-change triggers - * attempt to do something to that object after it changes zones. During - * resolution, these abilities look for the object in the zone that it - * moved to. If the object is unable to be found in the zone it went to, - * the part of the ability attempting to do something to the object will - * fail to do anything. The ability could be unable to find the object - * because the object never entered the specified zone, because it left - * the zone before the ability resolved, or because it is in a zone that - * is hidden from a player, such as a library or an opponent's hand. - * (This rule applies even if the object leaves the zone and returns - * again before the ability resolves.) The most common zone-change - * triggers are enters-the-battlefield triggers and - * leaves-the-battlefield triggers. - * - * from: - * http://www.mtgsalvation.com/forums/magic-fundamentals/magic-rulings/magic-rulings-archives/537065-ixidron-and-kozilek - * There are two types of triggers that involve the graveyard: dies - * triggers (which are a subset of leave-the-battlefield triggers) and - * put into the graveyard from anywhere triggers. - * - * The former triggers trigger based on the game state prior to the move - * where the Kozilek permanent is face down and has no abilities. The - * latter triggers trigger from the game state after the move where the - * Kozilek card is itself and has the ability. - */ + // workaround for singleton abilities like Flying + UUID affectedSourceId = getRealSourceObjectId(this, sourceObject); - Set eventTargets = CardUtil.getEventTargets(event); - if (!eventTargets.contains(getSourceId())) { - return super.isInUseableZone(game, source, event); - } + // 603.6 + // Trigger events that involve objects changing zones are called "zone-change triggers." Many abilities with + // zone-change triggers attempt to do something to that object after it changes zones. During resolution, + // these abilities look for the object in the zone that it moved to. If the object is unable to be found + // in the zone it went to, the part of the ability attempting to do something to the object will fail to + // do anything. The ability could be unable to find the object because the object never entered the + // specified zone, because it left the zone before the ability resolved, or because it is in a zone that + // is hidden from a player, such as a library or an opponent’s hand. (This rule applies even if the + // object leaves the zone and returns again before the ability resolves.) The most common zone-change + // triggers are enters-the-battlefield triggers and leaves-the-battlefield triggers. - switch (event.getType()) { - case ZONE_CHANGE: - ZoneChangeEvent zce = (ZoneChangeEvent) event; - if (eventTargets.contains(getSourceId()) && !zce.getToZone().isPublicZone()) { - // If an ability triggers when the object that has it is put into a hidden zone from a graveyard, - // that ability triggers from the graveyard, (such as Golgari Brownscale), - // Yixlid Jailer will prevent that ability from triggering. - if (zce.getFromZone().match(Zone.GRAVEYARD)) { - if (!CardUtil.cardHadAbility(this, game.getLastKnownInformationCard(getSourceId(), zce.getFromZone()), getSourceId(), game)) { - return false; + // There are possible two different use cases: + // * look in current game state (normal events): + // * look back in time (leaves battlefield, dies, etc); + + // TODO: need sync or shared code with AbilityImpl.isInUseableZone + MageObject affectedSourceObject = sourceObject; + if (event == null) { + // state base triggers - use only actual state + } else { + // event triggers - can look back in time for some use cases + switch (event.getType()) { + case ZONE_CHANGE: + ZoneChangeEvent zce = (ZoneChangeEvent) event; + Set eventTargets = CardUtil.getEventTargets(event); + if (eventTargets.contains(getSourceId()) && !zce.getToZone().isPublicZone()) { + // TODO: need research and share with AbilityImpl + // If an ability triggers when the object that has it is put into a hidden zone from a graveyard, + // that ability triggers from the graveyard, (such as Golgari Brownscale), + // Yixlid Jailer will prevent that ability from triggering. + if (zce.getFromZone().match(Zone.GRAVEYARD)) { + if (!CardUtil.cardHadAbility(this, game.getLastKnownInformationCard(getSourceId(), zce.getFromZone()), getSourceId(), game)) { + return false; + } } } - } - if (isLeavesTheBattlefieldTrigger()) { - source = zce.getTarget(); - } - break; - case DESTROYED_PERMANENT: - if (isLeavesTheBattlefieldTrigger()) { - source = game.getLastKnownInformation(getSourceId(), Zone.BATTLEFIELD); - } - break; + if (isLeavesTheBattlefieldTrigger() && game.checkShortLivingLKI(affectedSourceId, Zone.BATTLEFIELD)) { + affectedSourceObject = game.getLastKnownInformation(affectedSourceId, Zone.BATTLEFIELD); + } + break; + case DESTROYED_PERMANENT: + case EXPLOITED_CREATURE: + if (isLeavesTheBattlefieldTrigger() && game.checkShortLivingLKI(affectedSourceId, Zone.BATTLEFIELD)) { + affectedSourceObject = game.getPermanentOrLKIBattlefield(affectedSourceId); + } + break; + } } - return super.isInUseableZone(game, source, event); + + return super.isInUseableZone(game, affectedSourceObject, event); } - /* - 603.6c Leaves-the-battlefield abilities, 603.6d - if true the game “looks back in time” to determine if those abilities trigger, - using the existence of those abilities and the appearance of objects immediately prior to the event (603.10) - */ @Override public boolean isLeavesTheBattlefieldTrigger() { return leavesTheBattlefieldTrigger; } - /* - 603.6c,603.6d - This has to be set, if the triggered ability has to check back in time if the permanent the ability is connected to had the ability on the battlefield while the trigger is checked - */ @Override public final void setLeavesTheBattlefieldTrigger(boolean leavesTheBattlefieldTrigger) { this.leavesTheBattlefieldTrigger = leavesTheBattlefieldTrigger; + + // TODO: replace override of isInUseableZone in dies only triggers by like "isDiesOnlyTrigger" here } @Override @@ -453,16 +443,35 @@ public abstract class TriggeredAbilityImpl extends AbilityImpl implements Trigge } /** + * Looking object in GRAVEYARD zone only. If you need multi zone then use default isInUseableZone + * - good example: Whenever another creature you control dies + * - bad example: When {this} dies or is put into exile from the battlefield + *

* For triggered abilities that function from the battlefield that must trigger when the source permanent dies * and/or for any other events that happen simultaneously to the source permanent dying. * (Similar logic must be used for any leaves-the-battlefield, but this method assumes to graveyard only.) * NOTE: If your ability functions from another zone (not battlefield) then must use standard logic, not this. */ - public static boolean isInUseableZoneDiesTrigger(TriggeredAbility source, GameEvent event, Game game) { - // Get the source permanent of the ability - MageObject sourceObject = null; - if (game.getState().getZone(source.getSourceId()) == Zone.BATTLEFIELD) { - sourceObject = game.getPermanent(source.getSourceId()); + public static boolean isInUseableZoneDiesTrigger(TriggeredAbility sourceAbility, MageObject sourceObject, GameEvent event, Game game) { + // runtime check: wrong trigger settings + if (!sourceAbility.isLeavesTheBattlefieldTrigger()) { + throw new IllegalArgumentException("Wrong code usage: all dies triggers must use setLeavesTheBattlefieldTrigger(true) and override isInUseableZone - " + + sourceAbility.getSourceObject(game) + " - " + sourceAbility); + } + + // runtime check: wrong isInUseableZone for batch related triggers + if (event instanceof BatchEvent) { + throw new IllegalArgumentException("Wrong code usage: batch events unsupported here, possible miss of override isInUseableZone - " + + sourceAbility.getSourceObject(game) + " - " + sourceAbility); + } + + // workaround for singleton abilities like Flying + UUID affectedSourceId = getRealSourceObjectId(sourceAbility, sourceObject); + + // on permanent - can use actual or look back in time + MageObject affectedObject = null; + if (game.getState().getZone(affectedSourceId) == Zone.BATTLEFIELD) { + affectedObject = game.getPermanent(affectedSourceId); } else { // The idea: short living LKI must help to find a moment in the inner of resolve // - @@ -480,26 +489,29 @@ public abstract class TriggeredAbilityImpl extends AbilityImpl implements Trigge // - ! empty stack ! graveyard ! no ! no ! no more to resolve // --!---------------!-------------!-----!-----------! // - - if (game.checkShortLivingLKI(source.getSourceId(), Zone.BATTLEFIELD)) { - sourceObject = (Permanent) game.getLastKnownInformation(source.getSourceId(), Zone.BATTLEFIELD); - } - } - if (sourceObject == null) { // source is no permanent - sourceObject = game.getObject(source); - if (sourceObject == null || sourceObject.isPermanent(game)) { - return false; // No source object found => ability is not valid + if (game.checkShortLivingLKI(affectedSourceId, Zone.BATTLEFIELD)) { + affectedObject = game.getLastKnownInformation(affectedSourceId, Zone.BATTLEFIELD); } } - if (!source.hasSourceObjectAbility(game, sourceObject, event)) { + if (affectedObject == null) { + affectedObject = game.getObject(sourceAbility); + if (affectedObject == null || affectedObject.isPermanent(game)) { + // if it was a permanent, but now removed then ignore + return false; + } + } + + if (!sourceAbility.hasSourceObjectAbility(game, affectedObject, event)) { return false; // the permanent does currently not have or before it dies the ability so no trigger } // check now it is in graveyard (only if it is no token and was the target itself) - if (source.getSourceId().equals(event.getTargetId()) // source is also the target - && !(sourceObject instanceof PermanentToken) // it's no token - && sourceObject.getZoneChangeCounter(game) + 1 == game.getState().getZoneChangeCounter(source.getSourceId())) { // It's in the next zone - Zone after = game.getState().getZone(source.getSourceId()); + // TODO: need research + if (affectedSourceId.equals(event.getTargetId()) // source is also the target + && !(affectedObject instanceof PermanentToken) // it's no token + && affectedObject.getZoneChangeCounter(game) + 1 == game.getState().getZoneChangeCounter(affectedSourceId)) { // It's in the next zone + Zone after = game.getState().getZone(affectedSourceId); if (!Zone.GRAVEYARD.match(after)) { // Zone is not the graveyard return false; // Moving to graveyard was replaced so no trigger } diff --git a/Mage/src/main/java/mage/abilities/common/CastFromGraveyardOnceEachTurnAbility.java b/Mage/src/main/java/mage/abilities/common/CastFromGraveyardOnceEachTurnAbility.java index 6ee178ca922..17ddc9a8bd5 100644 --- a/Mage/src/main/java/mage/abilities/common/CastFromGraveyardOnceEachTurnAbility.java +++ b/Mage/src/main/java/mage/abilities/common/CastFromGraveyardOnceEachTurnAbility.java @@ -116,7 +116,7 @@ class CastFromGraveyardOnceWatcher extends Watcher { public void watch(GameEvent event, Game game) { if (GameEvent.EventType.SPELL_CAST.equals(event.getType()) && event.hasApprovingIdentifier(MageIdentifier.CastFromGraveyardOnceWatcher)) { - usedFrom.add(event.getAdditionalReference().getApprovingMageObjectReference()); + usedFrom.add(event.getApprovingObject().getApprovingMageObjectReference()); } } diff --git a/Mage/src/main/java/mage/abilities/common/DealtDamageAndDiedTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/DealtDamageAndDiedTriggeredAbility.java index e2903456db2..6afca9b9c41 100644 --- a/Mage/src/main/java/mage/abilities/common/DealtDamageAndDiedTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/DealtDamageAndDiedTriggeredAbility.java @@ -1,5 +1,6 @@ package mage.abilities.common; +import mage.MageObject; import mage.MageObjectReference; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.effects.Effect; @@ -35,6 +36,7 @@ public class DealtDamageAndDiedTriggeredAbility extends TriggeredAbilityImpl { this.filter = filter; this.setTargetPointer = setTargetPointer; setTriggerPhrase(getWhen() + CardUtil.addArticle(filter.getMessage()) + " dealt damage by {this} this turn dies, "); + setLeavesTheBattlefieldTrigger(true); } protected DealtDamageAndDiedTriggeredAbility(final DealtDamageAndDiedTriggeredAbility ability) { @@ -55,6 +57,9 @@ public class DealtDamageAndDiedTriggeredAbility extends TriggeredAbilityImpl { @Override public boolean checkTrigger(GameEvent event, Game game) { + // If Axelrod Gunnarson and a creature it dealt damage to are both put into a graveyard at the same time, + // Axelrod Gunnarson’s second ability will trigger. + // (2009-10-01) if (((ZoneChangeEvent) event).isDiesEvent()) { ZoneChangeEvent zEvent = (ZoneChangeEvent) event; if (filter.match(zEvent.getTarget(), game)) { @@ -78,4 +83,9 @@ public class DealtDamageAndDiedTriggeredAbility extends TriggeredAbilityImpl { } return false; } + + @Override + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); + } } diff --git a/Mage/src/main/java/mage/abilities/common/DealtDamageAttachedAndDiedTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/DealtDamageAttachedAndDiedTriggeredAbility.java index 1ad19fa0246..04a36d16338 100644 --- a/Mage/src/main/java/mage/abilities/common/DealtDamageAttachedAndDiedTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/DealtDamageAttachedAndDiedTriggeredAbility.java @@ -1,5 +1,6 @@ package mage.abilities.common; +import mage.MageObject; import mage.MageObjectReference; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.effects.Effect; @@ -32,6 +33,7 @@ public class DealtDamageAttachedAndDiedTriggeredAbility extends TriggeredAbility setTriggerPhrase(getWhen() + CardUtil.addArticle(filter.getMessage()) + " dealt damage by " + CardUtil.getTextWithFirstCharLowerCase(attachmentType.verb()) + " creature this turn dies, "); + setLeavesTheBattlefieldTrigger(true); } protected DealtDamageAttachedAndDiedTriggeredAbility(final DealtDamageAttachedAndDiedTriggeredAbility ability) { @@ -75,4 +77,9 @@ public class DealtDamageAttachedAndDiedTriggeredAbility extends TriggeredAbility } return true; } + + @Override + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); + } } diff --git a/Mage/src/main/java/mage/abilities/common/DiesAttachedTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/DiesAttachedTriggeredAbility.java index d88602941b5..b291486ec39 100644 --- a/Mage/src/main/java/mage/abilities/common/DiesAttachedTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/DiesAttachedTriggeredAbility.java @@ -1,5 +1,6 @@ package mage.abilities.common; +import mage.MageObject; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.effects.Effect; import mage.cards.Card; @@ -48,6 +49,7 @@ public class DiesAttachedTriggeredAbility extends TriggeredAbilityImpl { this.setTargetPointer = setTargetPointer; this.rememberSource = rememberSource; setTriggerPhrase(generateTriggerPhrase()); + setLeavesTheBattlefieldTrigger(true); } protected DiesAttachedTriggeredAbility(final DiesAttachedTriggeredAbility ability) { @@ -157,4 +159,9 @@ public class DiesAttachedTriggeredAbility extends TriggeredAbilityImpl { } return sb.toString(); } + + @Override + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); + } } diff --git a/Mage/src/main/java/mage/abilities/common/DiesCreatureTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/DiesCreatureTriggeredAbility.java index cc6f9fc8624..9b885b1bf6e 100644 --- a/Mage/src/main/java/mage/abilities/common/DiesCreatureTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/DiesCreatureTriggeredAbility.java @@ -48,6 +48,7 @@ public class DiesCreatureTriggeredAbility extends TriggeredAbilityImpl { super(zone, effect, optional); this.filter = filter; this.setTargetPointer = setTargetPointer; + setLeavesTheBattlefieldTrigger(true); setTriggerPhrase("Whenever " + filter.getMessage() + (filter.getMessage().startsWith("one or more") ? " die, " : " dies, ")); } @@ -81,11 +82,11 @@ public class DiesCreatureTriggeredAbility extends TriggeredAbilityImpl { } @Override - public boolean isInUseableZone(Game game, MageObject source, GameEvent event) { + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { if (this.zone == Zone.BATTLEFIELD) { - return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, event, game); + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); } else { - return super.isInUseableZone(game, source, event); + return super.isInUseableZone(game, sourceObject, event); } } } diff --git a/Mage/src/main/java/mage/abilities/common/DiesOneOrMoreTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/DiesOneOrMoreTriggeredAbility.java index 655a7d57839..a537a79f359 100644 --- a/Mage/src/main/java/mage/abilities/common/DiesOneOrMoreTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/DiesOneOrMoreTriggeredAbility.java @@ -23,6 +23,7 @@ public class DiesOneOrMoreTriggeredAbility extends TriggeredAbilityImpl implemen super(Zone.BATTLEFIELD, effect, optional); this.filter = filter; this.setTriggerPhrase("Whenever one or more " + filter.getMessage() + " die, "); + setLeavesTheBattlefieldTrigger(true); } private DiesOneOrMoreTriggeredAbility(final DiesOneOrMoreTriggeredAbility ability) { @@ -55,10 +56,10 @@ public class DiesOneOrMoreTriggeredAbility extends TriggeredAbilityImpl implemen } @Override - public boolean isInUseableZone(Game game, MageObject source, GameEvent event) { + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { return ((ZoneChangeBatchEvent) event) .getEvents() .stream() - .allMatch(e -> TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, e, game)); + .anyMatch(e -> TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, e, game)); } } diff --git a/Mage/src/main/java/mage/abilities/common/DiesSourceTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/DiesSourceTriggeredAbility.java index 790e94ae41e..fdadfae2743 100644 --- a/Mage/src/main/java/mage/abilities/common/DiesSourceTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/DiesSourceTriggeredAbility.java @@ -42,7 +42,7 @@ public class DiesSourceTriggeredAbility extends ZoneChangeTriggeredAbility { } @Override - public boolean isInUseableZone(Game game, MageObject source, GameEvent event) { - return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, event, game); + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); } } diff --git a/Mage/src/main/java/mage/abilities/common/DiesThisOrAnotherTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/DiesThisOrAnotherTriggeredAbility.java index 8a19656b323..e55160ee720 100644 --- a/Mage/src/main/java/mage/abilities/common/DiesThisOrAnotherTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/DiesThisOrAnotherTriggeredAbility.java @@ -30,6 +30,7 @@ public class DiesThisOrAnotherTriggeredAbility extends TriggeredAbilityImpl { filterMessage = filterMessage.substring(2); } setTriggerPhrase("Whenever {this} or another " + filterMessage + " dies, "); + setLeavesTheBattlefieldTrigger(true); } protected DiesThisOrAnotherTriggeredAbility(final DiesThisOrAnotherTriggeredAbility ability) { @@ -65,7 +66,7 @@ public class DiesThisOrAnotherTriggeredAbility extends TriggeredAbilityImpl { } @Override - public boolean isInUseableZone(Game game, MageObject source, GameEvent event) { - return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, event, game); + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); } } diff --git a/Mage/src/main/java/mage/abilities/common/ExploitCreatureTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/ExploitCreatureTriggeredAbility.java index 9989905a53f..1a380412bc9 100644 --- a/Mage/src/main/java/mage/abilities/common/ExploitCreatureTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/ExploitCreatureTriggeredAbility.java @@ -48,22 +48,6 @@ public class ExploitCreatureTriggeredAbility extends TriggeredAbilityImpl { return event.getType() == GameEvent.EventType.EXPLOITED_CREATURE; } - @Override - public boolean isInUseableZone(Game game, MageObject source, GameEvent event) { - Permanent sourcePermanent = null; - if (game.getState().getZone(getSourceId()) == Zone.BATTLEFIELD) { - sourcePermanent = game.getPermanent(getSourceId()); - } else { - if (game.checkShortLivingLKI(getSourceId(), Zone.BATTLEFIELD)) { - sourcePermanent = (Permanent) game.getLastKnownInformation(getSourceId(), Zone.BATTLEFIELD); - } - } - if (sourcePermanent == null) { - return false; - } - return hasSourceObjectAbility(game, sourcePermanent, event); - } - @Override public boolean checkTrigger(GameEvent event, Game game) { if (event.getSourceId().equals(getSourceId())) { diff --git a/Mage/src/main/java/mage/abilities/common/GodEternalDiesTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/GodEternalDiesTriggeredAbility.java index f8cc58d95b0..26a89a93904 100644 --- a/Mage/src/main/java/mage/abilities/common/GodEternalDiesTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/GodEternalDiesTriggeredAbility.java @@ -21,6 +21,7 @@ public class GodEternalDiesTriggeredAbility extends TriggeredAbilityImpl { public GodEternalDiesTriggeredAbility() { super(Zone.ALL, null, true); + setLeavesTheBattlefieldTrigger(true); } private GodEternalDiesTriggeredAbility(GodEternalDiesTriggeredAbility ability) { @@ -49,22 +50,6 @@ public class GodEternalDiesTriggeredAbility extends TriggeredAbilityImpl { return false; } - @Override - public boolean isInUseableZone(Game game, MageObject source, GameEvent event) { - Permanent sourcePermanent = null; - if (game.getState().getZone(getSourceId()) == Zone.BATTLEFIELD) { - sourcePermanent = game.getPermanent(getSourceId()); - } else { - if (game.checkShortLivingLKI(getSourceId(), Zone.BATTLEFIELD)) { - sourcePermanent = (Permanent) game.getLastKnownInformation(getSourceId(), Zone.BATTLEFIELD); - } - } - if (sourcePermanent == null) { - return false; - } - return hasSourceObjectAbility(game, sourcePermanent, event); - } - @Override public GodEternalDiesTriggeredAbility copy() { return new GodEternalDiesTriggeredAbility(this); diff --git a/Mage/src/main/java/mage/abilities/common/PutIntoGraveFromAnywhereSourceTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/PutIntoGraveFromAnywhereSourceTriggeredAbility.java index a50025271ad..cbf4ccd4da2 100644 --- a/Mage/src/main/java/mage/abilities/common/PutIntoGraveFromAnywhereSourceTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/PutIntoGraveFromAnywhereSourceTriggeredAbility.java @@ -41,7 +41,7 @@ public class PutIntoGraveFromAnywhereSourceTriggeredAbility extends ZoneChangeTr // * @return // */ // @Override -// public boolean isInUseableZone(Game game, MageObject source, GameEvent event) { +// public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { // if (game.getState().getZone(source.getId()).equals(Zone.GRAVEYARD)) { // return this.hasSourceObjectAbility(game, source, event); // } diff --git a/Mage/src/main/java/mage/abilities/common/PutIntoGraveFromBattlefieldAllTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/PutIntoGraveFromBattlefieldAllTriggeredAbility.java index b86baa07a3a..f67a6bdba61 100644 --- a/Mage/src/main/java/mage/abilities/common/PutIntoGraveFromBattlefieldAllTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/PutIntoGraveFromBattlefieldAllTriggeredAbility.java @@ -26,7 +26,7 @@ public class PutIntoGraveFromBattlefieldAllTriggeredAbility extends TriggeredAbi public PutIntoGraveFromBattlefieldAllTriggeredAbility(Effect effect, boolean optional, FilterPermanent filter, boolean setTargetPointer, boolean onlyToControllerGraveyard) { super(Zone.BATTLEFIELD, effect, optional); - this.setLeavesTheBattlefieldTrigger(true); + setLeavesTheBattlefieldTrigger(true); this.filter = filter; this.onlyToControllerGraveyard = onlyToControllerGraveyard; this.setTargetPointer = setTargetPointer; @@ -66,7 +66,7 @@ public class PutIntoGraveFromBattlefieldAllTriggeredAbility extends TriggeredAbi } @Override - public boolean isInUseableZone(Game game, MageObject source, GameEvent event) { - return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, event, game); + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); } } diff --git a/Mage/src/main/java/mage/abilities/common/PutIntoGraveFromBattlefieldSourceTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/PutIntoGraveFromBattlefieldSourceTriggeredAbility.java index 629f3cd1937..fbff5dc43d1 100644 --- a/Mage/src/main/java/mage/abilities/common/PutIntoGraveFromBattlefieldSourceTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/PutIntoGraveFromBattlefieldSourceTriggeredAbility.java @@ -58,7 +58,7 @@ public class PutIntoGraveFromBattlefieldSourceTriggeredAbility extends Triggered } @Override - public boolean isInUseableZone(Game game, MageObject source, GameEvent event) { - return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, event, game); + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); } } diff --git a/Mage/src/main/java/mage/abilities/common/ZoneChangeTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/ZoneChangeTriggeredAbility.java index 848ef2bc366..05629813f62 100644 --- a/Mage/src/main/java/mage/abilities/common/ZoneChangeTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/ZoneChangeTriggeredAbility.java @@ -21,7 +21,9 @@ public class ZoneChangeTriggeredAbility extends TriggeredAbilityImpl { protected final Zone toZone; public ZoneChangeTriggeredAbility(Zone fromZone, Zone toZone, Effect effect, String triggerPhrase, boolean optional) { + // fix 3 this(toZone == null ? Zone.ALL : toZone, fromZone, toZone, effect, triggerPhrase, optional); + //this(fromZone == null ? Zone.ALL : fromZone, fromZone, toZone, effect, triggerPhrase, optional); } public ZoneChangeTriggeredAbility(Zone worksInZone, Zone fromZone, Zone toZone, Effect effect, String triggerPhrase, boolean optional) { diff --git a/Mage/src/main/java/mage/abilities/common/delayed/UntilYourNextTurnDelayedTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/delayed/UntilYourNextTurnDelayedTriggeredAbility.java index 136431ae8e0..33f9a066f40 100644 --- a/Mage/src/main/java/mage/abilities/common/delayed/UntilYourNextTurnDelayedTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/delayed/UntilYourNextTurnDelayedTriggeredAbility.java @@ -1,9 +1,11 @@ package mage.abilities.common.delayed; +import mage.MageObject; import mage.abilities.DelayedTriggeredAbility; import mage.abilities.Modes; import mage.abilities.TriggeredAbility; +import mage.abilities.TriggeredAbilityImpl; import mage.abilities.effects.Effect; import mage.abilities.effects.Effects; import mage.constants.Duration; @@ -27,7 +29,7 @@ public class UntilYourNextTurnDelayedTriggeredAbility extends DelayedTriggeredAb public UntilYourNextTurnDelayedTriggeredAbility(TriggeredAbility ability) { super(null, Duration.UntilYourNextTurn, false); if (ability.isLeavesTheBattlefieldTrigger()) { - this.setLeavesTheBattlefieldTrigger(true); + setLeavesTheBattlefieldTrigger(true); } this.ability = ability; } @@ -103,4 +105,14 @@ public class UntilYourNextTurnDelayedTriggeredAbility extends DelayedTriggeredAb public int getSourceObjectZoneChangeCounter() { return ability.getSourceObjectZoneChangeCounter(); } + + @Override + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + if (isLeavesTheBattlefieldTrigger()) { + // TODO: leaves battlefield and die are not same! Is it possible make a diff logic? + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); + } else { + return super.isInUseableZone(game, sourceObject, event); + } + } } diff --git a/Mage/src/main/java/mage/abilities/costs/common/UntapSourceCost.java b/Mage/src/main/java/mage/abilities/costs/common/UntapSourceCost.java index 09ec8fe5b03..2e379faac2a 100644 --- a/Mage/src/main/java/mage/abilities/costs/common/UntapSourceCost.java +++ b/Mage/src/main/java/mage/abilities/costs/common/UntapSourceCost.java @@ -6,6 +6,7 @@ import mage.abilities.Ability; import mage.abilities.costs.Cost; import mage.abilities.costs.CostImpl; import mage.constants.AsThoughEffectType; +import mage.counters.CounterType; import mage.game.Game; import mage.game.permanent.Permanent; @@ -27,7 +28,13 @@ public class UntapSourceCost extends CostImpl { public boolean pay(Ability ability, Game game, Ability source, UUID controllerId, boolean noMana, Cost costToPay) { Permanent permanent = game.getPermanent(source.getSourceId()); if (permanent != null) { + int stunCount = permanent.getCounters(game).getCount(CounterType.STUN); paid = permanent.untap(game); + // 118.11 - if a stun counter replaces the untap, the cost has still been paid. + // Fear of Sleep Paralysis ruling - if the stun counter can't be removed, the untap cost hasn't been paid. + if (stunCount > 0) { + paid = permanent.getCounters(game).getCount(CounterType.STUN) < stunCount; + } } return paid; } diff --git a/Mage/src/main/java/mage/abilities/costs/mana/ManaCostImpl.java b/Mage/src/main/java/mage/abilities/costs/mana/ManaCostImpl.java index 2244f6f8123..1fd9bdd5f90 100644 --- a/Mage/src/main/java/mage/abilities/costs/mana/ManaCostImpl.java +++ b/Mage/src/main/java/mage/abilities/costs/mana/ManaCostImpl.java @@ -259,8 +259,8 @@ public abstract class ManaCostImpl extends CostImpl implements ManaCost { return false; } - // TODO: is it require Phyrexian stile effects here for single payment? - //AbilityImpl.preparePhyrexianCost(game, source, player, ability, this); + // no needs to call + //AbilityImpl.handlePhyrexianLikeEffects(game, source, ability, this); if (!player.getManaPool().isForcedToPay()) { assignPayment(game, ability, player.getManaPool(), costToPay != null ? costToPay : this); diff --git a/Mage/src/main/java/mage/abilities/decorator/ConditionalInterveningIfTriggeredAbility.java b/Mage/src/main/java/mage/abilities/decorator/ConditionalInterveningIfTriggeredAbility.java index b8e0b6f57d3..ab4e86f5d4d 100644 --- a/Mage/src/main/java/mage/abilities/decorator/ConditionalInterveningIfTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/decorator/ConditionalInterveningIfTriggeredAbility.java @@ -1,5 +1,6 @@ package mage.abilities.decorator; +import mage.MageObject; import mage.abilities.Modes; import mage.abilities.TriggeredAbility; import mage.abilities.TriggeredAbilityImpl; @@ -7,6 +8,7 @@ import mage.abilities.condition.Condition; import mage.abilities.effects.Effect; import mage.abilities.effects.Effects; import mage.constants.EffectType; +import mage.constants.Zone; import mage.game.Game; import mage.game.events.GameEvent; import mage.util.CardUtil; @@ -38,7 +40,7 @@ public class ConditionalInterveningIfTriggeredAbility extends TriggeredAbilityIm public ConditionalInterveningIfTriggeredAbility(TriggeredAbility ability, Condition condition, String text) { super(ability.getZone(), null); if (ability.isLeavesTheBattlefieldTrigger()) { - this.setLeavesTheBattlefieldTrigger(true); + setLeavesTheBattlefieldTrigger(true); } this.ability = ability; this.condition = condition; @@ -133,4 +135,14 @@ public class ConditionalInterveningIfTriggeredAbility extends TriggeredAbilityIm public boolean caresAboutManaColor() { return condition.caresAboutManaColor(); } + + @Override + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + if (isLeavesTheBattlefieldTrigger()) { + // TODO: leaves battlefield and die are not same! Is it possible make a diff logic? + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); + } else { + return super.isInUseableZone(game, sourceObject, event); + } + } } diff --git a/Mage/src/main/java/mage/abilities/decorator/ConditionalTriggeredAbility.java b/Mage/src/main/java/mage/abilities/decorator/ConditionalTriggeredAbility.java index cb12f7b2b66..4a2b89ce8f5 100644 --- a/Mage/src/main/java/mage/abilities/decorator/ConditionalTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/decorator/ConditionalTriggeredAbility.java @@ -1,5 +1,6 @@ package mage.abilities.decorator; +import mage.MageObject; import mage.abilities.Ability; import mage.abilities.Modes; import mage.abilities.TriggeredAbility; @@ -41,6 +42,9 @@ public class ConditionalTriggeredAbility extends TriggeredAbilityImpl { this.ability = ability; this.condition = condition; this.abilityText = text; + if (ability.isLeavesTheBattlefieldTrigger()) { + this.setLeavesTheBattlefieldTrigger(true); + } } protected ConditionalTriggeredAbility(final ConditionalTriggeredAbility triggered) { @@ -118,4 +122,13 @@ public class ConditionalTriggeredAbility extends TriggeredAbilityImpl { return this; } + @Override + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + if (isLeavesTheBattlefieldTrigger()) { + // TODO: leaves battlefield and die are not same! Is it possible make a diff logic? + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); + } else { + return super.isInUseableZone(game, sourceObject, event); + } + } } diff --git a/Mage/src/main/java/mage/abilities/effects/ContinuousEffects.java b/Mage/src/main/java/mage/abilities/effects/ContinuousEffects.java index 8dd4e6cf9ea..5ad74ac0fb1 100644 --- a/Mage/src/main/java/mage/abilities/effects/ContinuousEffects.java +++ b/Mage/src/main/java/mage/abilities/effects/ContinuousEffects.java @@ -325,7 +325,7 @@ public class ContinuousEffects implements Serializable { if (effect instanceof PayCostToAttackBlockEffect) { Set abilities = replacementEffects.getAbility(effect.getId()); for (Ability ability : abilities) { - // for replacment effects of static abilities do not use LKI to check if to apply + // for replacement effects of static abilities do not use LKI to check if to apply if (ability.getAbilityType() != AbilityType.STATIC || ability.isInUseableZone(game, null, event)) { if (effect.getDuration() != Duration.OneUse || !effect.isUsed()) { if (!game.getScopeRelevant() || effect.hasSelfScope() || !event.getTargetId().equals(ability.getSourceId())) { @@ -370,7 +370,7 @@ public class ContinuousEffects implements Serializable { Set abilities = replacementEffects.getAbility(effect.getId()); Set applicableAbilities = new HashSet<>(); for (Ability ability : abilities) { - // for replacment effects of static abilities do not use LKI to check if to apply + // for replacement effects of static abilities do not use LKI to check if to apply if (ability.getAbilityType() != AbilityType.STATIC || ability.isInUseableZone(game, null, event)) { if (!effect.isUsed()) { if (!game.getScopeRelevant() diff --git a/Mage/src/main/java/mage/abilities/effects/common/DrawDiscardTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/DrawDiscardTargetEffect.java index 77bb69b3c65..8eb8c4a7a76 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/DrawDiscardTargetEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/DrawDiscardTargetEffect.java @@ -1,6 +1,7 @@ package mage.abilities.effects.common; import mage.abilities.Ability; +import mage.abilities.Mode; import mage.abilities.effects.OneShotEffect; import mage.constants.Outcome; import mage.game.Game; @@ -25,16 +26,6 @@ public class DrawDiscardTargetEffect extends OneShotEffect { this.cardsToDraw = cardsToDraw; this.cardsToDiscard = cardsToDiscard; this.random = random; - staticText = new StringBuilder("target player draws ") - .append(CardUtil.numberToText(cardsToDraw, "a")) - .append(" card") - .append(cardsToDraw > 1 ? "s" : "") - .append(", then discards ") - .append(CardUtil.numberToText(cardsToDiscard, "a")) - .append(" card") - .append(cardsToDiscard > 1 ? "s" : "") - .append(random ? " at random" : "") - .toString(); } private DrawDiscardTargetEffect(final DrawDiscardTargetEffect effect) { @@ -59,4 +50,16 @@ public class DrawDiscardTargetEffect extends OneShotEffect { } return false; } + + @Override + public String getText(Mode mode) { + if (staticText != null && !staticText.isEmpty()) { + return staticText; + } + return getTargetPointer().describeTargets(mode.getTargets(), "that player") + " draws " + + CardUtil.numberToText(cardsToDraw, "a") + " card" + (cardsToDraw > 1 ? "s" : "") + + ", then discards " + CardUtil.numberToText(cardsToDiscard, "a") + + " card" + (cardsToDiscard > 1 ? "s" : "") + (random ? " at random" : ""); + } + } diff --git a/Mage/src/main/java/mage/abilities/effects/common/PutOnTopOrBottomLibraryTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/PutOnTopOrBottomLibraryTargetEffect.java index 3a9b6e9092c..4a99ba55bd2 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/PutOnTopOrBottomLibraryTargetEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/PutOnTopOrBottomLibraryTargetEffect.java @@ -13,24 +13,15 @@ import mage.players.Player; public class PutOnTopOrBottomLibraryTargetEffect extends OneShotEffect { private final boolean textOwnerOf; - private final boolean textTheirChoice; public PutOnTopOrBottomLibraryTargetEffect(boolean textOwnerOf) { super(Outcome.ReturnToHand); this.textOwnerOf = textOwnerOf; - this.textTheirChoice = false; - } - - public PutOnTopOrBottomLibraryTargetEffect(boolean textOwnerOf, boolean textTheirChoice) { - super(Outcome.ReturnToHand); - this.textOwnerOf = textOwnerOf; - this.textTheirChoice = textTheirChoice; } private PutOnTopOrBottomLibraryTargetEffect(final PutOnTopOrBottomLibraryTargetEffect effect) { super(effect); this.textOwnerOf = effect.textOwnerOf; - this.textTheirChoice = effect.textTheirChoice; } @Override @@ -58,6 +49,6 @@ public class PutOnTopOrBottomLibraryTargetEffect extends OneShotEffect { } String targetText = getTargetPointer().describeTargets(mode.getTargets(), "that permanent"); return (textOwnerOf ? "the owner of " + targetText : targetText + "'s owner") + - " puts it on " + (textTheirChoice ? "their choice of " : "") + "the top or bottom of their library"; + " puts it on their choice of the top or bottom of their library"; } } diff --git a/Mage/src/main/java/mage/abilities/effects/common/SacrificeAllEffect.java b/Mage/src/main/java/mage/abilities/effects/common/SacrificeAllEffect.java index 6b637d76dbd..d426ccf9627 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/SacrificeAllEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/SacrificeAllEffect.java @@ -139,6 +139,9 @@ public class SacrificeAllEffect extends OneShotEffect { sb.append(' '); sb.append(filter.getMessage()); } + if (!filter.getMessage().contains("with")) { + sb.append(" of their choice"); + } staticText = sb.toString(); } diff --git a/Mage/src/main/java/mage/abilities/effects/common/SacrificeEffect.java b/Mage/src/main/java/mage/abilities/effects/common/SacrificeEffect.java index 32577d03b00..034cff1cd46 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/SacrificeEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/SacrificeEffect.java @@ -91,7 +91,8 @@ public class SacrificeEffect extends OneShotEffect { if (preText != null) { sb.append(preText); } - if (preText != null && (preText.endsWith("player") || preText.endsWith("opponent") || preText.endsWith("controller"))) { + boolean playerSacs = preText != null && (preText.endsWith("player") || preText.endsWith("opponent") || preText.endsWith("controller")); + if (playerSacs) { sb.append(" sacrifices "); } else { if (preText == null || preText.isEmpty()) { @@ -107,6 +108,9 @@ public class SacrificeEffect extends OneShotEffect { sb.append(" "); sb.append(filter.getMessage()); } + if (playerSacs && !filter.getMessage().contains("with")) { + sb.append(" of their choice"); + } staticText = sb.toString(); } } diff --git a/Mage/src/main/java/mage/abilities/effects/common/SacrificeOpponentsUnlessPayEffect.java b/Mage/src/main/java/mage/abilities/effects/common/SacrificeOpponentsUnlessPayEffect.java index 841164a7b29..63bca73a3b5 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/SacrificeOpponentsUnlessPayEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/SacrificeOpponentsUnlessPayEffect.java @@ -34,7 +34,7 @@ public class SacrificeOpponentsUnlessPayEffect extends OneShotEffect { super(Outcome.Sacrifice); this.cost = cost; this.filter = filter; - this.staticText = "each opponent sacrifices " + CardUtil.addArticle(filter.getMessage()) + " unless they " + CardUtil.addCostVerb(cost.getText()); + this.staticText = "each opponent sacrifices " + CardUtil.addArticle(filter.getMessage()) + " of their choice unless they " + CardUtil.addCostVerb(cost.getText()); } protected SacrificeOpponentsUnlessPayEffect(final SacrificeOpponentsUnlessPayEffect effect) { diff --git a/Mage/src/main/java/mage/abilities/effects/common/counter/DistributeCountersEffect.java b/Mage/src/main/java/mage/abilities/effects/common/counter/DistributeCountersEffect.java index 0dd023ffcb8..d1ee03ce613 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/counter/DistributeCountersEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/counter/DistributeCountersEffect.java @@ -1,4 +1,3 @@ - package mage.abilities.effects.common.counter; import mage.abilities.Ability; @@ -24,22 +23,24 @@ public class DistributeCountersEffect extends OneShotEffect { private final CounterType counterType; private final DynamicValue amount; - private final boolean removeAtEndOfTurn; + private boolean removeAtEndOfTurn = false; private final String targetDescription; + /** + * Distribute +1/+1 counters among targets + */ + public DistributeCountersEffect(int amount, String targetDescription) { + this(CounterType.P1P1, StaticValue.get(amount), targetDescription); + } + public DistributeCountersEffect(CounterType counterType, int amount, String targetDescription) { - this(counterType, amount, false, targetDescription); + this(counterType, StaticValue.get(amount), targetDescription); } - public DistributeCountersEffect(CounterType counterType, int amount, boolean removeAtEndOfTurn, String targetDescription) { - this(counterType, StaticValue.get(amount), removeAtEndOfTurn, targetDescription); - } - - public DistributeCountersEffect(CounterType counterType, DynamicValue amount, boolean removeAtEndOfTurn, String targetDescription) { + public DistributeCountersEffect(CounterType counterType, DynamicValue amount, String targetDescription) { super(Outcome.BoostCreature); this.counterType = counterType; this.amount = amount; - this.removeAtEndOfTurn = removeAtEndOfTurn; this.targetDescription = targetDescription; } @@ -56,6 +57,11 @@ public class DistributeCountersEffect extends OneShotEffect { return new DistributeCountersEffect(this); } + public DistributeCountersEffect withRemoveAtEndOfTurn() { + this.removeAtEndOfTurn = true; + return this; + } + @Override public boolean apply(Game game, Ability source) { if (!source.getTargets().isEmpty()) { @@ -99,7 +105,7 @@ class RemoveCountersAtEndOfTurn extends OneShotEffect { private final CounterType counterType; - public RemoveCountersAtEndOfTurn(CounterType counterType) { + RemoveCountersAtEndOfTurn(CounterType counterType) { super(Outcome.Detriment); this.counterType = counterType; String name = counterType.getName(); diff --git a/Mage/src/main/java/mage/abilities/keyword/HauntAbility.java b/Mage/src/main/java/mage/abilities/keyword/HauntAbility.java index c6343ba97e8..f4d0a650c5b 100644 --- a/Mage/src/main/java/mage/abilities/keyword/HauntAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/HauntAbility.java @@ -47,6 +47,7 @@ public class HauntAbility extends TriggeredAbilityImpl { setTriggerPhrase((creatureHaunt ? "When {this} enters or the creature it haunts dies, " : "When the creature {this} haunts dies, ") ); + setLeavesTheBattlefieldTrigger(true); } private HauntAbility(final HauntAbility ability) { @@ -128,7 +129,7 @@ class HauntExileAbility extends ZoneChangeTriggeredAbility { } @Override - public boolean isInUseableZone(Game game, MageObject source, GameEvent event) { + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { boolean fromOK = true; Permanent sourcePermanent = (Permanent) game.getLastKnownInformation(sourceId, Zone.BATTLEFIELD); if (creatureHaunt diff --git a/Mage/src/main/java/mage/abilities/keyword/RecoverAbility.java b/Mage/src/main/java/mage/abilities/keyword/RecoverAbility.java index 3bcd02f36db..6b07d43f59e 100644 --- a/Mage/src/main/java/mage/abilities/keyword/RecoverAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/RecoverAbility.java @@ -1,20 +1,16 @@ - package mage.abilities.keyword; -import mage.abilities.Ability; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.costs.Cost; import mage.abilities.costs.mana.ManaCost; -import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.DoIfCostPaid; import mage.abilities.effects.common.ExileSourceEffect; import mage.abilities.effects.common.ReturnToHandSourceEffect; import mage.cards.Card; -import mage.constants.Outcome; import mage.constants.Zone; import mage.game.Game; import mage.game.events.GameEvent; import mage.game.events.ZoneChangeEvent; -import mage.players.Player; /** * 702.58a Recover is a triggered ability that functions only while the card @@ -28,7 +24,8 @@ import mage.players.Player; public class RecoverAbility extends TriggeredAbilityImpl { public RecoverAbility(Cost cost, Card card) { - super(Zone.GRAVEYARD, new RecoverEffect(cost, card.isCreature()), false); + super(Zone.GRAVEYARD, new RecoverEffect(cost, card), false); + setLeavesTheBattlefieldTrigger(true); } protected RecoverAbility(final RecoverAbility ability) { @@ -64,19 +61,15 @@ public class RecoverAbility extends TriggeredAbilityImpl { } } -class RecoverEffect extends OneShotEffect { +class RecoverEffect extends DoIfCostPaid { - protected Cost cost; - - public RecoverEffect(Cost cost, boolean creature) { - super(Outcome.ReturnToHand); - this.cost = cost; - this.staticText = setText(cost, creature); + public RecoverEffect(Cost cost, Card card) { + super(new ReturnToHandSourceEffect(), new ExileSourceEffect(), cost); + this.staticText = setText(cost, card.isCreature()); } protected RecoverEffect(final RecoverEffect effect) { super(effect); - this.cost = effect.cost.copy(); } @Override @@ -84,23 +77,6 @@ class RecoverEffect extends OneShotEffect { return new RecoverEffect(this); } - @Override - public boolean apply(Game game, Ability source) { - Player controller = game.getPlayer(source.getControllerId()); - Card sourceCard = game.getCard(source.getSourceId()); - if (controller != null && sourceCard != null - && game.getState().getZone(source.getSourceId()) == Zone.GRAVEYARD) { - if (controller.chooseUse(Outcome.Damage, "Pay " + cost.getText() + " to recover " + sourceCard.getLogName() + "? (Otherwise the card will be exiled)", source, game)) { - cost.clearPaid(); - if (cost.pay(source, game, source, controller.getId(), false, null)) { - return new ReturnToHandSourceEffect().apply(game, source); - } - } - return new ExileSourceEffect().apply(game, source); - } - return false; - } - private String setText(Cost cost, boolean creature) { StringBuilder sb = new StringBuilder(); sb.append("Recover"); diff --git a/Mage/src/main/java/mage/abilities/meta/OrTriggeredAbility.java b/Mage/src/main/java/mage/abilities/meta/OrTriggeredAbility.java index c5fce43af6c..2342564f662 100644 --- a/Mage/src/main/java/mage/abilities/meta/OrTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/meta/OrTriggeredAbility.java @@ -1,5 +1,6 @@ package mage.abilities.meta; +import mage.MageObject; import mage.abilities.Ability; import mage.abilities.TriggeredAbility; import mage.abilities.TriggeredAbilityImpl; @@ -10,10 +11,7 @@ import mage.game.events.GameEvent; import mage.util.CardUtil; import mage.watchers.Watcher; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.UUID; +import java.util.*; import java.util.stream.Collectors; /** @@ -46,6 +44,10 @@ public class OrTriggeredAbility extends TriggeredAbilityImpl { for(Watcher watcher : ability.getWatchers()) { super.addWatcher(watcher); } + + if (ability.isLeavesTheBattlefieldTrigger()) { + setLeavesTheBattlefieldTrigger(true); + } } setTriggerPhrase(generateTriggerPhrase()); } @@ -123,4 +125,19 @@ public class OrTriggeredAbility extends TriggeredAbilityImpl { ability.addWatcher(watcher); } } + + @Override + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + boolean res = false; + for (TriggeredAbility ability : triggeredAbilities) { + // TODO: call full inner trigger instead like ability.isInUseableZone()?! Need research why it fails + if (ability.isLeavesTheBattlefieldTrigger()) { + // TODO: leaves battlefield and die are not same! Is it possible make a diff logic? + res |= TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); + } else { + res |= super.isInUseableZone(game, sourceObject, event); + } + } + return res; + } } diff --git a/Mage/src/main/java/mage/constants/SetType.java b/Mage/src/main/java/mage/constants/SetType.java index c4a68115e75..cf426394a5e 100644 --- a/Mage/src/main/java/mage/constants/SetType.java +++ b/Mage/src/main/java/mage/constants/SetType.java @@ -37,7 +37,10 @@ public enum SetType { public boolean isEternalLegal() { // any official sets except un-sets - return this != SetType.CUSTOM_SET && this != SetType.JOKE_SET && this != SetType.MAGIC_ARENA; + return this != SetType.CUSTOM_SET + && this != SetType.JOKE_SET + && this != SetType.REMIX // to exclude MB2 playtest cards + && this != SetType.MAGIC_ARENA; } public boolean isStandardLegal() { diff --git a/Mage/src/main/java/mage/designations/Monarch.java b/Mage/src/main/java/mage/designations/Monarch.java index 01666c62460..20cbc62b599 100644 --- a/Mage/src/main/java/mage/designations/Monarch.java +++ b/Mage/src/main/java/mage/designations/Monarch.java @@ -61,7 +61,7 @@ class MonarchDrawTriggeredAbility extends BeginningOfEndStepTriggeredAbility { } @Override - public boolean isInUseableZone(Game game, MageObject source, GameEvent event) { + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { return true; } @@ -108,7 +108,7 @@ class MonarchDealsCombatDamageToAPlayerTriggeredAbility extends TriggeredAbility } @Override - public boolean isInUseableZone(Game game, MageObject source, GameEvent event) { + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { return true; } diff --git a/Mage/src/main/java/mage/game/command/emblems/DackFaydenEmblem.java b/Mage/src/main/java/mage/game/command/emblems/DackFaydenEmblem.java index f56faea246d..0a2dabc7efb 100644 --- a/Mage/src/main/java/mage/game/command/emblems/DackFaydenEmblem.java +++ b/Mage/src/main/java/mage/game/command/emblems/DackFaydenEmblem.java @@ -63,7 +63,7 @@ class DackFaydenEmblemTriggeredAbility extends TriggeredAbilityImpl { @Override public boolean checkTrigger(GameEvent event, Game game) { boolean returnValue = false; - List targetedPermanentIds = new ArrayList<>(0); + List targetedPermanentIds = new ArrayList<>(); Player player = game.getPlayer(this.getControllerId()); if (player != null) { if (event.getPlayerId().equals(this.getControllerId())) { diff --git a/Mage/src/main/java/mage/game/events/GameEvent.java b/Mage/src/main/java/mage/game/events/GameEvent.java index 90a34cf5ee3..73d985e9493 100644 --- a/Mage/src/main/java/mage/game/events/GameEvent.java +++ b/Mage/src/main/java/mage/game/events/GameEvent.java @@ -511,11 +511,6 @@ public class GameEvent implements Serializable { DAMAGED_BATCH_FOR_ONE_PERMANENT(true), DESTROY_PERMANENT, - /* DESTROY_PERMANENT_BY_LEGENDARY_RULE - targetId id of the permanent to destroy - playerId controller of the permanent to detroy - */ - DESTROY_PERMANENT_BY_LEGENDARY_RULE, /* DESTROYED_PERMANENT targetId id of the destroyed creature sourceId sourceId of the ability with the destroy effect @@ -819,16 +814,12 @@ public class GameEvent implements Serializable { } /** - * Returns possibly approving object that allowed the creation of the event. + * Returns possibly approving object that allowed the creation of the event. Used for cast spell and play land events. */ - public ApprovingObject getAdditionalReference() { + public ApprovingObject getApprovingObject() { return approvingObject; } - public void setAdditionalReference(ApprovingObject approvingObject) { - this.approvingObject = approvingObject; - } - /** * used to store which replacement effects were already applied to an event * or any modified events that may replace it diff --git a/Mage/src/main/java/mage/game/events/ZoneChangeEvent.java b/Mage/src/main/java/mage/game/events/ZoneChangeEvent.java index 0ed685d8150..576723e93c1 100644 --- a/Mage/src/main/java/mage/game/events/ZoneChangeEvent.java +++ b/Mage/src/main/java/mage/game/events/ZoneChangeEvent.java @@ -96,8 +96,6 @@ public class ZoneChangeEvent extends GameEvent { /** * Source ability of the event, can be null in rare cases - * - * @return */ public Ability getSource() { return this.source; @@ -105,6 +103,9 @@ public class ZoneChangeEvent extends GameEvent { @Override public String toString() { - return super.toString() + ", from " + getFromZone() + " to " + getToZone(); + return super.toString() + + ", from " + getFromZone() + " to " + getToZone() + + ", " + (this.target == null ? "no target" : "target " + this.target) + + ", " + (this.source == null ? "no source" : "source " + this.source); } } diff --git a/Mage/src/main/java/mage/game/permanent/PermanentImpl.java b/Mage/src/main/java/mage/game/permanent/PermanentImpl.java index ccb394e13a4..46f0a89a656 100644 --- a/Mage/src/main/java/mage/game/permanent/PermanentImpl.java +++ b/Mage/src/main/java/mage/game/permanent/PermanentImpl.java @@ -1255,11 +1255,26 @@ public abstract class PermanentImpl extends CardImpl implements Permanent { } // own etb event + // 616.1a + // If any of the replacement and/or prevention effects are self-replacement effects (see rule 614.15), + // one of them must be chosen. If not, proceed to rule 616.1b. if (game.replaceEvent(new EntersTheBattlefieldEvent(this, source, getControllerId(), fromZone, EnterEventType.SELF))) { return false; } + // 616.1b + // If any of the replacement and/or prevention effects would modify under whose control an object would + // enter the battlefield, one of them must be chosen. If not, proceed to rule 616.1c. + // TODO: need implementation? See #13062 + + // 616.1c + // If any of the replacement and/or prevention effects would cause an object to become a copy of another + // object as it enters the battlefield, one of them must be chosen. If not, proceed to rule 616.1d. + // TODO: need implementation? See #13062 + // normal etb event + // 616.1d + // Any of the applicable replacement and/or prevention effects may be chosen. EntersTheBattlefieldEvent event = new EntersTheBattlefieldEvent(this, source, getControllerId(), fromZone); if (game.replaceEvent(event)) { return false; diff --git a/Mage/src/main/java/mage/game/stack/StackAbility.java b/Mage/src/main/java/mage/game/stack/StackAbility.java index d87f9fdf871..4f9244234ba 100644 --- a/Mage/src/main/java/mage/game/stack/StackAbility.java +++ b/Mage/src/main/java/mage/game/stack/StackAbility.java @@ -517,12 +517,12 @@ public class StackAbility extends StackObjectImpl implements Ability { } @Override - public boolean isInUseableZone(Game game, MageObject source, GameEvent event) { + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { throw new UnsupportedOperationException("Not supported."); } @Override - public boolean hasSourceObjectAbility(Game game, MageObject source, GameEvent event) { + public boolean hasSourceObjectAbility(Game game, MageObject sourceObject, GameEvent event) { throw new UnsupportedOperationException("Not supported."); } diff --git a/Mage/src/main/java/mage/target/TargetAmount.java b/Mage/src/main/java/mage/target/TargetAmount.java index 76ac8c7186f..6bd1e51c510 100644 --- a/Mage/src/main/java/mage/target/TargetAmount.java +++ b/Mage/src/main/java/mage/target/TargetAmount.java @@ -2,7 +2,6 @@ package mage.target; import mage.abilities.Ability; import mage.abilities.dynamicvalue.DynamicValue; -import mage.abilities.dynamicvalue.common.StaticValue; import mage.constants.Outcome; import mage.game.Game; import mage.players.Player; @@ -19,14 +18,8 @@ public abstract class TargetAmount extends TargetImpl { DynamicValue amount; int remainingAmount; - public TargetAmount(int amount) { - this(StaticValue.get(amount)); - } - - public TargetAmount(DynamicValue amount) { + protected TargetAmount(DynamicValue amount) { this.amount = amount; - //this.remainingAmount = amount; - amountWasSet = false; } protected TargetAmount(final TargetAmount target) { diff --git a/Mage/src/main/java/mage/target/common/TargetCreatureOrPlayerAmount.java b/Mage/src/main/java/mage/target/common/TargetCreatureOrPlayerAmount.java deleted file mode 100644 index 11cb59db031..00000000000 --- a/Mage/src/main/java/mage/target/common/TargetCreatureOrPlayerAmount.java +++ /dev/null @@ -1,46 +0,0 @@ -package mage.target.common; - -import mage.abilities.dynamicvalue.DynamicValue; -import mage.abilities.dynamicvalue.common.StaticValue; -import mage.constants.CardType; -import mage.constants.Zone; -import mage.filter.common.FilterPermanentOrPlayer; - -/** - * @author BetaSteward_at_googlemail.com - */ -public class TargetCreatureOrPlayerAmount extends TargetPermanentOrPlayerAmount { - - private static final FilterPermanentOrPlayer defaultFilter - = new FilterPermanentOrPlayer("creatures and/or players"); - - static { - defaultFilter.getPermanentFilter().add(CardType.CREATURE.getPredicate()); - } - - public TargetCreatureOrPlayerAmount(int amount) { - // 107.1c If a rule or ability instructs a player to choose “any number,” that player may choose - // any positive number or zero, unless something (such as damage or counters) is being divided - // or distributed among “any number” of players and/or objects. In that case, a nonzero number - // of players and/or objects must be chosen if possible. - this(StaticValue.get(amount)); - this.minNumberOfTargets = 1; - } - - public TargetCreatureOrPlayerAmount(DynamicValue amount) { - super(amount); - this.zone = Zone.ALL; - this.filter = defaultFilter; - this.targetName = filter.getMessage(); - } - - private TargetCreatureOrPlayerAmount(final TargetCreatureOrPlayerAmount target) { - super(target); - this.filter = target.filter.copy(); - } - - @Override - public TargetCreatureOrPlayerAmount copy() { - return new TargetCreatureOrPlayerAmount(this); - } -} diff --git a/Mage/src/main/java/mage/target/common/TargetPermanentOrPlayerAmount.java b/Mage/src/main/java/mage/target/common/TargetPermanentOrPlayerAmount.java index 63b3799d33a..9e42eb94fb6 100644 --- a/Mage/src/main/java/mage/target/common/TargetPermanentOrPlayerAmount.java +++ b/Mage/src/main/java/mage/target/common/TargetPermanentOrPlayerAmount.java @@ -22,10 +22,6 @@ public abstract class TargetPermanentOrPlayerAmount extends TargetAmount { protected FilterPermanentOrPlayer filter; - TargetPermanentOrPlayerAmount(DynamicValue amount) { - this(amount, 0); - } - TargetPermanentOrPlayerAmount(DynamicValue amount, int maxNumberOfTargets) { super(amount); this.maxNumberOfTargets = maxNumberOfTargets; diff --git a/Mage/src/main/java/mage/target/targetpointer/NthTargetPointer.java b/Mage/src/main/java/mage/target/targetpointer/NthTargetPointer.java index c94d9db9f48..cb79e86e0a5 100644 --- a/Mage/src/main/java/mage/target/targetpointer/NthTargetPointer.java +++ b/Mage/src/main/java/mage/target/targetpointer/NthTargetPointer.java @@ -15,7 +15,7 @@ import java.util.*; */ public abstract class NthTargetPointer extends TargetPointerImpl { - private static final List emptyTargets = Collections.unmodifiableList(new ArrayList<>(0)); + private static final List emptyTargets = Collections.unmodifiableList(new ArrayList<>()); // TODO: rework to list of MageObjectReference instead zcc private final Map zoneChangeCounter = new HashMap<>(); diff --git a/Mage/src/main/java/mage/watchers/common/OnceEachTurnCastWatcher.java b/Mage/src/main/java/mage/watchers/common/OnceEachTurnCastWatcher.java index 2ac2b578add..fb29f87ac28 100644 --- a/Mage/src/main/java/mage/watchers/common/OnceEachTurnCastWatcher.java +++ b/Mage/src/main/java/mage/watchers/common/OnceEachTurnCastWatcher.java @@ -32,7 +32,7 @@ public class OnceEachTurnCastWatcher extends Watcher { && event.getPlayerId() != null && event.hasApprovingIdentifier(MageIdentifier.OnceEachTurnCastWatcher)) { usedFrom.computeIfAbsent(event.getPlayerId(), k -> new HashSet<>()) - .add(event.getAdditionalReference().getApprovingMageObjectReference()); + .add(event.getApprovingObject().getApprovingMageObjectReference()); } } diff --git a/pom.xml b/pom.xml index ae0996dbc27..34f0f0a8109 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.mage mage-root - 1.4.54 + 1.4.55 pom Mage Root Mage Root POM @@ -16,7 +16,7 @@ ${project.basedir} - 1.4.54 + 1.4.55 -Dfile.encoding=UTF-8 UTF-8 yyyy-MM-dd'T'HH:mm:ss'Z'