From d0308485521ee45cb4d5b8e6414d46a875ec52e0 Mon Sep 17 00:00:00 2001 From: Evan Kranzler Date: Sun, 30 Jan 2022 22:00:10 -0500 Subject: [PATCH] Reworking "as long as you control this" effects (WIP) (#8620) * added WhileControlled duration, removed SourceOnBattlefieldControlUnchangedCondition * refactored effects which keep things tapped * a few additional missed cards * refactored cards which check for being controlled and tapped * [NEO] Implemented Kyodai, Soul of Kamigawa --- Mage.Sets/src/mage/cards/a/AegisAngel.java | 16 +-- Mage.Sets/src/mage/cards/a/Aladdin.java | 17 +-- .../src/mage/cards/d/DragonlordSilumgar.java | 61 +-------- Mage.Sets/src/mage/cards/d/DungeonGeists.java | 123 ++--------------- .../src/mage/cards/h/HelmOfPossession.java | 44 ++---- .../src/mage/cards/h/HivisOfTheScale.java | 31 ++--- Mage.Sets/src/mage/cards/i/IcefallRegent.java | 119 ++--------------- .../mage/cards/k/KyodaiSoulOfKamigawa.java | 72 ++++++++++ Mage.Sets/src/mage/cards/m/MasterThief.java | 14 +- .../src/mage/cards/m/MerchantRaiders.java | 111 +-------------- .../src/mage/cards/m/MeriekeRiBerit.java | 15 +-- Mage.Sets/src/mage/cards/m/MindFlayer.java | 34 +---- .../src/mage/cards/o/OliviaVoldaren.java | 20 ++- .../src/mage/cards/o/OrcishSquatters.java | 14 +- .../src/mage/cards/q/QuicksmithRebel.java | 21 +-- Mage.Sets/src/mage/cards/q/QuicksmithSpy.java | 25 ++-- Mage.Sets/src/mage/cards/r/RoilElemental.java | 17 +-- .../src/mage/cards/r/RubiniaSoulsinger.java | 43 ++---- Mage.Sets/src/mage/cards/s/Seasinger.java | 56 ++++---- .../src/mage/cards/s/ShipbreakerKraken.java | 119 ++--------------- Mage.Sets/src/mage/cards/t/TheWretched.java | 64 ++++----- .../src/mage/cards/t/ThrullChampion.java | 22 +-- .../src/mage/cards/t/TidebinderMage.java | 126 +++--------------- Mage.Sets/src/mage/cards/t/TimeOfIce.java | 120 +---------------- .../mage/cards/w/WallOfStolenIdentity.java | 65 +++------ Mage.Sets/src/mage/cards/w/Willbreaker.java | 49 +++---- Mage.Sets/src/mage/cards/w/WillowSatyr.java | 18 +-- .../src/mage/sets/KamigawaNeonDynasty.java | 1 + .../test/cards/single/m12/AegisAngelTest.java | 118 ++++++++++++++++ ...nBattlefieldControlUnchangedCondition.java | 43 ------ .../ConditionalContinuousEffect.java | 24 ++-- .../abilities/effects/ContinuousEffects.java | 1 + .../effects/ContinuousEffectsList.java | 24 ++-- ...tapInControllersUntapStepTargetEffect.java | 47 ++++--- .../main/java/mage/constants/Duration.java | 1 + .../watchers/common/LostControlWatcher.java | 39 ------ 36 files changed, 495 insertions(+), 1239 deletions(-) create mode 100644 Mage.Sets/src/mage/cards/k/KyodaiSoulOfKamigawa.java create mode 100644 Mage.Tests/src/test/java/org/mage/test/cards/single/m12/AegisAngelTest.java delete mode 100644 Mage/src/main/java/mage/abilities/condition/common/SourceOnBattlefieldControlUnchangedCondition.java delete mode 100644 Mage/src/main/java/mage/watchers/common/LostControlWatcher.java diff --git a/Mage.Sets/src/mage/cards/a/AegisAngel.java b/Mage.Sets/src/mage/cards/a/AegisAngel.java index 7a7d812249..7c5e4a6721 100644 --- a/Mage.Sets/src/mage/cards/a/AegisAngel.java +++ b/Mage.Sets/src/mage/cards/a/AegisAngel.java @@ -1,11 +1,8 @@ package mage.cards.a; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldTriggeredAbility; -import mage.abilities.condition.common.SourceOnBattlefieldControlUnchangedCondition; -import mage.abilities.decorator.ConditionalContinuousEffect; import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; import mage.abilities.keyword.FlyingAbility; import mage.abilities.keyword.IndestructibleAbility; @@ -17,7 +14,8 @@ import mage.constants.SubType; import mage.filter.FilterPermanent; import mage.filter.predicate.mageobject.AnotherPredicate; import mage.target.TargetPermanent; -import mage.watchers.common.LostControlWatcher; + +import java.util.UUID; /** * @author Loki @@ -41,13 +39,10 @@ public final class AegisAngel extends CardImpl { this.addAbility(FlyingAbility.getInstance()); // When Aegis Angel enters the battlefield, another target permanent is indestructible for as long as you control Aegis Angel. - ConditionalContinuousEffect effect = new ConditionalContinuousEffect( - new GainAbilityTargetEffect(IndestructibleAbility.getInstance(), Duration.Custom), - new SourceOnBattlefieldControlUnchangedCondition(), - "another target permanent is indestructible for as long as you control Aegis Angel"); - Ability ability = new EntersBattlefieldTriggeredAbility(effect, false); + Ability ability = new EntersBattlefieldTriggeredAbility(new GainAbilityTargetEffect( + IndestructibleAbility.getInstance(), Duration.WhileControlled + ), false); ability.addTarget(new TargetPermanent(filter)); - ability.addWatcher(new LostControlWatcher()); this.addAbility(ability); } @@ -59,5 +54,4 @@ public final class AegisAngel extends CardImpl { public AegisAngel copy() { return new AegisAngel(this); } - } diff --git a/Mage.Sets/src/mage/cards/a/Aladdin.java b/Mage.Sets/src/mage/cards/a/Aladdin.java index 4afa37d8da..15c85f9ce8 100644 --- a/Mage.Sets/src/mage/cards/a/Aladdin.java +++ b/Mage.Sets/src/mage/cards/a/Aladdin.java @@ -1,25 +1,21 @@ package mage.cards.a; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; -import mage.abilities.condition.common.SourceOnBattlefieldControlUnchangedCondition; import mage.abilities.costs.common.TapSourceCost; import mage.abilities.costs.mana.ManaCostsImpl; -import mage.abilities.decorator.ConditionalContinuousEffect; import mage.abilities.effects.common.continuous.GainControlTargetEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Duration; import mage.constants.SubType; -import mage.constants.Zone; import mage.target.common.TargetArtifactPermanent; -import mage.watchers.common.LostControlWatcher; + +import java.util.UUID; /** - * * @author nigelzor */ public final class Aladdin extends CardImpl { @@ -32,14 +28,11 @@ public final class Aladdin extends CardImpl { this.toughness = new MageInt(1); // {1}{R}{R}, {tap}: Gain control of target artifact for as long as you control Aladdin. - ConditionalContinuousEffect effect = new ConditionalContinuousEffect( - new GainControlTargetEffect(Duration.Custom), - new SourceOnBattlefieldControlUnchangedCondition(), - "Gain control of target artifact for as long as you control Aladdin"); - Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, effect, new ManaCostsImpl("{1}{R}{R}")); + Ability ability = new SimpleActivatedAbility( + new GainControlTargetEffect(Duration.WhileControlled), new ManaCostsImpl<>("{1}{R}{R}") + ); ability.addCost(new TapSourceCost()); ability.addTarget(new TargetArtifactPermanent()); - ability.addWatcher(new LostControlWatcher()); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/d/DragonlordSilumgar.java b/Mage.Sets/src/mage/cards/d/DragonlordSilumgar.java index d9645e8e05..a83a6fb6a8 100644 --- a/Mage.Sets/src/mage/cards/d/DragonlordSilumgar.java +++ b/Mage.Sets/src/mage/cards/d/DragonlordSilumgar.java @@ -1,14 +1,8 @@ package mage.cards.d; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldTriggeredAbility; -import mage.abilities.condition.CompoundCondition; -import mage.abilities.condition.common.SourceHasRemainedInSameZoneCondition; -import mage.abilities.condition.common.SourceOnBattlefieldControlUnchangedCondition; -import mage.abilities.decorator.ConditionalContinuousEffect; -import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.continuous.GainControlTargetEffect; import mage.abilities.keyword.DeathtouchAbility; import mage.abilities.keyword.FlyingAbility; @@ -16,19 +10,13 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Duration; -import mage.constants.Outcome; import mage.constants.SubType; import mage.constants.SuperType; -import mage.game.Game; -import mage.game.permanent.Permanent; -import mage.players.Player; import mage.target.common.TargetCreatureOrPlaneswalker; -import mage.util.CardUtil; -import mage.util.GameLog; -import mage.watchers.common.LostControlWatcher; + +import java.util.UUID; /** - * * @author jeffwadsworth */ public final class DragonlordSilumgar extends CardImpl { @@ -48,11 +36,9 @@ public final class DragonlordSilumgar extends CardImpl { this.addAbility(DeathtouchAbility.getInstance()); // When Dragonlord Silumgar enters the battlefield, gain control of target creature or planeswalker for as long as you control Dragonlord Silumgar. - Ability ability = new EntersBattlefieldTriggeredAbility(new DragonlordSilumgarEffect(), false); + Ability ability = new EntersBattlefieldTriggeredAbility(new GainControlTargetEffect(Duration.WhileControlled)); ability.addTarget(new TargetCreatureOrPlaneswalker()); - ability.addWatcher(new LostControlWatcher()); this.addAbility(ability); - } private DragonlordSilumgar(final DragonlordSilumgar card) { @@ -64,44 +50,3 @@ public final class DragonlordSilumgar extends CardImpl { return new DragonlordSilumgar(this); } } - -class DragonlordSilumgarEffect extends OneShotEffect { - - public DragonlordSilumgarEffect() { - super(Outcome.GainControl); - this.staticText = "gain control of target creature or planeswalker for as long as you control {this}"; - } - - public DragonlordSilumgarEffect(final DragonlordSilumgarEffect effect) { - super(effect); - } - - @Override - public DragonlordSilumgarEffect copy() { - return new DragonlordSilumgarEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Permanent sourcePermanent = game.getPermanent(source.getSourceId()); - Player controller = game.getPlayer(source.getControllerId()); - Permanent target = game.getPermanent(getTargetPointer().getFirst(game, source)); - if (controller != null && sourcePermanent != null) { - if (target != null && controller.getId().equals(sourcePermanent.getControllerId())) { - SourceHasRemainedInSameZoneCondition condition = new SourceHasRemainedInSameZoneCondition(sourcePermanent.getId()); - - game.addEffect(new ConditionalContinuousEffect( - new GainControlTargetEffect(Duration.Custom), - new CompoundCondition(new SourceOnBattlefieldControlUnchangedCondition(), condition), - null), - source); - if (!game.isSimulation()) { - game.informPlayers(sourcePermanent.getLogName() + ": " + controller.getLogName() + " gained control of " + target.getLogName()); - } - sourcePermanent.addInfo("gained control of", CardUtil.addToolTipMarkTags("Gained control of: " + GameLog.getColoredObjectIdNameForTooltip(target)), game); - } - return true; - } - return false; - } -} diff --git a/Mage.Sets/src/mage/cards/d/DungeonGeists.java b/Mage.Sets/src/mage/cards/d/DungeonGeists.java index 8276c5ac0b..f15cecd6eb 100644 --- a/Mage.Sets/src/mage/cards/d/DungeonGeists.java +++ b/Mage.Sets/src/mage/cards/d/DungeonGeists.java @@ -1,34 +1,33 @@ - package mage.cards.d; -import java.util.UUID; import mage.MageInt; -import mage.MageObject; import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.effects.ContinuousRuleModifyingEffectImpl; +import mage.abilities.effects.common.DontUntapInControllersUntapStepTargetEffect; import mage.abilities.effects.common.TapTargetEffect; import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.*; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.SubType; 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.target.Target; import mage.target.common.TargetCreaturePermanent; -import mage.watchers.Watcher; + +import java.util.UUID; /** - * * @author BetaSteward */ public final class DungeonGeists extends CardImpl { public DungeonGeists(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{2}{U}{U}"); + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{U}{U}"); this.subtype.add(SubType.SPIRIT); this.power = new MageInt(3); @@ -38,11 +37,9 @@ public final class DungeonGeists extends CardImpl { // When Dungeon Geists enters the battlefield, tap target creature an opponent controls. That creature doesn't untap during its controller's untap step for as long as you control Dungeon Geists. Ability ability = new EntersBattlefieldTriggeredAbility(new TapTargetEffect(), false); - ability.addEffect(new DungeonGeistsEffect()); - Target target = new TargetCreaturePermanent(StaticFilters.FILTER_OPPONENTS_PERMANENT_CREATURE); - ability.addTarget(target); - this.addAbility(ability, new DungeonGeistsWatcher()); - // watcher needed to send normal events to Dungeon Geists ReplacementEffect + ability.addEffect(new DontUntapInControllersUntapStepTargetEffect(Duration.WhileControlled)); + ability.addTarget(new TargetCreaturePermanent(StaticFilters.FILTER_OPPONENTS_PERMANENT_CREATURE)); + this.addAbility(ability); } private DungeonGeists(final DungeonGeists card) { @@ -54,101 +51,3 @@ public final class DungeonGeists extends CardImpl { return new DungeonGeists(this); } } - -class DungeonGeistsEffect extends ContinuousRuleModifyingEffectImpl { - - public DungeonGeistsEffect() { - super(Duration.Custom, Outcome.Detriment, false, false); - this.staticText = "That creature doesn't untap during its controller's untap step for as long as you control {this}"; - } - - public DungeonGeistsEffect(final DungeonGeistsEffect effect) { - super(effect); - } - - @Override - public DungeonGeistsEffect copy() { - return new DungeonGeistsEffect(this); - } - - @Override - public boolean checksEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.UNTAP - || event.getType() == GameEvent.EventType.ZONE_CHANGE - || event.getType() == GameEvent.EventType.LOST_CONTROL; - } - - @Override - public boolean applies(GameEvent event, Ability source, Game game) { - // Source must be on the battlefield (it's neccessary to check here because if as response to the enter - // the battlefield triggered ability the source dies (or will be exiled), then the ZONE_CHANGE or LOST_CONTROL - // event will happen before this effect is applied ever) - MageObject sourceObject = source.getSourceObjectIfItStillExists(game); - if (!(sourceObject instanceof Permanent) || !((Permanent) sourceObject).isControlledBy(source.getControllerId())) { - discard(); - return false; - } - switch (event.getType()) { - case ZONE_CHANGE: - // end effect if source does a zone move - if (event.getTargetId().equals(source.getSourceId())) { - ZoneChangeEvent zEvent = (ZoneChangeEvent) event; - if (zEvent.getFromZone() == Zone.BATTLEFIELD) { - discard(); - return false; - } - } - break; - case UNTAP: - // prevent to untap the target creature - if (game.getTurn().getStepType() == PhaseStep.UNTAP && event.getTargetId().equals(targetPointer.getFirst(game, source))) { - Permanent targetCreature = game.getPermanent(targetPointer.getFirst(game, source)); - if (targetCreature != null) { - return targetCreature.isControlledBy(game.getActivePlayerId()); - } else { - discard(); - return false; - } - } - break; - case LOST_CONTROL: - // end effect if source control is changed - if (event.getTargetId().equals(source.getSourceId())) { - discard(); - return false; - } - break; - } - return false; - } -} - -class DungeonGeistsWatcher extends Watcher { - - DungeonGeistsWatcher() { - super(WatcherScope.CARD); - } - - @Override - public void watch(GameEvent event, Game game) { - if (event.getType() == GameEvent.EventType.LOST_CONTROL - && event.getPlayerId().equals(controllerId) - && event.getTargetId().equals(sourceId)) { - condition = true; - game.replaceEvent(event); - return; - } - if (event.getType() == GameEvent.EventType.ZONE_CHANGE && event.getTargetId().equals(sourceId)) { - ZoneChangeEvent zEvent = (ZoneChangeEvent) event; - if (zEvent.getFromZone() == Zone.BATTLEFIELD) { - condition = true; - game.replaceEvent(event); - } - } - } - - @Override - public void reset() { - //don't reset condition each turn - only when this leaves the battlefield - } -} diff --git a/Mage.Sets/src/mage/cards/h/HelmOfPossession.java b/Mage.Sets/src/mage/cards/h/HelmOfPossession.java index 9b631d2191..89a2c68763 100644 --- a/Mage.Sets/src/mage/cards/h/HelmOfPossession.java +++ b/Mage.Sets/src/mage/cards/h/HelmOfPossession.java @@ -1,11 +1,10 @@ package mage.cards.h; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.common.SkipUntapOptionalAbility; -import mage.abilities.condition.Condition; +import mage.abilities.condition.common.SourceTappedCondition; import mage.abilities.costs.common.SacrificeTargetCost; import mage.abilities.costs.common.TapSourceCost; import mage.abilities.costs.mana.GenericManaCost; @@ -15,15 +14,14 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Duration; -import mage.constants.Zone; -import static mage.filter.StaticFilters.FILTER_CONTROLLED_CREATURE_SHORT_TEXT; -import mage.game.Game; -import mage.game.permanent.Permanent; -import mage.target.common.TargetControlledCreaturePermanent; +import mage.target.common.TargetControlledPermanent; import mage.target.common.TargetCreaturePermanent; +import java.util.UUID; + +import static mage.filter.StaticFilters.FILTER_CONTROLLED_CREATURE_SHORT_TEXT; + /** - * * @author fireshoes */ public final class HelmOfPossession extends CardImpl { @@ -35,14 +33,13 @@ public final class HelmOfPossession extends CardImpl { this.addAbility(new SkipUntapOptionalAbility()); // {2}, {tap}, Sacrifice a creature: Gain control of target creature for as long as you control Helm of Possession and Helm of Possession remains tapped. - ConditionalContinuousEffect effect = new ConditionalContinuousEffect( - new GainControlTargetEffect(Duration.Custom), - new HelmOfPossessionCondition(), - "Gain control of target creature for as long as you control {this} and {this} remains tapped"); - Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, effect, new GenericManaCost(2)); + Ability ability = new SimpleActivatedAbility(new ConditionalContinuousEffect( + new GainControlTargetEffect(Duration.WhileControlled), SourceTappedCondition.instance, + "gain control of target creature for as long as you control {this} and {this} remains tapped" + ), new GenericManaCost(2)); ability.addCost(new TapSourceCost()); + ability.addCost(new SacrificeTargetCost(new TargetControlledPermanent(FILTER_CONTROLLED_CREATURE_SHORT_TEXT))); ability.addTarget(new TargetCreaturePermanent()); - ability.addCost(new SacrificeTargetCost(new TargetControlledCreaturePermanent(FILTER_CONTROLLED_CREATURE_SHORT_TEXT))); this.addAbility(ability); } @@ -55,22 +52,3 @@ public final class HelmOfPossession extends CardImpl { return new HelmOfPossession(this); } } - -class HelmOfPossessionCondition implements Condition { - - private UUID controllerId; - - @Override - public boolean apply(Game game, Ability source) { - if (controllerId == null) { - controllerId = source.getControllerId(); - } - Permanent permanent = game.getBattlefield().getPermanent(source.getSourceId()); - if (permanent != null) { - if (permanent.isTapped()) { - return controllerId.equals(source.getControllerId()); - } - } - return false; - } -} diff --git a/Mage.Sets/src/mage/cards/h/HivisOfTheScale.java b/Mage.Sets/src/mage/cards/h/HivisOfTheScale.java index 07c11c9ebb..28e288221c 100644 --- a/Mage.Sets/src/mage/cards/h/HivisOfTheScale.java +++ b/Mage.Sets/src/mage/cards/h/HivisOfTheScale.java @@ -1,16 +1,13 @@ package mage.cards.h; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.common.SkipUntapOptionalAbility; -import mage.abilities.condition.Condition; -import mage.abilities.condition.common.SourceMatchesFilterCondition; +import mage.abilities.condition.common.SourceTappedCondition; import mage.abilities.costs.common.TapSourceCost; import mage.abilities.decorator.ConditionalContinuousEffect; -import mage.abilities.effects.Effect; import mage.abilities.effects.common.continuous.GainControlTargetEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -18,31 +15,25 @@ import mage.constants.CardType; import mage.constants.Duration; import mage.constants.SubType; import mage.constants.SuperType; -import mage.constants.TargetController; -import mage.constants.Zone; import mage.filter.FilterPermanent; -import mage.filter.predicate.permanent.TappedPredicate; import mage.target.TargetPermanent; +import java.util.UUID; + /** - * * @author jeffwadsworth */ public final class HivisOfTheScale extends CardImpl { - - private static final FilterPermanent filter = new FilterPermanent(); + private static final FilterPermanent filterDragon = new FilterPermanent(); - private static final String rule = "Gain control of target Dragon for as long as you control Hivis and Hivis remains tapped."; - + static { - filter.add(TappedPredicate.TAPPED); - filter.add(TargetController.YOU.getControllerPredicate()); filterDragon.add(SubType.DRAGON.getPredicate()); } public HivisOfTheScale(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{R}{R}"); - + addSuperType(SuperType.LEGENDARY); this.subtype.add(SubType.VIASHINO); this.subtype.add(SubType.SHAMAN); @@ -51,14 +42,14 @@ public final class HivisOfTheScale extends CardImpl { // You may choose not to untap Hivis of the Scale during your untap step. this.addAbility(new SkipUntapOptionalAbility()); - + // {tap}: Gain control of target Dragon for as long as you control Hivis and Hivis remains tapped. - Condition condition = new SourceMatchesFilterCondition(filter); - Effect effect = new ConditionalContinuousEffect(new GainControlTargetEffect(Duration.Custom), condition, rule); - Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, effect, new TapSourceCost()); + Ability ability = new SimpleActivatedAbility(new ConditionalContinuousEffect( + new GainControlTargetEffect(Duration.WhileControlled), SourceTappedCondition.instance, + "gain control of target Dragon for as long as you control {this} and {this} remains tapped" + ), new TapSourceCost()); ability.addTarget(new TargetPermanent(filterDragon)); this.addAbility(ability); - } private HivisOfTheScale(final HivisOfTheScale card) { diff --git a/Mage.Sets/src/mage/cards/i/IcefallRegent.java b/Mage.Sets/src/mage/cards/i/IcefallRegent.java index b67d629228..4454729a6f 100644 --- a/Mage.Sets/src/mage/cards/i/IcefallRegent.java +++ b/Mage.Sets/src/mage/cards/i/IcefallRegent.java @@ -4,22 +4,19 @@ import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.effects.ContinuousRuleModifyingEffectImpl; +import mage.abilities.effects.common.DontUntapInControllersUntapStepTargetEffect; import mage.abilities.effects.common.TapTargetEffect; import mage.abilities.effects.common.cost.SpellsCostModificationThatTargetSourceEffect; import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.*; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.constants.TargetController; import mage.filter.FilterCard; 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.target.Target; import mage.target.common.TargetCreaturePermanent; -import mage.watchers.Watcher; import java.util.UUID; @@ -40,15 +37,14 @@ public final class IcefallRegent extends CardImpl { // When Icefall Regent enters the battlefield, tap target creature an opponent controls. // That creature doesn't untap during its controller's untap step for as long as you control Icefall Regent. Ability ability = new EntersBattlefieldTriggeredAbility(new TapTargetEffect(), false); - ability.addEffect(new IcefallRegentEffect()); - Target target = new TargetCreaturePermanent(StaticFilters.FILTER_OPPONENTS_PERMANENT_CREATURE); - ability.addTarget(target); - this.addAbility(ability, new IcefallRegentWatcher()); + ability.addEffect(new DontUntapInControllersUntapStepTargetEffect(Duration.WhileControlled)); + ability.addTarget(new TargetCreaturePermanent(StaticFilters.FILTER_OPPONENTS_PERMANENT_CREATURE)); + this.addAbility(ability); // Spells your opponents cast that target Icefall Regent cost {2} more to cast. - this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, - new SpellsCostModificationThatTargetSourceEffect(2, new FilterCard("Spells"), TargetController.OPPONENT)) - ); + this.addAbility(new SimpleStaticAbility(new SpellsCostModificationThatTargetSourceEffect( + 2, new FilterCard("Spells"), TargetController.OPPONENT + ))); } private IcefallRegent(final IcefallRegent card) { @@ -60,96 +56,3 @@ public final class IcefallRegent extends CardImpl { return new IcefallRegent(this); } } - -class IcefallRegentEffect extends ContinuousRuleModifyingEffectImpl { - - public IcefallRegentEffect() { - super(Duration.Custom, Outcome.Detriment, false, false); - this.staticText = "That creature doesn't untap during its controller's untap step for as long as you control {this}"; - } - - public IcefallRegentEffect(final IcefallRegentEffect effect) { - super(effect); - } - - @Override - public IcefallRegentEffect copy() { - return new IcefallRegentEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - return false; - } - - @Override - public boolean checksEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.LOST_CONTROL - || event.getType() == GameEvent.EventType.ZONE_CHANGE - || event.getType() == GameEvent.EventType.UNTAP; - } - - @Override - public boolean applies(GameEvent event, Ability source, Game game) { - // Source must be on the battlefield (it's neccessary to check here because if as response to the enter - // the battlefield triggered ability the source dies (or will be exiled), then the ZONE_CHANGE or LOST_CONTROL - // event will happen before this effect is applied ever) - Permanent sourcePermanent = game.getPermanent(source.getSourceId()); - if (sourcePermanent == null || !sourcePermanent.isControlledBy(source.getControllerId())) { - discard(); - return false; - } - if (event.getType() == GameEvent.EventType.LOST_CONTROL) { - if (event.getTargetId().equals(source.getSourceId())) { - discard(); - return false; - } - } - if (event.getType() == GameEvent.EventType.ZONE_CHANGE && event.getTargetId().equals(source.getSourceId())) { - ZoneChangeEvent zEvent = (ZoneChangeEvent) event; - if (zEvent.getFromZone() == Zone.BATTLEFIELD) { - discard(); - return false; - } - } - - if (game.getTurn().getStepType() == PhaseStep.UNTAP && event.getType() == GameEvent.EventType.UNTAP) { - if (event.getTargetId().equals(targetPointer.getFirst(game, source))) { - Permanent targetCreature = game.getPermanent(targetPointer.getFirst(game, source)); - return targetCreature != null && game.isActivePlayer(targetCreature.getControllerId()); - } - } - - return false; - } -} - -class IcefallRegentWatcher extends Watcher { - - IcefallRegentWatcher() { - super(WatcherScope.CARD); - } - - @Override - public void watch(GameEvent event, Game game) { - if (event.getType() == GameEvent.EventType.LOST_CONTROL - && event.getPlayerId().equals(controllerId) - && event.getTargetId().equals(sourceId)) { - condition = true; - game.replaceEvent(event); - return; - } - if (event.getType() == GameEvent.EventType.ZONE_CHANGE && event.getTargetId().equals(sourceId)) { - ZoneChangeEvent zEvent = (ZoneChangeEvent) event; - if (zEvent.getFromZone() == Zone.BATTLEFIELD) { - condition = true; - game.replaceEvent(event); - } - } - } - - @Override - public void reset() { - //don't reset condition each turn - only when this leaves the battlefield - } -} diff --git a/Mage.Sets/src/mage/cards/k/KyodaiSoulOfKamigawa.java b/Mage.Sets/src/mage/cards/k/KyodaiSoulOfKamigawa.java new file mode 100644 index 0000000000..10e194c278 --- /dev/null +++ b/Mage.Sets/src/mage/cards/k/KyodaiSoulOfKamigawa.java @@ -0,0 +1,72 @@ +package mage.cards.k; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.continuous.BoostSourceEffect; +import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; +import mage.abilities.keyword.FlashAbility; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.IndestructibleAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.filter.FilterPermanent; +import mage.filter.predicate.mageobject.AnotherPredicate; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class KyodaiSoulOfKamigawa extends CardImpl { + + private static final FilterPermanent filter = new FilterPermanent("another target permanent"); + + static { + filter.add(AnotherPredicate.instance); + } + + public KyodaiSoulOfKamigawa(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{W}"); + + this.addSuperType(SuperType.LEGENDARY); + this.subtype.add(SubType.DRAGON); + this.subtype.add(SubType.SPIRIT); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // Flash + this.addAbility(FlashAbility.getInstance()); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // When Kyodai, Soul of Kamigawa enters the battlefield, another target permanent gains indestructible for as long as you control Kyodai. + Ability ability = new EntersBattlefieldTriggeredAbility(new GainAbilityTargetEffect( + IndestructibleAbility.getInstance(), Duration.WhileControlled + ), false); + ability.addTarget(new TargetPermanent(filter)); + this.addAbility(ability); + + // {W}{U}{B}{R}{G}: Kyodai, Soul of Kamigawa gets +5/+5 until end of turn. + this.addAbility(new SimpleActivatedAbility(new BoostSourceEffect( + 5, 5, Duration.EndOfTurn + ), new ManaCostsImpl<>("{W}{U}{B}{R}{G}"))); + } + + private KyodaiSoulOfKamigawa(final KyodaiSoulOfKamigawa card) { + super(card); + } + + @Override + public KyodaiSoulOfKamigawa copy() { + return new KyodaiSoulOfKamigawa(this); + } +} diff --git a/Mage.Sets/src/mage/cards/m/MasterThief.java b/Mage.Sets/src/mage/cards/m/MasterThief.java index dfe7617e94..011c3d7d49 100644 --- a/Mage.Sets/src/mage/cards/m/MasterThief.java +++ b/Mage.Sets/src/mage/cards/m/MasterThief.java @@ -1,11 +1,8 @@ package mage.cards.m; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldTriggeredAbility; -import mage.abilities.condition.common.SourceOnBattlefieldControlUnchangedCondition; -import mage.abilities.decorator.ConditionalContinuousEffect; import mage.abilities.effects.common.continuous.GainControlTargetEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -13,7 +10,8 @@ import mage.constants.CardType; import mage.constants.Duration; import mage.constants.SubType; import mage.target.common.TargetArtifactPermanent; -import mage.watchers.common.LostControlWatcher; + +import java.util.UUID; /** * @author Loki, JayDi85 @@ -29,14 +27,8 @@ public final class MasterThief extends CardImpl { this.toughness = new MageInt(2); // When Master Thief enters the battlefield, gain control of target artifact for as long as you control Master Thief. - Ability ability = new EntersBattlefieldTriggeredAbility(new ConditionalContinuousEffect( - new GainControlTargetEffect(Duration.Custom), - new SourceOnBattlefieldControlUnchangedCondition(), - "gain control of target artifact for as long as you control {this}"), - false); - + Ability ability = new EntersBattlefieldTriggeredAbility(new GainControlTargetEffect(Duration.WhileControlled)); ability.addTarget(new TargetArtifactPermanent()); - ability.addWatcher(new LostControlWatcher()); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/m/MerchantRaiders.java b/Mage.Sets/src/mage/cards/m/MerchantRaiders.java index 9a9dbd385c..d3bea90ebf 100644 --- a/Mage.Sets/src/mage/cards/m/MerchantRaiders.java +++ b/Mage.Sets/src/mage/cards/m/MerchantRaiders.java @@ -1,21 +1,17 @@ package mage.cards.m; import mage.MageInt; -import mage.MageObject; import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldThisOrAnotherTriggeredAbility; -import mage.abilities.effects.ContinuousRuleModifyingEffectImpl; +import mage.abilities.effects.common.DontUntapInControllersUntapStepTargetEffect; import mage.abilities.effects.common.TapTargetEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.*; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; import mage.filter.FilterPermanent; -import mage.game.Game; -import mage.game.events.GameEvent; -import mage.game.events.ZoneChangeEvent; -import mage.game.permanent.Permanent; import mage.target.common.TargetCreaturePermanent; -import mage.watchers.Watcher; import java.util.UUID; @@ -39,9 +35,9 @@ public final class MerchantRaiders extends CardImpl { new TapTargetEffect("tap up to one target creature"), filter, false, true ); - ability.addEffect(new MerchantRaidersEffect()); + ability.addEffect(new DontUntapInControllersUntapStepTargetEffect(Duration.WhileControlled)); ability.addTarget(new TargetCreaturePermanent(0, 1)); - this.addAbility(ability, new MerchantRaidersWatcher()); + this.addAbility(ability); } private MerchantRaiders(final MerchantRaiders card) { @@ -53,98 +49,3 @@ public final class MerchantRaiders extends CardImpl { return new MerchantRaiders(this); } } - -class MerchantRaidersEffect extends ContinuousRuleModifyingEffectImpl { - - public MerchantRaidersEffect() { - super(Duration.Custom, Outcome.Detriment, false, false); - this.staticText = "That creature doesn't untap during its controller's untap step for as long as you control {this}"; - } - - public MerchantRaidersEffect(final MerchantRaidersEffect effect) { - super(effect); - } - - @Override - public MerchantRaidersEffect copy() { - return new MerchantRaidersEffect(this); - } - - @Override - public boolean checksEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.UNTAP - || event.getType() == GameEvent.EventType.ZONE_CHANGE - || event.getType() == GameEvent.EventType.LOST_CONTROL; - } - - @Override - public boolean applies(GameEvent event, Ability source, Game game) { - // Source must be on the battlefield (it's neccessary to check here because if as response to the enter - // the battlefield triggered ability the source dies (or will be exiled), then the ZONE_CHANGE or LOST_CONTROL - // event will happen before this effect is applied ever) - MageObject sourceObject = source.getSourceObjectIfItStillExists(game); - if (!(sourceObject instanceof Permanent) || !((Permanent) sourceObject).isControlledBy(source.getControllerId())) { - discard(); - return false; - } - switch (event.getType()) { - case ZONE_CHANGE: - // end effect if source does a zone move - if (!event.getTargetId().equals(source.getSourceId())) { - break; - } - ZoneChangeEvent zEvent = (ZoneChangeEvent) event; - if (zEvent.getFromZone() == Zone.BATTLEFIELD) { - discard(); - return false; - } - break; - case UNTAP: - // prevent to untap the target creature - if (game.getTurn().getStepType() != PhaseStep.UNTAP - || !event.getTargetId().equals(targetPointer.getFirst(game, source))) { - break; - } - Permanent targetCreature = game.getPermanent(targetPointer.getFirst(game, source)); - if (targetCreature != null) { - return targetCreature.isControlledBy(game.getActivePlayerId()); - } else { - discard(); - return false; - } - case LOST_CONTROL: - // end effect if source control is changed - if (event.getTargetId().equals(source.getSourceId())) { - discard(); - return false; - } - break; - } - return false; - } -} - -class MerchantRaidersWatcher extends Watcher { - - MerchantRaidersWatcher() { - super(WatcherScope.CARD); - } - - @Override - public void watch(GameEvent event, Game game) { - if (event.getType() == GameEvent.EventType.LOST_CONTROL - && event.getPlayerId().equals(controllerId) - && event.getTargetId().equals(sourceId)) { - condition = true; - game.replaceEvent(event); - return; - } else if (event.getType() == GameEvent.EventType.ZONE_CHANGE - && event.getTargetId().equals(sourceId)) { - ZoneChangeEvent zEvent = (ZoneChangeEvent) event; - if (zEvent.getFromZone() == Zone.BATTLEFIELD) { - condition = true; - game.replaceEvent(event); - } - } - } -} diff --git a/Mage.Sets/src/mage/cards/m/MeriekeRiBerit.java b/Mage.Sets/src/mage/cards/m/MeriekeRiBerit.java index 0be01c90a3..98ff18706f 100644 --- a/Mage.Sets/src/mage/cards/m/MeriekeRiBerit.java +++ b/Mage.Sets/src/mage/cards/m/MeriekeRiBerit.java @@ -1,14 +1,11 @@ package mage.cards.m; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.DelayedTriggeredAbility; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.condition.common.SourceOnBattlefieldControlUnchangedCondition; import mage.abilities.costs.common.TapSourceCost; -import mage.abilities.decorator.ConditionalContinuousEffect; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.DestroyTargetEffect; import mage.abilities.effects.common.DontUntapInControllersUntapStepSourceEffect; @@ -24,10 +21,10 @@ import mage.game.events.ZoneChangeEvent; import mage.game.permanent.Permanent; import mage.target.TargetPermanent; import mage.target.targetpointer.FixedTarget; -import mage.watchers.common.LostControlWatcher; + +import java.util.UUID; /** - * * @author jeffwadsworth */ public final class MeriekeRiBerit extends CardImpl { @@ -43,16 +40,10 @@ public final class MeriekeRiBerit extends CardImpl { this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new DontUntapInControllersUntapStepSourceEffect())); // {tap}: Gain control of target creature for as long as you control Merieke Ri Berit. When Merieke Ri Berit leaves the battlefield or becomes untapped, destroy that creature. It can't be regenerated. - ConditionalContinuousEffect MeriekeRiBeritGainControlEffect = new ConditionalContinuousEffect( - new GainControlTargetEffect(Duration.Custom), - new SourceOnBattlefieldControlUnchangedCondition(), - "Gain control of target creature for as long as you control {this}"); - Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, MeriekeRiBeritGainControlEffect, new TapSourceCost()); + Ability ability = new SimpleActivatedAbility(new GainControlTargetEffect(Duration.WhileControlled), new TapSourceCost()); ability.addTarget(new TargetPermanent(new FilterCreaturePermanent("target creature"))); ability.addEffect(new MeriekeRiBeritCreateDelayedTriggerEffect()); - ability.addWatcher(new LostControlWatcher()); this.addAbility(ability); - } private MeriekeRiBerit(final MeriekeRiBerit card) { diff --git a/Mage.Sets/src/mage/cards/m/MindFlayer.java b/Mage.Sets/src/mage/cards/m/MindFlayer.java index 287fbd6097..ceead1061f 100644 --- a/Mage.Sets/src/mage/cards/m/MindFlayer.java +++ b/Mage.Sets/src/mage/cards/m/MindFlayer.java @@ -9,8 +9,6 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Duration; import mage.constants.SubType; -import mage.game.Game; -import mage.game.permanent.Permanent; import mage.target.common.TargetCreaturePermanent; import java.util.UUID; @@ -28,7 +26,7 @@ public final class MindFlayer extends CardImpl { this.toughness = new MageInt(3); // Dominate Monster — When Mind Flayer enters the battlefield, gain control of target creature for as long as you control Mind Flayer. - Ability ability = new EntersBattlefieldTriggeredAbility(new MindFlayerEffect()); + Ability ability = new EntersBattlefieldTriggeredAbility(new GainControlTargetEffect(Duration.WhileControlled)); ability.addTarget(new TargetCreaturePermanent()); this.addAbility(ability.withFlavorWord("Dominate Monster")); } @@ -42,33 +40,3 @@ public final class MindFlayer extends CardImpl { return new MindFlayer(this); } } - -class MindFlayerEffect extends GainControlTargetEffect { - - MindFlayerEffect() { - super(Duration.Custom, true); - staticText = "gain control of target creature for as long as you control {this}"; - } - - private MindFlayerEffect(final MindFlayerEffect effect) { - super(effect); - } - - @Override - public MindFlayerEffect copy() { - return new MindFlayerEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Permanent sourcePermanent = source.getSourcePermanentIfItStillExists(game); - Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); - if (sourcePermanent == null - || permanent == null - || !sourcePermanent.isControlledBy(source.getControllerId())) { - discard(); - return false; - } - return super.apply(game, source); - } -} diff --git a/Mage.Sets/src/mage/cards/o/OliviaVoldaren.java b/Mage.Sets/src/mage/cards/o/OliviaVoldaren.java index 1041711538..250b50b08d 100644 --- a/Mage.Sets/src/mage/cards/o/OliviaVoldaren.java +++ b/Mage.Sets/src/mage/cards/o/OliviaVoldaren.java @@ -1,12 +1,9 @@ - package mage.cards.o; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; -import mage.abilities.condition.common.PermanentsOnTheBattlefieldCondition; import mage.abilities.costs.mana.ManaCostsImpl; -import mage.abilities.decorator.ConditionalContinuousEffect; import mage.abilities.effects.Effect; import mage.abilities.effects.common.DamageTargetEffect; import mage.abilities.effects.common.continuous.AddCardSubTypeTargetEffect; @@ -19,14 +16,13 @@ import mage.constants.*; import mage.counters.CounterType; import mage.filter.FilterPermanent; import mage.filter.common.FilterCreaturePermanent; -import mage.filter.predicate.mageobject.CardIdPredicate; import mage.filter.predicate.mageobject.AnotherPredicate; +import mage.filter.predicate.mageobject.CardIdPredicate; import mage.target.common.TargetCreaturePermanent; import java.util.UUID; /** - * * @author nantuko */ public final class OliviaVoldaren extends CardImpl { @@ -40,7 +36,7 @@ public final class OliviaVoldaren extends CardImpl { } public OliviaVoldaren(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{2}{B}{R}"); + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{B}{R}"); addSuperType(SuperType.LEGENDARY); this.subtype.add(SubType.VAMPIRE); @@ -57,9 +53,9 @@ public final class OliviaVoldaren extends CardImpl { // {1}{R}: Olivia Voldaren deals 1 damage to another target creature. That creature becomes a Vampire in addition to its other types. Put a +1/+1 counter on Olivia Voldaren. Ability ability = new SimpleActivatedAbility( - Zone.BATTLEFIELD, - new DamageTargetEffect(1).setText("{this} deals 1 damage to another target creature"), - new ManaCostsImpl("{1}{R}") + new DamageTargetEffect(1) + .setText("{this} deals 1 damage to another target creature"), + new ManaCostsImpl<>("{1}{R}") ); ability.addTarget(new TargetCreaturePermanent(filter)); Effect effect = new AddCardSubTypeTargetEffect(SubType.VAMPIRE, Duration.WhileOnBattlefield); @@ -69,9 +65,9 @@ public final class OliviaVoldaren extends CardImpl { this.addAbility(ability); // {3}{B}{B}: Gain control of target Vampire for as long as you control Olivia Voldaren. - Ability ability2 = new SimpleActivatedAbility(Zone.BATTLEFIELD, - new ConditionalContinuousEffect(new GainControlTargetEffect(Duration.Custom), new PermanentsOnTheBattlefieldCondition(filter2), rule), - new ManaCostsImpl("{3}{B}{B}")); + Ability ability2 = new SimpleActivatedAbility( + new GainControlTargetEffect(Duration.WhileControlled), new ManaCostsImpl<>("{3}{B}{B}") + ); ability2.addTarget(new TargetCreaturePermanent(vampireFilter)); this.addAbility(ability2); } diff --git a/Mage.Sets/src/mage/cards/o/OrcishSquatters.java b/Mage.Sets/src/mage/cards/o/OrcishSquatters.java index 50c37a93a8..84ad9b29a1 100644 --- a/Mage.Sets/src/mage/cards/o/OrcishSquatters.java +++ b/Mage.Sets/src/mage/cards/o/OrcishSquatters.java @@ -1,11 +1,8 @@ package mage.cards.o; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.AttacksAndIsNotBlockedTriggeredAbility; -import mage.abilities.condition.common.SourceOnBattlefieldControlUnchangedCondition; -import mage.abilities.decorator.ConditionalContinuousEffect; import mage.abilities.effects.common.continuous.AssignNoCombatDamageSourceEffect; import mage.abilities.effects.common.continuous.GainControlTargetEffect; import mage.cards.CardImpl; @@ -16,10 +13,10 @@ import mage.constants.SubType; import mage.filter.common.FilterLandPermanent; import mage.filter.predicate.permanent.DefendingPlayerControlsPredicate; import mage.target.TargetPermanent; -import mage.watchers.common.LostControlWatcher; + +import java.util.UUID; /** - * * @author TheElk801 */ public final class OrcishSquatters extends CardImpl { @@ -38,14 +35,9 @@ public final class OrcishSquatters extends CardImpl { this.toughness = new MageInt(3); // Whenever Orcish Squatters attacks and isn't blocked, you may gain control of target land defending player controls for as long as you control Orcish Squatters. If you do, Orcish Squatters assigns no combat damage this turn. - Ability ability = new AttacksAndIsNotBlockedTriggeredAbility(new ConditionalContinuousEffect( - new GainControlTargetEffect(Duration.Custom), - new SourceOnBattlefieldControlUnchangedCondition(), - "gain control of target land defending player controls for as long as you control {this}" - ), true); + Ability ability = new AttacksAndIsNotBlockedTriggeredAbility(new GainControlTargetEffect(Duration.WhileControlled), true); ability.addEffect(new AssignNoCombatDamageSourceEffect(Duration.EndOfTurn, true)); ability.addTarget(new TargetPermanent(filter)); - ability.addWatcher(new LostControlWatcher()); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/q/QuicksmithRebel.java b/Mage.Sets/src/mage/cards/q/QuicksmithRebel.java index a51b2dab92..0fa59234bc 100644 --- a/Mage.Sets/src/mage/cards/q/QuicksmithRebel.java +++ b/Mage.Sets/src/mage/cards/q/QuicksmithRebel.java @@ -1,13 +1,10 @@ package mage.cards.q; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.common.SimpleActivatedAbility; -import mage.abilities.condition.common.SourceOnBattlefieldControlUnchangedCondition; import mage.abilities.costs.common.TapSourceCost; -import mage.abilities.decorator.ConditionalContinuousEffect; import mage.abilities.effects.common.DamageTargetEffect; import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; import mage.cards.CardImpl; @@ -15,14 +12,13 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Duration; import mage.constants.SubType; -import mage.constants.Zone; -import mage.filter.common.FilterControlledArtifactPermanent; +import mage.filter.StaticFilters; import mage.target.TargetPermanent; import mage.target.common.TargetAnyTarget; -import mage.watchers.common.LostControlWatcher; + +import java.util.UUID; /** - * * @author Styxo */ public final class QuicksmithRebel extends CardImpl { @@ -36,15 +32,10 @@ public final class QuicksmithRebel extends CardImpl { this.toughness = new MageInt(2); // When Quicksmith Rebel enters the battlefield, target artifact you control gains "{T}: This artifact deals 2 damage to any target" for as long as you control Quicksmith Rebel. - Ability artifactAbility = new SimpleActivatedAbility(Zone.BATTLEFIELD, new DamageTargetEffect(2), new TapSourceCost()); + Ability artifactAbility = new SimpleActivatedAbility(new DamageTargetEffect(2), new TapSourceCost()); artifactAbility.addTarget(new TargetAnyTarget()); - ConditionalContinuousEffect effect = new ConditionalContinuousEffect( - new GainAbilityTargetEffect(artifactAbility, Duration.Custom), - new SourceOnBattlefieldControlUnchangedCondition(), - "target artifact you control gains \"{T}: This artifact deals 2 damage to any target\" for as long as you control {this}"); - Ability ability = new EntersBattlefieldTriggeredAbility(effect, false); - ability.addTarget(new TargetPermanent(new FilterControlledArtifactPermanent())); - ability.addWatcher(new LostControlWatcher()); + Ability ability = new EntersBattlefieldTriggeredAbility(new GainAbilityTargetEffect(artifactAbility, Duration.WhileControlled).setText("target artifact you control gains \"{T}: This artifact deals 2 damage to any target\" for as long as you control {this}")); + ability.addTarget(new TargetPermanent(StaticFilters.FILTER_CONTROLLED_PERMANENT_ARTIFACT)); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/q/QuicksmithSpy.java b/Mage.Sets/src/mage/cards/q/QuicksmithSpy.java index 9a67593677..daa5a2ac99 100644 --- a/Mage.Sets/src/mage/cards/q/QuicksmithSpy.java +++ b/Mage.Sets/src/mage/cards/q/QuicksmithSpy.java @@ -1,27 +1,24 @@ package mage.cards.q; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.common.SimpleActivatedAbility; -import mage.abilities.condition.common.SourceOnBattlefieldControlUnchangedCondition; import mage.abilities.costs.common.TapSourceCost; -import mage.abilities.decorator.ConditionalContinuousEffect; -import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.DamageTargetEffect; import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Duration; import mage.constants.SubType; -import mage.constants.Zone; -import mage.filter.common.FilterControlledArtifactPermanent; +import mage.filter.StaticFilters; import mage.target.TargetPermanent; -import mage.watchers.common.LostControlWatcher; +import mage.target.common.TargetAnyTarget; + +import java.util.UUID; /** - * * @author Styxo */ public final class QuicksmithSpy extends CardImpl { @@ -35,14 +32,10 @@ public final class QuicksmithSpy extends CardImpl { this.toughness = new MageInt(3); // When Quicksmith Spy enters the battlefield, target artifact you control gains "{T}: Draw a card" for as long as you control Quicksmith Spy. - Ability artifactAbility = new SimpleActivatedAbility(Zone.BATTLEFIELD, new DrawCardSourceControllerEffect(1), new TapSourceCost()); - ConditionalContinuousEffect effect = new ConditionalContinuousEffect( - new GainAbilityTargetEffect(artifactAbility, Duration.Custom), - new SourceOnBattlefieldControlUnchangedCondition(), - "target artifact you control gains \"{T}: Draw a card\" for as long as you control {this}"); - Ability ability = new EntersBattlefieldTriggeredAbility(effect, false); - ability.addTarget(new TargetPermanent(new FilterControlledArtifactPermanent())); - ability.addWatcher(new LostControlWatcher()); + Ability artifactAbility = new SimpleActivatedAbility(new DamageTargetEffect(2), new TapSourceCost()); + artifactAbility.addTarget(new TargetAnyTarget()); + Ability ability = new EntersBattlefieldTriggeredAbility(new GainAbilityTargetEffect(artifactAbility, Duration.WhileControlled).setText("target artifact you control gains \"{T}: Draw a card\" for as long as you control {this}")); + ability.addTarget(new TargetPermanent(StaticFilters.FILTER_CONTROLLED_PERMANENT_ARTIFACT)); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/r/RoilElemental.java b/Mage.Sets/src/mage/cards/r/RoilElemental.java index 410af04027..b12fb6d454 100644 --- a/Mage.Sets/src/mage/cards/r/RoilElemental.java +++ b/Mage.Sets/src/mage/cards/r/RoilElemental.java @@ -1,21 +1,20 @@ package mage.cards.r; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.LandfallAbility; -import mage.abilities.condition.common.SourceOnBattlefieldControlUnchangedCondition; -import mage.abilities.decorator.ConditionalContinuousEffect; import mage.abilities.effects.common.continuous.GainControlTargetEffect; import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.*; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; import mage.target.common.TargetCreaturePermanent; -import mage.watchers.common.LostControlWatcher; + +import java.util.UUID; /** - * * @author jeffwadsworth */ public final class RoilElemental extends CardImpl { @@ -27,16 +26,12 @@ public final class RoilElemental extends CardImpl { this.power = new MageInt(3); this.toughness = new MageInt(2); - String rule = "you may gain control of target creature for as long as you control Roil Elemental"; - // Flying this.addAbility(FlyingAbility.getInstance()); // Landfall - Whenever a land enters the battlefield under your control, you may gain control of target creature for as long as you control Roil Elemental. - ConditionalContinuousEffect effect = new ConditionalContinuousEffect(new GainControlTargetEffect(Duration.Custom), new SourceOnBattlefieldControlUnchangedCondition(), rule); - Ability ability = new LandfallAbility(Zone.BATTLEFIELD, effect, true); + Ability ability = new LandfallAbility(new GainControlTargetEffect(Duration.WhileControlled), true); ability.addTarget(new TargetCreaturePermanent()); - ability.addWatcher(new LostControlWatcher()); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/r/RubiniaSoulsinger.java b/Mage.Sets/src/mage/cards/r/RubiniaSoulsinger.java index bd21fd70fc..c89f66f613 100644 --- a/Mage.Sets/src/mage/cards/r/RubiniaSoulsinger.java +++ b/Mage.Sets/src/mage/cards/r/RubiniaSoulsinger.java @@ -1,34 +1,30 @@ - package mage.cards.r; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.common.SkipUntapOptionalAbility; -import mage.abilities.condition.Condition; +import mage.abilities.condition.common.SourceTappedCondition; import mage.abilities.costs.common.TapSourceCost; import mage.abilities.decorator.ConditionalContinuousEffect; import mage.abilities.effects.common.continuous.GainControlTargetEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.SubType; import mage.constants.Duration; +import mage.constants.SubType; import mage.constants.SuperType; -import mage.constants.Zone; -import mage.game.Game; -import mage.game.permanent.Permanent; import mage.target.common.TargetCreaturePermanent; +import java.util.UUID; + /** - * * @author LevelX2 */ public final class RubiniaSoulsinger extends CardImpl { public RubiniaSoulsinger(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{2}{G}{W}{U}"); + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{G}{W}{U}"); addSuperType(SuperType.LEGENDARY); this.subtype.add(SubType.FAERIE); @@ -37,12 +33,12 @@ public final class RubiniaSoulsinger extends CardImpl { // You may choose not to untap Rubinia Soulsinger during your untap step. this.addAbility(new SkipUntapOptionalAbility()); + // {tap}: Gain control of target creature for as long as you control Rubinia and Rubinia remains tapped. - ConditionalContinuousEffect effect = new ConditionalContinuousEffect( - new GainControlTargetEffect(Duration.OneUse), - new RubiniaSoulsingerCondition(), - "Gain control of target creature for as long as you control Rubinia and Rubinia remains tapped"); - Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, effect, new TapSourceCost()); + Ability ability = new SimpleActivatedAbility(new ConditionalContinuousEffect( + new GainControlTargetEffect(Duration.WhileControlled), SourceTappedCondition.instance, + "gain control of target creature for as long as you control {this} and {this} remains tapped" + ), new TapSourceCost()); ability.addTarget(new TargetCreaturePermanent()); this.addAbility(ability); } @@ -56,22 +52,3 @@ public final class RubiniaSoulsinger extends CardImpl { return new RubiniaSoulsinger(this); } } - -class RubiniaSoulsingerCondition implements Condition { - - private UUID controllerId; - - @Override - public boolean apply(Game game, Ability source) { - if (controllerId == null) { - controllerId = source.getControllerId(); - } - Permanent permanent = game.getBattlefield().getPermanent(source.getSourceId()); - if (permanent != null) { - if (permanent.isTapped()) { - return controllerId.equals(source.getControllerId()); - } - } - return false; - } -} diff --git a/Mage.Sets/src/mage/cards/s/Seasinger.java b/Mage.Sets/src/mage/cards/s/Seasinger.java index 24f16be159..eb1062b241 100644 --- a/Mage.Sets/src/mage/cards/s/Seasinger.java +++ b/Mage.Sets/src/mage/cards/s/Seasinger.java @@ -5,9 +5,6 @@ import mage.abilities.Ability; import mage.abilities.common.ControlsPermanentsControllerTriggeredAbility; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.common.SkipUntapOptionalAbility; -import mage.abilities.condition.CompoundCondition; -import mage.abilities.condition.Condition; -import mage.abilities.condition.common.PermanentsOnTheBattlefieldCondition; import mage.abilities.condition.common.SourceTappedCondition; import mage.abilities.costs.common.TapSourceCost; import mage.abilities.decorator.ConditionalContinuousEffect; @@ -17,11 +14,13 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; import mage.filter.FilterPermanent; +import mage.filter.common.FilterControlledPermanent; import mage.filter.common.FilterCreaturePermanent; -import mage.filter.common.FilterLandPermanent; -import mage.filter.predicate.mageobject.CardIdPredicate; -import mage.filter.predicate.permanent.ControllerControlsIslandPredicate; -import mage.target.common.TargetCreaturePermanent; +import mage.filter.predicate.ObjectSourcePlayer; +import mage.filter.predicate.ObjectSourcePlayerPredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.target.TargetPermanent; import java.util.UUID; @@ -30,13 +29,15 @@ import java.util.UUID; */ public final class Seasinger extends CardImpl { - private static final String rule = "Gain control of target creature whose controller controls an Island for as long as you control Seasinger and Seasinger remains tapped"; - private static final FilterPermanent islandYouControl = new FilterPermanent("Island"); - private static final FilterCreaturePermanent creatureWhoseControllerControlsIsland = new FilterCreaturePermanent("creature whose controller controls an island"); + private static final FilterPermanent filter + = new FilterPermanent("Island"); + private static final FilterPermanent filter2 + = new FilterCreaturePermanent("creature whose controler controls an Island"); static { - islandYouControl.add(SubType.ISLAND.getPredicate()); - islandYouControl.add(TargetController.YOU.getControllerPredicate()); + filter.add(SubType.ISLAND.getPredicate()); + filter.add(TargetController.YOU.getControllerPredicate()); + filter2.add(SeasingerPredicate.instance); } public Seasinger(UUID ownerId, CardSetInfo setInfo) { @@ -46,26 +47,21 @@ public final class Seasinger extends CardImpl { this.power = new MageInt(0); this.toughness = new MageInt(1); - FilterPermanent seasinger = new FilterPermanent(); - seasinger.add(TargetController.YOU.getControllerPredicate()); - seasinger.add(new CardIdPredicate(this.getId())); - Condition condition = new CompoundCondition(new PermanentsOnTheBattlefieldCondition(seasinger, ComparisonType.EQUAL_TO, 1), SourceTappedCondition.instance); - // When you control no Islands, sacrifice Seasinger. this.addAbility(new ControlsPermanentsControllerTriggeredAbility( - new FilterLandPermanent(SubType.ISLAND, "no Islands"), ComparisonType.EQUAL_TO, 0, - new SacrificeSourceEffect())); + filter, ComparisonType.EQUAL_TO, 0, new SacrificeSourceEffect() + )); // You may choose not to untap Seasinger during your untap step. this.addAbility(new SkipUntapOptionalAbility()); // {tap}: Gain control of target creature whose controller controls an Island for as long as you control Seasinger and Seasinger remains tapped. - ConditionalContinuousEffect effect = new ConditionalContinuousEffect( - new GainControlTargetEffect(Duration.Custom), - condition, rule); - Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, effect, new TapSourceCost()); - creatureWhoseControllerControlsIsland.add(new ControllerControlsIslandPredicate()); - ability.addTarget(new TargetCreaturePermanent(creatureWhoseControllerControlsIsland)); + Ability ability = new SimpleActivatedAbility(new ConditionalContinuousEffect( + new GainControlTargetEffect(Duration.WhileControlled), SourceTappedCondition.instance, + "gain control of target creature whose controller controls " + + "an Island for as long as you control {this} and {this} remains tapped" + ), new TapSourceCost()); + ability.addTarget(new TargetPermanent(filter2)); this.addAbility(ability); } @@ -78,3 +74,13 @@ public final class Seasinger extends CardImpl { return new Seasinger(this); } } + +enum SeasingerPredicate implements ObjectSourcePlayerPredicate { + instance; + private static final FilterPermanent filter = new FilterControlledPermanent(SubType.ISLAND); + + @Override + public boolean apply(ObjectSourcePlayer input, Game game) { + return game.getBattlefield().contains(filter, input.getSourceId(), input.getObject().getControllerId(), game, 1); + } +} diff --git a/Mage.Sets/src/mage/cards/s/ShipbreakerKraken.java b/Mage.Sets/src/mage/cards/s/ShipbreakerKraken.java index 3156591d5c..cb022bbd3d 100644 --- a/Mage.Sets/src/mage/cards/s/ShipbreakerKraken.java +++ b/Mage.Sets/src/mage/cards/s/ShipbreakerKraken.java @@ -1,37 +1,28 @@ package mage.cards.s; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.BecomesMonstrousSourceTriggeredAbility; -import mage.abilities.effects.ContinuousRuleModifyingEffectImpl; +import mage.abilities.effects.common.DontUntapInControllersUntapStepTargetEffect; import mage.abilities.effects.common.TapTargetEffect; import mage.abilities.keyword.MonstrosityAbility; 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.PhaseStep; -import mage.constants.WatcherScope; -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.constants.SubType; import mage.target.common.TargetCreaturePermanent; -import mage.watchers.Watcher; + +import java.util.UUID; /** - * * @author LevelX2 */ public final class ShipbreakerKraken extends CardImpl { public ShipbreakerKraken(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{4}{U}{U}"); + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{U}{U}"); this.subtype.add(SubType.KRAKEN); this.power = new MageInt(6); @@ -39,11 +30,13 @@ public final class ShipbreakerKraken extends CardImpl { // {6}{U}{U}: Monstrosity 4. this.addAbility(new MonstrosityAbility("{6}{U}{U}", 4)); + // When Shipbreaker Kraken becomes monstrous, tap up to four target creatures. Those creatures don't untap during their controllers' untap steps for as long as you control Shipbreaker Kraken. Ability ability = new BecomesMonstrousSourceTriggeredAbility(new TapTargetEffect()); - ability.addTarget(new TargetCreaturePermanent(0,4)); - ability.addEffect(new ShipbreakerKrakenReplacementEffect()); - this.addAbility(ability, new ShipbreakerKrakenWatcher()); + ability.addTarget(new TargetCreaturePermanent(0, 4)); + ability.addEffect(new DontUntapInControllersUntapStepTargetEffect(Duration.WhileControlled) + .setText("Those creatures don't untap during their controllers' untap steps for as long as you control {this}")); + this.addAbility(ability); } private ShipbreakerKraken(final ShipbreakerKraken card) { @@ -55,95 +48,3 @@ public final class ShipbreakerKraken extends CardImpl { return new ShipbreakerKraken(this); } } - -class ShipbreakerKrakenReplacementEffect extends ContinuousRuleModifyingEffectImpl { - - public ShipbreakerKrakenReplacementEffect() { - super(Duration.Custom, Outcome.Detriment); - this.staticText = "Those creatures don't untap during their controllers' untap steps for as long as you control {this}"; - } - - public ShipbreakerKrakenReplacementEffect(final ShipbreakerKrakenReplacementEffect effect) { - super(effect); - } - - @Override - public ShipbreakerKrakenReplacementEffect copy() { - return new ShipbreakerKrakenReplacementEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - return false; - } - - @Override - public boolean checksEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.LOST_CONTROL || - event.getType() == GameEvent.EventType.ZONE_CHANGE || - event.getType() == GameEvent.EventType.UNTAP; - } - - @Override - public boolean applies(GameEvent event, Ability source, Game game) { - // Source must be on the battlefield (it's neccessary to check here because if as response to the enter - // the battlefield triggered ability the source dies (or will be exiled), then the ZONE_CHANGE or LOST_CONTROL - // event will happen before this effect is applied ever) - Permanent sourcePermanent = game.getPermanent(source.getSourceId()); - if (sourcePermanent == null || !sourcePermanent.isControlledBy(source.getControllerId())) { - discard(); - return false; - } - if (event.getType() == GameEvent.EventType.LOST_CONTROL) { - if (event.getTargetId().equals(source.getSourceId())) { - discard(); - return false; - } - } - if (event.getType() == GameEvent.EventType.ZONE_CHANGE && event.getTargetId().equals(source.getSourceId())) { - ZoneChangeEvent zEvent = (ZoneChangeEvent) event; - if (zEvent.getFromZone() == Zone.BATTLEFIELD) { - discard(); - return false; - } - } - - if (game.getTurn().getStepType() == PhaseStep.UNTAP && event.getType() == GameEvent.EventType.UNTAP) { - if (targetPointer.getTargets(game, source).contains(event.getTargetId())) { - return true; - } - } - - return false; - } -} - -class ShipbreakerKrakenWatcher extends Watcher { - - ShipbreakerKrakenWatcher () { - super(WatcherScope.CARD); - } - - @Override - public void watch(GameEvent event, Game game) { - if (event.getType() == GameEvent.EventType.LOST_CONTROL - && event.getPlayerId().equals(controllerId) - && event.getTargetId().equals(sourceId)) { - condition = true; - game.replaceEvent(event); - return; - } - if (event.getType() == GameEvent.EventType.ZONE_CHANGE && event.getTargetId().equals(sourceId)) { - ZoneChangeEvent zEvent = (ZoneChangeEvent)event; - if (zEvent.getFromZone() == Zone.BATTLEFIELD) { - condition = true; - game.replaceEvent(event); - } - } - } - - @Override - public void reset() { - //don't reset condition each turn - only when this leaves the battlefield - } -} diff --git a/Mage.Sets/src/mage/cards/t/TheWretched.java b/Mage.Sets/src/mage/cards/t/TheWretched.java index 4c81c0f702..77190199ea 100644 --- a/Mage.Sets/src/mage/cards/t/TheWretched.java +++ b/Mage.Sets/src/mage/cards/t/TheWretched.java @@ -1,12 +1,8 @@ package mage.cards.t; -import java.util.Objects; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.EndOfCombatTriggeredAbility; -import mage.abilities.condition.common.SourceOnBattlefieldControlUnchangedCondition; -import mage.abilities.decorator.ConditionalContinuousEffect; import mage.abilities.effects.ContinuousEffect; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.continuous.GainControlTargetEffect; @@ -21,37 +17,37 @@ import mage.game.combat.CombatGroup; import mage.game.permanent.Permanent; import mage.target.targetpointer.FixedTarget; import mage.watchers.common.BlockedAttackerWatcher; -import mage.watchers.common.LostControlWatcher; + +import java.util.UUID; /** - * * @author jeffwadsworth - * + *

* 5/1/2009 The ability grants you control of all creatures that are blocking it * as the ability resolves. This will include any creatures that were put onto * the battlefield blocking it. - * + *

* 5/1/2009 Any blocking creatures that regenerated during combat will have been * removed from combat. Since such creatures are no longer in combat, they * cannot be blocking The Wretched, which means you won't be able to gain * control of them. - * + *

* 5/1/2009 If The Wretched itself regenerated during combat, then it will have * been removed from combat. Since it is no longer in combat, there cannot be * any creatures blocking it, which means you won't be able to gain control of * any creatures. - * + *

* 10/1/2009 The Wretched's ability triggers only if it's still on the * battlefield when the end of combat step begins (after the combat damage * step). For example, if it's blocked by a 7/7 creature and is destroyed, its * ability won't trigger at all. - * + *

* 10/1/2009 If The Wretched leaves the battlefield, you no longer control it, * so the duration of its control-change effect ends. - * + *

* 10/1/2009 If you lose control of The Wretched before its ability resolves, * you won't gain control of the creatures blocking it at all. - * + *

* 10/1/2009 Once the ability resolves, it doesn't care whether the permanents * you gained control of remain creatures, only that they remain on the * battlefield. @@ -65,9 +61,7 @@ public final class TheWretched extends CardImpl { this.toughness = new MageInt(5); // At end of combat, gain control of all creatures blocking The Wretched for as long as you control The Wretched. - Ability ability = new EndOfCombatTriggeredAbility(new TheWretchedEffect(), false); - this.addAbility(ability, new BlockedAttackerWatcher()); - ability.addWatcher(new LostControlWatcher()); + this.addAbility(new EndOfCombatTriggeredAbility(new TheWretchedEffect(), false), new BlockedAttackerWatcher()); } private TheWretched(final TheWretched card) { @@ -93,33 +87,27 @@ class TheWretchedEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Permanent theWretched = (Permanent) source.getSourceObjectIfItStillExists(game); - if (theWretched == null) { - return false; - } - if (theWretched.isRemovedFromCombat() || !theWretched.isAttacking()) { + Permanent theWretched = source.getSourcePermanentIfItStillExists(game); + if (theWretched == null + || theWretched.isRemovedFromCombat() + || !theWretched.isAttacking() + || !source.isControlledBy(theWretched.getControllerId())) { return false; } // Check if control of source has changed since ability triggered????? (does it work is it neccessary???) - Permanent permanent = game.getBattlefield().getPermanent(source.getSourceId()); - if (permanent != null && !Objects.equals(permanent.getControllerId(), source.getControllerId())) { - return false; - } - for (CombatGroup combatGroup : game.getCombat().getGroups()) { - if (combatGroup.getAttackers().contains(source.getSourceId())) { - for (UUID creatureId : combatGroup.getBlockers()) { - Permanent blocker = game.getPermanent(creatureId); - if (blocker != null - && blocker.getBlocking() > 0) { - ContinuousEffect effect = new ConditionalContinuousEffect( - new GainControlTargetEffect(Duration.Custom, source.getControllerId()), - new SourceOnBattlefieldControlUnchangedCondition(), ""); - effect.setTargetPointer(new FixedTarget(blocker.getId(), game)); - game.addEffect(effect, source); - - } + if (!combatGroup.getAttackers().contains(source.getSourceId())) { + continue; + } + for (UUID creatureId : combatGroup.getBlockers()) { + Permanent blocker = game.getPermanent(creatureId); + if (blocker == null + || blocker.getBlocking() <= 0) { + continue; } + ContinuousEffect effect = new GainControlTargetEffect(Duration.WhileControlled, source.getControllerId()); + effect.setTargetPointer(new FixedTarget(blocker.getId(), game)); + game.addEffect(effect, source); } } return true; diff --git a/Mage.Sets/src/mage/cards/t/ThrullChampion.java b/Mage.Sets/src/mage/cards/t/ThrullChampion.java index 86771434f8..f715edf991 100644 --- a/Mage.Sets/src/mage/cards/t/ThrullChampion.java +++ b/Mage.Sets/src/mage/cards/t/ThrullChampion.java @@ -1,13 +1,10 @@ package mage.cards.t; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.condition.common.SourceOnBattlefieldControlUnchangedCondition; import mage.abilities.costs.common.TapSourceCost; -import mage.abilities.decorator.ConditionalContinuousEffect; import mage.abilities.effects.common.continuous.BoostAllEffect; import mage.abilities.effects.common.continuous.GainControlTargetEffect; import mage.cards.CardImpl; @@ -18,19 +15,15 @@ import mage.constants.SubType; import mage.constants.Zone; import mage.filter.common.FilterCreaturePermanent; import mage.target.TargetPermanent; -import mage.watchers.common.LostControlWatcher; + +import java.util.UUID; /** - * * @author MarcoMarin */ public final class ThrullChampion extends CardImpl { - private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("Thrull creatures"); - - static { - filter.add(SubType.THRULL.getPredicate()); - } + private static final FilterCreaturePermanent filter = new FilterCreaturePermanent(SubType.THRULL, "Thrull creatures"); public ThrullChampion(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{B}"); @@ -40,14 +33,11 @@ public final class ThrullChampion extends CardImpl { // Thrull creatures get +1/+1. this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new BoostAllEffect(1, 1, Duration.WhileOnBattlefield, filter, false))); + // {tap}: Gain control of target Thrull for as long as you control Thrull Champion. - ConditionalContinuousEffect ThrullChampionGainControlEffect = new ConditionalContinuousEffect( - new GainControlTargetEffect(Duration.Custom), - new SourceOnBattlefieldControlUnchangedCondition(), - "Gain control of target Thrull for as long as you control {this}"); - Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, ThrullChampionGainControlEffect, new TapSourceCost()); + Ability ability = new SimpleActivatedAbility(new GainControlTargetEffect(Duration.WhileControlled) + .setText("gain control of target Thrull for as long as you control {this}"), new TapSourceCost()); ability.addTarget(new TargetPermanent(filter)); - ability.addWatcher(new LostControlWatcher()); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/t/TidebinderMage.java b/Mage.Sets/src/mage/cards/t/TidebinderMage.java index 39aa56875f..ebf8ae0c75 100644 --- a/Mage.Sets/src/mage/cards/t/TidebinderMage.java +++ b/Mage.Sets/src/mage/cards/t/TidebinderMage.java @@ -1,48 +1,43 @@ package mage.cards.t; -import java.util.UUID; import mage.MageInt; import mage.ObjectColor; import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldTriggeredAbility; -import mage.abilities.effects.ContinuousRuleModifyingEffectImpl; +import mage.abilities.effects.common.DontUntapInControllersUntapStepTargetEffect; 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.PhaseStep; +import mage.constants.SubType; import mage.constants.TargetController; -import mage.constants.WatcherScope; -import mage.constants.Zone; import mage.filter.common.FilterCreaturePermanent; import mage.filter.predicate.Predicates; import mage.filter.predicate.mageobject.ColorPredicate; -import mage.game.Game; -import mage.game.events.GameEvent; -import mage.game.events.ZoneChangeEvent; -import mage.game.permanent.Permanent; -import mage.target.Target; import mage.target.common.TargetCreaturePermanent; -import mage.watchers.Watcher; + +import java.util.UUID; /** - * * @author LevelX2 */ public final class TidebinderMage extends CardImpl { - - private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("red or green creature an opponent controls"); + + private static final FilterCreaturePermanent filter + = new FilterCreaturePermanent("red or green creature an opponent controls"); + static { filter.add(TargetController.OPPONENT.getControllerPredicate()); - filter.add(Predicates.or(new ColorPredicate(ObjectColor.RED), new ColorPredicate(ObjectColor.GREEN))); + filter.add(Predicates.or( + new ColorPredicate(ObjectColor.RED), + new ColorPredicate(ObjectColor.GREEN) + )); } public TidebinderMage(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{U}{U}"); + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{U}{U}"); this.subtype.add(SubType.MERFOLK); this.subtype.add(SubType.WIZARD); @@ -52,11 +47,9 @@ public final class TidebinderMage extends CardImpl { // When Tidebinder Mage enters the battlefield, tap target red or green creature an opponent controls. // That creature doesn't untap during its controller's untap step for as long as you control Tidebinder Mage. Ability ability = new EntersBattlefieldTriggeredAbility(new TapTargetEffect(), false); - ability.addEffect(new TidebinderMageEffect()); - Target target = new TargetCreaturePermanent(filter); - ability.addTarget(target); - this.addAbility(ability, new TidebinderMageWatcher()); - + ability.addEffect(new DontUntapInControllersUntapStepTargetEffect(Duration.WhileControlled)); + ability.addTarget(new TargetCreaturePermanent(filter)); + this.addAbility(ability); } private TidebinderMage(final TidebinderMage card) { @@ -68,90 +61,3 @@ public final class TidebinderMage extends CardImpl { return new TidebinderMage(this); } } - -class TidebinderMageEffect extends ContinuousRuleModifyingEffectImpl { - - public TidebinderMageEffect() { - super(Duration.OneUse, Outcome.Detriment, false, false); - this.staticText = "That creature doesn't untap during its controller's untap step for as long as you control {this}"; - } - - public TidebinderMageEffect(final TidebinderMageEffect effect) { - super(effect); - } - - @Override - public TidebinderMageEffect copy() { - return new TidebinderMageEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - return false; - } - - @Override - public boolean applies(GameEvent event, Ability source, Game game) { - // Source must be on the battlefield (it's neccessary to check here because if as response to the enter - // the battlefield triggered ability the source dies (or will be exiled), then the ZONE_CHANGE or LOST_CONTROL - // event will happen before this effect is applied ever) - Permanent sourcePermanent = game.getPermanent(source.getSourceId()); - if (sourcePermanent == null || !sourcePermanent.isControlledBy(source.getControllerId())) { - discard(); - return false; - } - if (event.getType() == GameEvent.EventType.LOST_CONTROL) { - if (event.getTargetId().equals(source.getSourceId())) { - discard(); - return false; - } - } - if (event.getType() == GameEvent.EventType.ZONE_CHANGE && event.getTargetId().equals(source.getSourceId())) { - ZoneChangeEvent zEvent = (ZoneChangeEvent)event; - if (zEvent.getFromZone() == Zone.BATTLEFIELD) { - discard(); - return false; - } - } - - if (game.getTurn().getStepType() == PhaseStep.UNTAP && event.getType() == GameEvent.EventType.UNTAP) { - if (event.getTargetId().equals(targetPointer.getFirst(game, source))) { - Permanent permanent = game.getPermanent(event.getTargetId()); - if (permanent != null && game.isActivePlayer(permanent.getControllerId())) { - return true; - } - } - } - return false; - } -} - -class TidebinderMageWatcher extends Watcher { - - TidebinderMageWatcher () { - super(WatcherScope.CARD); - } - - @Override - public void watch(GameEvent event, Game game) { - if (event.getType() == GameEvent.EventType.LOST_CONTROL - && event.getPlayerId().equals(controllerId) - && event.getTargetId().equals(sourceId)) { - condition = true; - game.replaceEvent(event); - return; - } - if (event.getType() == GameEvent.EventType.ZONE_CHANGE && event.getTargetId().equals(sourceId)) { - ZoneChangeEvent zEvent = (ZoneChangeEvent)event; - if (zEvent.getFromZone() == Zone.BATTLEFIELD) { - condition = true; - game.replaceEvent(event); - } - } - } - - @Override - public void reset() { - //don't reset condition each turn - only when this leaves the battlefield - } -} diff --git a/Mage.Sets/src/mage/cards/t/TimeOfIce.java b/Mage.Sets/src/mage/cards/t/TimeOfIce.java index 2cbbd58b6f..dd2443987b 100644 --- a/Mage.Sets/src/mage/cards/t/TimeOfIce.java +++ b/Mage.Sets/src/mage/cards/t/TimeOfIce.java @@ -1,35 +1,25 @@ package mage.cards.t; -import java.util.UUID; -import mage.abilities.Ability; import mage.abilities.common.SagaAbility; -import mage.abilities.effects.ContinuousRuleModifyingEffectImpl; import mage.abilities.effects.Effects; +import mage.abilities.effects.common.DontUntapInControllersUntapStepTargetEffect; import mage.abilities.effects.common.ReturnToHandFromBattlefieldAllEffect; import mage.abilities.effects.common.TapTargetEffect; -import mage.constants.SubType; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Duration; -import mage.constants.Outcome; -import mage.constants.PhaseStep; import mage.constants.SagaChapter; -import mage.constants.WatcherScope; -import mage.constants.Zone; +import mage.constants.SubType; 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.events.ZoneChangeEvent; -import mage.game.permanent.Permanent; import mage.target.common.TargetCreaturePermanent; -import mage.watchers.Watcher; + +import java.util.UUID; /** - * * @author TheElk801 */ public final class TimeOfIce extends CardImpl { @@ -51,7 +41,7 @@ public final class TimeOfIce extends CardImpl { // I, II — Tap target creature an opponent controls. It doesn't untap during its controller's untap step for as long as you control Time of Ice. Effects effects = new Effects(); effects.add(new TapTargetEffect()); - effects.add(new TimeOfIceEffect()); + effects.add(new DontUntapInControllersUntapStepTargetEffect(Duration.WhileControlled, "It")); sagaAbility.addChapterEffect( this, SagaChapter.CHAPTER_I, SagaChapter.CHAPTER_II, effects, new TargetCreaturePermanent(StaticFilters.FILTER_OPPONENTS_PERMANENT_CREATURE) @@ -59,7 +49,7 @@ public final class TimeOfIce extends CardImpl { // III — Return all tapped creatures to their owners' hands. sagaAbility.addChapterEffect(this, SagaChapter.CHAPTER_III, new ReturnToHandFromBattlefieldAllEffect(filter)); - this.addAbility(sagaAbility, new TimeOfIceWatcher()); + this.addAbility(sagaAbility); } private TimeOfIce(final TimeOfIce card) { @@ -71,101 +61,3 @@ public final class TimeOfIce extends CardImpl { return new TimeOfIce(this); } } - -class TimeOfIceEffect extends ContinuousRuleModifyingEffectImpl { - - public TimeOfIceEffect() { - super(Duration.Custom, Outcome.Detriment, false, false); - this.staticText = "That creature doesn't untap during its controller's untap step for as long as you control {this}"; - } - - public TimeOfIceEffect(final TimeOfIceEffect effect) { - super(effect); - } - - @Override - public TimeOfIceEffect copy() { - return new TimeOfIceEffect(this); - } - - @Override - public boolean checksEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.UNTAP - || event.getType() == GameEvent.EventType.ZONE_CHANGE - || event.getType() == GameEvent.EventType.LOST_CONTROL; - } - - @Override - public boolean applies(GameEvent event, Ability source, Game game) { - // Source must be on the battlefield (it's neccessary to check here because if as response to the enter - // the battlefield triggered ability the source dies (or will be exiled), then the ZONE_CHANGE or LOST_CONTROL - // event will happen before this effect is applied ever) - Permanent sourceObject = game.getPermanent(source.getSourceId()); - if (sourceObject == null || sourceObject.getZoneChangeCounter(game) > source.getSourceObjectZoneChangeCounter() + 1) { - discard(); - return false; - } - switch (event.getType()) { - case ZONE_CHANGE: - // end effect if source does a zone move - if (event.getTargetId().equals(source.getSourceId())) { - ZoneChangeEvent zEvent = (ZoneChangeEvent) event; - if (zEvent.getFromZone() == Zone.BATTLEFIELD) { - discard(); - return false; - } - } - break; - case UNTAP: - // prevent to untap the target creature - if (game.getTurn().getStepType() == PhaseStep.UNTAP && event.getTargetId().equals(targetPointer.getFirst(game, source))) { - Permanent targetCreature = game.getPermanent(targetPointer.getFirst(game, source)); - if (targetCreature != null) { - return targetCreature.isControlledBy(game.getActivePlayerId()); - } else { - discard(); - return false; - } - } - break; - case LOST_CONTROL: - // end effect if source control is changed - if (event.getTargetId().equals(source.getSourceId())) { - discard(); - return false; - } - break; - } - return false; - } -} - -class TimeOfIceWatcher extends Watcher { - - TimeOfIceWatcher() { - super(WatcherScope.CARD); - } - - @Override - public void watch(GameEvent event, Game game) { - if (event.getType() == GameEvent.EventType.LOST_CONTROL - && event.getPlayerId().equals(controllerId) - && event.getTargetId().equals(sourceId)) { - condition = true; - game.replaceEvent(event); - return; - } - if (event.getType() == GameEvent.EventType.ZONE_CHANGE && event.getTargetId().equals(sourceId)) { - ZoneChangeEvent zEvent = (ZoneChangeEvent) event; - if (zEvent.getFromZone() == Zone.BATTLEFIELD) { - condition = true; - game.replaceEvent(event); - } - } - } - - @Override - public void reset() { - //don't reset condition each turn - only when this leaves the battlefield - } -} diff --git a/Mage.Sets/src/mage/cards/w/WallOfStolenIdentity.java b/Mage.Sets/src/mage/cards/w/WallOfStolenIdentity.java index 9cdc465c52..fb50a9c4fc 100644 --- a/Mage.Sets/src/mage/cards/w/WallOfStolenIdentity.java +++ b/Mage.Sets/src/mage/cards/w/WallOfStolenIdentity.java @@ -4,14 +4,11 @@ import mage.MageInt; import mage.MageObject; import mage.abilities.Ability; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.condition.Condition; -import mage.abilities.decorator.ConditionalContinuousEffect; -import mage.abilities.effects.ContinuousEffect; -import mage.abilities.effects.ContinuousRuleModifyingEffect; +import mage.abilities.common.delayed.ReflexiveTriggeredAbility; import mage.abilities.effects.EntersBattlefieldEffect; import mage.abilities.effects.OneShotEffect; -import mage.abilities.effects.common.DontUntapInControllersUntapStepSourceEffect; -import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; +import mage.abilities.effects.common.DontUntapInControllersUntapStepTargetEffect; +import mage.abilities.effects.common.TapTargetEffect; import mage.abilities.keyword.DefenderAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -35,8 +32,8 @@ import java.util.UUID; */ public final class WallOfStolenIdentity extends CardImpl { - final static private String rule = "You may have Wall of Stolen Identity enter the battlefield as a copy of any " - + "creature on the battlefield, except it's a wall in addition to its other types and it has defender. " + final static private String rule = "You may have {this} enter the battlefield as a copy of any " + + "creature on the battlefield, except it's a Wall in addition to its other types and it has defender. " + "When you do, tap the copied creature and it doesn't untap during its " + "controller's untap step for as long as you control {this}"; @@ -49,9 +46,9 @@ public final class WallOfStolenIdentity extends CardImpl { this.toughness = new MageInt(0); // You may have Wall of Stolen Identity enter the battlefield as a copy of any creature on the battlefield, except it's a wall in addition to its other types and it has defender. When you do, tap the copied creature and it doesn't untap during its controller's untap step for as long as you control Wall of Stolen Identity. - Ability ability = new SimpleStaticAbility( - new EntersBattlefieldEffect(new WallOfStolenIdentityCopyEffect(), rule, true) - ); + Ability ability = new SimpleStaticAbility(new EntersBattlefieldEffect( + new WallOfStolenIdentityCopyEffect(), rule, true + )); this.addAbility(ability); } @@ -67,8 +64,7 @@ public final class WallOfStolenIdentity extends CardImpl { class WallOfStolenIdentityCopyEffect extends OneShotEffect { - private static final String rule2 = "When you do, tap the copied creature and it doesn't untap during its " - + "controller's untap step for as long as you control {this}."; + private static final String rule2 = "When you do, ."; public WallOfStolenIdentityCopyEffect() { super(Outcome.Copy); @@ -115,18 +111,13 @@ class WallOfStolenIdentityCopyEffect extends OneShotEffect { } }); - copyFromPermanent.tap(source, game); - // Incredibly, you can't just add a fixed target to a continuousrulemodifyingeffect, thus the workaround. - ContinuousRuleModifyingEffect effect = new DontUntapInControllersUntapStepSourceEffect(); - Ability ability = new SimpleStaticAbility(effect); - ContinuousEffect effect2 = new GainAbilityTargetEffect(ability, Duration.Custom); - ConditionalContinuousEffect conditionalEffect = new ConditionalContinuousEffect( - effect2, new WallOfStolenIdentityCondition( - source, - source.getControllerId(), - sourcePermanent.getZoneChangeCounter(game)), ""); - conditionalEffect.setTargetPointer(new FixedTarget(target.getFirstTarget())); - game.addEffect(conditionalEffect, source); + ReflexiveTriggeredAbility ability = new ReflexiveTriggeredAbility( + new TapTargetEffect(), false, "tap the copied creature " + + "and it doesn't untap during its controller's untap step for as long as you control {this}" + ); + ability.addEffect(new DontUntapInControllersUntapStepTargetEffect(Duration.WhileControlled)); + ability.getEffects().setTargetPointer(new FixedTarget(copyFromPermanent, game)); + game.fireReflexiveTriggeredAbility(ability, source); return true; } @@ -135,27 +126,3 @@ class WallOfStolenIdentityCopyEffect extends OneShotEffect { return new WallOfStolenIdentityCopyEffect(this); } } - -class WallOfStolenIdentityCondition implements Condition { - - // Checks for when it leaves play or changes control - private final Ability ability; - private final UUID controllerId; - private final int zcc; - - public WallOfStolenIdentityCondition(Ability ability, UUID controllerId, int zcc) { - this.ability = ability; - this.controllerId = controllerId; - this.zcc = zcc; - } - - @Override - public boolean apply(Game game, Ability source) { - Permanent permanentSource = game.getPermanent(ability.getSourceId()); - if (permanentSource != null) { - return permanentSource.getZoneChangeCounter(game) == zcc + 1 - && permanentSource.getControllerId() == controllerId; - } - return false; - } -} diff --git a/Mage.Sets/src/mage/cards/w/Willbreaker.java b/Mage.Sets/src/mage/cards/w/Willbreaker.java index f75f3face0..2c4a38a28a 100644 --- a/Mage.Sets/src/mage/cards/w/Willbreaker.java +++ b/Mage.Sets/src/mage/cards/w/Willbreaker.java @@ -1,11 +1,7 @@ package mage.cards.w; -import java.util.UUID; import mage.MageInt; import mage.abilities.TriggeredAbilityImpl; -import mage.abilities.condition.common.SourceOnBattlefieldControlUnchangedCondition; -import mage.abilities.decorator.ConditionalContinuousEffect; -import mage.abilities.effects.Effect; import mage.abilities.effects.common.continuous.GainControlTargetEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -16,12 +12,11 @@ import mage.constants.Zone; import mage.game.Game; import mage.game.events.GameEvent; import mage.game.permanent.Permanent; -import mage.players.Player; import mage.target.targetpointer.FixedTarget; -import mage.watchers.common.LostControlWatcher; + +import java.util.UUID; /** - * * @author LevelX2 */ public final class Willbreaker extends CardImpl { @@ -34,11 +29,7 @@ public final class Willbreaker extends CardImpl { this.toughness = new MageInt(3); // Whenever a creature an opponent controls becomes the target of a spell or ability you control, gain control of that creature for as long as you control Willbreaker. - ConditionalContinuousEffect effect = new ConditionalContinuousEffect( - new GainControlTargetEffect(Duration.EndOfGame), - new SourceOnBattlefieldControlUnchangedCondition(), null); - effect.setText("gain control of that creature for as long as you control {this}"); - this.addAbility(new WillbreakerTriggeredAbility(effect), new LostControlWatcher()); + this.addAbility(new WillbreakerTriggeredAbility()); } private Willbreaker(final Willbreaker card) { @@ -53,11 +44,11 @@ public final class Willbreaker extends CardImpl { class WillbreakerTriggeredAbility extends TriggeredAbilityImpl { - public WillbreakerTriggeredAbility(Effect effect) { - super(Zone.BATTLEFIELD, effect); + WillbreakerTriggeredAbility() { + super(Zone.BATTLEFIELD, new GainControlTargetEffect(Duration.WhileControlled)); } - public WillbreakerTriggeredAbility(WillbreakerTriggeredAbility ability) { + private WillbreakerTriggeredAbility(final WillbreakerTriggeredAbility ability) { super(ability); } @@ -68,25 +59,23 @@ class WillbreakerTriggeredAbility extends TriggeredAbilityImpl { @Override public boolean checkTrigger(GameEvent event, Game game) { - if (isControlledBy(event.getPlayerId())) { - Permanent permanent = game.getPermanent(event.getTargetId()); - if (permanent != null - && permanent.isCreature(game)) { - Player controller = game.getPlayer(getControllerId()); - if (controller != null - && controller.hasOpponent(permanent.getControllerId(), game)) { - // always call this method for FixedTargets in case it is blinked - getEffects().setTargetPointer(new FixedTarget(event.getTargetId(), game)); - return true; - } - } + if (!isControlledBy(event.getPlayerId())) { + return false; } - return false; + Permanent permanent = game.getPermanent(event.getTargetId()); + if (permanent == null || !permanent.isCreature(game) + || !game.getOpponents(getControllerId()).contains(permanent.getControllerId())) { + return false; + } + // always call this method for FixedTargets in case it is blinked + this.getEffects().setTargetPointer(new FixedTarget(event.getTargetId(), game)); + return true; } @Override - public String getTriggerPhrase() { - return "Whenever a creature an opponent controls becomes the target of a spell or ability you control, "; + public String getRule() { + return "Whenever a creature an opponent controls becomes the target of a spell or ability you control, " + + "gain control of that creature for as long as you control {this}"; } @Override diff --git a/Mage.Sets/src/mage/cards/w/WillowSatyr.java b/Mage.Sets/src/mage/cards/w/WillowSatyr.java index 6a62d4d6e3..a1621045aa 100644 --- a/Mage.Sets/src/mage/cards/w/WillowSatyr.java +++ b/Mage.Sets/src/mage/cards/w/WillowSatyr.java @@ -1,12 +1,9 @@ package mage.cards.w; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.common.SkipUntapOptionalAbility; -import mage.abilities.condition.CompoundCondition; -import mage.abilities.condition.common.SourceOnBattlefieldControlUnchangedCondition; import mage.abilities.condition.common.SourceTappedCondition; import mage.abilities.costs.common.TapSourceCost; import mage.abilities.decorator.ConditionalContinuousEffect; @@ -17,13 +14,12 @@ import mage.constants.CardType; import mage.constants.Duration; import mage.constants.SubType; import mage.constants.SuperType; -import mage.constants.Zone; import mage.filter.common.FilterCreaturePermanent; import mage.target.common.TargetCreaturePermanent; -import mage.watchers.common.LostControlWatcher; + +import java.util.UUID; /** - * * @author fireshoes */ public final class WillowSatyr extends CardImpl { @@ -42,13 +38,13 @@ public final class WillowSatyr extends CardImpl { // You may choose not to untap Willow Satyr during your untap step. this.addAbility(new SkipUntapOptionalAbility()); + // {tap}: Gain control of target legendary creature for as long as you control Willow Satyr and Willow Satyr remains tapped. - ConditionalContinuousEffect effect = new ConditionalContinuousEffect( - new GainControlTargetEffect(Duration.Custom), new CompoundCondition(SourceTappedCondition.instance, new SourceOnBattlefieldControlUnchangedCondition()), - "Gain control of target legendary creature for as long as you control {this} and {this} remains tapped"); - Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, effect, new TapSourceCost()); + Ability ability = new SimpleActivatedAbility(new ConditionalContinuousEffect( + new GainControlTargetEffect(Duration.WhileControlled), SourceTappedCondition.instance, + "Gain control of target legendary creature for as long as you control {this} and {this} remains tapped" + ), new TapSourceCost()); ability.addTarget(new TargetCreaturePermanent(filter)); - ability.addWatcher(new LostControlWatcher()); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/sets/KamigawaNeonDynasty.java b/Mage.Sets/src/mage/sets/KamigawaNeonDynasty.java index 8e4636a81f..0921f2ccc9 100644 --- a/Mage.Sets/src/mage/sets/KamigawaNeonDynasty.java +++ b/Mage.Sets/src/mage/sets/KamigawaNeonDynasty.java @@ -57,6 +57,7 @@ public final class KamigawaNeonDynasty extends ExpansionSet { cards.add(new SetCardInfo("Kaito Shizuki", 226, Rarity.MYTHIC, mage.cards.k.KaitoShizuki.class)); cards.add(new SetCardInfo("Kappa Tech-Wrecker", 198, Rarity.UNCOMMON, mage.cards.k.KappaTechWrecker.class)); cards.add(new SetCardInfo("Kodama of the West Tree", 199, Rarity.MYTHIC, mage.cards.k.KodamaOfTheWestTree.class)); + cards.add(new SetCardInfo("Kyodai, Soul of Kamigawa", 23, Rarity.RARE, mage.cards.k.KyodaiSoulOfKamigawa.class)); cards.add(new SetCardInfo("Leech Gauntlet", 106, Rarity.UNCOMMON, mage.cards.l.LeechGauntlet.class)); cards.add(new SetCardInfo("Lion Sash", 26, Rarity.RARE, mage.cards.l.LionSash.class)); cards.add(new SetCardInfo("Lizard Blades", 153, Rarity.RARE, mage.cards.l.LizardBlades.class)); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/m12/AegisAngelTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/m12/AegisAngelTest.java new file mode 100644 index 0000000000..b330ad64a1 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/m12/AegisAngelTest.java @@ -0,0 +1,118 @@ +package org.mage.test.cards.single.m12; + +import mage.abilities.keyword.IndestructibleAbility; +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author TheElk801 + */ +public class AegisAngelTest extends CardTestPlayerBase { + private static final String angel = "Aegis Angel"; + private static final String lion = "Silvercoat Lion"; + private static final String murder = "Murder"; + private static final String act = "Act of Treason"; + + @Test + public void testGainsAbility() { + addCard(Zone.BATTLEFIELD, playerA, "Plains", 6); + addCard(Zone.BATTLEFIELD, playerA, lion); + addCard(Zone.HAND, playerA, angel); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, angel); + addTarget(playerA, lion); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + assertAbility(playerA, lion, IndestructibleAbility.getInstance(), true); + } + + @Test + public void testKeepsAbility() { + addCard(Zone.BATTLEFIELD, playerA, "Plains", 6); + addCard(Zone.BATTLEFIELD, playerA, lion); + addCard(Zone.HAND, playerA, angel); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, angel); + addTarget(playerA, lion); + + setStrictChooseMode(true); + setStopAt(3, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + assertAbility(playerA, lion, IndestructibleAbility.getInstance(), true); + } + + @Test + public void testAngelDiesBeforeEntering() { + addCard(Zone.BATTLEFIELD, playerA, "Scrubland", 9); + addCard(Zone.BATTLEFIELD, playerA, lion); + addCard(Zone.HAND, playerA, angel); + addCard(Zone.HAND, playerA, murder); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, angel); + addTarget(playerA, lion); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, murder, angel); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + assertGraveyardCount(playerA, angel, 1); + assertGraveyardCount(playerA, murder, 1); + assertAbility(playerA, lion, IndestructibleAbility.getInstance(), false); + } + + @Test + public void testAngelDiesAfterEntering() { + addCard(Zone.BATTLEFIELD, playerA, "Scrubland", 9); + addCard(Zone.BATTLEFIELD, playerA, lion); + addCard(Zone.HAND, playerA, angel); + addCard(Zone.HAND, playerA, murder); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, angel); + addTarget(playerA, lion); + + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, murder, angel); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + assertGraveyardCount(playerA, angel, 1); + assertGraveyardCount(playerA, murder, 1); + assertAbility(playerA, lion, IndestructibleAbility.getInstance(), false); + } + + @Test + public void testAngelLoseControl() { + addCard(Zone.BATTLEFIELD, playerA, "Plains", 6); + addCard(Zone.BATTLEFIELD, playerB, "Mountain", 6); + addCard(Zone.BATTLEFIELD, playerA, lion); + addCard(Zone.HAND, playerA, angel); + addCard(Zone.HAND, playerB, act); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, angel); + addTarget(playerA, lion); + + castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, act, angel); + + setStrictChooseMode(true); + setStopAt(2, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + assertGraveyardCount(playerB, act, 1); + assertPermanentCount(playerB, angel, 1); + assertAbility(playerA, lion, IndestructibleAbility.getInstance(), false); + } +} diff --git a/Mage/src/main/java/mage/abilities/condition/common/SourceOnBattlefieldControlUnchangedCondition.java b/Mage/src/main/java/mage/abilities/condition/common/SourceOnBattlefieldControlUnchangedCondition.java deleted file mode 100644 index 64f600e444..0000000000 --- a/Mage/src/main/java/mage/abilities/condition/common/SourceOnBattlefieldControlUnchangedCondition.java +++ /dev/null @@ -1,43 +0,0 @@ -package mage.abilities.condition.common; - -import mage.abilities.Ability; -import mage.abilities.condition.Condition; -import mage.game.Game; -import mage.watchers.common.LostControlWatcher; - -/** - * This condition checks if ever since first call of the apply method the - * controller of the source has changed - * - * Monitoring the LOST_CONTROL event has the advantage that also all layered - * effects can correctly check for controller change because comparing old and - * new controller during their apply time does not take into account layered - * change control effects that will be applied later. - * - * This condition needs the LostControlWatcher, so be sure to add it to the card - * that uses the condition. - * - * @author LevelX2 - */ -public class SourceOnBattlefieldControlUnchangedCondition implements Condition { - - private Long checkingSince; - private int startingZoneChangeCounter; - - @Override - public boolean apply(Game game, Ability source) { - if (checkingSince == null) { - checkingSince = System.currentTimeMillis() - 1; - startingZoneChangeCounter = game.getState().getZoneChangeCounter(source.getSourceId()); - } - if (game.getState().getZoneChangeCounter(source.getSourceId()) > startingZoneChangeCounter) { - return false; - } - LostControlWatcher watcher = game.getState().getWatcher(LostControlWatcher.class); - if (watcher != null) { - return checkingSince > watcher.getOrderOfLastLostControl(source.getSourceId()); - } - throw new UnsupportedOperationException("LostControlWatcher not found!"); - } - -} diff --git a/Mage/src/main/java/mage/abilities/decorator/ConditionalContinuousEffect.java b/Mage/src/main/java/mage/abilities/decorator/ConditionalContinuousEffect.java index d4904a82f6..d3600a01f8 100644 --- a/Mage/src/main/java/mage/abilities/decorator/ConditionalContinuousEffect.java +++ b/Mage/src/main/java/mage/abilities/decorator/ConditionalContinuousEffect.java @@ -104,8 +104,13 @@ public class ConditionalContinuousEffect extends ContinuousEffectImpl { if (!conditionState && effect.getDuration() == Duration.OneUse) { used = true; } - if (!conditionState && effect.getDuration() == Duration.Custom) { - this.discard(); + switch (effect.getDuration()) { + case OneUse: + used = true; + break; + case Custom: + case WhileControlled: + this.discard(); } return false; } @@ -123,11 +128,13 @@ public class ConditionalContinuousEffect extends ContinuousEffectImpl { otherwiseEffect.setTargetPointer(this.targetPointer); return otherwiseEffect.apply(game, source); } - if (effect.getDuration() == Duration.OneUse) { - used = true; - } - if (effect.getDuration() == Duration.Custom) { - this.discard(); + switch (effect.getDuration()) { + case OneUse: + used = true; + break; + case Custom: + case WhileControlled: + this.discard(); } return false; } @@ -176,7 +183,8 @@ public class ConditionalContinuousEffect extends ContinuousEffectImpl { /** * Return all effects list, for tests only - * @return + * + * @return */ public List getAllEffects() { List res = new ArrayList<>(); diff --git a/Mage/src/main/java/mage/abilities/effects/ContinuousEffects.java b/Mage/src/main/java/mage/abilities/effects/ContinuousEffects.java index 0e8ac85bcf..7697ea27ac 100644 --- a/Mage/src/main/java/mage/abilities/effects/ContinuousEffects.java +++ b/Mage/src/main/java/mage/abilities/effects/ContinuousEffects.java @@ -177,6 +177,7 @@ public class ContinuousEffects implements Serializable { for (ContinuousEffect effect : layeredEffects) { switch (effect.getDuration()) { case WhileOnBattlefield: + case WhileControlled: case WhileOnStack: case WhileInGraveyard: Set abilities = layeredEffects.getAbility(effect.getId()); diff --git a/Mage/src/main/java/mage/abilities/effects/ContinuousEffectsList.java b/Mage/src/main/java/mage/abilities/effects/ContinuousEffectsList.java index a8e118d3ca..502bca43c5 100644 --- a/Mage/src/main/java/mage/abilities/effects/ContinuousEffectsList.java +++ b/Mage/src/main/java/mage/abilities/effects/ContinuousEffectsList.java @@ -1,6 +1,5 @@ package mage.abilities.effects; -import java.util.*; import mage.MageObject; import mage.abilities.Ability; import mage.abilities.MageSingleton; @@ -8,9 +7,12 @@ import mage.cards.Card; import mage.constants.Duration; import mage.constants.Zone; import mage.game.Game; +import mage.game.permanent.Permanent; import mage.players.Player; import org.apache.log4j.Logger; +import java.util.*; + /** * @param * @author BetaSteward_at_googlemail.com @@ -145,6 +147,12 @@ public class ContinuousEffectsList extends ArrayList } break; case Custom: + case UntilYourNextTurn: + case UntilEndOfYourNextTurn: + // until your turn effects continue until real turn reached, their used it's own inactive method + // 514.2 Second, the following actions happen simultaneously: all damage marked on permanents + // (including phased-out permanents) is removed and all "until end of turn" and "this turn" effects end. + // This turn-based action doesn’t use the stack. // custom effects must process it's own inactive method (override) // custom effects may not end, if the source permanent of the effect has left the game // 800.4a (only any effects which give that player control of any objects or players end) @@ -156,18 +164,14 @@ public class ContinuousEffectsList extends ArrayList // end of turn discards on cleanup steps // 514.2 break; - case UntilYourNextTurn: - case UntilEndOfYourNextTurn: - // until your turn effects continue until real turn reached, their used it's own inactive method - // 514.2 Second, the following actions happen simultaneously: all damage marked on permanents - // (including phased-out permanents) is removed and all "until end of turn" and "this turn" effects end. - // This turn-based action doesn’t use the stack. - if (effect.isInactive(ability, game)) { + case UntilSourceLeavesBattlefield: + if (hasOwnerLeftGame || game.getState().getZone(ability.getSourceId()) != Zone.BATTLEFIELD) { it.remove(); } break; - case UntilSourceLeavesBattlefield: - if (hasOwnerLeftGame || Zone.BATTLEFIELD != game.getState().getZone(ability.getSourceId())) { + case WhileControlled: + Permanent permanent = ability.getSourcePermanentIfItStillExists(game); + if (hasOwnerLeftGame || permanent == null || !permanent.isControlledBy(ability.getControllerId())) { it.remove(); } break; diff --git a/Mage/src/main/java/mage/abilities/effects/common/DontUntapInControllersUntapStepTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/DontUntapInControllersUntapStepTargetEffect.java index ea1aaf6a44..c7c264c680 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/DontUntapInControllersUntapStepTargetEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/DontUntapInControllersUntapStepTargetEffect.java @@ -1,7 +1,5 @@ package mage.abilities.effects.common; -import java.util.UUID; -import mage.MageObject; import mage.abilities.Ability; import mage.abilities.Mode; import mage.abilities.effects.ContinuousRuleModifyingEffectImpl; @@ -12,18 +10,27 @@ import mage.game.Game; import mage.game.events.GameEvent; import mage.game.permanent.Permanent; +import java.util.UUID; + /** - * * @author BetaSteward_at_googlemail.com */ public class DontUntapInControllersUntapStepTargetEffect extends ContinuousRuleModifyingEffectImpl { + private final String targetName; + public DontUntapInControllersUntapStepTargetEffect(Duration duration) { + this(duration, "That creature"); + } + + public DontUntapInControllersUntapStepTargetEffect(Duration duration, String targetName) { super(duration, Outcome.Detriment); + this.targetName = targetName; } public DontUntapInControllersUntapStepTargetEffect(final DontUntapInControllersUntapStepTargetEffect effect) { super(effect); + this.targetName = effect.targetName; } @Override @@ -36,16 +43,6 @@ public class DontUntapInControllersUntapStepTargetEffect extends ContinuousRuleM return false; } - @Override - public String getInfoMessage(Ability source, GameEvent event, Game game) { - MageObject mageObject = game.getObject(source.getSourceId()); - Permanent permanentToUntap = game.getPermanent((event.getTargetId())); - if (permanentToUntap != null && mageObject != null) { - return permanentToUntap.getIdName() + " doesn't untap (" + mageObject.getIdName() + ')'; - } - return null; - } - @Override public boolean checksEventType(GameEvent event, Game game) { return event.getType() == GameEvent.EventType.UNTAP; @@ -53,14 +50,16 @@ public class DontUntapInControllersUntapStepTargetEffect extends ContinuousRuleM @Override public boolean applies(GameEvent event, Ability source, Game game) { - if (game.getTurn().getStepType() == PhaseStep.UNTAP) { - for (UUID targetId : targetPointer.getTargets(game, source)) { - if (event.getTargetId().equals(targetId)) { - Permanent permanent = game.getPermanent(targetId); - if (permanent != null && game.isActivePlayer(permanent.getControllerId())) { - return true; - } - } + if (game.getTurn().getStepType() != PhaseStep.UNTAP) { + return false; + } + for (UUID targetId : targetPointer.getTargets(game, source)) { + if (!event.getTargetId().equals(targetId)) { + continue; + } + Permanent permanent = game.getPermanent(targetId); + if (permanent != null && game.isActivePlayer(permanent.getControllerId())) { + return true; } } return false; @@ -68,11 +67,11 @@ public class DontUntapInControllersUntapStepTargetEffect extends ContinuousRuleM @Override public String getText(Mode mode) { - if (staticText != null) { + if (staticText != null && !staticText.isEmpty()) { return staticText; } - return "target " + mode.getTargets().get(0).getTargetName() - + " doesn't untap during its controller's untap step" + (getDuration().toString().isEmpty() ? "" : " " + getDuration()); + return targetName + " doesn't untap during its controller's untap step" + + (getDuration().toString().isEmpty() ? "" : " ") + getDuration(); } } diff --git a/Mage/src/main/java/mage/constants/Duration.java b/Mage/src/main/java/mage/constants/Duration.java index 7e9e43b5df..243cb7b28e 100644 --- a/Mage/src/main/java/mage/constants/Duration.java +++ b/Mage/src/main/java/mage/constants/Duration.java @@ -7,6 +7,7 @@ public enum Duration { OneUse("", true, true), EndOfGame("for the rest of the game", false, false), WhileOnBattlefield("", false, false), + WhileControlled("for as long as you control {this}", true, false), WhileOnStack("", false, true), WhileInGraveyard("", false, false), EndOfTurn("until end of turn", true, true), diff --git a/Mage/src/main/java/mage/watchers/common/LostControlWatcher.java b/Mage/src/main/java/mage/watchers/common/LostControlWatcher.java deleted file mode 100644 index 86f8cf26dd..0000000000 --- a/Mage/src/main/java/mage/watchers/common/LostControlWatcher.java +++ /dev/null @@ -1,39 +0,0 @@ -package mage.watchers.common; - -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; -import mage.constants.WatcherScope; -import mage.game.Game; -import mage.game.events.GameEvent; -import mage.watchers.Watcher; - -/** - * - * @author LevelX2 - */ -public class LostControlWatcher extends Watcher { - - private final Map lastLostControl = new HashMap<>(); - - public LostControlWatcher() { - super(WatcherScope.GAME); - } - - @Override - public void watch(GameEvent event, Game game) { - if (event.getType() == GameEvent.EventType.LOST_CONTROL) { - lastLostControl.put(event.getTargetId(), System.currentTimeMillis()); - } - } - - @Override - public void reset() { - super.reset(); - lastLostControl.clear(); - } - - public long getOrderOfLastLostControl(UUID sourceId) { - return lastLostControl.getOrDefault(sourceId, new Long(0)); - } -}