From 3abce2f5c8d639105de757280877c5a04052b216 Mon Sep 17 00:00:00 2001 From: Susucre <34709007+Susucre@users.noreply.github.com> Date: Thu, 16 May 2024 13:37:53 +0200 Subject: [PATCH] support until your next turn delayed triggers (#12233) --- Mage.Sets/src/mage/cards/d/DontMove.java | 102 +++-------------- .../mage/cards/j/JaceArchitectOfThought.java | 89 +++------------ .../mage/cards/t/TamiyoFieldResearcher.java | 21 +--- Mage.Sets/src/mage/cards/t/Tephraderm.java | 62 ++-------- .../src/mage/cards/v/VraskaTheUnseen.java | 106 +++--------------- .../emn/TamiyoFieldResearcherTest.java} | 36 ++++-- .../single/hou/BontusLastReckoningTest.java | 8 +- .../test/cards/single/ons/TephradermTest.java | 78 +++++++++++++ .../test/cards/single/rex/DontMoveTest.java | 82 ++++++++++++++ .../rtr/JaceArchitectOfThoughtTest.java | 6 + .../rtr/VraskaTheUnseenTest.java} | 33 +++++- .../abilities/DelayedTriggeredAbilities.java | 8 +- ...sDamageToACreatureAllTriggeredAbility.java | 27 ++--- .../DealsDamageToThisAllTriggeredAbility.java | 80 +++++++++++++ ...ilYourNextTurnDelayedTriggeredAbility.java | 106 ++++++++++++++++++ Mage/src/main/java/mage/game/GameState.java | 4 + .../main/java/mage/players/PlayerImpl.java | 1 + 17 files changed, 502 insertions(+), 347 deletions(-) rename Mage.Tests/src/test/java/org/mage/test/cards/{planeswalker/TamiyoTest.java => single/emn/TamiyoFieldResearcherTest.java} (90%) create mode 100644 Mage.Tests/src/test/java/org/mage/test/cards/single/ons/TephradermTest.java create mode 100644 Mage.Tests/src/test/java/org/mage/test/cards/single/rex/DontMoveTest.java rename Mage.Tests/src/test/java/org/mage/test/cards/{continuous/VraskaTheUnseenNextTurnTest.java => single/rtr/VraskaTheUnseenTest.java} (52%) create mode 100644 Mage/src/main/java/mage/abilities/common/DealsDamageToThisAllTriggeredAbility.java create mode 100644 Mage/src/main/java/mage/abilities/common/delayed/UntilYourNextTurnDelayedTriggeredAbility.java diff --git a/Mage.Sets/src/mage/cards/d/DontMove.java b/Mage.Sets/src/mage/cards/d/DontMove.java index 19e2d58c0ee..6b1a6144475 100644 --- a/Mage.Sets/src/mage/cards/d/DontMove.java +++ b/Mage.Sets/src/mage/cards/d/DontMove.java @@ -1,27 +1,21 @@ package mage.cards.d; -import java.util.UUID; - -import mage.abilities.Ability; -import mage.abilities.DelayedTriggeredAbility; -import mage.abilities.effects.OneShotEffect; +import mage.abilities.common.BecomesTappedTriggeredAbility; +import mage.abilities.common.delayed.UntilYourNextTurnDelayedTriggeredAbility; +import mage.abilities.effects.common.CreateDelayedTriggeredAbilityEffect; import mage.abilities.effects.common.DestroyAllEffect; import mage.abilities.effects.common.DestroyTargetEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.Duration; -import mage.constants.Outcome; +import mage.filter.StaticFilters; import mage.filter.common.FilterCreaturePermanent; import mage.filter.predicate.permanent.TappedPredicate; -import mage.game.Game; -import mage.game.events.GameEvent; -import mage.game.permanent.Permanent; -import mage.target.targetpointer.FixedTarget; + +import java.util.UUID; /** - * - * @author jimga150 + * @author jimga150, Susucr */ public final class DontMove extends CardImpl { @@ -39,9 +33,17 @@ public final class DontMove extends CardImpl { this.getSpellAbility().addEffect(new DestroyAllEffect(filter, false)); // Until your next turn, whenever a creature becomes tapped, destroy it. - this.getSpellAbility().addEffect(new DontMoveEffect()); + this.getSpellAbility().addEffect(new CreateDelayedTriggeredAbilityEffect( + new UntilYourNextTurnDelayedTriggeredAbility( + new BecomesTappedTriggeredAbility( + new DestroyTargetEffect(), false, + StaticFilters.FILTER_PERMANENT_CREATURE, true + ) + ) + )); - // Don't Move won't affect a creature that enters the battlefield tapped. + // Ruling (2023-11-10) + // > Don't Move won't affect a creature that enters the battlefield tapped. } private DontMove(final DontMove card) { @@ -52,72 +54,4 @@ public final class DontMove extends CardImpl { public DontMove copy() { return new DontMove(this); } -} - -class DontMoveEffect extends OneShotEffect { - - DontMoveEffect() { - super(Outcome.PlayForFree); - this.staticText = "Until your next turn, whenever a creature becomes tapped, destroy it."; - } - - private DontMoveEffect(final DontMoveEffect effect) { - super(effect); - } - - @Override - public DontMoveEffect copy() { - return new DontMoveEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - game.addDelayedTriggeredAbility(new DontMoveAbility(game.getTurnNum()), source); - return true; - } -} - -// Instead of using Duration.UntilYourNextTurn, since currently DelayedTriggeredAbility does not support checking for -// this, instead this subclass will manually check for the end of this trigger's life by tracking the turn number -// and ending when next the game circles back to the casting player, after the turn number has changed. -// This workaround was taken directly from the diff helpfully provided by michaelstephendavies in issue #2078: -// https://github.com/magefree/mage/issues/2078 -class DontMoveAbility extends DelayedTriggeredAbility { - - private final int startingTurn; - - public DontMoveAbility(int startingTurn) { - super(new DestroyTargetEffect(), Duration.Custom, false); - this.startingTurn = startingTurn; - } - - private DontMoveAbility(final DontMoveAbility ability) { - super(ability); - this.startingTurn = ability.startingTurn; - } - - @Override - public DelayedTriggeredAbility copy() { - return new DontMoveAbility(this); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.TAPPED; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - Permanent permanent = game.getPermanent(event.getTargetId()); - if (permanent == null || !permanent.isCreature(game)){ - return false; - } - this.getAllEffects().get(0).setTargetPointer(new FixedTarget(permanent, game)); - return true; - } - - @Override - public boolean isInactive(Game game) { - return game.getActivePlayerId().equals(getControllerId()) && game.getTurnNum() != startingTurn; - } -} +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/j/JaceArchitectOfThought.java b/Mage.Sets/src/mage/cards/j/JaceArchitectOfThought.java index 6256ca5afdb..ad4ef1f1cff 100644 --- a/Mage.Sets/src/mage/cards/j/JaceArchitectOfThought.java +++ b/Mage.Sets/src/mage/cards/j/JaceArchitectOfThought.java @@ -1,19 +1,19 @@ package mage.cards.j; import mage.abilities.Ability; -import mage.abilities.DelayedTriggeredAbility; import mage.abilities.LoyaltyAbility; +import mage.abilities.common.AttacksAllTriggeredAbility; +import mage.abilities.common.delayed.UntilYourNextTurnDelayedTriggeredAbility; import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.CreateDelayedTriggeredAbilityEffect; import mage.abilities.effects.common.RevealAndSeparatePilesEffect; import mage.abilities.effects.common.continuous.BoostTargetEffect; import mage.cards.*; import mage.constants.*; import mage.filter.StaticFilters; import mage.game.Game; -import mage.game.events.GameEvent; import mage.players.Player; import mage.target.common.TargetCardInLibrary; -import mage.target.targetpointer.FixedTarget; import mage.util.CardUtil; import java.util.UUID; @@ -31,7 +31,16 @@ public final class JaceArchitectOfThought extends CardImpl { this.setStartingLoyalty(4); // +1: Until your next turn, whenever a creature an opponent controls attacks, it gets -1/-0 until end of turn. - this.addAbility(new LoyaltyAbility(new JaceArchitectOfThoughtStartEffect1(), 1)); + this.addAbility(new LoyaltyAbility(new CreateDelayedTriggeredAbilityEffect( + new UntilYourNextTurnDelayedTriggeredAbility( + new AttacksAllTriggeredAbility( + new BoostTargetEffect(-1, 0, Duration.EndOfTurn) + .setText("it gets -1/-0 until end of turn"), + false, StaticFilters.FILTER_OPPONENTS_PERMANENT_A_CREATURE, + SetTargetPointer.PERMANENT, false + ) + ) + ), 1)); // -2: Reveal the top three cards of your library. An opponent separates those cards into two piles. // Put one pile into your hand and the other on the bottom of your library in any order. @@ -54,78 +63,6 @@ public final class JaceArchitectOfThought extends CardImpl { } } -class JaceArchitectOfThoughtStartEffect1 extends OneShotEffect { - - public JaceArchitectOfThoughtStartEffect1() { - super(Outcome.UnboostCreature); - this.staticText = "Until your next turn, whenever a creature an opponent " - + "controls attacks, it gets -1/-0 until end of turn"; - } - - private JaceArchitectOfThoughtStartEffect1(final JaceArchitectOfThoughtStartEffect1 effect) { - super(effect); - } - - @Override - public JaceArchitectOfThoughtStartEffect1 copy() { - return new JaceArchitectOfThoughtStartEffect1(this); - } - - @Override - public boolean apply(Game game, Ability source) { - DelayedTriggeredAbility delayedAbility = new JaceArchitectOfThoughtDelayedTriggeredAbility(game.getTurnNum()); - game.addDelayedTriggeredAbility(delayedAbility, source); - return true; - } -} - -class JaceArchitectOfThoughtDelayedTriggeredAbility extends DelayedTriggeredAbility { - - private final int startingTurn; - - public JaceArchitectOfThoughtDelayedTriggeredAbility(int startingTurn) { - super(new BoostTargetEffect(-1, 0, Duration.EndOfTurn), Duration.Custom, false); - this.startingTurn = startingTurn; - } - - private JaceArchitectOfThoughtDelayedTriggeredAbility(final JaceArchitectOfThoughtDelayedTriggeredAbility ability) { - super(ability); - this.startingTurn = ability.startingTurn; - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.ATTACKER_DECLARED; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - if (game.getOpponents(getControllerId()).contains(event.getPlayerId())) { - getEffects().forEach((effect) -> { - effect.setTargetPointer(new FixedTarget(event.getSourceId(), game)); - }); - return true; - } - return false; - } - - @Override - public JaceArchitectOfThoughtDelayedTriggeredAbility copy() { - return new JaceArchitectOfThoughtDelayedTriggeredAbility(this); - } - - @Override - public boolean isInactive(Game game) { - return game.isActivePlayer(getControllerId()) - && game.getTurnNum() != startingTurn; - } - - @Override - public String getRule() { - return "Until your next turn, whenever a creature an opponent controls attacks, it gets -1/-0 until end of turn."; - } -} - class JaceArchitectOfThoughtEffect3 extends OneShotEffect { public JaceArchitectOfThoughtEffect3() { diff --git a/Mage.Sets/src/mage/cards/t/TamiyoFieldResearcher.java b/Mage.Sets/src/mage/cards/t/TamiyoFieldResearcher.java index e752c317d7b..44019344521 100644 --- a/Mage.Sets/src/mage/cards/t/TamiyoFieldResearcher.java +++ b/Mage.Sets/src/mage/cards/t/TamiyoFieldResearcher.java @@ -12,10 +12,7 @@ import mage.abilities.effects.common.GetEmblemEffect; import mage.abilities.effects.common.TapTargetEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.CardType; -import mage.constants.SubType; -import mage.constants.Duration; -import mage.constants.Outcome; +import mage.constants.*; import mage.filter.FilterPermanent; import mage.filter.StaticFilters; import mage.filter.predicate.Predicates; @@ -31,10 +28,8 @@ import mage.target.common.TargetCreaturePermanent; import java.util.ArrayList; import java.util.List; import java.util.UUID; -import mage.constants.SuperType; /** - * * @author LevelX2 */ public final class TamiyoFieldResearcher extends CardImpl { @@ -104,7 +99,7 @@ class TamiyoFieldResearcherEffect1 extends OneShotEffect { creatures.add(new MageObjectReference(uuid, game)); } if (!creatures.isEmpty()) { - DelayedTriggeredAbility delayedAbility = new TamiyoFieldResearcherDelayedTriggeredAbility(creatures, game.getTurnNum()); + DelayedTriggeredAbility delayedAbility = new TamiyoFieldResearcherDelayedTriggeredAbility(creatures); game.addDelayedTriggeredAbility(delayedAbility, source); } return true; @@ -115,19 +110,16 @@ class TamiyoFieldResearcherEffect1 extends OneShotEffect { class TamiyoFieldResearcherDelayedTriggeredAbility extends DelayedTriggeredAbility { - private int startingTurn; private List creatures; - public TamiyoFieldResearcherDelayedTriggeredAbility(List creatures, int startingTurn) { - super(new DrawCardSourceControllerEffect(1), Duration.Custom, false); + public TamiyoFieldResearcherDelayedTriggeredAbility(List creatures) { + super(new DrawCardSourceControllerEffect(1), Duration.UntilYourNextTurn, false); this.creatures = creatures; - this.startingTurn = startingTurn; } private TamiyoFieldResearcherDelayedTriggeredAbility(final TamiyoFieldResearcherDelayedTriggeredAbility ability) { super(ability); this.creatures = ability.creatures; - this.startingTurn = ability.startingTurn; } @Override @@ -146,11 +138,6 @@ class TamiyoFieldResearcherDelayedTriggeredAbility extends DelayedTriggeredAbili return false; } - @Override - public boolean isInactive(Game game) { - return game.isActivePlayer(getControllerId()) && game.getTurnNum() != startingTurn; - } - @Override public TamiyoFieldResearcherDelayedTriggeredAbility copy() { return new TamiyoFieldResearcherDelayedTriggeredAbility(this); diff --git a/Mage.Sets/src/mage/cards/t/Tephraderm.java b/Mage.Sets/src/mage/cards/t/Tephraderm.java index 19cf51a11cf..b11e1487ed2 100644 --- a/Mage.Sets/src/mage/cards/t/Tephraderm.java +++ b/Mage.Sets/src/mage/cards/t/Tephraderm.java @@ -2,26 +2,26 @@ package mage.cards.t; import mage.MageInt; import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.common.DealsDamageToThisAllTriggeredAbility; +import mage.abilities.dynamicvalue.common.SavedDamageValue; import mage.abilities.dynamicvalue.common.StaticValue; import mage.abilities.effects.Effect; import mage.abilities.effects.common.DamageTargetEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; +import mage.constants.SetTargetPointer; import mage.constants.SubType; import mage.constants.Zone; import mage.filter.StaticFilters; -import mage.filter.common.FilterCreaturePermanent; import mage.game.Game; import mage.game.events.GameEvent; -import mage.game.permanent.Permanent; import mage.game.stack.StackObject; import mage.target.targetpointer.FixedTarget; import java.util.UUID; /** - * * @author Quercitron */ public final class Tephraderm extends CardImpl { @@ -33,7 +33,12 @@ public final class Tephraderm extends CardImpl { this.toughness = new MageInt(5); // Whenever a creature deals damage to Tephraderm, Tephraderm deals that much damage to that creature. - this.addAbility(new TephradermCreatureDamageTriggeredAbility()); + this.addAbility(new DealsDamageToThisAllTriggeredAbility( + new DamageTargetEffect(SavedDamageValue.MUCH) + .setText("{this} deals that much damage to that creature"), + false, StaticFilters.FILTER_PERMANENT_CREATURE, + SetTargetPointer.PERMANENT, false + )); // Whenever a spell deals damage to Tephraderm, Tephraderm deals that much damage to that spell's controller. this.addAbility(new TephradermSpellDamageTriggeredAbility()); @@ -49,55 +54,6 @@ public final class Tephraderm extends CardImpl { } } -class TephradermCreatureDamageTriggeredAbility extends TriggeredAbilityImpl { - - private static final FilterCreaturePermanent FILTER_CREATURE = new FilterCreaturePermanent(); - - TephradermCreatureDamageTriggeredAbility() { - super(Zone.BATTLEFIELD, new DamageTargetEffect(0)); - } - - private TephradermCreatureDamageTriggeredAbility(final TephradermCreatureDamageTriggeredAbility ability) { - super(ability); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.DAMAGED_PERMANENT; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - if (!event.getTargetId().equals(this.getSourceId())) { - return false; - } - - Permanent sourcePermanent = game.getPermanent(event.getSourceId()); - if (sourcePermanent != null - && FILTER_CREATURE.match(sourcePermanent, getControllerId(), this, game)) { - for (Effect effect : getEffects()) { - if (effect instanceof DamageTargetEffect) { - effect.setTargetPointer(new FixedTarget(sourcePermanent.getId(), game)); - ((DamageTargetEffect) effect).setAmount(StaticValue.get(event.getAmount())); - } - } - return true; - } - - return false; - } - - @Override - public TephradermCreatureDamageTriggeredAbility copy() { - return new TephradermCreatureDamageTriggeredAbility(this); - } - - @Override - public String getRule() { - return "Whenever a creature deals damage to {this}, {this} deals that much damage to that creature."; - } -} - class TephradermSpellDamageTriggeredAbility extends TriggeredAbilityImpl { TephradermSpellDamageTriggeredAbility() { diff --git a/Mage.Sets/src/mage/cards/v/VraskaTheUnseen.java b/Mage.Sets/src/mage/cards/v/VraskaTheUnseen.java index 030927e658f..003ce5fe271 100644 --- a/Mage.Sets/src/mage/cards/v/VraskaTheUnseen.java +++ b/Mage.Sets/src/mage/cards/v/VraskaTheUnseen.java @@ -1,22 +1,20 @@ package mage.cards.v; -import mage.abilities.Ability; import mage.abilities.LoyaltyAbility; -import mage.abilities.TriggeredAbilityImpl; -import mage.abilities.effects.ContinuousEffectImpl; -import mage.abilities.effects.Effect; +import mage.abilities.common.DealsDamageToThisAllTriggeredAbility; +import mage.abilities.common.delayed.UntilYourNextTurnDelayedTriggeredAbility; +import mage.abilities.effects.common.CreateDelayedTriggeredAbilityEffect; import mage.abilities.effects.common.CreateTokenEffect; import mage.abilities.effects.common.DestroyTargetEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.*; -import mage.game.Game; -import mage.game.events.DamagedEvent; -import mage.game.events.GameEvent; -import mage.game.permanent.Permanent; +import mage.constants.CardType; +import mage.constants.SetTargetPointer; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.filter.StaticFilters; import mage.game.permanent.token.AssassinToken; import mage.target.common.TargetNonlandPermanent; -import mage.target.targetpointer.FixedTarget; import java.util.UUID; @@ -39,7 +37,15 @@ public final class VraskaTheUnseen extends CardImpl { this.setStartingLoyalty(5); // +1: Until your next turn, whenever a creature deals combat damage to Vraska the Unseen, destroy that creature. - this.addAbility(new LoyaltyAbility(new VraskaTheUnseenGainAbilityEffect(new VraskaTheUnseenTriggeredAbility()), 1)); + this.addAbility(new LoyaltyAbility(new CreateDelayedTriggeredAbilityEffect( + new UntilYourNextTurnDelayedTriggeredAbility( + new DealsDamageToThisAllTriggeredAbility( + new DestroyTargetEffect().setText("destroy that creature"), + false, StaticFilters.FILTER_PERMANENT_CREATURE, + SetTargetPointer.PERMANENT, true + ) + ) + ), 1)); // -3: Destroy target nonland permanent. LoyaltyAbility ability = new LoyaltyAbility(new DestroyTargetEffect(), -3); @@ -58,80 +64,4 @@ public final class VraskaTheUnseen extends CardImpl { public VraskaTheUnseen copy() { return new VraskaTheUnseen(this); } -} - -class VraskaTheUnseenGainAbilityEffect extends ContinuousEffectImpl { - - protected Ability ability; - - public VraskaTheUnseenGainAbilityEffect(Ability ability) { - super(Duration.Custom, Layer.AbilityAddingRemovingEffects_6, SubLayer.NA, Outcome.AddAbility); - this.ability = ability; - staticText = "Until your next turn, whenever a creature deals combat damage to {this}, destroy that creature"; - } - - private VraskaTheUnseenGainAbilityEffect(final VraskaTheUnseenGainAbilityEffect effect) { - super(effect); - this.ability = effect.ability.copy(); - } - - @Override - public VraskaTheUnseenGainAbilityEffect copy() { - return new VraskaTheUnseenGainAbilityEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Permanent permanent = game.getPermanent(source.getSourceId()); - if (permanent != null) { - permanent.addAbility(ability, source.getSourceId(), game); - return true; - } - return false; - } - - @Override - public boolean isInactive(Ability source, Game game) { - return game.getTurnPhaseType() == TurnPhase.END && this.isYourNextTurn(game); - } -} - -class VraskaTheUnseenTriggeredAbility extends TriggeredAbilityImpl { - - public VraskaTheUnseenTriggeredAbility() { - super(Zone.BATTLEFIELD, new DestroyTargetEffect()); - } - - private VraskaTheUnseenTriggeredAbility(final VraskaTheUnseenTriggeredAbility ability) { - super(ability); - } - - @Override - public VraskaTheUnseenTriggeredAbility copy() { - return new VraskaTheUnseenTriggeredAbility(this); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.DAMAGED_PERMANENT; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - if (((DamagedEvent) event).isCombatDamage() && getSourceId().equals(event.getTargetId())) { - Permanent sourceOfDamage = game.getPermanent(event.getSourceId()); - if (sourceOfDamage != null && sourceOfDamage.isCreature(game)) { - Effect effect = this.getEffects().get(0); - effect.setTargetPointer(new FixedTarget(sourceOfDamage.getId(), game)); - return true; - } - } - return false; - } - - @Override - public String getRule() { - return "Until your next turn, whenever a creature deals combat damage to {this}, destroy that creature"; - } - -} +} \ No newline at end of file diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/planeswalker/TamiyoTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/emn/TamiyoFieldResearcherTest.java similarity index 90% rename from Mage.Tests/src/test/java/org/mage/test/cards/planeswalker/TamiyoTest.java rename to Mage.Tests/src/test/java/org/mage/test/cards/single/emn/TamiyoFieldResearcherTest.java index 1aa68e226eb..812090aab8a 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/planeswalker/TamiyoTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/emn/TamiyoFieldResearcherTest.java @@ -1,5 +1,5 @@ -package org.mage.test.cards.planeswalker; +package org.mage.test.cards.single.emn; import mage.constants.PhaseStep; import mage.constants.Zone; @@ -8,29 +8,31 @@ import org.mage.test.serverside.base.CardTestPlayerBase; /** * {@link mage.cards.t.TamiyoFieldResearcher Tamiyo, Field Researcher} - * {1}{G}{W}{U} - * 4 loyalty + * {1}{G}{W}{U} + * 4 loyalty * +1: Choose up to two target creatures. Until your next turn, whenever either of those creatures deals combat damage, you draw a card. * −2: Tap up to two target nonland permanents. They don't untap during their controller's next untap step. * −7: Draw three cards. You get an emblem with "You may cast nonland cards from your hand without paying their mana costs." * * @author escplan9 (Derek Monturo - dmontur1 at gmail dot com) */ -public class TamiyoTest extends CardTestPlayerBase { +public class TamiyoFieldResearcherTest extends CardTestPlayerBase { /** * Reported bug: I activated Tamiyo's +1 ability on a 5/5 Gideon and his 2/2 Knight Ally, but when they both attacked - * and dealt damage I only drew one card when I'm pretty sure I was supposed to draw for each of the two. + * and dealt damage I only drew one card when I'm pretty sure I was supposed to draw for each of the two. */ @Test public void testFieldResearcherFirstEffectOnGideon() { + setStrictChooseMode(true); + addCard(Zone.BATTLEFIELD, playerA, "Tamiyo, Field Researcher", 1); /* Gideon, Ally of Zendikar {2}{W}{W} - 4 loyalty * +1: Until end of turn, Gideon, Ally of Zendikar becomes a 5/5 Human Soldier Ally creature with indestructible * that's still a planeswalker. Prevent all damage that would be dealt to him this turn. * 0: Create a 2/2 white Knight Ally creature token. - **/ + **/ addCard(Zone.BATTLEFIELD, playerA, "Gideon, Ally of Zendikar", 1); // put 2/2 knight ally token on battlefield @@ -46,6 +48,7 @@ public class TamiyoTest extends CardTestPlayerBase { // attack with both unblocked attack(3, playerA, "Knight Ally Token"); attack(3, playerA, "Gideon, Ally of Zendikar"); + setChoice(playerA, "Until your next turn"); // stack both trigger. setStopAt(3, PhaseStep.END_COMBAT); execute(); @@ -62,6 +65,8 @@ public class TamiyoTest extends CardTestPlayerBase { */ @Test public void testFieldResearcherFirstEffectSimpleCreatureAttacks() { + setStrictChooseMode(true); + addCard(Zone.HAND, playerA, "Tamiyo, Field Researcher", 1); addCard(Zone.BATTLEFIELD, playerA, "Forest", 1); addCard(Zone.BATTLEFIELD, playerA, "Plains", 1); @@ -87,6 +92,8 @@ public class TamiyoTest extends CardTestPlayerBase { */ @Test public void testFieldResearcherFirstEffectSimpleCreaturesAttacks() { + setStrictChooseMode(true); + addCard(Zone.HAND, playerA, "Tamiyo, Field Researcher", 1); addCard(Zone.BATTLEFIELD, playerA, "Forest", 1); addCard(Zone.BATTLEFIELD, playerA, "Plains", 1); @@ -101,6 +108,7 @@ public class TamiyoTest extends CardTestPlayerBase { attack(1, playerA, "Bronze Sable"); attack(1, playerA, "Sylvan Advocate"); + setChoice(playerA, "Until your next turn"); // stack both trigger. setStopAt(1, PhaseStep.END_COMBAT); execute(); @@ -114,6 +122,8 @@ public class TamiyoTest extends CardTestPlayerBase { */ @Test public void testFieldResearcherFirstEffectAttackAndBlock() { + setStrictChooseMode(true); + addCard(Zone.HAND, playerA, "Tamiyo, Field Researcher", 1); addCard(Zone.BATTLEFIELD, playerA, "Forest", 1); addCard(Zone.BATTLEFIELD, playerA, "Plains", 1); @@ -142,6 +152,8 @@ public class TamiyoTest extends CardTestPlayerBase { */ @Test public void testFieldResearcherFirstEffectOnlyPersistsUntilYourNextTurn() { + setStrictChooseMode(true); + addCard(Zone.HAND, playerA, "Tamiyo, Field Researcher", 1); addCard(Zone.BATTLEFIELD, playerA, "Forest", 1); addCard(Zone.BATTLEFIELD, playerA, "Plains", 1); @@ -176,11 +188,13 @@ public class TamiyoTest extends CardTestPlayerBase { } /** - * I activated his +1 ability once. then, the next turn, i activated it one more time, and then - * i get to draw 3 cards of three creatures. So i think the first activation wasn't away. + * I activated his +1 ability once. then, the next turn, i activated it one more time, and then + * i get to draw 3 cards of three creatures. So i think the first activation wasn't away. */ @Test public void testDrawEffectGetsRemoved() { + setStrictChooseMode(true); + addCard(Zone.HAND, playerA, "Tamiyo, Field Researcher", 1); addCard(Zone.BATTLEFIELD, playerA, "Forest", 1); addCard(Zone.BATTLEFIELD, playerA, "Plains", 1); @@ -198,6 +212,7 @@ public class TamiyoTest extends CardTestPlayerBase { attack(3, playerA, "Pillarfield Ox"); attack(3, playerA, "Silvercoat Lion"); + setChoice(playerA, "Until your next turn"); // stack both trigger. setStopAt(3, PhaseStep.END_COMBAT); execute(); @@ -209,6 +224,8 @@ public class TamiyoTest extends CardTestPlayerBase { @Test public void testFieldResearcherFirstAbilityTargetOpponentCreature() { + setStrictChooseMode(true); + addCard(Zone.HAND, playerA, "Tamiyo, Field Researcher", 1); addCard(Zone.BATTLEFIELD, playerA, "Forest", 1); addCard(Zone.BATTLEFIELD, playerA, "Plains", 1); @@ -231,6 +248,8 @@ public class TamiyoTest extends CardTestPlayerBase { @Test public void testFieldResearcherFirstAbilityTargetOpponentCreatures() { + setStrictChooseMode(true); + addCard(Zone.HAND, playerA, "Tamiyo, Field Researcher", 1); addCard(Zone.BATTLEFIELD, playerA, "Forest", 1); addCard(Zone.BATTLEFIELD, playerA, "Plains", 1); @@ -245,6 +264,7 @@ public class TamiyoTest extends CardTestPlayerBase { attack(2, playerB, "Bronze Sable"); attack(2, playerB, "Memnite"); + setChoice(playerA, "Until your next turn"); // stack both trigger. setStopAt(2, PhaseStep.END_COMBAT); execute(); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/hou/BontusLastReckoningTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/hou/BontusLastReckoningTest.java index 2846c501740..93292d76892 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/hou/BontusLastReckoningTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/hou/BontusLastReckoningTest.java @@ -10,16 +10,18 @@ public class BontusLastReckoningTest extends CardTestPlayerBase { private String reckoning = "Bontu's Last Reckoning"; @Test - public void testDelayedUntap(){ + public void testDelayedUntap() { + setStrictChooseMode(true); + String pouncer = "Adorned Pouncer"; String angel = "Angel of Condemnation"; addCard(Zone.HAND, playerA, reckoning); addCard(Zone.BATTLEFIELD, playerA, "Swamp", 4); addCard(Zone.BATTLEFIELD, playerA, pouncer); - addCard(Zone.BATTLEFIELD, playerB, angel); + addCard(Zone.BATTLEFIELD, playerB, angel); addCard(Zone.BATTLEFIELD, playerB, "Island"); - castSpell(1, PhaseStep.PRECOMBAT_MAIN,playerA, reckoning); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, reckoning); setStopAt(3, PhaseStep.PRECOMBAT_MAIN); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/ons/TephradermTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/ons/TephradermTest.java new file mode 100644 index 00000000000..31cff4299ec --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/ons/TephradermTest.java @@ -0,0 +1,78 @@ +package org.mage.test.cards.single.ons; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author Susucr + */ +public class TephradermTest extends CardTestPlayerBase { + + /** + * {@link mage.cards.t.Tephraderm Tephraderm} {4}{R} + * Creature — Beast + * Whenever a creature deals damage to Tephraderm, Tephraderm deals that much damage to that creature. + * Whenever a spell deals damage to Tephraderm, Tephraderm deals that much damage to that spell’s controller. + * 4/5 + */ + private static final String tephraderm = "Tephraderm"; + + @Test + public void test_Bolt() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, tephraderm); + addCard(Zone.HAND, playerB, "Lightning Bolt"); + addCard(Zone.BATTLEFIELD, playerB, "Mountain"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Lightning Bolt", tephraderm); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertDamageReceived(playerA, tephraderm, 3); + assertLife(playerA, 20); + assertLife(playerB, 20 - 3); + } + + @Test + public void test_Combat_Damage() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, tephraderm); + addCard(Zone.BATTLEFIELD, playerB, "Indomitable Ancients"); // 2/10 + + attack(1, playerA, tephraderm, playerB); + block(1, playerB, "Indomitable Ancients", tephraderm); + + setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); + execute(); + + assertDamageReceived(playerA, tephraderm, 2); + assertDamageReceived(playerB, "Indomitable Ancients", 4 + 2); + assertLife(playerA, 20); + assertLife(playerB, 20); + } + + @Test + public void test_NonCombat_Damage() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, tephraderm); + addCard(Zone.BATTLEFIELD, playerB, "Indomitable Ancients"); // 2/10 + addCard(Zone.HAND, playerB, "Bite Down"); + addCard(Zone.BATTLEFIELD, playerB, "Forest", 2); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Bite Down", "Indomitable Ancients^" + tephraderm); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertDamageReceived(playerA, tephraderm, 2); + assertDamageReceived(playerB, "Indomitable Ancients", 2); + assertLife(playerA, 20); + assertLife(playerB, 20); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/rex/DontMoveTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/rex/DontMoveTest.java new file mode 100644 index 00000000000..038f2cdd51c --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/rex/DontMoveTest.java @@ -0,0 +1,82 @@ +package org.mage.test.cards.single.rex; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author Susucr + */ +public class DontMoveTest extends CardTestPlayerBase { + + /** + * {@link mage.cards.d.DontMove Don't Move} {3}{W}{W} + * Sorcery + * Destroy all tapped creatures. Until your next turn, whenever a creature becomes tapped, destroy it. + */ + private static final String dont = "Don't Move"; + + /** + * {@link mage.cards.s.Soulmender Soulmender} {W} + * Creature — Human Cleric + * {T}: You gain 1 life. + * 1/1 + */ + private static final String mender = "Soulmender"; + + @Test + public void test_TappedThisTurn() { + setStrictChooseMode(true); + + addCard(Zone.HAND, playerA, dont, 1); + addCard(Zone.BATTLEFIELD, playerA, "Plains", 5); + addCard(Zone.BATTLEFIELD, playerA, mender, 1); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, dont); + activateAbility(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "{T}: You gain 1 life"); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertLife(playerA, 20 + 1); + assertGraveyardCount(playerA, mender, 1); + } + + @Test + public void test_TappedOppTurn() { + setStrictChooseMode(true); + + addCard(Zone.HAND, playerA, dont, 1); + addCard(Zone.BATTLEFIELD, playerA, "Plains", 5); + addCard(Zone.BATTLEFIELD, playerA, mender, 1); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, dont); + activateAbility(2, PhaseStep.UPKEEP, playerA, "{T}: You gain 1 life"); + + setStopAt(2, PhaseStep.END_TURN); + execute(); + + assertLife(playerA, 20 + 1); + assertGraveyardCount(playerA, mender, 1); + } + + @Test + public void test_TappedYourNextTurn_EffectExpired() { + setStrictChooseMode(true); + + addCard(Zone.HAND, playerA, dont, 1); + addCard(Zone.BATTLEFIELD, playerA, "Plains", 5); + addCard(Zone.BATTLEFIELD, playerA, mender, 1); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, dont); + activateAbility(3, PhaseStep.UPKEEP, playerA, "{T}: You gain 1 life"); + + setStopAt(3, PhaseStep.END_TURN); + execute(); + + assertLife(playerA, 20 + 1); + assertGraveyardCount(playerA, mender, 0); + assertTappedCount(mender, true, 1); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/rtr/JaceArchitectOfThoughtTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/rtr/JaceArchitectOfThoughtTest.java index fcef9c5e190..89fdab4d4b7 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/rtr/JaceArchitectOfThoughtTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/rtr/JaceArchitectOfThoughtTest.java @@ -23,6 +23,8 @@ public class JaceArchitectOfThoughtTest extends CardTestPlayerBase { @Test public void testAbility1normal() { + setStrictChooseMode(true); + addCard(Zone.BATTLEFIELD, playerA, "Jace, Architect of Thought"); addCard(Zone.BATTLEFIELD, playerB, "Silvercoat Lion", 1); @@ -44,6 +46,8 @@ public class JaceArchitectOfThoughtTest extends CardTestPlayerBase { @Test public void testAbilit1lastOnlyUntilNextTurn() { + setStrictChooseMode(true); + addCard(Zone.BATTLEFIELD, playerA, "Jace, Architect of Thought"); addCard(Zone.BATTLEFIELD, playerB, "Silvercoat Lion", 1); @@ -69,6 +73,8 @@ public class JaceArchitectOfThoughtTest extends CardTestPlayerBase { @Test public void testAbility1AfterJacesWasExiled() { + setStrictChooseMode(true); + addCard(Zone.BATTLEFIELD, playerA, "Jace, Architect of Thought"); // Sorcery {R}{B} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/continuous/VraskaTheUnseenNextTurnTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/rtr/VraskaTheUnseenTest.java similarity index 52% rename from Mage.Tests/src/test/java/org/mage/test/cards/continuous/VraskaTheUnseenNextTurnTest.java rename to Mage.Tests/src/test/java/org/mage/test/cards/single/rtr/VraskaTheUnseenTest.java index ce24a69e549..a73911870b8 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/continuous/VraskaTheUnseenNextTurnTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/rtr/VraskaTheUnseenTest.java @@ -1,4 +1,4 @@ -package org.mage.test.cards.continuous; +package org.mage.test.cards.single.rtr; import mage.constants.PhaseStep; import mage.constants.Zone; @@ -7,9 +7,9 @@ import org.junit.Test; import org.mage.test.serverside.base.CardTestPlayerBase; /** - * @author JayDi85 + * @author JayDi85, Susucr */ -public class VraskaTheUnseenNextTurnTest extends CardTestPlayerBase { +public class VraskaTheUnseenTest extends CardTestPlayerBase { @Test public void test_SingleOpponentMustAttack() { @@ -40,4 +40,31 @@ public class VraskaTheUnseenNextTurnTest extends CardTestPlayerBase { setStrictChooseMode(true); execute(); } + + @Test + public void test_OnlyCombat() { + addCard(Zone.BATTLEFIELD, playerA, "Vraska the Unseen"); + addCard(Zone.BATTLEFIELD, playerB, "Balduvian Bears", 1); // 2/2 + addCard(Zone.BATTLEFIELD, playerB, "Cinder Pyromancer", 1); // {T}: Cinder Pyromancer deals 1 damage to target player or planeswalker. + + // 1 - activate + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "+1:"); + checkPermanentCounters("turn 1", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Vraska the Unseen", CounterType.LOYALTY, 5 + 1); + checkPermanentCount("turn 1", 1, PhaseStep.POSTCOMBAT_MAIN, playerB, "Balduvian Bears", 1); + checkPermanentCount("turn 1", 1, PhaseStep.POSTCOMBAT_MAIN, playerB, "Cinder Pyromancer", 1); + + // 2 - attack and destroy + attack(2, playerB, "Balduvian Bears", "Vraska the Unseen"); + checkPermanentCounters("turn 2", 2, PhaseStep.POSTCOMBAT_MAIN, playerA, "Vraska the Unseen", CounterType.LOYALTY, 5 + 1 - 2); + checkPermanentCount("turn 2", 2, PhaseStep.POSTCOMBAT_MAIN, playerB, "Balduvian Bears", 0); + + // 3 - ping, no destroy + activateAbility(2, PhaseStep.POSTCOMBAT_MAIN, playerB, "{T}: {this} deals", "Vraska the Unseen"); + checkPermanentCounters("turn 2", 2, PhaseStep.END_TURN, playerA, "Vraska the Unseen", CounterType.LOYALTY, 5 + 1 - 2 - 1); + checkPermanentCount("turn 2", 2, PhaseStep.END_TURN, playerB, "Cinder Pyromancer", 1); + + setStopAt(3, PhaseStep.UPKEEP); + setStrictChooseMode(true); + execute(); + } } diff --git a/Mage/src/main/java/mage/abilities/DelayedTriggeredAbilities.java b/Mage/src/main/java/mage/abilities/DelayedTriggeredAbilities.java index 52d9892d00c..d0a5a20dcd6 100644 --- a/Mage/src/main/java/mage/abilities/DelayedTriggeredAbilities.java +++ b/Mage/src/main/java/mage/abilities/DelayedTriggeredAbilities.java @@ -49,10 +49,14 @@ public class DelayedTriggeredAbilities extends AbilitiesImpl ability.getDuration() == Duration.EndOfTurn); // TODO: add Duration.EndOfYourTurn like effects } + public void removeStartOfNewTurn(Game game) { + this.removeIf(ability -> ability.getDuration() == Duration.UntilYourNextTurn + && game.getActivePlayerId().equals(ability.getControllerId()) + ); + } + public void removeEndOfCombatAbilities() { this.removeIf(ability -> ability.getDuration() == Duration.EndOfCombat); } - - } diff --git a/Mage/src/main/java/mage/abilities/common/DealsDamageToACreatureAllTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/DealsDamageToACreatureAllTriggeredAbility.java index 10cb19f83b5..2e66f993b11 100644 --- a/Mage/src/main/java/mage/abilities/common/DealsDamageToACreatureAllTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/DealsDamageToACreatureAllTriggeredAbility.java @@ -63,31 +63,32 @@ public class DealsDamageToACreatureAllTriggeredAbility extends TriggeredAbilityI @Override public boolean checkTrigger(GameEvent event, Game game) { - Permanent permanent = game.getPermanent(event.getTargetId()); - if (permanent == null || !permanent.isCreature(game)) { - return false; - } if (combatOnly && !((DamagedEvent) event).isCombatDamage()) { return false; } - permanent = game.getPermanentOrLKIBattlefield(event.getSourceId()); - if (!filterPermanent.match(permanent, getControllerId(), this, game)) { + Permanent permanentDealtDamage = game.getPermanent(event.getTargetId()); + if (permanentDealtDamage == null || !permanentDealtDamage.isCreature(game)) { return false; } - this.getEffects().setValue("damage", event.getAmount()); + Permanent permanentDealingDamage = game.getPermanentOrLKIBattlefield(event.getSourceId()); + if (!filterPermanent.match(permanentDealingDamage, getControllerId(), this, game)) { + return false; + } + int damageAmount = event.getAmount(); + if (damageAmount < 1) { + return false; + } + this.getEffects().setValue("damage", damageAmount); this.getEffects().setValue("sourceId", event.getSourceId()); switch (setTargetPointer) { case PLAYER: - this.getEffects().setTargetPointer(new FixedTarget(permanent.getControllerId())); + this.getEffects().setTargetPointer(new FixedTarget(permanentDealingDamage.getControllerId())); break; case PERMANENT: - this.getEffects().setTargetPointer(new FixedTarget(permanent, game)); + this.getEffects().setTargetPointer(new FixedTarget(permanentDealingDamage, game)); break; case PERMANENT_TARGET: - Permanent permanent_target = game.getPermanentOrLKIBattlefield(event.getTargetId()); - if (permanent_target != null) { - this.getEffects().setTargetPointer(new FixedTarget(permanent_target, game)); - } + this.getEffects().setTargetPointer(new FixedTarget(permanentDealtDamage, game)); break; } return true; diff --git a/Mage/src/main/java/mage/abilities/common/DealsDamageToThisAllTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/DealsDamageToThisAllTriggeredAbility.java new file mode 100644 index 00000000000..4c764f80f0b --- /dev/null +++ b/Mage/src/main/java/mage/abilities/common/DealsDamageToThisAllTriggeredAbility.java @@ -0,0 +1,80 @@ +package mage.abilities.common; + +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.effects.Effect; +import mage.constants.SetTargetPointer; +import mage.constants.Zone; +import mage.filter.FilterPermanent; +import mage.game.Game; +import mage.game.events.DamagedEvent; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; +import mage.target.targetpointer.FixedTarget; + +/** + * @author Susucr + */ +public class DealsDamageToThisAllTriggeredAbility extends TriggeredAbilityImpl { + + private final boolean combatOnly; + private final FilterPermanent filterPermanent; + private final SetTargetPointer setTargetPointer; + + public DealsDamageToThisAllTriggeredAbility( + Effect effect, boolean optional, FilterPermanent filterPermanent, + SetTargetPointer setTargetPointer, boolean combatOnly + ) { + super(Zone.BATTLEFIELD, effect, optional); + this.setTargetPointer = setTargetPointer; + this.filterPermanent = filterPermanent; + this.combatOnly = combatOnly; + setTriggerPhrase("Whenever " + filterPermanent.getMessage() + " deals " + + (combatOnly ? "combat " : "") + "damage to a {this}, "); + } + + protected DealsDamageToThisAllTriggeredAbility(final DealsDamageToThisAllTriggeredAbility ability) { + super(ability); + this.combatOnly = ability.combatOnly; + this.filterPermanent = ability.filterPermanent; + this.setTargetPointer = ability.setTargetPointer; + } + + @Override + public DealsDamageToThisAllTriggeredAbility copy() { + return new DealsDamageToThisAllTriggeredAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.DAMAGED_PERMANENT; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + if (combatOnly && !((DamagedEvent) event).isCombatDamage()) { + return false; + } + if (!event.getTargetId().equals(this.sourceId)) { + return false; + } + Permanent permanent = game.getPermanentOrLKIBattlefield(event.getSourceId()); + if (!filterPermanent.match(permanent, getControllerId(), this, game)) { + return false; + } + int damageAmount = event.getAmount(); + if (damageAmount < 1) { + return false; + } + this.getEffects().setValue("damage", damageAmount); + this.getEffects().setValue("sourceId", event.getSourceId()); + switch (setTargetPointer) { + case PLAYER: + this.getEffects().setTargetPointer(new FixedTarget(permanent.getControllerId())); + break; + case PERMANENT: + this.getEffects().setTargetPointer(new FixedTarget(permanent, game)); + break; + } + return true; + } +} diff --git a/Mage/src/main/java/mage/abilities/common/delayed/UntilYourNextTurnDelayedTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/delayed/UntilYourNextTurnDelayedTriggeredAbility.java new file mode 100644 index 00000000000..b911c207952 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/common/delayed/UntilYourNextTurnDelayedTriggeredAbility.java @@ -0,0 +1,106 @@ + +package mage.abilities.common.delayed; + +import mage.abilities.DelayedTriggeredAbility; +import mage.abilities.Modes; +import mage.abilities.TriggeredAbility; +import mage.abilities.effects.Effect; +import mage.abilities.effects.Effects; +import mage.constants.Duration; +import mage.constants.EffectType; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.util.CardUtil; +import mage.watchers.Watcher; + +import java.util.List; + +/** + * "Until your next turn, [trigger]" + * + * @author Susucr + */ +public class UntilYourNextTurnDelayedTriggeredAbility extends DelayedTriggeredAbility { + + private final TriggeredAbility trigger; + + public UntilYourNextTurnDelayedTriggeredAbility(TriggeredAbility trigger) { + super(null, Duration.UntilYourNextTurn); + if (trigger.isLeavesTheBattlefieldTrigger()) { + this.setLeavesTheBattlefieldTrigger(true); + } + this.trigger = trigger; + } + + protected UntilYourNextTurnDelayedTriggeredAbility(final UntilYourNextTurnDelayedTriggeredAbility ability) { + super(ability); + this.trigger = ability.trigger.copy(); + } + + @Override + public UntilYourNextTurnDelayedTriggeredAbility copy() { + return new UntilYourNextTurnDelayedTriggeredAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return trigger.checkEventType(event, game); + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + trigger.setSourceId(this.getSourceId()); + trigger.setControllerId(this.getControllerId()); + return trigger.checkTrigger(event, game); + } + + @Override + public String getRule() { + return "Until your next turn, " + CardUtil.getTextWithFirstCharLowerCase(trigger.getRule()); + } + + @Override + public Effects getEffects() { + return trigger.getEffects(); + } + + @Override + public void addEffect(Effect effect) { + trigger.addEffect(effect); + } + + @Override + public Modes getModes() { + return trigger.getModes(); + } + + @Override + public List getWatchers() { + return trigger.getWatchers(); + } + + @Override + public void addWatcher(Watcher watcher) { + trigger.addWatcher(watcher); + } + + @Override + public Effects getEffects(Game game, EffectType effectType) { + return trigger.getEffects(game, effectType); + } + + @Override + public boolean isOptional() { + return trigger.isOptional(); + } + + @Override + public void setSourceObjectZoneChangeCounter(int sourceObjectZoneChangeCounter) { + trigger.setSourceObjectZoneChangeCounter(sourceObjectZoneChangeCounter); + } + + @Override + public int getSourceObjectZoneChangeCounter() { + return trigger.getSourceObjectZoneChangeCounter(); + } +} diff --git a/Mage/src/main/java/mage/game/GameState.java b/Mage/src/main/java/mage/game/GameState.java index cb64727abaa..43a841b790a 100644 --- a/Mage/src/main/java/mage/game/GameState.java +++ b/Mage/src/main/java/mage/game/GameState.java @@ -699,6 +699,10 @@ public class GameState implements Serializable, Copyable { game.applyEffects(); } + public void removeTurnStartEffect(Game game) { + delayed.removeStartOfNewTurn(game); + } + public void addEffect(ContinuousEffect effect, Ability source) { addEffect(effect, null, source); } diff --git a/Mage/src/main/java/mage/players/PlayerImpl.java b/Mage/src/main/java/mage/players/PlayerImpl.java index 64df1c12f88..96eff0bd30b 100644 --- a/Mage/src/main/java/mage/players/PlayerImpl.java +++ b/Mage/src/main/java/mage/players/PlayerImpl.java @@ -539,6 +539,7 @@ public abstract class PlayerImpl implements Player, Serializable { public void beginTurn(Game game) { resetLandsPlayed(); updateRange(game); + game.getState().removeTurnStartEffect(game); } @Override