diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/SaddleTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/SaddleTest.java index 855082b3938..920049d3acb 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/SaddleTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/SaddleTest.java @@ -1,9 +1,11 @@ package org.mage.test.cards.abilities.keywords; +import mage.MageObjectReference; import mage.abilities.keyword.MenaceAbility; import mage.constants.PhaseStep; import mage.constants.Zone; import mage.game.permanent.Permanent; +import mage.watchers.common.SaddledMountWatcher; import org.junit.Assert; import org.junit.Test; import org.mage.test.serverside.base.CardTestPlayerBase; @@ -20,7 +22,7 @@ public class SaddleTest extends CardTestPlayerBase { Permanent permanent = getPermanent(name); Assert.assertEquals( name + " should " + (saddled ? "" : "not ") + "be saddled", - saddled, permanent.isSaddled() + saddled, SaddledMountWatcher.hasBeenSaddledThisTurn(new MageObjectReference(permanent.getId(), currentGame), currentGame) ); } @@ -78,13 +80,10 @@ public class SaddleTest extends CardTestPlayerBase { setChoice(playerA, bear); activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Saddle"); - setStrictChooseMode(true); - setStopAt(1, PhaseStep.PRECOMBAT_MAIN); - execute(); - attack(1, playerA, possum, playerB); setChoice(playerA, bear); + setStrictChooseMode(true); setStopAt(1, PhaseStep.END_TURN); execute(); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/otj/FortuneLoyalSteedTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/otj/FortuneLoyalSteedTest.java new file mode 100644 index 00000000000..048ed3391df --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/otj/FortuneLoyalSteedTest.java @@ -0,0 +1,223 @@ +package org.mage.test.cards.single.otj; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author Susucr + */ +public class FortuneLoyalSteedTest extends CardTestPlayerBase { + + /** + * {@link mage.cards.f.FortuneLoyalSteed Fortune, Loyal Steed} {W} + * Legendary Creature — Beast Mount + * When Fortune, Loyal Steed enters the battlefield, scry 2. + * Whenever Fortune attacks while saddled, at end of combat, exile it and up to one creature that saddled it this turn, then return those cards to the battlefield under their owner’s control. + * Saddle 1 + * 2/4 + */ + private static final String fortune = "Fortune, Loyal Steed"; + + @Test + public void test_Saddling() { + setStrictChooseMode(true); + skipInitShuffling(); + + addCard(Zone.BATTLEFIELD, playerA, fortune); + addCard(Zone.BATTLEFIELD, playerA, "Lone Missionary"); // ETB, gain 4 life + addCard(Zone.LIBRARY, playerA, "Taiga", 2); + + setChoice(playerA, "Lone Missionary"); // Saddling choice + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Saddle"); + attack(1, playerA, fortune, playerB); + + setChoice(playerA, "Lone Missionary"); // Choose to blink Lone Missionary + + setChoice(playerA, "When {this} enters the battlefield, you gain 4 life"); // stack triggers + addTarget(playerA, "Taiga"); // for the scry trigger + + setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); + execute(); + + assertLife(playerA, 20 + 4); + assertHandCount(playerA, 0); + assertTapped(fortune, false); + assertTapped("Lone Missionary", false); + } + + @Test + public void test_Saddling_FortuneDies() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, fortune); + addCard(Zone.BATTLEFIELD, playerA, "Lone Missionary"); // ETB, gain 4 life + addCard(Zone.BATTLEFIELD, playerB, "Ankle Biter"); // 1/1 Deathtouch + + setChoice(playerA, "Lone Missionary"); // Saddling choice + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Saddle"); + attack(1, playerA, fortune, playerB); + block(1, playerB, "Ankle Biter", fortune); + + setChoice(playerA, "Lone Missionary"); // Choose to blink Lone Missionary + + setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); + execute(); + + assertLife(playerA, 20 + 4); + assertGraveyardCount(playerA, fortune, 1); + assertTapped("Lone Missionary", false); + } + + @Test + public void test_Saddling_FortuneBlinks() { + setStrictChooseMode(true); + skipInitShuffling(); + + addCard(Zone.BATTLEFIELD, playerA, fortune); + addCard(Zone.BATTLEFIELD, playerA, "Lone Missionary"); // ETB, gain 4 life + addCard(Zone.BATTLEFIELD, playerA, "Plains"); + addCard(Zone.HAND, playerA, "Ephemerate"); + addCard(Zone.LIBRARY, playerA, "Taiga", 2); + + setChoice(playerA, "Lone Missionary"); // Saddling choice + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Saddle"); + attack(1, playerA, fortune, playerB); + + castSpell(1, PhaseStep.DECLARE_ATTACKERS, playerA, "Ephemerate", fortune); + addTarget(playerA, "Taiga"); // for the scry trigger + + setChoice(playerA, "Lone Missionary"); // Choose to blink Lone Missionary + + setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); + execute(); + + assertLife(playerA, 20 + 4); + assertTapped(fortune, false); + assertTapped("Lone Missionary", false); + } + + @Test + public void test_Saddling_FortuneBlinksBefore() { + setStrictChooseMode(true); + skipInitShuffling(); + + addCard(Zone.BATTLEFIELD, playerA, fortune); + addCard(Zone.BATTLEFIELD, playerA, "Fervor"); // To give haste + addCard(Zone.BATTLEFIELD, playerA, "Lone Missionary"); // ETB, gain 4 life + addCard(Zone.BATTLEFIELD, playerA, "Plains", 2); + addCard(Zone.HAND, playerA, "Ephemerate", 2); + addCard(Zone.LIBRARY, playerA, "Taiga", 2); + + // Just to check zcc + castSpell(1, PhaseStep.UPKEEP, playerA, "Ephemerate", fortune); + addTarget(playerA, "Taiga"); // for the scry trigger + + setChoice(playerA, "Lone Missionary"); // Saddling choice + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Saddle"); + attack(1, playerA, fortune, playerB); + + castSpell(1, PhaseStep.DECLARE_ATTACKERS, playerA, "Ephemerate", fortune); + addTarget(playerA, "Taiga"); // for the scry trigger + + setChoice(playerA, "Lone Missionary"); // Choose to blink Lone Missionary + + setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); + execute(); + + assertLife(playerA, 20 + 4); + assertTapped(fortune, false); + assertTapped("Lone Missionary", false); + } + + @Test + public void test_Saddling_FortuneBlinksAfterSaddlingBeforeCombat() { + setStrictChooseMode(true); + skipInitShuffling(); + + addCard(Zone.BATTLEFIELD, playerA, fortune); + addCard(Zone.BATTLEFIELD, playerA, "Fervor"); // To give haste + addCard(Zone.BATTLEFIELD, playerA, "Lone Missionary"); // ETB, gain 4 life + addCard(Zone.BATTLEFIELD, playerA, "Plains"); + addCard(Zone.HAND, playerA, "Ephemerate"); + addCard(Zone.LIBRARY, playerA, "Taiga", 2); + + setChoice(playerA, "Lone Missionary"); // Saddling choice + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Saddle"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, playerA); + + // That would make Fortune no longer saddled, so not trigger at beginning of combat + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Ephemerate", fortune); + addTarget(playerA, "Taiga"); // for the scry trigger + + attack(1, playerA, fortune, playerB); + + setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); + execute(); + + assertLife(playerA, 20); + assertTapped(fortune, true); + assertTapped("Lone Missionary", true); + } + + @Test + public void test_Saddling_FortuneBlinksInResponseOfSaddling() { + setStrictChooseMode(true); + skipInitShuffling(); + + addCard(Zone.BATTLEFIELD, playerA, fortune); + addCard(Zone.BATTLEFIELD, playerA, "Fervor"); // To give haste + addCard(Zone.BATTLEFIELD, playerA, "Lone Missionary"); // ETB, gain 4 life + addCard(Zone.BATTLEFIELD, playerA, "Plains"); + addCard(Zone.HAND, playerA, "Ephemerate"); + addCard(Zone.LIBRARY, playerA, "Taiga", 2); + + setChoice(playerA, "Lone Missionary"); // Saddling choice + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Saddle"); + + // That would make Fortune no longer saddled, so not trigger at beginning of combat + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Ephemerate", fortune); + addTarget(playerA, "Taiga"); // for the scry trigger + + attack(1, playerA, fortune, playerB); + + setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); + execute(); + + assertLife(playerA, 20); + assertTapped(fortune, true); + assertTapped("Lone Missionary", true); + } + + @Test + public void test_Saddling_FortuneBlinksTwice() { + setStrictChooseMode(true); + skipInitShuffling(); + + addCard(Zone.BATTLEFIELD, playerA, fortune); + addCard(Zone.BATTLEFIELD, playerA, "Lone Missionary"); // ETB, gain 4 life + addCard(Zone.BATTLEFIELD, playerA, "Plains", 2); + addCard(Zone.HAND, playerA, "Ephemerate", 2); + addCard(Zone.LIBRARY, playerA, "Taiga", 2); + + setChoice(playerA, "Lone Missionary"); // Saddling choice + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Saddle"); + attack(1, playerA, fortune, playerB); + + castSpell(1, PhaseStep.DECLARE_ATTACKERS, playerA, "Ephemerate", fortune, true); + addTarget(playerA, "Taiga"); // for the scry trigger + + castSpell(1, PhaseStep.DECLARE_ATTACKERS, playerA, "Ephemerate", fortune); + addTarget(playerA, "Taiga"); // for the scry trigger + + setChoice(playerA, "Lone Missionary"); // Choose to blink Lone Missionary + + setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); + execute(); + + assertLife(playerA, 20 + 4); + assertTapped(fortune, false); + assertTapped("Lone Missionary", false); + } +} diff --git a/Mage/src/main/java/mage/abilities/common/AttacksWhileSaddledTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/AttacksWhileSaddledTriggeredAbility.java index a1e422f800b..f16a463a4fa 100644 --- a/Mage/src/main/java/mage/abilities/common/AttacksWhileSaddledTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/AttacksWhileSaddledTriggeredAbility.java @@ -1,9 +1,10 @@ package mage.abilities.common; +import mage.MageObjectReference; import mage.abilities.effects.Effect; import mage.game.Game; import mage.game.events.GameEvent; -import mage.game.permanent.Permanent; +import mage.watchers.common.SaddledMountWatcher; import java.util.Optional; @@ -15,6 +16,7 @@ public class AttacksWhileSaddledTriggeredAbility extends AttacksTriggeredAbility public AttacksWhileSaddledTriggeredAbility(Effect effect) { super(effect); this.setTriggerPhrase("Whenever {this} attacks while saddled, "); + this.addWatcher(new SaddledMountWatcher()); } private AttacksWhileSaddledTriggeredAbility(final AttacksWhileSaddledTriggeredAbility ability) { @@ -31,7 +33,7 @@ public class AttacksWhileSaddledTriggeredAbility extends AttacksTriggeredAbility return super.checkTrigger(event, game) && Optional .ofNullable(getSourcePermanentIfItStillExists(game)) - .map(Permanent::isSaddled) + .map(p -> SaddledMountWatcher.hasBeenSaddledThisTurn(new MageObjectReference(p, game), game)) .orElse(false); } } diff --git a/Mage/src/main/java/mage/abilities/condition/common/SaddledCondition.java b/Mage/src/main/java/mage/abilities/condition/common/SaddledCondition.java index a2c1d4770e6..579c86b1e5d 100644 --- a/Mage/src/main/java/mage/abilities/condition/common/SaddledCondition.java +++ b/Mage/src/main/java/mage/abilities/condition/common/SaddledCondition.java @@ -1,9 +1,10 @@ package mage.abilities.condition.common; +import mage.MageObjectReference; import mage.abilities.Ability; import mage.abilities.condition.Condition; import mage.game.Game; -import mage.game.permanent.Permanent; +import mage.watchers.common.SaddledMountWatcher; import java.util.Optional; @@ -17,7 +18,7 @@ public enum SaddledCondition implements Condition { public boolean apply(Game game, Ability source) { return Optional .ofNullable(source.getSourcePermanentIfItStillExists(game)) - .map(Permanent::isSaddled) + .map(p -> SaddledMountWatcher.hasBeenSaddledThisTurn(new MageObjectReference(p, game), game)) .orElse(false); } } diff --git a/Mage/src/main/java/mage/abilities/keyword/SaddleAbility.java b/Mage/src/main/java/mage/abilities/keyword/SaddleAbility.java index 3add5303ef5..61fbec35e35 100644 --- a/Mage/src/main/java/mage/abilities/keyword/SaddleAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/SaddleAbility.java @@ -7,11 +7,12 @@ import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.condition.common.SaddledCondition; import mage.abilities.costs.Cost; import mage.abilities.costs.CostImpl; -import mage.abilities.effects.ContinuousEffectImpl; +import mage.abilities.effects.OneShotEffect; import mage.abilities.hint.ConditionHint; import mage.abilities.hint.Hint; import mage.abilities.hint.HintUtils; -import mage.constants.*; +import mage.constants.Outcome; +import mage.constants.TimingRule; import mage.filter.common.FilterControlledCreaturePermanent; import mage.filter.predicate.mageobject.AnotherPredicate; import mage.filter.predicate.permanent.TappedPredicate; @@ -24,11 +25,10 @@ import mage.watchers.common.SaddledMountWatcher; import java.awt.*; import java.util.Objects; -import java.util.Optional; import java.util.UUID; /** - * @author TheElk801 + * @author TheElk801, Susucr */ public class SaddleAbility extends SimpleActivatedAbility { @@ -36,7 +36,7 @@ public class SaddleAbility extends SimpleActivatedAbility { private static final Hint hint = new ConditionHint(SaddledCondition.instance, "This permanent is saddled"); public SaddleAbility(int value) { - super(new SaddleEffect(), new SaddleCost(value)); + super(new SaddleEventEffect(), new SaddleCost(value)); this.value = value; this.addHint(hint); this.setTiming(TimingRule.SORCERY); @@ -60,42 +60,36 @@ public class SaddleAbility extends SimpleActivatedAbility { } } -class SaddleEffect extends ContinuousEffectImpl { +class SaddleEventEffect extends OneShotEffect { - SaddleEffect() { - super(Duration.EndOfTurn, Layer.RulesEffects, SubLayer.NA, Outcome.Benefit); + SaddleEventEffect() { + super(Outcome.Benefit); } - private SaddleEffect(final SaddleEffect effect) { + private SaddleEventEffect(final SaddleEventEffect effect) { super(effect); } @Override - public SaddleEffect copy() { - return new SaddleEffect(this); - } - - @Override - public void init(Ability source, Game game) { - super.init(source, game); - game.fireEvent(GameEvent.getEvent( - GameEvent.EventType.MOUNT_SADDLED, - source.getSourceId(), - source, source.getControllerId() - )); + public SaddleEventEffect copy() { + return new SaddleEventEffect(this); } @Override public boolean apply(Game game, Ability source) { - Optional.ofNullable(source.getSourcePermanentIfItStillExists(game)) - .ifPresent(permanent -> permanent.setSaddled(true)); + if (source.getSourcePermanentIfItStillExists(game) != null) { + game.fireEvent(GameEvent.getEvent( + GameEvent.EventType.MOUNT_SADDLED, + source.getSourceId(), + source, source.getControllerId() + )); + } return true; } } class SaddleCost extends CostImpl { - private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent("another untapped creature you control"); diff --git a/Mage/src/main/java/mage/abilities/keyword/UnearthAbility.java b/Mage/src/main/java/mage/abilities/keyword/UnearthAbility.java index dc669636304..7333596bb50 100644 --- a/Mage/src/main/java/mage/abilities/keyword/UnearthAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/UnearthAbility.java @@ -1,12 +1,13 @@ package mage.abilities.keyword; +import mage.MageObjectReference; import mage.abilities.Ability; import mage.abilities.ActivatedAbilityImpl; import mage.abilities.DelayedTriggeredAbility; import mage.abilities.costs.Cost; import mage.abilities.effects.ReplacementEffectImpl; import mage.abilities.effects.common.CreateDelayedTriggeredAbilityEffect; -import mage.abilities.effects.common.ExileSourceEffect; +import mage.abilities.effects.common.ExileTargetEffect; import mage.abilities.effects.common.ReturnSourceFromGraveyardToBattlefieldEffect; import mage.constants.Duration; import mage.constants.Outcome; @@ -15,6 +16,8 @@ import mage.constants.Zone; import mage.game.Game; import mage.game.events.GameEvent; import mage.game.events.ZoneChangeEvent; +import mage.game.permanent.Permanent; +import mage.target.targetpointer.FixedTarget; /** * @author BetaSteward_at_googlemail.com @@ -60,7 +63,7 @@ public class UnearthAbility extends ActivatedAbilityImpl { class UnearthDelayedTriggeredAbility extends DelayedTriggeredAbility { public UnearthDelayedTriggeredAbility() { - super(new ExileSourceEffect()); + super(new ExileTargetEffect()); } protected UnearthDelayedTriggeredAbility(final UnearthDelayedTriggeredAbility ability) { @@ -79,7 +82,19 @@ class UnearthDelayedTriggeredAbility extends DelayedTriggeredAbility { @Override public boolean checkTrigger(GameEvent event, Game game) { - return event.getPlayerId().equals(this.controllerId); + if (!event.getPlayerId().equals(this.controllerId)) { + return false; + } + // The delayed trigger source is the card in the graveyard. + // So we need to exile the zcc + 1 permanent + MageObjectReference object = new MageObjectReference(getSourceId(), getSourceObjectZoneChangeCounter() + 1, game); + Permanent permanent = object.getPermanent(game); + if (permanent == null || !permanent.isPhasedIn()) { + // Triggers, but do nothing. + return true; + } + getEffects().setTargetPointer(new FixedTarget(permanent, game)); + return true; } @Override diff --git a/Mage/src/main/java/mage/filter/predicate/permanent/SaddledSourceThisTurnPredicate.java b/Mage/src/main/java/mage/filter/predicate/permanent/SaddledSourceThisTurnPredicate.java index 497a70dd568..ed6593a8d91 100644 --- a/Mage/src/main/java/mage/filter/predicate/permanent/SaddledSourceThisTurnPredicate.java +++ b/Mage/src/main/java/mage/filter/predicate/permanent/SaddledSourceThisTurnPredicate.java @@ -1,5 +1,6 @@ package mage.filter.predicate.permanent; +import mage.MageObjectReference; import mage.filter.predicate.ObjectSourcePlayer; import mage.filter.predicate.ObjectSourcePlayerPredicate; import mage.game.Game; @@ -17,7 +18,7 @@ public enum SaddledSourceThisTurnPredicate implements ObjectSourcePlayerPredicat @Override public boolean apply(ObjectSourcePlayer input, Game game) { return SaddledMountWatcher.checkIfSaddledThisTurn( - input.getObject(), input.getSource().getSourcePermanentOrLKI(game), game + input.getObject(), new MageObjectReference(input.getSourceId(), input.getSource().getSourceObjectZoneChangeCounter(), game), game ); } diff --git a/Mage/src/main/java/mage/game/GameImpl.java b/Mage/src/main/java/mage/game/GameImpl.java index c477a6ee83a..7ca471242c5 100644 --- a/Mage/src/main/java/mage/game/GameImpl.java +++ b/Mage/src/main/java/mage/game/GameImpl.java @@ -2162,11 +2162,27 @@ public abstract class GameImpl implements Game { delayedAbility.setSourceId(source.getSourceId()); delayedAbility.setControllerId(source.getControllerId()); } - // return addDelayedTriggeredAbility(delayedAbility); DelayedTriggeredAbility newAbility = delayedAbility.copy(); newAbility.newId(); if (source != null) { - newAbility.setSourceObjectZoneChangeCounter(getState().getZoneChangeCounter(source.getSourceId())); + // Relevant ruling: + // 603.7e If an activated or triggered ability creates a delayed triggered ability, + // the source of that delayed triggered ability is the same as the source of that other ability. + // The controller of that delayed triggered ability is the player who controlled that other ability as it resolved. + // 603.7f If a static ability generates a replacement effect which causes a delayed triggered ability to be created, + // the source of that delayed triggered ability is the object with that static ability. + // The controller of that delayed triggered ability is the same as the controller of that object at the time + // the replacement effect was applied. + // + // There are two possibility for the zcc: + // 1/ the source is an Ability with a valid (not 0) zcc, and we must use the same. + int zcc = source.getSourceObjectZoneChangeCounter(); + if (zcc == 0) { + // 2/ the source has not a valid zcc (it is most likely a StaticAbility instantiated at beginning of game) + // we use the source objects's zcc + zcc = getState().getZoneChangeCounter(source.getSourceId()); + } + newAbility.setSourceObjectZoneChangeCounter(zcc); newAbility.setSourcePermanentTransformCount(this); } newAbility.init(this); diff --git a/Mage/src/main/java/mage/game/permanent/Permanent.java b/Mage/src/main/java/mage/game/permanent/Permanent.java index ce3323d0635..6ae7a036b65 100644 --- a/Mage/src/main/java/mage/game/permanent/Permanent.java +++ b/Mage/src/main/java/mage/game/permanent/Permanent.java @@ -78,10 +78,6 @@ public interface Permanent extends Card, Controllable { void setSuspected(boolean value, Game game, Ability source); - boolean isSaddled(); - - void setSaddled(boolean value); - boolean isPrototyped(); void setPrototyped(boolean value); diff --git a/Mage/src/main/java/mage/game/permanent/PermanentImpl.java b/Mage/src/main/java/mage/game/permanent/PermanentImpl.java index f21686aca3d..c42b4f278de 100644 --- a/Mage/src/main/java/mage/game/permanent/PermanentImpl.java +++ b/Mage/src/main/java/mage/game/permanent/PermanentImpl.java @@ -72,7 +72,6 @@ public abstract class PermanentImpl extends CardImpl implements Permanent { protected boolean monstrous; protected boolean renowned; protected boolean suspected; - protected boolean saddled; protected boolean manifested = false; protected boolean morphed = false; protected boolean disguised = false; @@ -176,7 +175,6 @@ public abstract class PermanentImpl extends CardImpl implements Permanent { this.monstrous = permanent.monstrous; this.renowned = permanent.renowned; this.suspected = permanent.suspected; - this.saddled = permanent.saddled; this.ringBearerFlag = permanent.ringBearerFlag; this.classLevel = permanent.classLevel; this.goadingPlayers.addAll(permanent.goadingPlayers); @@ -239,7 +237,6 @@ public abstract class PermanentImpl extends CardImpl implements Permanent { this.maxBlockedBy = 0; this.copy = false; this.goadingPlayers.clear(); - this.saddled = false; this.loyaltyActivationsAvailable = 1; this.legendRuleApplies = true; this.canBeSacrificed = true; @@ -1730,16 +1727,6 @@ public abstract class PermanentImpl extends CardImpl implements Permanent { } } - @Override - public boolean isSaddled() { - return saddled; - } - - @Override - public void setSaddled(boolean saddled) { - this.saddled = saddled; - } - // Used as key for the ring bearer info. private static final String ringbearerInfoKey = "IS_RINGBEARER"; diff --git a/Mage/src/main/java/mage/watchers/common/SaddledMountWatcher.java b/Mage/src/main/java/mage/watchers/common/SaddledMountWatcher.java index cbc6c0859df..aa49f537643 100644 --- a/Mage/src/main/java/mage/watchers/common/SaddledMountWatcher.java +++ b/Mage/src/main/java/mage/watchers/common/SaddledMountWatcher.java @@ -10,22 +10,30 @@ import mage.watchers.Watcher; import java.util.*; /** - * @author TheElk801 + * @author TheElk801, Susucr */ public class SaddledMountWatcher extends Watcher { - // key: the mount, value: set of creatures which saddled + // key: the mount mor, value: set of creatures which saddled (on Saddle Cost payment) private final Map> saddleMap = new HashMap<>(); + // set of mount mor actually saddled (on Saddle Ability resolution) + private final Set saddledSet = new HashSet<>(); + public SaddledMountWatcher() { super(WatcherScope.GAME); } @Override public void watch(GameEvent event, Game game) { - if (event.getType() == GameEvent.EventType.SADDLED_MOUNT) { - saddleMap.computeIfAbsent(new MageObjectReference(event.getSourceId(), game), x -> new HashSet<>()) - .add(new MageObjectReference(event.getTargetId(), game)); + switch (event.getType()) { + case SADDLED_MOUNT: + saddleMap.computeIfAbsent(new MageObjectReference(event.getSourceId(), game), x -> new HashSet<>()) + .add(new MageObjectReference(event.getTargetId(), game)); + break; + case MOUNT_SADDLED: + saddledSet.add(new MageObjectReference(event.getSourceId(), game)); + break; } } @@ -33,24 +41,21 @@ public class SaddledMountWatcher extends Watcher { public void reset() { super.reset(); saddleMap.clear(); + saddledSet.clear(); } - public static boolean checkIfSaddledThisTurn(Permanent saddler, Permanent mount, Game game) { - return game + public static boolean hasBeenSaddledThisTurn(MageObjectReference mountMOR, Game game) { + SaddledMountWatcher watcher = game.getState().getWatcher(SaddledMountWatcher.class); + return watcher != null && watcher.saddledSet.contains(mountMOR); + } + + public static boolean checkIfSaddledThisTurn(Permanent saddler, MageObjectReference mountMOR, Game game) { + return hasBeenSaddledThisTurn(mountMOR, game) && game .getState() .getWatcher(SaddledMountWatcher.class) .saddleMap - .getOrDefault(new MageObjectReference(mount, game), Collections.emptySet()) + .getOrDefault(mountMOR, Collections.emptySet()) .stream() .anyMatch(mor -> mor.refersTo(saddler, game)); } - - public static int getSaddleCount(Permanent vehicle, Game game) { - return game - .getState() - .getWatcher(SaddledMountWatcher.class) - .saddleMap - .getOrDefault(new MageObjectReference(vehicle, game), Collections.emptySet()) - .size(); - } }