From 7e867f4fcf7b571577eae59d24bbb40de08282af Mon Sep 17 00:00:00 2001 From: MTGfan Date: Wed, 21 Dec 2016 19:46:46 -0500 Subject: [PATCH 01/10] Cyclopean Tomb TODO: remove counters part of effect --- Mage.Sets/src/mage/cards/c/CyclopeanTomb.java | 197 ++++++++++++++++++ .../src/mage/sets/LimitedEditionAlpha.java | 1 + .../src/mage/sets/LimitedEditionBeta.java | 1 + Mage.Sets/src/mage/sets/MastersEditionIV.java | 1 + Mage.Sets/src/mage/sets/UnlimitedEdition.java | 1 + .../common/PermanentHasCounterCondition.java | 13 ++ .../RemoveAllCountersTargetEffect.java | 71 +++++++ .../main/java/mage/counters/CounterType.java | 1 + 8 files changed, 286 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/c/CyclopeanTomb.java create mode 100644 Mage/src/main/java/mage/abilities/effects/common/counter/RemoveAllCountersTargetEffect.java diff --git a/Mage.Sets/src/mage/cards/c/CyclopeanTomb.java b/Mage.Sets/src/mage/cards/c/CyclopeanTomb.java new file mode 100644 index 00000000000..ad2e02a2ed2 --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/CyclopeanTomb.java @@ -0,0 +1,197 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.cards.c; + +import java.util.UUID; +import mage.abilities.Ability; +import mage.abilities.DelayedTriggeredAbility; +import mage.abilities.common.PutIntoGraveFromBattlefieldSourceTriggeredAbility; +import mage.abilities.condition.common.IsStepCondition; +import mage.abilities.condition.common.PermanentHasCounterCondition; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.decorator.ConditionalActivatedAbility; +import mage.abilities.decorator.ConditionalOneShotEffect; +import mage.abilities.effects.Effect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.CreateDelayedTriggeredAbilityEffect; +import mage.abilities.effects.common.continuous.BecomesBasicLandTargetEffect; +import mage.abilities.effects.common.counter.AddCountersTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Layer; +import mage.constants.Outcome; +import mage.constants.PhaseStep; +import mage.constants.SubLayer; +import mage.constants.Zone; +import mage.counters.CounterType; +import mage.filter.common.FilterLandPermanent; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.SubtypePredicate; +import mage.filter.predicate.permanent.CounterPredicate; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.common.TargetLandPermanent; + +/** + * + * @author MTGfan + */ +public class CyclopeanTomb extends CardImpl { + + private static final FilterLandPermanent filter = new FilterLandPermanent(); + + static { + filter.add(Predicates.not(new SubtypePredicate("Swamp"))); + } + + public CyclopeanTomb(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{4}"); + + + // {2}, {tap}: Put a mire counter on target non-Swamp land. That land is a Swamp for as long as it has a mire counter on it. Activate this ability only during your upkeep. + Ability ability = new ConditionalActivatedAbility(Zone.BATTLEFIELD, new AddCountersTargetEffect(CounterType.MIRE.createInstance()), new GenericManaCost(2), new IsStepCondition(PhaseStep.UPKEEP), "{2}, {T}: Put a mire counter on target non-Swamp land. That land is a Swamp for as long as it has a mire counter on it. Activate this ability only during your upkeep."); + ability.addCost(new TapSourceCost()); + ability.addTarget(new TargetLandPermanent(filter)); + ability.addEffect(new BecomeSwampEffect(Duration.Custom, false, true, "Swamp")); + this.addAbility(ability); + // When Cyclopean Tomb is put into a graveyard from the battlefield, at the beginning of each of your upkeeps for the rest of the game, remove all mire counters from a land that a mire counter was put onto with Cyclopean Tomb but that a mire counter has not been removed from with Cyclopean Tomb. + Effect effect = new CreateDelayedTriggeredAbilityEffect(new CycleDelayedTriggeredAbility()); + effect.setText("at the beginning of each of your upkeeps for the rest of the game, remove all mire counters from a land that a mire counter was put onto with {this} but that a mire counter has not been removed from with {this}."); + this.addAbility(new PutIntoGraveFromBattlefieldSourceTriggeredAbility(effect)); + } + + public CyclopeanTomb(final CyclopeanTomb card) { + super(card); + } + + @Override + public CyclopeanTomb copy() { + return new CyclopeanTomb(this); + } +} + +class BecomeSwampEffect extends BecomesBasicLandTargetEffect { + + public BecomeSwampEffect(Duration duration, boolean chooseLandType, boolean loseOther, String... landNames) { + super(duration, chooseLandType, loseOther, landNames); + staticText = "That land is a Swamp for as long as it has a mire counter on it."; + } + + public BecomeSwampEffect(final BecomeSwampEffect effect) { + super(effect); + } + + @Override + public boolean apply(Layer layer, SubLayer sublayer, Ability source, Game game) { + Permanent land = game.getPermanent(this.targetPointer.getFirst(game, source)); + if (land == null) { + // if permanent left battlefield the effect can be removed because it was only valid for that object + this.discard(); + } else if (land.getCounters(game).getCount(CounterType.MIRE) > 0) { + // only if Mire counter is on the object it becomes a Swamp. + super.apply(layer, sublayer, source, game); + } + return true; + } + + @Override + public BecomeSwampEffect copy() { + return new BecomeSwampEffect(this); + } +} + +class CycleDelayedTriggeredAbility extends DelayedTriggeredAbility { + + CycleDelayedTriggeredAbility() { + super(new CyclopeanTombEffect(), Duration.OneUse, true, false); + } + + CycleDelayedTriggeredAbility(CycleDelayedTriggeredAbility ability) { + super(ability); + } + + @Override + public CycleDelayedTriggeredAbility copy() { + return new CycleDelayedTriggeredAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.BEGINNING_PHASE_PRE; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + return event.getPlayerId().equals(this.controllerId); + } +} + +class CyclopeanTombEffect extends OneShotEffect { + + private static final FilterLandPermanent mireFilter = new FilterLandPermanent(); + + static { + mireFilter.add(new CounterPredicate(CounterType.MIRE)); + } + + public CyclopeanTombEffect() { + super(Outcome.Benefit); + this.staticText = "at the beginning of each of your upkeeps for the rest of the game, remove all mire counters from a land that a mire counter was put onto with {this} but that a mire counter has not been removed from with {this}."; + } + + public CyclopeanTombEffect(final CyclopeanTombEffect effect) { + super(effect); + } + + @Override + public CyclopeanTombEffect copy() { + return new CyclopeanTombEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if(controller != null){ + /* + * + *Remove all counters from target code goes here. Created a RemoveAllCountersTargetEffect for that. + * + */ + //CyclopianTombEffect and CycleDelayedTriggeredAbility will maintain a loop + //as long as there are one or more mire counters left to be removed + new ConditionalOneShotEffect(new CreateDelayedTriggeredAbilityEffect(new CycleDelayedTriggeredAbility(), false), new PermanentHasCounterCondition(CounterType.MIRE, 0, new FilterLandPermanent(), PermanentHasCounterCondition.CountType.MORE_THAN, true)).apply(game, source); + return true; + } + return false; + } +} diff --git a/Mage.Sets/src/mage/sets/LimitedEditionAlpha.java b/Mage.Sets/src/mage/sets/LimitedEditionAlpha.java index b01fe62f9d9..fc8932374ff 100644 --- a/Mage.Sets/src/mage/sets/LimitedEditionAlpha.java +++ b/Mage.Sets/src/mage/sets/LimitedEditionAlpha.java @@ -73,6 +73,7 @@ public class LimitedEditionAlpha extends ExpansionSet { cards.add(new SetCardInfo("Crusade", 200, Rarity.RARE, mage.cards.c.Crusade.class)); cards.add(new SetCardInfo("Crystal Rod", 239, Rarity.UNCOMMON, mage.cards.c.CrystalRod.class)); cards.add(new SetCardInfo("Cursed Land", 6, Rarity.UNCOMMON, mage.cards.c.CursedLand.class)); + cards.add(new SetCardInfo("Cyclopean Tomb", 240, Rarity.RARE, mage.cards.c.CyclopeanTomb.class)); cards.add(new SetCardInfo("Dark Ritual", 7, Rarity.COMMON, mage.cards.d.DarkRitual.class)); cards.add(new SetCardInfo("Deathgrip", 9, Rarity.UNCOMMON, mage.cards.d.Deathgrip.class)); cards.add(new SetCardInfo("Deathlace", 10, Rarity.RARE, mage.cards.d.Deathlace.class)); diff --git a/Mage.Sets/src/mage/sets/LimitedEditionBeta.java b/Mage.Sets/src/mage/sets/LimitedEditionBeta.java index 95055547027..cc95e865cf3 100644 --- a/Mage.Sets/src/mage/sets/LimitedEditionBeta.java +++ b/Mage.Sets/src/mage/sets/LimitedEditionBeta.java @@ -74,6 +74,7 @@ public class LimitedEditionBeta extends ExpansionSet { cards.add(new SetCardInfo("Crusade", 202, Rarity.RARE, mage.cards.c.Crusade.class)); cards.add(new SetCardInfo("Crystal Rod", 241, Rarity.UNCOMMON, mage.cards.c.CrystalRod.class)); cards.add(new SetCardInfo("Cursed Land", 6, Rarity.UNCOMMON, mage.cards.c.CursedLand.class)); + cards.add(new SetCardInfo("Cyclopean Tomb", 242, Rarity.RARE, mage.cards.c.CyclopeanTomb.class)); cards.add(new SetCardInfo("Dark Ritual", 7, Rarity.COMMON, mage.cards.d.DarkRitual.class)); cards.add(new SetCardInfo("Deathgrip", 9, Rarity.UNCOMMON, mage.cards.d.Deathgrip.class)); cards.add(new SetCardInfo("Deathlace", 10, Rarity.RARE, mage.cards.d.Deathlace.class)); diff --git a/Mage.Sets/src/mage/sets/MastersEditionIV.java b/Mage.Sets/src/mage/sets/MastersEditionIV.java index ba8957b1b4b..ed9a0246d7e 100644 --- a/Mage.Sets/src/mage/sets/MastersEditionIV.java +++ b/Mage.Sets/src/mage/sets/MastersEditionIV.java @@ -105,6 +105,7 @@ public class MastersEditionIV extends ExpansionSet { cards.add(new SetCardInfo("Crumble", 147, Rarity.COMMON, mage.cards.c.Crumble.class)); cards.add(new SetCardInfo("Cyclone", 148, Rarity.RARE, mage.cards.c.Cyclone.class)); cards.add(new SetCardInfo("Cyclopean Mummy", 72, Rarity.COMMON, mage.cards.c.CyclopeanMummy.class)); + cards.add(new SetCardInfo("Cyclopean Tomb", 195, Rarity.RARE, mage.cards.c.CyclopeanTomb.class)); cards.add(new SetCardInfo("Dakmor Plague", 73, Rarity.UNCOMMON, mage.cards.d.DakmorPlague.class)); cards.add(new SetCardInfo("Dark Ritual", 74, Rarity.COMMON, mage.cards.d.DarkRitual.class)); cards.add(new SetCardInfo("Deathcoil Wurm", 149, Rarity.RARE, mage.cards.d.DeathcoilWurm.class)); diff --git a/Mage.Sets/src/mage/sets/UnlimitedEdition.java b/Mage.Sets/src/mage/sets/UnlimitedEdition.java index c63a06be4b1..a33f4d15572 100644 --- a/Mage.Sets/src/mage/sets/UnlimitedEdition.java +++ b/Mage.Sets/src/mage/sets/UnlimitedEdition.java @@ -74,6 +74,7 @@ public class UnlimitedEdition extends ExpansionSet { cards.add(new SetCardInfo("Crusade", 201, Rarity.RARE, mage.cards.c.Crusade.class)); cards.add(new SetCardInfo("Crystal Rod", 240, Rarity.UNCOMMON, mage.cards.c.CrystalRod.class)); cards.add(new SetCardInfo("Cursed Land", 6, Rarity.UNCOMMON, mage.cards.c.CursedLand.class)); + cards.add(new SetCardInfo("Cyclopean Tomb", 241, Rarity.RARE, mage.cards.c.CyclopeanTomb.class)); cards.add(new SetCardInfo("Dark Ritual", 7, Rarity.COMMON, mage.cards.d.DarkRitual.class)); cards.add(new SetCardInfo("Deathgrip", 9, Rarity.UNCOMMON, mage.cards.d.Deathgrip.class)); cards.add(new SetCardInfo("Deathlace", 10, Rarity.RARE, mage.cards.d.Deathlace.class)); diff --git a/Mage/src/main/java/mage/abilities/condition/common/PermanentHasCounterCondition.java b/Mage/src/main/java/mage/abilities/condition/common/PermanentHasCounterCondition.java index 16d45219dc1..91609cc857a 100644 --- a/Mage/src/main/java/mage/abilities/condition/common/PermanentHasCounterCondition.java +++ b/Mage/src/main/java/mage/abilities/condition/common/PermanentHasCounterCondition.java @@ -49,9 +49,11 @@ public class PermanentHasCounterCondition implements Condition { private int amount; private FilterPermanent filter; private CountType type; + private boolean anyPlayer; public PermanentHasCounterCondition(CounterType counterType, int amount, FilterPermanent filter) { this(counterType, amount, filter, CountType.EQUAL_TO); + this.anyPlayer = false; } public PermanentHasCounterCondition(CounterType counterType, int amount, FilterPermanent filter, CountType type) { @@ -59,11 +61,22 @@ public class PermanentHasCounterCondition implements Condition { this.amount = amount; this.filter = filter; this.type = type; + this.anyPlayer = false; } + public PermanentHasCounterCondition(CounterType counterType, int amount, FilterPermanent filter, CountType type, boolean any) { + this.counterType = counterType; + this.amount = amount; + this.filter = filter; + this.type = type; + this.anyPlayer = any; + } @Override public boolean apply(Game game, Ability source) { List permanents = game.getBattlefield().getActivePermanents(this.filter, source.getControllerId(), game); + if(this.anyPlayer == true) { + permanents = game.getBattlefield().getAllActivePermanents(this.filter, game); + } for (Permanent permanent : permanents) { switch (this.type) { case FEWER_THAN: diff --git a/Mage/src/main/java/mage/abilities/effects/common/counter/RemoveAllCountersTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/counter/RemoveAllCountersTargetEffect.java new file mode 100644 index 00000000000..dfbdf3b5915 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/effects/common/counter/RemoveAllCountersTargetEffect.java @@ -0,0 +1,71 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.abilities.effects.common.counter; + +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +import mage.constants.Outcome; +import mage.counters.CounterType; +import mage.game.Game; +import mage.game.permanent.Permanent; + +/** + * + * @author MTGfan + */ +public class RemoveAllCountersTargetEffect extends OneShotEffect { + + private final CounterType counterType; + + public RemoveAllCountersTargetEffect(CounterType counterType) { + super(Outcome.Neutral); + this.counterType = counterType; + staticText = "remove all " + counterType.getName() + " counters from it."; + } + + public RemoveAllCountersTargetEffect(RemoveAllCountersTargetEffect effect) { + super(effect); + this.counterType = effect.counterType; + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent permanent = game.getPermanent(targetPointer.getFirst(game, source)); + if(permanent != null) { + int count = permanent.getCounters(game).getCount(counterType); + permanent.removeCounters(counterType.getName(), count, game); + return true; + } + return false; + } + + @Override + public RemoveAllCountersTargetEffect copy() { + return new RemoveAllCountersTargetEffect(this); + } +} diff --git a/Mage/src/main/java/mage/counters/CounterType.java b/Mage/src/main/java/mage/counters/CounterType.java index d546c2b875b..1f359e697fc 100644 --- a/Mage/src/main/java/mage/counters/CounterType.java +++ b/Mage/src/main/java/mage/counters/CounterType.java @@ -82,6 +82,7 @@ public enum CounterType { M2M1(new BoostCounter(-2, -1).name), M2M2(new BoostCounter(-2, -2).name), MINING("mining"), + MIRE("mire"), MUSTER("muster"), P0P1(new BoostCounter(0, 1).name), P1P0(new BoostCounter(1, 0).name), From 4183e3198438e9253775fd0edc613903102d5fe5 Mon Sep 17 00:00:00 2001 From: MTGfan Date: Thu, 22 Dec 2016 00:17:38 -0500 Subject: [PATCH 02/10] Celestial Sword: removed unused imports --- Mage.Sets/src/mage/cards/c/CelestialSword.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Mage.Sets/src/mage/cards/c/CelestialSword.java b/Mage.Sets/src/mage/cards/c/CelestialSword.java index fedf9772bbd..33ef469455f 100644 --- a/Mage.Sets/src/mage/cards/c/CelestialSword.java +++ b/Mage.Sets/src/mage/cards/c/CelestialSword.java @@ -30,22 +30,18 @@ package mage.cards.c; import java.util.UUID; import mage.abilities.Ability; -import mage.abilities.common.OnEventTriggeredAbility; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.common.delayed.AtTheBeginOfNextEndStepDelayedTriggeredAbility; import mage.abilities.costs.common.TapSourceCost; import mage.abilities.costs.mana.GenericManaCost; import mage.abilities.effects.common.CreateDelayedTriggeredAbilityEffect; -import mage.abilities.effects.common.SacrificeSourceEffect; import mage.abilities.effects.common.SacrificeTargetEffect; import mage.abilities.effects.common.continuous.BoostTargetEffect; -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.Zone; -import mage.game.events.GameEvent; import mage.target.common.TargetControlledCreaturePermanent; /** From dc2e4569dbba4d36cb05ecdfe125611eebe66a8e Mon Sep 17 00:00:00 2001 From: MTGfan Date: Thu, 22 Dec 2016 03:27:45 -0500 Subject: [PATCH 03/10] Jihad: Fixed - was missing condition 2nd ability and corrected trigger that results in sacrificing Jihad --- Mage.Sets/src/mage/cards/j/Jihad.java | 55 +++++++++++++++++---------- 1 file changed, 35 insertions(+), 20 deletions(-) diff --git a/Mage.Sets/src/mage/cards/j/Jihad.java b/Mage.Sets/src/mage/cards/j/Jihad.java index aed2f07adba..06a6a0f9338 100644 --- a/Mage.Sets/src/mage/cards/j/Jihad.java +++ b/Mage.Sets/src/mage/cards/j/Jihad.java @@ -40,11 +40,11 @@ import mage.filter.FilterPermanent; import mage.filter.predicate.mageobject.ColorPredicate; import mage.ObjectColor; import mage.abilities.Ability; -import mage.abilities.TriggeredAbility; -import mage.abilities.common.OnEventTriggeredAbility; +import mage.abilities.StateTriggeredAbility; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.condition.Condition; -import mage.abilities.decorator.ConditionalTriggeredAbility; +import mage.abilities.decorator.ConditionalContinuousEffect; +import mage.abilities.effects.Effect; import mage.abilities.effects.common.SacrificeSourceEffect; import mage.abilities.effects.common.continuous.BoostAllEffect; import mage.constants.Duration; @@ -74,15 +74,10 @@ public class Jihad extends CardImpl { this.addAbility(new AsEntersBattlefieldAbility(new ChooseOpponentEffect(Outcome.Detriment))); // White creatures get +2/+1 as long as the chosen player controls a nontoken permanent of the chosen color. - this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new BoostAllEffect(2, 1, Duration.WhileOnBattlefield, filter, false))); + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new ConditionalContinuousEffect(new BoostAllEffect(2, 1, Duration.WhileOnBattlefield, filter, false), new JihadOpponentCondition(), "White creatures get +2/+1 as long as the chosen player controls a nontoken permanent of the chosen color."))); // When the chosen player controls no nontoken permanents of the chosen color, sacrifice Jihad. - - // Not quite as immediate as I'd like but.. Static doesnt accept that effect and probably only work upon casting (maybe a continuous ability would, if not hog the system) - TriggeredAbility triggered = new OnEventTriggeredAbility(GameEvent.EventType.STEP_CHANGED, "end Jihad", true, new SacrificeSourceEffect()); - this.addAbility(new ConditionalTriggeredAbility(triggered, new NoColoredPermanentOpponentCondition(), "no nontoken permanents of the chosen color, sacrifice Jihad")); - //this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new ConditionalOneShotEffect(new SacrificeSourceEffect()), new NoColoredPermanentOpponentCondition(), "Jihad ending")); - + this.addAbility(new JihadTriggeredAbility(new SacrificeSourceEffect())); } public Jihad(final Jihad card) { @@ -95,24 +90,44 @@ public class Jihad extends CardImpl { } } -class NoColoredPermanentOpponentCondition implements Condition { +class JihadTriggeredAbility extends StateTriggeredAbility { - private static NoColoredPermanentOpponentCondition fInstance = new NoColoredPermanentOpponentCondition(); - - public static Condition getInstance() { - return fInstance; + public JihadTriggeredAbility(Effect effect) { + super(Zone.BATTLEFIELD, effect); } + public JihadTriggeredAbility(final JihadTriggeredAbility ability) { + super(ability); + } + + @Override + public JihadTriggeredAbility copy() { + return new JihadTriggeredAbility(this); + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + UUID chosenOpponent = (UUID) game.getState().getValue(getSourceId().toString() + ChooseOpponentEffect.VALUE_KEY); + FilterPermanent filter = new FilterPermanent(); + filter.add(new ColorPredicate((ObjectColor) game.getState().getValue(getSourceId() + "_color"))); + filter.add(Predicates.not(new TokenPredicate())); + return game.getBattlefield().countAll(filter, chosenOpponent, game) == 0; + } + + @Override + public String getRule() { + return "When the chosen player controls no nontoken permanents of the chosen color, " + super.getRule(); + } +} + +class JihadOpponentCondition implements Condition { + @Override public boolean apply(Game game, Ability source) { UUID chosenOpponent = (UUID) game.getState().getValue(source.getSourceId().toString() + ChooseOpponentEffect.VALUE_KEY); - FilterPermanent filter = new FilterPermanent(); filter.add(new ColorPredicate((ObjectColor) game.getState().getValue(source.getSourceId() + "_color"))); filter.add(Predicates.not(new TokenPredicate())); - //filter.add(new ColorPredicate(ObjectColor.WHITE)); // Given this typing, somehow the above doesn't seem right, but I copied from StoryCircle card - - return game.getBattlefield().countAll(filter, chosenOpponent, game)==0; - + return game.getBattlefield().countAll(filter, chosenOpponent, game) > 0; } } \ No newline at end of file From cf17d026571e961ddc0d27b7615e5238db38391a Mon Sep 17 00:00:00 2001 From: MTGfan Date: Thu, 22 Dec 2016 15:58:03 -0500 Subject: [PATCH 04/10] CyclopeanTomb: updated to track what lands it is putting counters on --- Mage.Sets/src/mage/cards/c/CyclopeanTomb.java | 110 ++++++++++++++++-- 1 file changed, 102 insertions(+), 8 deletions(-) diff --git a/Mage.Sets/src/mage/cards/c/CyclopeanTomb.java b/Mage.Sets/src/mage/cards/c/CyclopeanTomb.java index ad2e02a2ed2..8626b5636a3 100644 --- a/Mage.Sets/src/mage/cards/c/CyclopeanTomb.java +++ b/Mage.Sets/src/mage/cards/c/CyclopeanTomb.java @@ -27,12 +27,15 @@ */ package mage.cards.c; +import java.util.ArrayList; +import java.util.List; import java.util.UUID; +import mage.MageObject; import mage.abilities.Ability; import mage.abilities.DelayedTriggeredAbility; import mage.abilities.common.PutIntoGraveFromBattlefieldSourceTriggeredAbility; +import mage.abilities.condition.Condition; import mage.abilities.condition.common.IsStepCondition; -import mage.abilities.condition.common.PermanentHasCounterCondition; import mage.abilities.costs.common.TapSourceCost; import mage.abilities.costs.mana.GenericManaCost; import mage.abilities.decorator.ConditionalActivatedAbility; @@ -42,6 +45,7 @@ import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.CreateDelayedTriggeredAbilityEffect; import mage.abilities.effects.common.continuous.BecomesBasicLandTargetEffect; import mage.abilities.effects.common.counter.AddCountersTargetEffect; +import mage.abilities.effects.common.counter.RemoveAllCountersTargetEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; @@ -53,6 +57,7 @@ import mage.constants.SubLayer; import mage.constants.Zone; import mage.counters.CounterType; import mage.filter.common.FilterLandPermanent; +import mage.filter.predicate.Predicate; import mage.filter.predicate.Predicates; import mage.filter.predicate.mageobject.SubtypePredicate; import mage.filter.predicate.permanent.CounterPredicate; @@ -61,6 +66,7 @@ import mage.game.events.GameEvent; import mage.game.permanent.Permanent; import mage.players.Player; import mage.target.common.TargetLandPermanent; +import mage.target.targetpointer.FixedTarget; /** * @@ -69,6 +75,7 @@ import mage.target.common.TargetLandPermanent; public class CyclopeanTomb extends CardImpl { private static final FilterLandPermanent filter = new FilterLandPermanent(); + public List lands = new ArrayList<>(); static { filter.add(Predicates.not(new SubtypePredicate("Swamp"))); @@ -84,8 +91,10 @@ public class CyclopeanTomb extends CardImpl { ability.addTarget(new TargetLandPermanent(filter)); ability.addEffect(new BecomeSwampEffect(Duration.Custom, false, true, "Swamp")); this.addAbility(ability); + //add the targets UUID to an array to track which specific lands the instance of Cyclopean Tomb added a mire counter to. + lands.add(ability.getTargets().getFirstTarget()); // When Cyclopean Tomb is put into a graveyard from the battlefield, at the beginning of each of your upkeeps for the rest of the game, remove all mire counters from a land that a mire counter was put onto with Cyclopean Tomb but that a mire counter has not been removed from with Cyclopean Tomb. - Effect effect = new CreateDelayedTriggeredAbilityEffect(new CycleDelayedTriggeredAbility()); + Effect effect = new CreateDelayedTriggeredAbilityEffect(new CycleDelayedTriggeredAbility(lands)); effect.setText("at the beginning of each of your upkeeps for the rest of the game, remove all mire counters from a land that a mire counter was put onto with {this} but that a mire counter has not been removed from with {this}."); this.addAbility(new PutIntoGraveFromBattlefieldSourceTriggeredAbility(effect)); } @@ -132,8 +141,8 @@ class BecomeSwampEffect extends BecomesBasicLandTargetEffect { class CycleDelayedTriggeredAbility extends DelayedTriggeredAbility { - CycleDelayedTriggeredAbility() { - super(new CyclopeanTombEffect(), Duration.OneUse, true, false); + CycleDelayedTriggeredAbility(List lands) { + super(new CyclopeanTombEffect(lands), Duration.OneUse, true, false); } CycleDelayedTriggeredAbility(CycleDelayedTriggeredAbility ability) { @@ -158,14 +167,16 @@ class CycleDelayedTriggeredAbility extends DelayedTriggeredAbility { class CyclopeanTombEffect extends OneShotEffect { - private static final FilterLandPermanent mireFilter = new FilterLandPermanent(); - + List lands = new ArrayList<>(); + public static final FilterLandPermanent mireFilter = new FilterLandPermanent(); + static { mireFilter.add(new CounterPredicate(CounterType.MIRE)); } - public CyclopeanTombEffect() { + public CyclopeanTombEffect(List lands) { super(Outcome.Benefit); + this.lands = lands; this.staticText = "at the beginning of each of your upkeeps for the rest of the game, remove all mire counters from a land that a mire counter was put onto with {this} but that a mire counter has not been removed from with {this}."; } @@ -187,11 +198,94 @@ class CyclopeanTombEffect extends OneShotEffect { *Remove all counters from target code goes here. Created a RemoveAllCountersTargetEffect for that. * */ + new ChooseLandEffect(lands).apply(game, source); + Effect effect = new RemoveAllCountersTargetEffect(CounterType.MIRE); + effect.setTargetPointer(new FixedTarget((UUID) game.getState().getValue(source.getSourceId().toString() + "_land"))); + effect.apply(game, source); //CyclopianTombEffect and CycleDelayedTriggeredAbility will maintain a loop //as long as there are one or more mire counters left to be removed - new ConditionalOneShotEffect(new CreateDelayedTriggeredAbilityEffect(new CycleDelayedTriggeredAbility(), false), new PermanentHasCounterCondition(CounterType.MIRE, 0, new FilterLandPermanent(), PermanentHasCounterCondition.CountType.MORE_THAN, true)).apply(game, source); + new ConditionalOneShotEffect(new CreateDelayedTriggeredAbilityEffect(new CycleDelayedTriggeredAbility(lands), false), new CyclopeanTombCounterCondition(lands)).apply(game, source); return true; } return false; } } + +class CyclopeanTombCounterCondition implements Condition { + + private List lands; + + public CyclopeanTombCounterCondition(List lands) { + this.lands = lands; + } + + @Override + public boolean apply(Game game, Ability source) { + List permanents = game.getBattlefield().getAllActivePermanents(CyclopeanTombEffect.mireFilter, game); + for (UUID landId : lands) { + for(Permanent permanent : permanents) { + if(landId.equals(permanent.getId())) { + return permanent.getCounters(game).getCount(CounterType.MIRE) > 0; + } + } + } + return false; + } +} + +class ChooseLandEffect extends OneShotEffect { + + private List lands; + + public ChooseLandEffect(List lands) { + super(Outcome.Neutral); + this.lands = lands; + this.staticText = "choose a land that a mire counter was put onto with Cyclopean Tomb but that a mire counter has not been removed from with Cyclopean Tomb"; + } + + public ChooseLandEffect(final ChooseLandEffect effect) { + super(effect); + } + + @Override + public ChooseLandEffect copy() { + return new ChooseLandEffect(this); + } + //UUID chosenLand = ; use with addTarget or setTargetPointer + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + MageObject mageObject = game.getObject(source.getSourceId()); + FilterLandPermanent filter = new FilterLandPermanent(); + filter.add(new LandIdPredicate(lands)); + if(controller != null){ + TargetLandPermanent target = new TargetLandPermanent(filter); + if (controller.choose(this.outcome, target, source.getSourceId(), game)) { + Permanent chosenLand = game.getPermanent(target.getFirstTarget()); + if(chosenLand != null) { + game.getState().setValue(mageObject.getId() + "_land", target.getFirstTarget()); + return true; + } + } + } + return false; + } +} + +class LandIdPredicate implements Predicate { + + private List lands; + + public LandIdPredicate(List lands) { + this.lands = lands; + } + @Override + public boolean apply(Permanent input, Game game) { + for(UUID landId : lands){ + return input.getId().equals(landId); + } + return false; + } +} + + From 7c6d19267be137faace102ef138abd0b79cf23fb Mon Sep 17 00:00:00 2001 From: MTGfan Date: Fri, 23 Dec 2016 00:27:13 -0500 Subject: [PATCH 05/10] Cyclopean Tomb: Progress, player is now prompted to pick a land, landId predicate not working yet --- Mage.Sets/src/mage/cards/c/CyclopeanTomb.java | 40 ++++++++++--------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/Mage.Sets/src/mage/cards/c/CyclopeanTomb.java b/Mage.Sets/src/mage/cards/c/CyclopeanTomb.java index 8626b5636a3..1a505580b0a 100644 --- a/Mage.Sets/src/mage/cards/c/CyclopeanTomb.java +++ b/Mage.Sets/src/mage/cards/c/CyclopeanTomb.java @@ -67,6 +67,7 @@ import mage.game.permanent.Permanent; import mage.players.Player; import mage.target.common.TargetLandPermanent; import mage.target.targetpointer.FixedTarget; +import mage.util.CardUtil; /** * @@ -74,8 +75,8 @@ import mage.target.targetpointer.FixedTarget; */ public class CyclopeanTomb extends CardImpl { - private static final FilterLandPermanent filter = new FilterLandPermanent(); public List lands = new ArrayList<>(); + private static final FilterLandPermanent filter = new FilterLandPermanent(); static { filter.add(Predicates.not(new SubtypePredicate("Swamp"))); @@ -94,7 +95,7 @@ public class CyclopeanTomb extends CardImpl { //add the targets UUID to an array to track which specific lands the instance of Cyclopean Tomb added a mire counter to. lands.add(ability.getTargets().getFirstTarget()); // When Cyclopean Tomb is put into a graveyard from the battlefield, at the beginning of each of your upkeeps for the rest of the game, remove all mire counters from a land that a mire counter was put onto with Cyclopean Tomb but that a mire counter has not been removed from with Cyclopean Tomb. - Effect effect = new CreateDelayedTriggeredAbilityEffect(new CycleDelayedTriggeredAbility(lands)); + Effect effect = new CreateDelayedTriggeredAbilityEffect(new CyclopeanTombDelayedTriggeredAbility(lands)); effect.setText("at the beginning of each of your upkeeps for the rest of the game, remove all mire counters from a land that a mire counter was put onto with {this} but that a mire counter has not been removed from with {this}."); this.addAbility(new PutIntoGraveFromBattlefieldSourceTriggeredAbility(effect)); } @@ -139,19 +140,19 @@ class BecomeSwampEffect extends BecomesBasicLandTargetEffect { } } -class CycleDelayedTriggeredAbility extends DelayedTriggeredAbility { +class CyclopeanTombDelayedTriggeredAbility extends DelayedTriggeredAbility { - CycleDelayedTriggeredAbility(List lands) { + CyclopeanTombDelayedTriggeredAbility(List lands) { super(new CyclopeanTombEffect(lands), Duration.OneUse, true, false); } - CycleDelayedTriggeredAbility(CycleDelayedTriggeredAbility ability) { + CyclopeanTombDelayedTriggeredAbility(CyclopeanTombDelayedTriggeredAbility ability) { super(ability); } @Override - public CycleDelayedTriggeredAbility copy() { - return new CycleDelayedTriggeredAbility(this); + public CyclopeanTombDelayedTriggeredAbility copy() { + return new CyclopeanTombDelayedTriggeredAbility(this); } @Override @@ -193,18 +194,13 @@ class CyclopeanTombEffect extends OneShotEffect { public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); if(controller != null){ - /* - * - *Remove all counters from target code goes here. Created a RemoveAllCountersTargetEffect for that. - * - */ new ChooseLandEffect(lands).apply(game, source); Effect effect = new RemoveAllCountersTargetEffect(CounterType.MIRE); effect.setTargetPointer(new FixedTarget((UUID) game.getState().getValue(source.getSourceId().toString() + "_land"))); effect.apply(game, source); - //CyclopianTombEffect and CycleDelayedTriggeredAbility will maintain a loop + //CyclopianTombEffect and CyclopeanTombDelayedTriggeredAbility will maintain a loop //as long as there are one or more mire counters left to be removed - new ConditionalOneShotEffect(new CreateDelayedTriggeredAbilityEffect(new CycleDelayedTriggeredAbility(lands), false), new CyclopeanTombCounterCondition(lands)).apply(game, source); + new ConditionalOneShotEffect(new CreateDelayedTriggeredAbilityEffect(new CyclopeanTombDelayedTriggeredAbility(lands), false), new CyclopeanTombCounterCondition(lands)).apply(game, source); return true; } return false; @@ -251,19 +247,25 @@ class ChooseLandEffect extends OneShotEffect { public ChooseLandEffect copy() { return new ChooseLandEffect(this); } - //UUID chosenLand = ; use with addTarget or setTargetPointer + @Override public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); - MageObject mageObject = game.getObject(source.getSourceId()); + MageObject mageObject = game.getPermanentEntering(source.getSourceId()); + if (mageObject == null) { + mageObject = game.getObject(source.getSourceId()); + } FilterLandPermanent filter = new FilterLandPermanent(); filter.add(new LandIdPredicate(lands)); - if(controller != null){ - TargetLandPermanent target = new TargetLandPermanent(filter); - if (controller.choose(this.outcome, target, source.getSourceId(), game)) { + if(controller != null && mageObject != null){ + TargetLandPermanent target = new TargetLandPermanent(1, 1, filter, true); + if (controller.chooseTarget(Outcome.Neutral, target, source, game)) { Permanent chosenLand = game.getPermanent(target.getFirstTarget()); if(chosenLand != null) { game.getState().setValue(mageObject.getId() + "_land", target.getFirstTarget()); + if (mageObject instanceof Permanent) { + ((Permanent) mageObject).addInfo("chosen land", CardUtil.addToolTipMarkTags("Chosen player: " + chosenLand.getLogName()), game); + } return true; } } From a40a67c2b8b536177a23d7707594284926862c4c Mon Sep 17 00:00:00 2001 From: MTGfan Date: Fri, 23 Dec 2016 23:59:29 -0500 Subject: [PATCH 06/10] Cyclopean Tomb: watcher stuff replacing the array stuff, card not working completely yet --- Mage.Sets/src/mage/cards/c/CyclopeanTomb.java | 102 ++++++++++-------- 1 file changed, 60 insertions(+), 42 deletions(-) diff --git a/Mage.Sets/src/mage/cards/c/CyclopeanTomb.java b/Mage.Sets/src/mage/cards/c/CyclopeanTomb.java index 1a505580b0a..fac79bdb0ca 100644 --- a/Mage.Sets/src/mage/cards/c/CyclopeanTomb.java +++ b/Mage.Sets/src/mage/cards/c/CyclopeanTomb.java @@ -54,6 +54,7 @@ import mage.constants.Layer; import mage.constants.Outcome; import mage.constants.PhaseStep; import mage.constants.SubLayer; +import mage.constants.WatcherScope; import mage.constants.Zone; import mage.counters.CounterType; import mage.filter.common.FilterLandPermanent; @@ -63,11 +64,13 @@ import mage.filter.predicate.mageobject.SubtypePredicate; import mage.filter.predicate.permanent.CounterPredicate; import mage.game.Game; import mage.game.events.GameEvent; +import mage.game.events.GameEvent.EventType; import mage.game.permanent.Permanent; import mage.players.Player; import mage.target.common.TargetLandPermanent; import mage.target.targetpointer.FixedTarget; import mage.util.CardUtil; +import mage.watchers.Watcher; /** * @@ -75,7 +78,6 @@ import mage.util.CardUtil; */ public class CyclopeanTomb extends CardImpl { - public List lands = new ArrayList<>(); private static final FilterLandPermanent filter = new FilterLandPermanent(); static { @@ -84,18 +86,15 @@ public class CyclopeanTomb extends CardImpl { public CyclopeanTomb(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{4}"); - // {2}, {tap}: Put a mire counter on target non-Swamp land. That land is a Swamp for as long as it has a mire counter on it. Activate this ability only during your upkeep. Ability ability = new ConditionalActivatedAbility(Zone.BATTLEFIELD, new AddCountersTargetEffect(CounterType.MIRE.createInstance()), new GenericManaCost(2), new IsStepCondition(PhaseStep.UPKEEP), "{2}, {T}: Put a mire counter on target non-Swamp land. That land is a Swamp for as long as it has a mire counter on it. Activate this ability only during your upkeep."); ability.addCost(new TapSourceCost()); ability.addTarget(new TargetLandPermanent(filter)); ability.addEffect(new BecomeSwampEffect(Duration.Custom, false, true, "Swamp")); - this.addAbility(ability); - //add the targets UUID to an array to track which specific lands the instance of Cyclopean Tomb added a mire counter to. - lands.add(ability.getTargets().getFirstTarget()); + this.addAbility(ability, new CyclopeanTombCounterWatcher()); // When Cyclopean Tomb is put into a graveyard from the battlefield, at the beginning of each of your upkeeps for the rest of the game, remove all mire counters from a land that a mire counter was put onto with Cyclopean Tomb but that a mire counter has not been removed from with Cyclopean Tomb. - Effect effect = new CreateDelayedTriggeredAbilityEffect(new CyclopeanTombDelayedTriggeredAbility(lands)); + Effect effect = new CreateDelayedTriggeredAbilityEffect(new CyclopeanTombDelayedTriggeredAbility()); effect.setText("at the beginning of each of your upkeeps for the rest of the game, remove all mire counters from a land that a mire counter was put onto with {this} but that a mire counter has not been removed from with {this}."); this.addAbility(new PutIntoGraveFromBattlefieldSourceTriggeredAbility(effect)); } @@ -142,8 +141,8 @@ class BecomeSwampEffect extends BecomesBasicLandTargetEffect { class CyclopeanTombDelayedTriggeredAbility extends DelayedTriggeredAbility { - CyclopeanTombDelayedTriggeredAbility(List lands) { - super(new CyclopeanTombEffect(lands), Duration.OneUse, true, false); + CyclopeanTombDelayedTriggeredAbility() { + super(new CyclopeanTombEffect(), Duration.OneUse, true, false); } CyclopeanTombDelayedTriggeredAbility(CyclopeanTombDelayedTriggeredAbility ability) { @@ -168,16 +167,8 @@ class CyclopeanTombDelayedTriggeredAbility extends DelayedTriggeredAbility { class CyclopeanTombEffect extends OneShotEffect { - List lands = new ArrayList<>(); - public static final FilterLandPermanent mireFilter = new FilterLandPermanent(); - - static { - mireFilter.add(new CounterPredicate(CounterType.MIRE)); - } - - public CyclopeanTombEffect(List lands) { + public CyclopeanTombEffect() { super(Outcome.Benefit); - this.lands = lands; this.staticText = "at the beginning of each of your upkeeps for the rest of the game, remove all mire counters from a land that a mire counter was put onto with {this} but that a mire counter has not been removed from with {this}."; } @@ -194,13 +185,13 @@ class CyclopeanTombEffect extends OneShotEffect { public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); if(controller != null){ - new ChooseLandEffect(lands).apply(game, source); + new ChooseLandEffect().apply(game, source); Effect effect = new RemoveAllCountersTargetEffect(CounterType.MIRE); effect.setTargetPointer(new FixedTarget((UUID) game.getState().getValue(source.getSourceId().toString() + "_land"))); effect.apply(game, source); //CyclopianTombEffect and CyclopeanTombDelayedTriggeredAbility will maintain a loop //as long as there are one or more mire counters left to be removed - new ConditionalOneShotEffect(new CreateDelayedTriggeredAbilityEffect(new CyclopeanTombDelayedTriggeredAbility(lands), false), new CyclopeanTombCounterCondition(lands)).apply(game, source); + new ConditionalOneShotEffect(new CreateDelayedTriggeredAbilityEffect(new CyclopeanTombDelayedTriggeredAbility(), false), new CyclopeanTombCounterCondition()).apply(game, source); return true; } return false; @@ -209,20 +200,24 @@ class CyclopeanTombEffect extends OneShotEffect { class CyclopeanTombCounterCondition implements Condition { - private List lands; - - public CyclopeanTombCounterCondition(List lands) { - this.lands = lands; + private static final FilterLandPermanent mireFilter = new FilterLandPermanent(); + + static { + mireFilter.add(new CounterPredicate(CounterType.MIRE)); + } + + public CyclopeanTombCounterCondition() { } @Override public boolean apply(Game game, Ability source) { - List permanents = game.getBattlefield().getAllActivePermanents(CyclopeanTombEffect.mireFilter, game); - for (UUID landId : lands) { - for(Permanent permanent : permanents) { - if(landId.equals(permanent.getId())) { - return permanent.getCounters(game).getCount(CounterType.MIRE) > 0; - } + + List permanents = game.getBattlefield().getAllActivePermanents(mireFilter, game); + CyclopeanTombCounterWatcher watcher = (CyclopeanTombCounterWatcher) game.getState().getWatchers().get("TombCounterWatcher"); + + for(Permanent permanent : permanents) { + if(watcher.getKey().equals(source.getSourceId().toString() + "TombCounterWatcher") && watcher.lands.contains(permanent.getId())) { + return permanent.getCounters(game).getCount(CounterType.MIRE) > 0; } } return false; @@ -231,11 +226,8 @@ class CyclopeanTombCounterCondition implements Condition { class ChooseLandEffect extends OneShotEffect { - private List lands; - - public ChooseLandEffect(List lands) { + public ChooseLandEffect() { super(Outcome.Neutral); - this.lands = lands; this.staticText = "choose a land that a mire counter was put onto with Cyclopean Tomb but that a mire counter has not been removed from with Cyclopean Tomb"; } @@ -250,13 +242,17 @@ class ChooseLandEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); MageObject mageObject = game.getPermanentEntering(source.getSourceId()); + if (mageObject == null) { mageObject = game.getObject(source.getSourceId()); } + FilterLandPermanent filter = new FilterLandPermanent(); - filter.add(new LandIdPredicate(lands)); + filter.add(new LandIdPredicate()); + if(controller != null && mageObject != null){ TargetLandPermanent target = new TargetLandPermanent(1, 1, filter, true); if (controller.chooseTarget(Outcome.Neutral, target, source, game)) { @@ -276,18 +272,40 @@ class ChooseLandEffect extends OneShotEffect { class LandIdPredicate implements Predicate { - private List lands; - - public LandIdPredicate(List lands) { - this.lands = lands; + public LandIdPredicate() { } + @Override public boolean apply(Permanent input, Game game) { - for(UUID landId : lands){ - return input.getId().equals(landId); - } - return false; + CyclopeanTombCounterWatcher watcher = (CyclopeanTombCounterWatcher) game.getState().getWatchers().get("TombCounterWatcher"); + return watcher.lands.contains(input.getId()); } } +class CyclopeanTombCounterWatcher extends Watcher { + + public List lands = new ArrayList<>(); + + public CyclopeanTombCounterWatcher() { + super("TombPutCounterOnWatcher", WatcherScope.CARD); + } + + public CyclopeanTombCounterWatcher(final CyclopeanTombCounterWatcher watcher) { + super(watcher); + this.lands.addAll(watcher.lands); + } + + @Override + public CyclopeanTombCounterWatcher copy() { + return new CyclopeanTombCounterWatcher(this); + } + @Override + public void watch(GameEvent event, Game game) { + if(event.getType() == EventType.ADD_COUNTER || event.getType() == EventType.ADD_COUNTERS) { + if(!lands.contains(event.getTargetId())) { + lands.add(event.getTargetId()); + } + } + } +} \ No newline at end of file From 5859633955dad908d8c85c011584f40538b2ddb4 Mon Sep 17 00:00:00 2001 From: MTGfan Date: Sun, 25 Dec 2016 15:07:03 -0500 Subject: [PATCH 07/10] Gaea's Liege: corrected sent to graveyard upon entering the battlefield issue that effect some players --- Mage.Sets/src/mage/cards/g/GaeasLiege.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Mage.Sets/src/mage/cards/g/GaeasLiege.java b/Mage.Sets/src/mage/cards/g/GaeasLiege.java index 6e17bd4ae84..52014ab9381 100644 --- a/Mage.Sets/src/mage/cards/g/GaeasLiege.java +++ b/Mage.Sets/src/mage/cards/g/GaeasLiege.java @@ -32,6 +32,7 @@ import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.InvertCondition; import mage.abilities.condition.common.SourceAttackingCondition; import mage.abilities.costs.common.TapSourceCost; import mage.abilities.decorator.ConditionalContinuousEffect; @@ -74,9 +75,9 @@ public class GaeasLiege extends CardImpl { // As long as Gaea's Liege isn't attacking, its power and toughness are each equal to the number of Forests you control. As long as Gaea's Liege is attacking, its power and toughness are each equal to the number of Forests defending player controls. this.addAbility(new SimpleStaticAbility(Zone.ALL, new ConditionalContinuousEffect( - new SetPowerToughnessSourceEffect(new DefendersForestCount(), Duration.EndOfCombat), new SetPowerToughnessSourceEffect(new PermanentsOnBattlefieldCount(filterLands), Duration.EndOfGame), - SourceAttackingCondition.getInstance(), + new SetPowerToughnessSourceEffect(new DefendersForestCount(), Duration.EndOfCombat), + new InvertCondition(SourceAttackingCondition.getInstance()), "As long as {this} isn't attacking, its power and toughness are each equal to the number of Forests you control. As long as {this} is attacking, its power and toughness are each equal to the number of Forests defending player controls."))); // {tap}: Target land becomes a Forest until Gaea's Liege leaves the battlefield. Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new BecomesBasicLandTargetEffect(Duration.WhileOnBattlefield, "Forest"), new TapSourceCost()); @@ -131,4 +132,4 @@ class DefendersForestCount implements DynamicValue { public String getMessage() { return "the number of Forests defending player controls"; } -} \ No newline at end of file +} From d9a1e724b61bc83c5ab18e7d883212b59d91dfc3 Mon Sep 17 00:00:00 2001 From: MTGfan Date: Sun, 25 Dec 2016 16:29:44 -0500 Subject: [PATCH 08/10] Cyclopean Tomb: Partial changes related to it's watcher. --- Mage.Sets/src/mage/cards/c/CyclopeanTomb.java | 36 ++++++++++--------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/Mage.Sets/src/mage/cards/c/CyclopeanTomb.java b/Mage.Sets/src/mage/cards/c/CyclopeanTomb.java index fac79bdb0ca..a21b9e97a2e 100644 --- a/Mage.Sets/src/mage/cards/c/CyclopeanTomb.java +++ b/Mage.Sets/src/mage/cards/c/CyclopeanTomb.java @@ -27,10 +27,13 @@ */ package mage.cards.c; -import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.Set; import java.util.UUID; import mage.MageObject; +import mage.MageObjectReference; import mage.abilities.Ability; import mage.abilities.DelayedTriggeredAbility; import mage.abilities.common.PutIntoGraveFromBattlefieldSourceTriggeredAbility; @@ -64,7 +67,6 @@ import mage.filter.predicate.mageobject.SubtypePredicate; import mage.filter.predicate.permanent.CounterPredicate; import mage.game.Game; import mage.game.events.GameEvent; -import mage.game.events.GameEvent.EventType; import mage.game.permanent.Permanent; import mage.players.Player; import mage.target.common.TargetLandPermanent; @@ -213,10 +215,10 @@ class CyclopeanTombCounterCondition implements Condition { public boolean apply(Game game, Ability source) { List permanents = game.getBattlefield().getAllActivePermanents(mireFilter, game); - CyclopeanTombCounterWatcher watcher = (CyclopeanTombCounterWatcher) game.getState().getWatchers().get("TombCounterWatcher"); + CyclopeanTombCounterWatcher watcher = (CyclopeanTombCounterWatcher) game.getState().getWatchers().get(CyclopeanTombCounterWatcher.class.getName()); for(Permanent permanent : permanents) { - if(watcher.getKey().equals(source.getSourceId().toString() + "TombCounterWatcher") && watcher.lands.contains(permanent.getId())) { + if(/* true that watcher is the instance of tomb && true that watcher contains iteration of permanent*/) { return permanent.getCounters(game).getCount(CounterType.MIRE) > 0; } } @@ -251,7 +253,7 @@ class ChooseLandEffect extends OneShotEffect { } FilterLandPermanent filter = new FilterLandPermanent(); - filter.add(new LandIdPredicate()); + filter.add(new LandIdPredicate(source)); if(controller != null && mageObject != null){ TargetLandPermanent target = new TargetLandPermanent(1, 1, filter, true); @@ -272,27 +274,29 @@ class ChooseLandEffect extends OneShotEffect { class LandIdPredicate implements Predicate { - public LandIdPredicate() { + public Ability source; + + public LandIdPredicate(Ability source) { + this.source = source; } @Override public boolean apply(Permanent input, Game game) { - CyclopeanTombCounterWatcher watcher = (CyclopeanTombCounterWatcher) game.getState().getWatchers().get("TombCounterWatcher"); - return watcher.lands.contains(input.getId()); + CyclopeanTombCounterWatcher watcher = (CyclopeanTombCounterWatcher) game.getState().getWatchers().get(CyclopeanTombCounterWatcher.class.getName()); + return /* true that watcher is the instance of tomb && true that watcher contains the targeted land*/; } } class CyclopeanTombCounterWatcher extends Watcher { - public List lands = new ArrayList<>(); + Map lands = new HashMap<>(); public CyclopeanTombCounterWatcher() { - super("TombPutCounterOnWatcher", WatcherScope.CARD); + super(CyclopeanTombCounterWatcher.class.getName(), WatcherScope.GAME); } public CyclopeanTombCounterWatcher(final CyclopeanTombCounterWatcher watcher) { super(watcher); - this.lands.addAll(watcher.lands); } @Override @@ -302,10 +306,10 @@ class CyclopeanTombCounterWatcher extends Watcher { @Override public void watch(GameEvent event, Game game) { - if(event.getType() == EventType.ADD_COUNTER || event.getType() == EventType.ADD_COUNTERS) { - if(!lands.contains(event.getTargetId())) { - lands.add(event.getTargetId()); + if(event.getType() == GameEvent.EventType.COUNTER_ADDED || event.getType() == GameEvent.EventType.COUNTERS_ADDED) { +/* if(land counter was added not in lands) { + lands.put(the stuff needed to identify the instance of tomb and the land the counter was added); } - } +*/ } } -} \ No newline at end of file +} From 5cc2a4f204e82f4df6cc4d46c8de983fc7f5a238 Mon Sep 17 00:00:00 2001 From: MTGfan Date: Mon, 26 Dec 2016 01:51:20 -0500 Subject: [PATCH 09/10] Cyclopean Tomb: Watcher is almost done --- Mage.Sets/src/mage/cards/c/CyclopeanTomb.java | 49 +++++++++++++++---- 1 file changed, 39 insertions(+), 10 deletions(-) diff --git a/Mage.Sets/src/mage/cards/c/CyclopeanTomb.java b/Mage.Sets/src/mage/cards/c/CyclopeanTomb.java index a21b9e97a2e..65d67df7184 100644 --- a/Mage.Sets/src/mage/cards/c/CyclopeanTomb.java +++ b/Mage.Sets/src/mage/cards/c/CyclopeanTomb.java @@ -28,6 +28,7 @@ package mage.cards.c; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; @@ -215,11 +216,12 @@ class CyclopeanTombCounterCondition implements Condition { public boolean apply(Game game, Ability source) { List permanents = game.getBattlefield().getAllActivePermanents(mireFilter, game); + Permanent cyclopeanTombInstance = game.getPermanentOrLKIBattlefield(source.getSourceId()); CyclopeanTombCounterWatcher watcher = (CyclopeanTombCounterWatcher) game.getState().getWatchers().get(CyclopeanTombCounterWatcher.class.getName()); - - for(Permanent permanent : permanents) { - if(/* true that watcher is the instance of tomb && true that watcher contains iteration of permanent*/) { - return permanent.getCounters(game).getCount(CounterType.MIRE) > 0; + + for(Permanent land : permanents) { + if(watcher.landMiredByCyclopeanTombInstance(land, cyclopeanTombInstance, game)) { + return land.getCounters(game).getCount(CounterType.MIRE) > 0; } } return false; @@ -257,7 +259,11 @@ class ChooseLandEffect extends OneShotEffect { if(controller != null && mageObject != null){ TargetLandPermanent target = new TargetLandPermanent(1, 1, filter, true); - if (controller.chooseTarget(Outcome.Neutral, target, source, game)) { + /*Player must choose a land each upkeep. Using the message are above the player hand where frequent interactions + * take place is the most logical way to prompt for this scenario. A new constructor added to provide a not optional + * option for any cards like this where the player must choose a target in such the way this card requires. + */ + if (controller.chooseTarget(Outcome.Neutral, target, source, game, false)) { Permanent chosenLand = game.getPermanent(target.getFirstTarget()); if(chosenLand != null) { game.getState().setValue(mageObject.getId() + "_land", target.getFirstTarget()); @@ -282,14 +288,15 @@ class LandIdPredicate implements Predicate { @Override public boolean apply(Permanent input, Game game) { + Permanent cyclopeanTombInstance = game.getPermanentOrLKIBattlefield(source.getSourceId()); CyclopeanTombCounterWatcher watcher = (CyclopeanTombCounterWatcher) game.getState().getWatchers().get(CyclopeanTombCounterWatcher.class.getName()); - return /* true that watcher is the instance of tomb && true that watcher contains the targeted land*/; + return watcher.landMiredByCyclopeanTombInstance(input, cyclopeanTombInstance, game); } } class CyclopeanTombCounterWatcher extends Watcher { - Map lands = new HashMap<>(); + public HashMap> counterData = new HashMap<>(); public CyclopeanTombCounterWatcher() { super(CyclopeanTombCounterWatcher.class.getName(), WatcherScope.GAME); @@ -297,6 +304,11 @@ class CyclopeanTombCounterWatcher extends Watcher { public CyclopeanTombCounterWatcher(final CyclopeanTombCounterWatcher watcher) { super(watcher); + for (MageObjectReference mageObjectReference : watcher.counterData.keySet()) { + Set miredLands = new HashSet<>(); + miredLands.addAll(watcher.counterData.get(mageObjectReference)); + counterData.put(mageObjectReference, miredLands); + } } @Override @@ -307,9 +319,26 @@ class CyclopeanTombCounterWatcher extends Watcher { @Override public void watch(GameEvent event, Game game) { if(event.getType() == GameEvent.EventType.COUNTER_ADDED || event.getType() == GameEvent.EventType.COUNTERS_ADDED) { -/* if(land counter was added not in lands) { - lands.put(the stuff needed to identify the instance of tomb and the land the counter was added); + MageObjectReference cylopeanTombInstance = new MageObjectReference(/*ID needs to go here*/, game); + Set miredLands = counterData.get(cylopeanTombInstance); + if (miredLands != null) { + miredLands.add(new MageObjectReference(event.getTargetId(), game)); + } else { + miredLands = new HashSet<>(); + miredLands.add(new MageObjectReference(event.getTargetId(), game)); + counterData.put(cylopeanTombInstance, miredLands); } -*/ } + } + } + + @Override + public void reset() { + super.reset(); + counterData.clear(); + } + + public boolean landMiredByCyclopeanTombInstance(Permanent land, Permanent cylopeanTombInstance, Game game) { + Set miredLands = counterData.get(new MageObjectReference(cylopeanTombInstance, game)); + return miredLands != null && miredLands.contains(new MageObjectReference(land, game)); } } From 949784af0d165b9287fd6adb08d7615bf6c39a72 Mon Sep 17 00:00:00 2001 From: MTGfan Date: Tue, 27 Dec 2016 02:33:55 -0500 Subject: [PATCH 10/10] Gilder Bairn: added filter so player can't accidentally pick a card without counter(s) on it. --- Mage.Sets/src/mage/cards/g/GilderBairn.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Mage.Sets/src/mage/cards/g/GilderBairn.java b/Mage.Sets/src/mage/cards/g/GilderBairn.java index 24881249dd0..321e66118c5 100644 --- a/Mage.Sets/src/mage/cards/g/GilderBairn.java +++ b/Mage.Sets/src/mage/cards/g/GilderBairn.java @@ -40,6 +40,8 @@ import mage.constants.CardType; import mage.constants.Outcome; import mage.constants.Zone; import mage.counters.Counter; +import mage.filter.FilterPermanent; +import mage.filter.predicate.permanent.CounterAnyPredicate; import mage.game.Game; import mage.game.permanent.Permanent; import mage.target.TargetPermanent; @@ -49,6 +51,12 @@ import mage.target.TargetPermanent; * @author LevelX2 */ public class GilderBairn extends CardImpl { + + private static final FilterPermanent filter = new FilterPermanent(); + + static { + filter.add(new CounterAnyPredicate()); + } public GilderBairn(UUID ownerId, CardSetInfo setInfo) { super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{1}{G/U}{G/U}"); @@ -60,7 +68,7 @@ public class GilderBairn extends CardImpl { // {2}{GU}, {untap}: For each counter on target permanent, put another of those counters on that permanent. Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new GilderBairnEffect(), new ManaCostsImpl("{2}{G/U}")); ability.addCost(new UntapSourceCost()); - ability.addTarget(new TargetPermanent()); + ability.addTarget(new TargetPermanent(filter)); this.addAbility(ability); }