From 63ccafacb1ec4de1bcffcdb70012c414804334f0 Mon Sep 17 00:00:00 2001 From: Duncan Townsend Date: Sun, 18 Jan 2015 23:50:39 -0500 Subject: [PATCH 01/14] Added implementation of Balduvian Rage --- .../src/mage/sets/coldsnap/BalduvianRage.java | 73 +++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 Mage.Sets/src/mage/sets/coldsnap/BalduvianRage.java diff --git a/Mage.Sets/src/mage/sets/coldsnap/BalduvianRage.java b/Mage.Sets/src/mage/sets/coldsnap/BalduvianRage.java new file mode 100644 index 0000000000..b4a414ed61 --- /dev/null +++ b/Mage.Sets/src/mage/sets/coldsnap/BalduvianRage.java @@ -0,0 +1,73 @@ +/* + * 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.sets.coldsnap; + +import java.util.UUID; + +import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Rarity; +import mage.abilities.dynamicvalue.common.ManacostVariableValue; +import mage.abilities.dynamicvalue.common.StaticValue; +import mage.abilities.effects.common.continious.BoostTargetEffect; +import mage.constants.Duration; +import mage.target.common.TargetCreaturePermanent; + +import mage.abilities.effects.common.CreateDelayedTriggeredAbilityEffect; +import mage.abilities.common.delayed.AtTheBeginOfNextUpkeepDelayedTriggeredAbility; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; + + +/** + * @author duncancmt + */ +public class BalduvianRage extends CardImpl { + + public BalduvianRage(UUID ownerId) { + super(ownerId, 76, "Balduvian Rage", Rarity.UNCOMMON, new CardType[]{CardType.INSTANT}, "{X}{R}"); + this.expansionSetCode = "CSP"; + this.color.setRed(true); + + // Target attacking creature gets +X/+0 until end of turn. + this.getSpellAbility().addEffect(new BoostTargetEffect(new ManacostVariableValue(), new StaticValue(0), Duration.EndOfTurn)); + this.getSpellAbility().addTarget(new TargetCreaturePermanent()); + + // Draw a card at the beginning of the next turn's upkeep. + this.getSpellAbility().addEffect(new CreateDelayedTriggeredAbilityEffect(new AtTheBeginOfNextUpkeepDelayedTriggeredAbility(new DrawCardSourceControllerEffect(1)),false)); + } + + public BalduvianRage(final BalduvianRage card) { + super(card); + } + + @Override + public BalduvianRage copy() { + return new BalduvianRage(this); + } +} From efd820c2ddce9505cf4bfd36a0733e0416e7a6ff Mon Sep 17 00:00:00 2001 From: Duncan Townsend Date: Mon, 19 Jan 2015 01:06:44 -0500 Subject: [PATCH 02/14] Added implementation of Rally the Righteous --- .../mage/sets/ravnika/RallyTheRighteous.java | 143 ++++++++++++++++++ 1 file changed, 143 insertions(+) create mode 100644 Mage.Sets/src/mage/sets/ravnika/RallyTheRighteous.java diff --git a/Mage.Sets/src/mage/sets/ravnika/RallyTheRighteous.java b/Mage.Sets/src/mage/sets/ravnika/RallyTheRighteous.java new file mode 100644 index 0000000000..b0c148e0f2 --- /dev/null +++ b/Mage.Sets/src/mage/sets/ravnika/RallyTheRighteous.java @@ -0,0 +1,143 @@ +/* + * 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.sets.ravnika; + +import java.util.UUID; + +import mage.constants.CardType; +import mage.constants.Rarity; +import mage.constants.Outcome; +import mage.cards.CardImpl; +import mage.filter.common.FilterCreaturePermanent; +import mage.target.common.TargetCreaturePermanent; +import mage.abilities.effects.ContinuousEffectImpl; +import mage.abilities.effects.OneShotEffect; +import mage.constants.Duration; +import mage.constants.Layer; +import mage.constants.SubLayer; +import mage.game.permanent.Permanent; +import mage.ObjectColor; +import mage.abilities.Ability; +import mage.game.Game; + +/** + * @author duncancmt + */ + +public class RallyTheRighteous extends CardImpl { + + public RallyTheRighteous(UUID ownerId) { + super(ownerId, 222, "Rally the Righteous", Rarity.COMMON, new CardType[]{CardType.INSTANT}, "{1}{R}{W}"); + this.expansionSetCode = "RAV"; + this.color.setRed(true); + this.color.setWhite(true); + + // Radiance — Untap target creature and each other creature that shares a color with it. Those creatures get +2/+0 until end of turn. + this.getSpellAbility().addTarget(new TargetCreaturePermanent()); + this.getSpellAbility().addEffect(new RallyTheRighteousUntapEffect()); + this.getSpellAbility().addEffect(new RallyTheRighteousBoostEffect()); + } + + public RallyTheRighteous(final RallyTheRighteous card) { + super(card); + } + + @Override + public RallyTheRighteous copy() { + return new RallyTheRighteous(this); + } +} + + +class RallyTheRighteousUntapEffect extends OneShotEffect { + + public RallyTheRighteousUntapEffect() { + super(Outcome.Untap); + staticText = "Radiance — Untap target creature and each other creature that shares a color with it."; + } + + public RallyTheRighteousUntapEffect(final RallyTheRighteousUntapEffect effect) { + super(effect); + } + + @Override + public RallyTheRighteousUntapEffect copy() { + return new RallyTheRighteousUntapEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent target = game.getPermanent(targetPointer.getFirst(game, source)); + if (target != null) { + ObjectColor color = target.getColor(); + target.untap(game); + for (Permanent p : game.getBattlefield().getActivePermanents(new FilterCreaturePermanent(), source.getControllerId(), game)) { + if (p.getColor().shares(color) && !p.getId().equals(target.getId())) { + p.untap(game); + } + } + return true; + } + return false; + } +} + + +class RallyTheRighteousBoostEffect extends ContinuousEffectImpl { + + public RallyTheRighteousBoostEffect() { + super(Duration.EndOfTurn, Layer.PTChangingEffects_7, SubLayer.ModifyPT_7c, Outcome.BoostCreature); + staticText = "Radiance — Target creature and each other creature that shares a color with it get +2/+0 until end of turn."; + } + + public RallyTheRighteousBoostEffect(final RallyTheRighteousBoostEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent target = game.getPermanent(targetPointer.getFirst(game, source)); + if (target != null) { + ObjectColor color = target.getColor(); + target.addPower(2); + for (Permanent p : game.getBattlefield().getActivePermanents(new FilterCreaturePermanent(), source.getControllerId(), game)) { + if (p.getColor().shares(color) && !p.getId().equals(target.getId())) { + p.addPower(2); + } + } + return true; + } + return false; + } + + @Override + public RallyTheRighteousBoostEffect copy() { + return new RallyTheRighteousBoostEffect(this); + } +} From 56e2fff814e98792c281de7896d9f88dd76a72f6 Mon Sep 17 00:00:00 2001 From: Duncan Townsend Date: Mon, 19 Jan 2015 01:51:13 -0500 Subject: [PATCH 03/14] Added implementation of Mercy Killing --- .../mage/sets/shadowmoor/MercyKilling.java | 100 ++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 Mage.Sets/src/mage/sets/shadowmoor/MercyKilling.java diff --git a/Mage.Sets/src/mage/sets/shadowmoor/MercyKilling.java b/Mage.Sets/src/mage/sets/shadowmoor/MercyKilling.java new file mode 100644 index 0000000000..2c47938f03 --- /dev/null +++ b/Mage.Sets/src/mage/sets/shadowmoor/MercyKilling.java @@ -0,0 +1,100 @@ +/* + * 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.sets.shadowmoor; + +import java.util.UUID; + +import mage.constants.CardType; +import mage.constants.Rarity; +import mage.abilities.effects.common.SacrificeTargetEffect; +import mage.cards.CardImpl; +import mage.target.common.TargetCreaturePermanent; +import mage.game.permanent.token.ElfToken; +import mage.abilities.effects.OneShotEffect; +import mage.constants.Zone; +import mage.game.Game; +import mage.abilities.Ability; +import mage.game.permanent.Permanent; +import mage.constants.Outcome; + + +/** + * @author duncancmt + */ +public class MercyKilling extends CardImpl { + + public MercyKilling(UUID ownerId) { + super(ownerId, 231, "Mercy Killing", Rarity.UNCOMMON, new CardType[]{CardType.INSTANT}, "{2}{G/W}"); + this.expansionSetCode = "SHM"; + this.color.setGreen(true); + this.color.setWhite(true); + + // Target creature's controller sacrifices it, then puts X 1/1 green and white Elf Warrior creature tokens onto the battlefield, where X is that creature's power. + this.getSpellAbility().addTarget(new TargetCreaturePermanent()); + this.getSpellAbility().addEffect(new SacrificeTargetEffect()); + this.getSpellAbility().addEffect(new MercyKillingTokenEffect()); + } + + public MercyKilling(final MercyKilling card) { + super(card); + } + + @Override + public MercyKilling copy() { + return new MercyKilling(this); + } +} + +class MercyKillingTokenEffect extends OneShotEffect { + + public MercyKillingTokenEffect() { + super(Outcome.PutCreatureInPlay); + staticText = "Its controller puts X 1/1 green and white Elf Warrior creature tokens onto the battlefield, where X is that creature's power."; + } + + public MercyKillingTokenEffect(final MercyKillingTokenEffect effect) { + super(effect); + } + + @Override + public MercyKillingTokenEffect copy() { + return new MercyKillingTokenEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent permanent = (Permanent) game.getLastKnownInformation(targetPointer.getFirst(game, source), Zone.BATTLEFIELD); + if (permanent != null) { + int power = permanent.getPower().getValue(); + ElfToken token = new ElfToken(); + token.putOntoBattlefield(power, game, source.getSourceId(), permanent.getControllerId()); + } + return true; + } + +} From faea2c847e8f29d666f9696e1dd651330d4818b3 Mon Sep 17 00:00:00 2001 From: Duncan Townsend Date: Mon, 19 Jan 2015 16:32:16 -0500 Subject: [PATCH 04/14] Added implementation of Veilstone Amulet --- .../sets/futuresight/VeilstoneAmulet.java | 111 ++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 Mage.Sets/src/mage/sets/futuresight/VeilstoneAmulet.java diff --git a/Mage.Sets/src/mage/sets/futuresight/VeilstoneAmulet.java b/Mage.Sets/src/mage/sets/futuresight/VeilstoneAmulet.java new file mode 100644 index 0000000000..624b130fc4 --- /dev/null +++ b/Mage.Sets/src/mage/sets/futuresight/VeilstoneAmulet.java @@ -0,0 +1,111 @@ +/* + * 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.sets.futuresight; + +import java.util.UUID; + +import mage.cards.CardImpl; +import mage.constants.Rarity; +import mage.constants.CardType; + + +import mage.abilities.common.SpellCastControllerTriggeredAbility; + +import mage.abilities.effects.ContinuousRuleModifiyingEffectImpl; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.game.Game; +import mage.abilities.Ability; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; +import mage.game.events.GameEvent.EventType; + +/** + * @author duncancmt + */ +public class VeilstoneAmulet extends CardImpl { + + public VeilstoneAmulet(UUID ownerId) { + super(ownerId, 166, "Veilstone Amulet", Rarity.RARE, new CardType[]{CardType.ARTIFACT}, "{3}"); + this.expansionSetCode = "FUT"; + + // Whenever you cast a spell, creatures you control can't be the targets of spells or abilities your opponents control this turn. + this.addAbility(new SpellCastControllerTriggeredAbility(new VeilstoneAmuletEffect(), false)); + } + + public VeilstoneAmulet(final VeilstoneAmulet card) { + super(card); + } + + @Override + public VeilstoneAmulet copy() { + return new VeilstoneAmulet(this); + } +} + +// Veilstone Amulet's effect is strange. It effects all creatures you control, +// even if they entered the battlefield after the ability resolved. It modifies +// the rules of the game until end of turn. +class VeilstoneAmuletEffect extends ContinuousRuleModifiyingEffectImpl { + + public VeilstoneAmuletEffect() { + super(Duration.EndOfTurn, Outcome.Benefit); + } + + public VeilstoneAmuletEffect(final VeilstoneAmuletEffect effect) { + super(effect); + } + + @Override + public VeilstoneAmuletEffect copy() { + return new VeilstoneAmuletEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + return true; + } + + @Override + public boolean applies(GameEvent event, Ability ability, Game game) { + if (event.getType() == EventType.TARGET) { + Permanent permanent = game.getPermanent(event.getTargetId()); + UUID permanentController = permanent.getControllerId(); + UUID abilityController = ability.getControllerId(); + UUID sourceController = event.getPlayerId(); + + if (permanent != null && + permanent.getCardType().contains(CardType.CREATURE) && + permanentController.equals(abilityController) && + game.getPlayer(abilityController).hasOpponent(sourceController, game)) { + return true; + } + } + return false; + } +} From 1a95461e4281cfd5d26d7409c3a0271af8d52d7f Mon Sep 17 00:00:00 2001 From: Duncan Townsend Date: Mon, 19 Jan 2015 16:32:36 -0500 Subject: [PATCH 05/14] Added implementation of Ink-Treader Nephilim Implementation is slightly wrong because it does not allow the player to choose the order that copies go on the stack. --- .../sets/guildpact/InkTreaderNephilim.java | 216 ++++++++++++++++++ 1 file changed, 216 insertions(+) create mode 100644 Mage.Sets/src/mage/sets/guildpact/InkTreaderNephilim.java diff --git a/Mage.Sets/src/mage/sets/guildpact/InkTreaderNephilim.java b/Mage.Sets/src/mage/sets/guildpact/InkTreaderNephilim.java new file mode 100644 index 0000000000..ac172d237a --- /dev/null +++ b/Mage.Sets/src/mage/sets/guildpact/InkTreaderNephilim.java @@ -0,0 +1,216 @@ +/* + * 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.sets.guildpact; + +import java.util.UUID; +import mage.constants.CardType; +import mage.constants.Rarity; +import mage.MageInt; +import mage.cards.CardImpl; + +import mage.abilities.Ability; +import mage.abilities.SpellAbility; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.effects.Effect; +import mage.abilities.effects.OneShotEffect; +import mage.constants.Outcome; +import mage.constants.Zone; +import mage.filter.FilterSpell; +import mage.filter.FilterPermanent; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.CardTypePredicate; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; +import mage.game.stack.Spell; +import mage.target.Target; +import mage.abilities.Modes; + + +/** + * @author duncancmt + */ +public class InkTreaderNephilim extends CardImpl { + + public InkTreaderNephilim(UUID ownerId) { + super(ownerId, 117, "Ink-Treader Nephilim", Rarity.RARE, new CardType[]{CardType.CREATURE}, "{R}{G}{W}{U}"); + this.expansionSetCode = "GPT"; + this.subtype.add("Nephilim"); + this.color.setRed(true); + this.color.setGreen(true); + this.color.setWhite(true); + this.color.setBlue(true); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // Whenever a player casts an instant or sorcery spell, if that spell targets only Ink-Treader Nephilim, copy the spell for each other creature that spell could target. Each copy targets a different one of those creatures. + this.addAbility(new InkTreaderNephilimTriggeredAbility()); + } + + public InkTreaderNephilim(final InkTreaderNephilim card) { + super(card); + } + + @Override + public InkTreaderNephilim copy() { + return new InkTreaderNephilim(this); + } +} + +class InkTreaderNephilimTriggeredAbility extends TriggeredAbilityImpl { + + private static final FilterSpell filter = new FilterSpell(); + + static { + filter.add(Predicates.or( + new CardTypePredicate(CardType.INSTANT), + new CardTypePredicate(CardType.SORCERY))); + } + + InkTreaderNephilimTriggeredAbility() { + super(Zone.BATTLEFIELD, new InkTreaderNephilimEffect(), false); + } + + InkTreaderNephilimTriggeredAbility(final InkTreaderNephilimTriggeredAbility ability) { + super(ability); + } + + @Override + public InkTreaderNephilimTriggeredAbility copy() { + return new InkTreaderNephilimTriggeredAbility(this); + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + if (event.getType() == GameEvent.EventType.SPELL_CAST) { + Spell spell = game.getStack().getSpell(event.getTargetId()); + if (spell != null && + spell.getCardType().contains(CardType.INSTANT) || spell.getCardType().contains(CardType.SORCERY)){ + for (Effect effect : getEffects()) { + effect.setValue("TriggeringSpell", spell); + } + return true; + } + } + return false; + } + + @Override + public boolean checkInterveningIfClause(Game game) { + Spell spell = (Spell) getEffects().get(0).getValue("TriggeringSpell"); + if (spell != null) { + boolean allTargetsInkTreaderNephilim = true; + boolean atLeastOneTargetsInkTreaderNephilim = false; + for (SpellAbility sa: spell.getSpellAbilities()){ + Modes modes = sa.getModes(); + for (UUID mode : modes.getSelectedModes()) { + for (Target targetInstance : modes.get(mode).getTargets()) { + for (UUID target : targetInstance.getTargets()) { + allTargetsInkTreaderNephilim &= target.equals(sourceId); + atLeastOneTargetsInkTreaderNephilim |= target.equals(sourceId); + } + } + } + } + if (allTargetsInkTreaderNephilim && atLeastOneTargetsInkTreaderNephilim) { + return true; + } + } + return false; + } + + @Override + public String getRule() { + return "Whenever a player casts an instant or sorcery spell, if that spell targets only Ink-Treader Nephilim, copy the spell for each other creature that spell could target. Each copy targets a different one of those creatures."; + } +} + +class InkTreaderNephilimEffect extends OneShotEffect { + + private static final FilterPermanent filter = new FilterPermanent(); + + static { + filter.add(new CardTypePredicate(CardType.CREATURE)); + } + + public InkTreaderNephilimEffect() { + super(Outcome.Copy); + } + + public InkTreaderNephilimEffect(final InkTreaderNephilimEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source) { + Spell spell = (Spell) getValue("TriggeringSpell"); + if (spell != null) { + for (Permanent permanent : game.getBattlefield().getActivePermanents(filter, source.getControllerId(), source.getSourceId(), game)) { + Spell copy = spell.copySpell(); + copy.setControllerId(source.getControllerId()); + copy.setCopiedSpell(true); + if (permanent.getId().equals(source.getSourceId())) { + continue; // copy only for other creatures + } + boolean legal = true; + for (SpellAbility sa : copy.getSpellAbilities()) { + Modes modes = sa.getModes(); + for (UUID mode : modes.getSelectedModes()) { + for (Target targetInstance : modes.get(mode).getTargets()) { + legal &= targetInstance.canTarget(permanent.getId(), sa, game); + } + } + } + if (legal) { + for (SpellAbility sa : copy.getSpellAbilities()) { + Modes modes = sa.getModes(); + for (UUID mode : modes.getSelectedModes()) { + for (Target targetInstance : modes.get(mode).getTargets()) { + int numTargets = targetInstance.getNumberOfTargets(); + targetInstance.clearChosen(); + while (numTargets > 0) { + targetInstance.add(permanent.getId(), game); + numTargets--; + } + } + } + } + game.getStack().push(copy); + } + } + return true; + } + return false; + } + + @Override + public InkTreaderNephilimEffect copy() { + return new InkTreaderNephilimEffect(this); + } + +} From 25ec2a95d008609b8c6af8c4cc79639d7c7706fe Mon Sep 17 00:00:00 2001 From: Duncan Townsend Date: Mon, 19 Jan 2015 19:21:21 -0500 Subject: [PATCH 06/14] Added implementation of Breath of Fury --- .../src/mage/sets/ravnika/BreathOfFury.java | 209 ++++++++++++++++++ 1 file changed, 209 insertions(+) create mode 100644 Mage.Sets/src/mage/sets/ravnika/BreathOfFury.java diff --git a/Mage.Sets/src/mage/sets/ravnika/BreathOfFury.java b/Mage.Sets/src/mage/sets/ravnika/BreathOfFury.java new file mode 100644 index 0000000000..5d493b9e82 --- /dev/null +++ b/Mage.Sets/src/mage/sets/ravnika/BreathOfFury.java @@ -0,0 +1,209 @@ +/* + * 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.sets.ravnika; + +import java.util.UUID; + +import mage.cards.CardImpl; +import mage.constants.Rarity; +import mage.constants.CardType; + +import mage.target.TargetPermanent; +import mage.target.common.TargetControlledCreaturePermanent; +import mage.abilities.effects.common.AttachEffect; +import mage.constants.Outcome; +import mage.abilities.Ability; +import mage.abilities.keyword.EnchantAbility; + +import mage.abilities.TriggeredAbilityImpl; +import mage.constants.Zone; +import mage.game.events.GameEvent; +import mage.game.Game; +import mage.game.events.DamagedPlayerEvent; +import mage.abilities.effects.OneShotEffect; +import mage.game.permanent.Permanent; +import mage.abilities.effects.Effect; +import mage.target.targetpointer.FixedTarget; +import mage.players.Player; +import mage.target.Target; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.game.turn.TurnMod; +import mage.constants.TurnPhase; +import mage.MageObject; +import mage.filter.predicate.Predicate; + +/** + * @author duncancmt + */ +public class BreathOfFury extends CardImpl { + public BreathOfFury(UUID ownerId) { + super(ownerId, 116, "Breath of Fury", Rarity.RARE, new CardType[]{CardType.ENCHANTMENT}, "{2}{R}{R}"); + this.expansionSetCode = "RAV"; + this.subtype.add("Aura"); + + this.color.setRed(true); + + // Enchant creature you control + TargetPermanent auraTarget = new TargetControlledCreaturePermanent(); + this.getSpellAbility().addTarget(auraTarget); + this.getSpellAbility().addEffect(new AttachEffect(Outcome.Benefit)); + Ability ability = new EnchantAbility(auraTarget.getTargetName()); + this.addAbility(ability); + + // When enchanted creature deals combat damage to a player, sacrifice it and attach Breath of Fury to a creature you control. If you do, untap all creatures you control and after this phase, there is an additional combat phase. + this.addAbility(new BreathOfFuryAbility()); + } + + public BreathOfFury(final BreathOfFury card) { + super(card); + } + + @Override + public BreathOfFury copy() { + return new BreathOfFury(this); + } +} + +class BreathOfFuryAbility extends TriggeredAbilityImpl { + + public BreathOfFuryAbility() { + super(Zone.BATTLEFIELD, new BreathOfFuryEffect()); + } + + public BreathOfFuryAbility(final BreathOfFuryAbility ability) { + super(ability); + } + + @Override + public BreathOfFuryAbility copy() { + return new BreathOfFuryAbility(this); + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + if (event instanceof DamagedPlayerEvent) { + DamagedPlayerEvent damageEvent = (DamagedPlayerEvent)event; + Permanent p = game.getPermanent(event.getSourceId()); + if (damageEvent.isCombatDamage() && p != null && p.getAttachments().contains(this.getSourceId())) { + for (Effect effect : getEffects()) { + effect.setTargetPointer(new FixedTarget(p.getId())); + } + return true; + } + } + return false; + } + + @Override + public String getRule() { + return "When enchanted creature deals combat damage to a player, sacrifice it and attach Breath of Fury to a creature you control. If you do, untap all creatures you control and after this phase, there is an additional combat phase."; + } +} + +class BreathOfFuryEffect extends OneShotEffect { + + public BreathOfFuryEffect() { + super(Outcome.Benefit); + staticText = "Sacrifice enchanted creature and attach Breath of Fury to a creature you control. If you do, untap all creatures you control and after this phase, there is an additional combat phase."; + } + + public BreathOfFuryEffect(final BreathOfFuryEffect effect) { + super(effect); + } + + @Override + public BreathOfFuryEffect copy() { + return new BreathOfFuryEffect(this); + } + + @Override + public boolean apply(Game game, Ability source){ + Permanent enchantedCreature = game.getPermanent(targetPointer.getFirst(game, source)); + Permanent enchantment = game.getPermanent(source.getSourceId()); + Player controller = game.getPlayer(source.getControllerId()); + Target target = new TargetControlledCreaturePermanent(new FilterCanBeEnchantedControlledCreaturePermanent(enchantment)); + target.setNotTarget(true); + if (enchantedCreature != null && + enchantedCreature.sacrifice(source.getSourceId(), game) && + enchantment != null && + controller != null && + target.canChoose(source.getSourceId(), source.getControllerId(), game)) { + controller.choose(outcome, target, source.getId(), game); + Permanent newCreature = game.getPermanent(target.getFirstTarget()); + if (newCreature != null && + newCreature.addAttachment(enchantment.getId(), game)) { + for (Permanent permanent : game.getBattlefield().getAllActivePermanents(new FilterControlledCreaturePermanent(), + controller.getId(), game)) { + permanent.untap(game); + } + game.getState().getTurnMods().add(new TurnMod(source.getControllerId(), TurnPhase.COMBAT, null, false)); + return true; + } + } + return false; + } +} + + + +class FilterCanBeEnchantedControlledCreaturePermanent extends FilterControlledCreaturePermanent { + + public FilterCanBeEnchantedControlledCreaturePermanent(final FilterCanBeEnchantedControlledCreaturePermanent filter) { + super(filter); + } + + public FilterCanBeEnchantedControlledCreaturePermanent(MageObject auraEnchantment) { + super("creature you control that could be enchanted by " + auraEnchantment.getName()); + this.add(new CanBeEnchantedPredicate(auraEnchantment)); + } + + @Override + public FilterCanBeEnchantedControlledCreaturePermanent copy() { + return new FilterCanBeEnchantedControlledCreaturePermanent(this); + } +} + + +class CanBeEnchantedPredicate implements Predicate { + + private final MageObject auraEnchantment; + + public CanBeEnchantedPredicate(MageObject auraEnchantment){ + this.auraEnchantment = auraEnchantment; + } + + @Override + public boolean apply(Permanent input, Game game) { + return !input.cantBeEnchantedBy(auraEnchantment, game); + } + + @Override + public String toString() { + return "CanBeEnchanted(" + auraEnchantment.toString() + ")"; + } +} From f08ec6f2aabe0b5b299f7a9119a19b3dd71317bd Mon Sep 17 00:00:00 2001 From: Duncan Townsend Date: Wed, 21 Jan 2015 22:16:31 -0500 Subject: [PATCH 07/14] Fixed null pointer dereference in Veilstone Amulet --- .../sets/futuresight/VeilstoneAmulet.java | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/Mage.Sets/src/mage/sets/futuresight/VeilstoneAmulet.java b/Mage.Sets/src/mage/sets/futuresight/VeilstoneAmulet.java index 624b130fc4..5861cc4a01 100644 --- a/Mage.Sets/src/mage/sets/futuresight/VeilstoneAmulet.java +++ b/Mage.Sets/src/mage/sets/futuresight/VeilstoneAmulet.java @@ -35,7 +35,7 @@ import mage.constants.CardType; import mage.abilities.common.SpellCastControllerTriggeredAbility; - +import mage.filter.FilterSpell; import mage.abilities.effects.ContinuousRuleModifiyingEffectImpl; import mage.constants.Duration; import mage.constants.Outcome; @@ -55,7 +55,10 @@ public class VeilstoneAmulet extends CardImpl { this.expansionSetCode = "FUT"; // Whenever you cast a spell, creatures you control can't be the targets of spells or abilities your opponents control this turn. - this.addAbility(new SpellCastControllerTriggeredAbility(new VeilstoneAmuletEffect(), false)); + this.addAbility(new SpellCastControllerTriggeredAbility(new VeilstoneAmuletEffect(), + new FilterSpell(), + false, + "Whenever you cast a spell, creatures you control can't be the targets of spells or abilities your opponents control this turn.")); } public VeilstoneAmulet(final VeilstoneAmulet card) { @@ -95,15 +98,15 @@ class VeilstoneAmuletEffect extends ContinuousRuleModifiyingEffectImpl { public boolean applies(GameEvent event, Ability ability, Game game) { if (event.getType() == EventType.TARGET) { Permanent permanent = game.getPermanent(event.getTargetId()); - UUID permanentController = permanent.getControllerId(); - UUID abilityController = ability.getControllerId(); - UUID sourceController = event.getPlayerId(); - - if (permanent != null && - permanent.getCardType().contains(CardType.CREATURE) && - permanentController.equals(abilityController) && - game.getPlayer(abilityController).hasOpponent(sourceController, game)) { - return true; + if (permanent != null) { + UUID permanentController = permanent.getControllerId(); + UUID abilityController = ability.getControllerId(); + UUID sourceController = event.getPlayerId(); + if (permanent.getCardType().contains(CardType.CREATURE) && + permanentController.equals(abilityController) && + game.getPlayer(abilityController).hasOpponent(sourceController, game)) { + return true; + } } } return false; From 00e79f2fdd9c1bc0d0992dad523ddd23f7bb4e38 Mon Sep 17 00:00:00 2001 From: Duncan Townsend Date: Wed, 21 Jan 2015 22:18:07 -0500 Subject: [PATCH 08/14] Fixed the color of Mercy Killing's tokens. --- .../mage/sets/shadowmoor/MercyKilling.java | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/Mage.Sets/src/mage/sets/shadowmoor/MercyKilling.java b/Mage.Sets/src/mage/sets/shadowmoor/MercyKilling.java index 2c47938f03..5d31d3636c 100644 --- a/Mage.Sets/src/mage/sets/shadowmoor/MercyKilling.java +++ b/Mage.Sets/src/mage/sets/shadowmoor/MercyKilling.java @@ -34,14 +34,14 @@ import mage.constants.Rarity; import mage.abilities.effects.common.SacrificeTargetEffect; import mage.cards.CardImpl; import mage.target.common.TargetCreaturePermanent; -import mage.game.permanent.token.ElfToken; import mage.abilities.effects.OneShotEffect; import mage.constants.Zone; import mage.game.Game; import mage.abilities.Ability; import mage.game.permanent.Permanent; import mage.constants.Outcome; - +import mage.game.permanent.token.Token; +import mage.MageInt; /** * @author duncancmt @@ -91,10 +91,24 @@ class MercyKillingTokenEffect extends OneShotEffect { Permanent permanent = (Permanent) game.getLastKnownInformation(targetPointer.getFirst(game, source), Zone.BATTLEFIELD); if (permanent != null) { int power = permanent.getPower().getValue(); - ElfToken token = new ElfToken(); + MercyKillingToken token = new MercyKillingToken(); token.putOntoBattlefield(power, game, source.getSourceId(), permanent.getControllerId()); } return true; } } + +class MercyKillingToken extends Token { + + public MercyKillingToken() { + super("Elf Warrior", "1/1 green and white Elf Warrior creature token"); + cardType.add(CardType.CREATURE); + color.setGreen(true); + color.setWhite(true); + subtype.add("Elf"); + subtype.add("Warrior"); + power = new MageInt(1); + toughness = new MageInt(1); + } +} From e27a504348197443e988b2f8f87f01b82f7ec52a Mon Sep 17 00:00:00 2001 From: Duncan Townsend Date: Wed, 21 Jan 2015 22:24:17 -0500 Subject: [PATCH 09/14] Allow Ink-Treader Nephilim's ability's controller to choose the order in which copies go on the stack. --- .../sets/guildpact/InkTreaderNephilim.java | 72 ++++++++++++++++++- 1 file changed, 69 insertions(+), 3 deletions(-) diff --git a/Mage.Sets/src/mage/sets/guildpact/InkTreaderNephilim.java b/Mage.Sets/src/mage/sets/guildpact/InkTreaderNephilim.java index ac172d237a..c632d7d58d 100644 --- a/Mage.Sets/src/mage/sets/guildpact/InkTreaderNephilim.java +++ b/Mage.Sets/src/mage/sets/guildpact/InkTreaderNephilim.java @@ -27,6 +27,9 @@ */ package mage.sets.guildpact; +import java.util.Collection; +import java.util.HashMap; +import java.util.ArrayList; import java.util.UUID; import mage.constants.CardType; import mage.constants.Rarity; @@ -48,8 +51,15 @@ import mage.game.Game; import mage.game.events.GameEvent; import mage.game.permanent.Permanent; import mage.game.stack.Spell; +import java.util.Map; +import java.util.Set; +import java.util.List; +import mage.MageObject; import mage.target.Target; import mage.abilities.Modes; +import mage.filter.predicate.ObjectPlayer; +import mage.filter.predicate.ObjectPlayerPredicate; +import mage.target.TargetPermanent; /** @@ -170,9 +180,11 @@ class InkTreaderNephilimEffect extends OneShotEffect { public boolean apply(Game game, Ability source) { Spell spell = (Spell) getValue("TriggeringSpell"); if (spell != null) { - for (Permanent permanent : game.getBattlefield().getActivePermanents(filter, source.getControllerId(), source.getSourceId(), game)) { + Map targetable = new HashMap<>(); + UUID controller = source.getControllerId(); + for (Permanent permanent : game.getBattlefield().getActivePermanents(filter, controller, source.getSourceId(), game)) { Spell copy = spell.copySpell(); - copy.setControllerId(source.getControllerId()); + copy.setControllerId(controller); copy.setCopiedSpell(true); if (permanent.getId().equals(source.getSourceId())) { continue; // copy only for other creatures @@ -200,7 +212,32 @@ class InkTreaderNephilimEffect extends OneShotEffect { } } } - game.getStack().push(copy); + targetable.put(permanent.getId(), copy); + } + } + while (targetable.size() > 0) { + TargetPermanent target = new TargetPermanent(0, 1, + new FilterPermanentFromSet("creature that spell could target ("+Integer.toString(targetable.size())+" remaining)", + targetable.keySet()), + true); + if (target.possibleTargets(controller, game).size() > 1 + && target.canChoose(source.getSourceId(), controller, game)) { + game.getPlayer(controller).choose(Outcome.Neutral, target, source.getId(), game); + } + Collection chosen = target.getTargets(); + if (chosen.size() == 0) { + chosen = targetable.keySet(); + } + List toDelete = new ArrayList<>(); + for (UUID chosenId : chosen) { + Spell chosenCopy = targetable.get(chosenId); + if (chosenCopy != null) { + game.getStack().push(chosenCopy); + toDelete.add(chosenId); + } + } + for (UUID id : toDelete) { + targetable.remove(id); } } return true; @@ -214,3 +251,32 @@ class InkTreaderNephilimEffect extends OneShotEffect { } } + +class FromSetPredicate> implements ObjectPlayerPredicate { + protected Set set; + + public FromSetPredicate(Set set) { + this.set = set; + } + + public boolean apply(T input, Game game) { + return set.contains(input.getObject().getId()); + } +} + +class FilterPermanentFromSet extends FilterPermanent { + public FilterPermanentFromSet(Set set) { + super(); + this.extraPredicates.add(new FromSetPredicate(set)); + } + + public FilterPermanentFromSet(String name, Set set) { + super(name); + this.extraPredicates.add(new FromSetPredicate(set)); + } + + public FilterPermanentFromSet(String subtype, String name, Set set) { + super(subtype, name); + this.extraPredicates.add(new FromSetPredicate(set)); + } +} From d341e39e0525451b952d6d7164fb0ae6a8b77748 Mon Sep 17 00:00:00 2001 From: Duncan Townsend Date: Thu, 22 Jan 2015 01:17:13 -0500 Subject: [PATCH 10/14] Added mage.util.SpellTargetAddress a utility for addressing and iterating over the targets of a spell --- Mage/src/mage/util/SpellTargetAddress.java | 155 +++++++++++++++++++++ 1 file changed, 155 insertions(+) create mode 100644 Mage/src/mage/util/SpellTargetAddress.java diff --git a/Mage/src/mage/util/SpellTargetAddress.java b/Mage/src/mage/util/SpellTargetAddress.java new file mode 100644 index 0000000000..9220afab77 --- /dev/null +++ b/Mage/src/mage/util/SpellTargetAddress.java @@ -0,0 +1,155 @@ +/* + * 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.util; + +import java.util.Iterator; +import java.util.UUID; +import mage.abilities.Mode; +import mage.abilities.Modes; +import mage.abilities.SpellAbility; +import mage.game.stack.Spell; +import mage.target.Target; + + +/** + * @author duncancmt + */ +public class SpellTargetAddress { + protected int spellAbilityIndex; + protected UUID mode; + protected int targetIndex; + + public SpellTargetAddress(int spellAbilityIndex, UUID mode, int targetIndex) { + this.spellAbilityIndex = spellAbilityIndex; + this.mode = mode; + this.targetIndex = targetIndex; + } + + protected static class SpellTargetAddressIterable implements Iterable { + protected final Spell spell; + + public SpellTargetAddressIterable(Spell spell) { + this.spell = spell; + } + + public Iterator iterator() { + return new SpellTargetAddressIterator(spell); + } + } + + protected static class SpellTargetAddressIterator implements Iterator { + protected Iterator spellAbilityIterator; + protected Integer lastSpellAbilityIndex = null; + protected Iterator modeIterator = null; + protected Modes modes = null; + protected UUID lastMode = null; + protected Iterator targetIterator = null; + protected Integer lastTargetIndex = null; + + public SpellTargetAddressIterator(Spell spell) { + this.spellAbilityIterator = spell.getSpellAbilities().iterator(); + calcNext(); + } + + public boolean hasNext() { + return lastTargetIndex != null; + } + + public SpellTargetAddress next() { + SpellTargetAddress ret = new SpellTargetAddress(lastSpellAbilityIndex, + lastMode, + lastTargetIndex); + calcNext(); + return ret; + + } + + public void remove() { + throw new UnsupportedOperationException(); + } + + protected void calcNext() { + if (targetIterator == null) { + if (modeIterator == null) { + if (spellAbilityIterator.hasNext()) { + if (lastSpellAbilityIndex == null) { + lastSpellAbilityIndex = 0; + } else { + lastSpellAbilityIndex++; + } + modes = spellAbilityIterator.next().getModes(); + modeIterator = modes.getSelectedModes().iterator(); + } else { + lastSpellAbilityIndex = null; + return; + } + } + + if (modeIterator != null && modeIterator.hasNext()) { + lastMode = modeIterator.next(); + targetIterator = modes.get(lastMode).getTargets().iterator(); + } else { + lastMode = null; + modes = null; + modeIterator = null; + calcNext(); + } + } + + if (targetIterator != null && targetIterator.hasNext()) { + if (lastTargetIndex == null) { + lastTargetIndex = 0; + } else { + lastTargetIndex++; + } + targetIterator.next(); + } else { + targetIterator = null; + lastTargetIndex = null; + calcNext(); + } + } + } + + + public static Iterable walk(Spell spell) { + return new SpellTargetAddressIterable(spell); + } + + public Target getTarget(Spell spell) { + return spell.getSpellAbilities().get(spellAbilityIndex).getModes().get(mode).getTargets().get(targetIndex); + } + + public Mode getMode(Spell spell) { + return spell.getSpellAbilities().get(spellAbilityIndex).getModes().get(mode); + } + + public SpellAbility getSpellAbility(Spell spell) { + return spell.getSpellAbilities().get(spellAbilityIndex); + } +} From 018a2a163e18fd44807fe2276c20daf9ce5cac5c Mon Sep 17 00:00:00 2001 From: Duncan Townsend Date: Thu, 22 Jan 2015 01:19:17 -0500 Subject: [PATCH 11/14] Use mage.util.SpellTargetAddress in Ink-Treader Nephilim. --- .../sets/guildpact/InkTreaderNephilim.java | 30 +++++++------------ 1 file changed, 11 insertions(+), 19 deletions(-) diff --git a/Mage.Sets/src/mage/sets/guildpact/InkTreaderNephilim.java b/Mage.Sets/src/mage/sets/guildpact/InkTreaderNephilim.java index c632d7d58d..7dfdd03cad 100644 --- a/Mage.Sets/src/mage/sets/guildpact/InkTreaderNephilim.java +++ b/Mage.Sets/src/mage/sets/guildpact/InkTreaderNephilim.java @@ -60,7 +60,7 @@ import mage.abilities.Modes; import mage.filter.predicate.ObjectPlayer; import mage.filter.predicate.ObjectPlayerPredicate; import mage.target.TargetPermanent; - +import mage.util.SpellTargetAddress; /** * @author duncancmt @@ -190,26 +190,18 @@ class InkTreaderNephilimEffect extends OneShotEffect { continue; // copy only for other creatures } boolean legal = true; - for (SpellAbility sa : copy.getSpellAbilities()) { - Modes modes = sa.getModes(); - for (UUID mode : modes.getSelectedModes()) { - for (Target targetInstance : modes.get(mode).getTargets()) { - legal &= targetInstance.canTarget(permanent.getId(), sa, game); - } - } + for (SpellTargetAddress addr : SpellTargetAddress.walk(copy)) { + Target targetInstance = addr.getTarget(copy); + legal &= targetInstance.canTarget(permanent.getId(), addr.getSpellAbility(copy), game); } if (legal) { - for (SpellAbility sa : copy.getSpellAbilities()) { - Modes modes = sa.getModes(); - for (UUID mode : modes.getSelectedModes()) { - for (Target targetInstance : modes.get(mode).getTargets()) { - int numTargets = targetInstance.getNumberOfTargets(); - targetInstance.clearChosen(); - while (numTargets > 0) { - targetInstance.add(permanent.getId(), game); - numTargets--; - } - } + for (SpellTargetAddress addr : SpellTargetAddress.walk(copy)) { + Target targetInstance = addr.getTarget(copy); + int numTargets = targetInstance.getNumberOfTargets(); + targetInstance.clearChosen(); + while (numTargets > 0) { + targetInstance.add(permanent.getId(), game); + numTargets--; } } targetable.put(permanent.getId(), copy); From c4d1150e32cca54721dfd294474dff45579050d0 Mon Sep 17 00:00:00 2001 From: Duncan Townsend Date: Thu, 22 Jan 2015 01:19:47 -0500 Subject: [PATCH 12/14] Use mage.util.SpellTargetAddress in Precursor Golem --- .../sets/scarsofmirrodin/PrecursorGolem.java | 35 +++++++++---------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/Mage.Sets/src/mage/sets/scarsofmirrodin/PrecursorGolem.java b/Mage.Sets/src/mage/sets/scarsofmirrodin/PrecursorGolem.java index 55da525676..65e614fb42 100644 --- a/Mage.Sets/src/mage/sets/scarsofmirrodin/PrecursorGolem.java +++ b/Mage.Sets/src/mage/sets/scarsofmirrodin/PrecursorGolem.java @@ -53,6 +53,7 @@ import mage.target.Target; import mage.target.targetpointer.FixedTarget; import java.util.UUID; +import mage.util.SpellTargetAddress; /** * @author nantuko @@ -122,22 +123,20 @@ class PrecursorGolemCopyTriggeredAbility extends TriggeredAbilityImpl { if (spell != null && (spell.getCardType().contains(CardType.INSTANT) || spell.getCardType().contains(CardType.SORCERY))) { UUID targetGolem = null; - SpellAbility sa = spell.getSpellAbility(); - for (Effect effect : sa.getEffects()) { - for (UUID target : effect.getTargetPointer().getTargets(game, sa)) { + for (SpellTargetAddress addr : SpellTargetAddress.walk(spell)) { + Target targetInstance = addr.getTarget(spell); + for (UUID target : targetInstance.getTargets()) { Permanent permanent = game.getPermanent(target); - if (permanent != null) { - if (!permanent.hasSubtype("Golem")) { + if (permanent == null || !permanent.hasSubtype("Golem")) { + return false; + } + if (targetGolem == null) { + targetGolem = target; + } else { + // If a spell has multiple targets, but it's targeting the same Golem with all of them, Precursor Golem's last ability will trigger + if (!targetGolem.equals(target)) { return false; } - if (targetGolem == null) { - targetGolem = target; - } else { - // If a spell has multiple targets, but it's targeting the same Golem with all of them, Precursor Golem's last ability will trigger - if (!targetGolem.equals(target)) { - return false; - } - } } } } @@ -176,14 +175,14 @@ class PrecursorGolemCopySpellEffect extends OneShotEffect { public boolean apply(Game game, Ability source) { Spell spell = game.getStack().getSpell(targetPointer.getFirst(game, source)); if (spell != null) { - SpellAbility sa = spell.getSpellAbility(); UUID targetedGolem = (UUID) getValue("targetedGolem"); for (Permanent permanent : game.getBattlefield().getActivePermanents(filterGolem, source.getControllerId(), source.getSourceId(), game)) { if (permanent.getId().equals(targetedGolem)) { continue; // copy only for other golems } boolean legal = true; - for (Target target : sa.getTargets()) { + for (SpellTargetAddress addr : SpellTargetAddress.walk(spell)) { + Target target = addr.getTarget(spell); if (!target.canTarget(permanent.getId(), game)) { legal = false; break; @@ -193,10 +192,8 @@ class PrecursorGolemCopySpellEffect extends OneShotEffect { Spell copy = spell.copySpell(); copy.setControllerId(spell.getControllerId()); copy.setCopiedSpell(true); - for (Effect effect : copy.getSpellAbility().getEffects()) { - effect.setTargetPointer(new FixedTarget(permanent.getId())); - } - for (Target target : copy.getSpellAbility().getTargets()) { + for (SpellTargetAddress addr : SpellTargetAddress.walk(copy)) { + Target target = addr.getTarget(copy); target.clearChosen(); target.add(permanent.getId(), game); } From 0e10ea0c84f8bba9eaf5a44c1dda413fe20c36ca Mon Sep 17 00:00:00 2001 From: Duncan Townsend Date: Thu, 22 Jan 2015 01:43:13 -0500 Subject: [PATCH 13/14] Moved FromSetPredicate to its own file, mage.filter.predicate.mageobject.FromSetPredicate --- .../sets/guildpact/InkTreaderNephilim.java | 41 ++------------- .../mageobject/FromSetPredicate.java | 50 +++++++++++++++++++ 2 files changed, 54 insertions(+), 37 deletions(-) create mode 100644 Mage/src/mage/filter/predicate/mageobject/FromSetPredicate.java diff --git a/Mage.Sets/src/mage/sets/guildpact/InkTreaderNephilim.java b/Mage.Sets/src/mage/sets/guildpact/InkTreaderNephilim.java index 7dfdd03cad..6751f1ddc9 100644 --- a/Mage.Sets/src/mage/sets/guildpact/InkTreaderNephilim.java +++ b/Mage.Sets/src/mage/sets/guildpact/InkTreaderNephilim.java @@ -52,13 +52,10 @@ import mage.game.events.GameEvent; import mage.game.permanent.Permanent; import mage.game.stack.Spell; import java.util.Map; -import java.util.Set; import java.util.List; -import mage.MageObject; import mage.target.Target; import mage.abilities.Modes; -import mage.filter.predicate.ObjectPlayer; -import mage.filter.predicate.ObjectPlayerPredicate; +import mage.filter.predicate.mageobject.FromSetPredicate; import mage.target.TargetPermanent; import mage.util.SpellTargetAddress; @@ -208,10 +205,9 @@ class InkTreaderNephilimEffect extends OneShotEffect { } } while (targetable.size() > 0) { - TargetPermanent target = new TargetPermanent(0, 1, - new FilterPermanentFromSet("creature that spell could target ("+Integer.toString(targetable.size())+" remaining)", - targetable.keySet()), - true); + FilterPermanent filter = new FilterPermanent("creature that spell could target ("+Integer.toString(targetable.size())+" remaining)"); + filter.add(new FromSetPredicate(targetable.keySet())); + TargetPermanent target = new TargetPermanent(0, 1, filter, true); if (target.possibleTargets(controller, game).size() > 1 && target.canChoose(source.getSourceId(), controller, game)) { game.getPlayer(controller).choose(Outcome.Neutral, target, source.getId(), game); @@ -243,32 +239,3 @@ class InkTreaderNephilimEffect extends OneShotEffect { } } - -class FromSetPredicate> implements ObjectPlayerPredicate { - protected Set set; - - public FromSetPredicate(Set set) { - this.set = set; - } - - public boolean apply(T input, Game game) { - return set.contains(input.getObject().getId()); - } -} - -class FilterPermanentFromSet extends FilterPermanent { - public FilterPermanentFromSet(Set set) { - super(); - this.extraPredicates.add(new FromSetPredicate(set)); - } - - public FilterPermanentFromSet(String name, Set set) { - super(name); - this.extraPredicates.add(new FromSetPredicate(set)); - } - - public FilterPermanentFromSet(String subtype, String name, Set set) { - super(subtype, name); - this.extraPredicates.add(new FromSetPredicate(set)); - } -} diff --git a/Mage/src/mage/filter/predicate/mageobject/FromSetPredicate.java b/Mage/src/mage/filter/predicate/mageobject/FromSetPredicate.java new file mode 100644 index 0000000000..0c0822e3d2 --- /dev/null +++ b/Mage/src/mage/filter/predicate/mageobject/FromSetPredicate.java @@ -0,0 +1,50 @@ +/* + * 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.filter.predicate.mageobject; + +import java.util.Set; +import java.util.UUID; +import mage.MageObject; +import mage.filter.predicate.ObjectPlayer; +import mage.filter.predicate.ObjectPlayerPredicate; +import mage.game.Game; + +/** + * @author duncancmt + */ +public class FromSetPredicate> implements ObjectPlayerPredicate { + protected Set set; + + public FromSetPredicate(Set set) { + this.set = set; + } + + public boolean apply(T input, Game game) { + return set.contains(input.getObject().getId()); + } +} From 5ee31e6cc0f1c5843f1e5efd0001918d334e810f Mon Sep 17 00:00:00 2001 From: Duncan Townsend Date: Thu, 22 Jan 2015 01:50:45 -0500 Subject: [PATCH 14/14] Fixed a bug where Precursor Golem triggers wouldn't use last-known information and would instead be countered if the triggering spell left the stack. Added the ability for the player to control the order in which Precursor Golem copies go on the stack. --- .../sets/scarsofmirrodin/PrecursorGolem.java | 42 +++++++++++++++++-- 1 file changed, 38 insertions(+), 4 deletions(-) diff --git a/Mage.Sets/src/mage/sets/scarsofmirrodin/PrecursorGolem.java b/Mage.Sets/src/mage/sets/scarsofmirrodin/PrecursorGolem.java index 65e614fb42..73e543efb2 100644 --- a/Mage.Sets/src/mage/sets/scarsofmirrodin/PrecursorGolem.java +++ b/Mage.Sets/src/mage/sets/scarsofmirrodin/PrecursorGolem.java @@ -27,6 +27,11 @@ */ package mage.sets.scarsofmirrodin; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.SpellAbility; @@ -53,6 +58,8 @@ import mage.target.Target; import mage.target.targetpointer.FixedTarget; import java.util.UUID; +import mage.filter.predicate.mageobject.FromSetPredicate; +import mage.target.TargetPermanent; import mage.util.SpellTargetAddress; /** @@ -141,7 +148,7 @@ class PrecursorGolemCopyTriggeredAbility extends TriggeredAbilityImpl { } } if (targetGolem != null) { - getEffects().get(0).setTargetPointer(new FixedTarget(spell.getId())); + getEffects().get(0).setValue("triggeringSpell", spell); getEffects().get(0).setValue("targetedGolem", targetGolem); return true; } @@ -173,9 +180,10 @@ class PrecursorGolemCopySpellEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Spell spell = game.getStack().getSpell(targetPointer.getFirst(game, source)); + Spell spell = (Spell) getValue("triggeringSpell"); if (spell != null) { UUID targetedGolem = (UUID) getValue("targetedGolem"); + Map targetable = new HashMap<>(); for (Permanent permanent : game.getBattlefield().getActivePermanents(filterGolem, source.getControllerId(), source.getSourceId(), game)) { if (permanent.getId().equals(targetedGolem)) { continue; // copy only for other golems @@ -190,14 +198,40 @@ class PrecursorGolemCopySpellEffect extends OneShotEffect { } if (legal) { Spell copy = spell.copySpell(); - copy.setControllerId(spell.getControllerId()); copy.setCopiedSpell(true); for (SpellTargetAddress addr : SpellTargetAddress.walk(copy)) { Target target = addr.getTarget(copy); target.clearChosen(); target.add(permanent.getId(), game); } - game.getStack().push(copy); + targetable.put(permanent.getId(), copy); + } + } + UUID spellController = spell.getControllerId(); + while (targetable.size() > 0) { + FilterPermanent filter = new FilterPermanent("Golem", + "Golem that spell could target ("+Integer.toString(targetable.size())+" remaining)"); + filter.add(new FromSetPredicate(targetable.keySet())); + TargetPermanent target = new TargetPermanent(0, 1, filter, true); + + if (target.possibleTargets(spellController, game).size() > 1 + && target.canChoose(spell.getSourceId(), spellController, game)) { + game.getPlayer(spellController).choose(Outcome.Neutral, target, source.getId(), game); + } + Collection chosen = target.getTargets(); + if (chosen.size() == 0) { + chosen = targetable.keySet(); + } + List toDelete = new ArrayList<>(); + for (UUID chosenId : chosen) { + Spell chosenCopy = targetable.get(chosenId); + if (chosenCopy != null) { + game.getStack().push(chosenCopy); + toDelete.add(chosenId); + } + } + for (UUID id : toDelete) { + targetable.remove(id); } } return true;