diff --git a/Mage.Sets/src/mage/sets/coldsnap/Commandeer.java b/Mage.Sets/src/mage/sets/coldsnap/Commandeer.java index a6e719e370f..4d1b1c471ed 100644 --- a/Mage.Sets/src/mage/sets/coldsnap/Commandeer.java +++ b/Mage.Sets/src/mage/sets/coldsnap/Commandeer.java @@ -107,7 +107,7 @@ class CommandeerEffect extends OneShotEffect { Spell spell = game.getStack().getSpell(targetPointer.getFirst(game, source)); if (controller != null && spell != null) { spell.setControllerId(controller.getId()); - spell.chooseNewTargets(game, controller.getId(), false, false); + spell.chooseNewTargets(game, controller.getId(), false, false, null); return true; } return false; diff --git a/Mage.Sets/src/mage/sets/coldsnap/LightningStorm.java b/Mage.Sets/src/mage/sets/coldsnap/LightningStorm.java index 601693ea440..a7ad11f4e5c 100644 --- a/Mage.Sets/src/mage/sets/coldsnap/LightningStorm.java +++ b/Mage.Sets/src/mage/sets/coldsnap/LightningStorm.java @@ -142,7 +142,7 @@ class LightningStormAddCounterEffect extends OneShotEffect { Spell spell = game.getStack().getSpell(source.getSourceId()); if (spell != null) { spell.addCounters(CounterType.CHARGE.createInstance(2), game); - return spell.chooseNewTargets(game, source.getControllerId(), false, false); + return spell.chooseNewTargets(game, source.getControllerId(), false, false, null); } return false; } diff --git a/Mage.Sets/src/mage/sets/exodus/Pandemonium.java b/Mage.Sets/src/mage/sets/exodus/Pandemonium.java new file mode 100644 index 00000000000..199f1c97fc4 --- /dev/null +++ b/Mage.Sets/src/mage/sets/exodus/Pandemonium.java @@ -0,0 +1,54 @@ +/* + * 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.exodus; + +import java.util.UUID; +import mage.constants.Rarity; + +/** + * + * @author LevelX2 + */ +public class Pandemonium extends mage.sets.timeshifted.Pandemonium { + + public Pandemonium(UUID ownerId) { + super(ownerId); + this.cardNumber = 93; + this.expansionSetCode = "EXO"; + this.rarity = Rarity.RARE; + } + + public Pandemonium(final Pandemonium card) { + super(card); + } + + @Override + public Pandemonium copy() { + return new Pandemonium(this); + } +} diff --git a/Mage.Sets/src/mage/sets/jacevschandra/QuicksilverDragon.java b/Mage.Sets/src/mage/sets/jacevschandra/QuicksilverDragon.java index 61ed7b2cd7e..2ff987f20dc 100644 --- a/Mage.Sets/src/mage/sets/jacevschandra/QuicksilverDragon.java +++ b/Mage.Sets/src/mage/sets/jacevschandra/QuicksilverDragon.java @@ -107,7 +107,7 @@ class QuicksilverDragonEffect extends OneShotEffect { numTargets += target.getTargets().size(); } if (numTargets == 1 && spell.getSpellAbility().getTargets().getFirstTarget().equals(source.getSourceId())) { - spell.chooseNewTargets(game, source.getControllerId(), true, false); + spell.chooseNewTargets(game, source.getControllerId(), true, false, null); } return true; } diff --git a/Mage.Sets/src/mage/sets/legends/TheAbyss.java b/Mage.Sets/src/mage/sets/legends/TheAbyss.java index 8957b130f4a..3664e65ad01 100644 --- a/Mage.Sets/src/mage/sets/legends/TheAbyss.java +++ b/Mage.Sets/src/mage/sets/legends/TheAbyss.java @@ -86,24 +86,27 @@ class TheAbyssTriggeredAbility extends TriggeredAbilityImpl { public TheAbyssTriggeredAbility copy() { return new TheAbyssTriggeredAbility(this); } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.UPKEEP_STEP_PRE; + } @Override public boolean checkTrigger(GameEvent event, Game game) { - if (event.getType() == GameEvent.EventType.UPKEEP_STEP_PRE) { - Player player = game.getPlayer(event.getPlayerId()); - if (player != null) { - FilterCreaturePermanent filter = new FilterCreaturePermanent("nonartifact creature you control"); - filter.add(Predicates.not(new CardTypePredicate(CardType.ARTIFACT))); - filter.add(new ControllerIdPredicate(player.getId())); - Target target = new TargetCreaturePermanent(filter); - if (target.canChoose(this.getSourceId(), this.getControllerId(), game) && player.chooseTarget(Outcome.DestroyPermanent, target, this, game)) { - for (Effect effect: this.getEffects()) { - if (effect instanceof DestroyTargetEffect) { - effect.setTargetPointer(new FixedTarget(target.getFirstTarget())); - } + Player player = game.getPlayer(event.getPlayerId()); + if (player != null) { + FilterCreaturePermanent filter = new FilterCreaturePermanent("nonartifact creature you control"); + filter.add(Predicates.not(new CardTypePredicate(CardType.ARTIFACT))); + filter.add(new ControllerIdPredicate(player.getId())); + Target target = new TargetCreaturePermanent(filter); + if (target.canChoose(this.getSourceId(), this.getControllerId(), game) && player.chooseTarget(Outcome.DestroyPermanent, target, this, game)) { + for (Effect effect: this.getEffects()) { + if (effect instanceof DestroyTargetEffect) { + effect.setTargetPointer(new FixedTarget(target.getFirstTarget())); } - return true; } + return true; } } return false; diff --git a/Mage.Sets/src/mage/sets/odyssey/Divert.java b/Mage.Sets/src/mage/sets/odyssey/Divert.java index 91af687c71c..f0755b8e183 100644 --- a/Mage.Sets/src/mage/sets/odyssey/Divert.java +++ b/Mage.Sets/src/mage/sets/odyssey/Divert.java @@ -94,7 +94,7 @@ class DivertEffect extends OneShotEffect { cost.clearPaid(); if (!cost.pay(source, game, spell.getControllerId(), spell.getControllerId(), false)) { - return spell.chooseNewTargets(game, source.getControllerId(), true, true); + return spell.chooseNewTargets(game, source.getControllerId(), true, true, null); } } } diff --git a/Mage.Sets/src/mage/sets/stronghold/Cannibalize.java b/Mage.Sets/src/mage/sets/stronghold/Cannibalize.java new file mode 100644 index 00000000000..8a4516af6c1 --- /dev/null +++ b/Mage.Sets/src/mage/sets/stronghold/Cannibalize.java @@ -0,0 +1,54 @@ +/* + * 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.stronghold; + +import java.util.UUID; +import mage.constants.Rarity; + +/** + * + * @author LevelX2 + */ +public class Cannibalize extends mage.sets.tempestremastered.Cannibalize { + + public Cannibalize(UUID ownerId) { + super(ownerId); + this.cardNumber = 3; + this.expansionSetCode = "STH"; + this.rarity = Rarity.COMMON; + } + + public Cannibalize(final Cannibalize card) { + super(card); + } + + @Override + public Cannibalize copy() { + return new Cannibalize(this); + } +} diff --git a/Mage.Sets/src/mage/sets/stronghold/SilverWyvern.java b/Mage.Sets/src/mage/sets/stronghold/SilverWyvern.java new file mode 100644 index 00000000000..28096c464f9 --- /dev/null +++ b/Mage.Sets/src/mage/sets/stronghold/SilverWyvern.java @@ -0,0 +1,52 @@ +/* + * 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.stronghold; + +import java.util.UUID; + +/** + * + * @author LevelX2 + */ +public class SilverWyvern extends mage.sets.tempestremastered.SilverWyvern { + + public SilverWyvern(UUID ownerId) { + super(ownerId); + this.cardNumber = 43; + this.expansionSetCode = "STH"; + } + + public SilverWyvern(final SilverWyvern card) { + super(card); + } + + @Override + public SilverWyvern copy() { + return new SilverWyvern(this); + } +} diff --git a/Mage.Sets/src/mage/sets/tempest/CoffinQueen.java b/Mage.Sets/src/mage/sets/tempest/CoffinQueen.java new file mode 100644 index 00000000000..b030b93e2af --- /dev/null +++ b/Mage.Sets/src/mage/sets/tempest/CoffinQueen.java @@ -0,0 +1,52 @@ +/* + * 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.tempest; + +import java.util.UUID; + +/** + * + * @author anonymous + */ +public class CoffinQueen extends mage.sets.tempestremastered.CoffinQueen { + + public CoffinQueen(UUID ownerId) { + super(ownerId); + this.cardNumber = 8; + this.expansionSetCode = "TMP"; + } + + public CoffinQueen(final CoffinQueen card) { + super(card); + } + + @Override + public CoffinQueen copy() { + return new CoffinQueen(this); + } +} diff --git a/Mage.Sets/src/mage/sets/tempestremastered/Cannibalize.java b/Mage.Sets/src/mage/sets/tempestremastered/Cannibalize.java new file mode 100644 index 00000000000..6a4df481c13 --- /dev/null +++ b/Mage.Sets/src/mage/sets/tempestremastered/Cannibalize.java @@ -0,0 +1,152 @@ +/* + * 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.tempestremastered; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.Rarity; +import mage.constants.Zone; +import mage.counters.CounterType; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.permanent.ControllerIdPredicate; +import mage.filter.predicate.permanent.PermanentIdPredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.Target; +import mage.target.TargetPermanent; +import mage.target.common.TargetCreaturePermanent; + +/** + * + * @author LevelX2 + */ +public class Cannibalize extends CardImpl { + + public Cannibalize(UUID ownerId) { + super(ownerId, 83, "Cannibalize", Rarity.UNCOMMON, new CardType[]{CardType.SORCERY}, "{1}{B}"); + this.expansionSetCode = "TPR"; + + // Choose two target creatures controlled by the same player. Exile one of the creatures and put two +1/+1 counters on the other. + this.getSpellAbility().addEffect(new CannibalizeEffect()); + this.getSpellAbility().addTarget(new TargetCreaturePermanentSameController(2,2,new FilterCreaturePermanent(),false)); + } + + public Cannibalize(final Cannibalize card) { + super(card); + } + + @Override + public Cannibalize copy() { + return new Cannibalize(this); + } +} + + + +class CannibalizeEffect extends OneShotEffect { + + public CannibalizeEffect() { + super(Outcome.Benefit); + this.staticText = "Choose two target creatures controlled by the same player. Exile one of the creatures and put two +1/+1 counters on the other"; + } + + public CannibalizeEffect(final CannibalizeEffect effect) { + super(effect); + } + + @Override + public CannibalizeEffect copy() { + return new CannibalizeEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + MageObject sourceObject = source.getSourceObject(game); + if (controller != null && sourceObject != null) { + boolean exileDone = false; + int count = 0; + for(UUID targetId: getTargetPointer().getTargets(game, source)) { + Permanent creature = game.getPermanent(targetId); + if (creature != null) { + if ((count == 0 && controller.chooseUse(Outcome.Exile, "Exile " + creature.getLogName() +"?", game)) + || (count == 1 && !exileDone)) { + controller.moveCardToExileWithInfo(creature, null, "", source.getSourceId(), game, Zone.BATTLEFIELD, true); + exileDone = true; + } else { + creature.addCounters(CounterType.P1P1.createInstance(2), game); + game.informPlayers("Added two +1/+1 counters on " + creature.getLogName()); + } + count++; + } + } + return true; + } + return false; + } +} + +class TargetCreaturePermanentSameController extends TargetCreaturePermanent { + + public TargetCreaturePermanentSameController(int minNumTargets, int maxNumTargets, FilterCreaturePermanent filter, boolean notTarget) { + super(minNumTargets, maxNumTargets, filter, notTarget); + this.targetName = filter.getMessage(); + } + + public TargetCreaturePermanentSameController(final TargetCreaturePermanentSameController target) { + super(target); + } + + @Override + public boolean canTarget(UUID id, Ability source, Game game) { + UUID firstTarget = this.getFirstTarget(); + if (firstTarget != null) { + Permanent permanent = game.getPermanent(firstTarget); + Permanent targetPermanent = game.getPermanent(id); + if (permanent == null || targetPermanent == null + || !permanent.getControllerId().equals(targetPermanent.getOwnerId())) { + return false; + } + } + return super.canTarget(id, source, game); + } + + @Override + public TargetCreaturePermanentSameController copy() { + return new TargetCreaturePermanentSameController(this); + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/sets/tempestremastered/CoffinQueen.java b/Mage.Sets/src/mage/sets/tempestremastered/CoffinQueen.java new file mode 100644 index 00000000000..eedb00ce94d --- /dev/null +++ b/Mage.Sets/src/mage/sets/tempestremastered/CoffinQueen.java @@ -0,0 +1,153 @@ +/* + * 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.tempestremastered; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.DelayedTriggeredAbility; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.common.SkipUntapOptionalAbility; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.CreateDelayedTriggeredAbilityEffect; +import mage.abilities.effects.common.ExileTargetEffect; +import mage.abilities.effects.common.ReturnFromGraveyardToBattlefieldTargetEffect; +import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.Rarity; +import mage.constants.Zone; +import mage.filter.common.FilterCreatureCard; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; +import mage.target.common.TargetCardInGraveyard; +import mage.target.targetpointer.FixedTarget; + +/** + * + * @author anonymous + */ +public class CoffinQueen extends CardImpl { + + public CoffinQueen(UUID ownerId) { + super(ownerId, 87, "Coffin Queen", Rarity.RARE, new CardType[]{CardType.CREATURE}, "{2}{B}"); + this.expansionSetCode = "TPR"; + this.subtype.add("Zombie"); + this.subtype.add("Wizard"); + this.power = new MageInt(1); + this.toughness = new MageInt(1); + + // You may choose not to untap Coffin Queen during your untap step. + this.addAbility(new SkipUntapOptionalAbility()); + + // {2}{B}, {tap}: Put target creature card from a graveyard onto the battlefield under your control. When Coffin Queen becomes untapped or you lose control of Coffin Queen, exile that creature. + Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new ReturnFromGraveyardToBattlefieldTargetEffect(), new ManaCostsImpl("{2}{B}")); + ability.addCost(new TapSourceCost()); + ability.addTarget(new TargetCardInGraveyard(new FilterCreatureCard("creature card from a graveyard"))); + ability.addEffect(new CoffinQueenCreateDelayedTriggerEffect()); + this.addAbility(ability); + + } + + public CoffinQueen(final CoffinQueen card) { + super(card); + } + + @Override + public CoffinQueen copy() { + return new CoffinQueen(this); + } +} +class CoffinQueenCreateDelayedTriggerEffect extends OneShotEffect { + + public CoffinQueenCreateDelayedTriggerEffect() { + super(Outcome.Detriment); + this.staticText = "When Coffin Queen becomes untapped or you lose control of Coffin Queen, exile that creature"; + } + + public CoffinQueenCreateDelayedTriggerEffect(final CoffinQueenCreateDelayedTriggerEffect effect) { + super(effect); + } + + @Override + public CoffinQueenCreateDelayedTriggerEffect copy() { + return new CoffinQueenCreateDelayedTriggerEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent controlledCreature = game.getPermanent(source.getFirstTarget()); + if (controlledCreature != null) { + DelayedTriggeredAbility delayedAbility = new CoffinQueenDelayedTriggeredAbility(); + delayedAbility.getEffects().get(0).setTargetPointer(new FixedTarget(controlledCreature.getId())); + delayedAbility.setSourceId(source.getSourceId()); + delayedAbility.setControllerId(source.getControllerId()); + delayedAbility.setSourceObject(source.getSourceObject(game), game); + delayedAbility.init(game); + game.addDelayedTriggeredAbility(delayedAbility); + return true; + } + return false; + } +} + +class CoffinQueenDelayedTriggeredAbility extends DelayedTriggeredAbility { + + CoffinQueenDelayedTriggeredAbility() { + super(new ExileTargetEffect(), Duration.EndOfGame, true); + } + + CoffinQueenDelayedTriggeredAbility(CoffinQueenDelayedTriggeredAbility ability) { + super(ability); + } + + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + if (GameEvent.EventType.LOST_CONTROL.equals(event.getType()) + && event.getSourceId().equals(getSourceId())) { + return true; + } + return GameEvent.EventType.UNTAPPED.equals(event.getType()) + && event.getTargetId() != null && event.getTargetId().equals(getSourceId()); + } + + @Override + public CoffinQueenDelayedTriggeredAbility copy() { + return new CoffinQueenDelayedTriggeredAbility(this); + } + + @Override + public String getRule() { + return "When {this} becomes untapped or you lose control of {this}, exile that creature"; + } +} diff --git a/Mage.Sets/src/mage/sets/tempestremastered/Pandemonium.java b/Mage.Sets/src/mage/sets/tempestremastered/Pandemonium.java new file mode 100644 index 00000000000..f458fbe40fc --- /dev/null +++ b/Mage.Sets/src/mage/sets/tempestremastered/Pandemonium.java @@ -0,0 +1,54 @@ +/* + * 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.tempestremastered; + +import java.util.UUID; +import mage.constants.Rarity; + +/** + * + * @author LevelX2 + */ +public class Pandemonium extends mage.sets.timeshifted.Pandemonium { + + public Pandemonium(UUID ownerId) { + super(ownerId); + this.cardNumber = 149; + this.expansionSetCode = "TPR"; + this.rarity = Rarity.RARE; + } + + public Pandemonium(final Pandemonium card) { + super(card); + } + + @Override + public Pandemonium copy() { + return new Pandemonium(this); + } +} diff --git a/Mage.Sets/src/mage/sets/tempestremastered/SilverWyvern.java b/Mage.Sets/src/mage/sets/tempestremastered/SilverWyvern.java new file mode 100644 index 00000000000..204897befb8 --- /dev/null +++ b/Mage.Sets/src/mage/sets/tempestremastered/SilverWyvern.java @@ -0,0 +1,118 @@ +/* + * 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.tempestremastered; + +import java.util.UUID; +import mage.MageInt; +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.ChooseNewTargetsTargetEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Rarity; +import mage.constants.Zone; +import mage.filter.FilterStackObject; +import mage.filter.predicate.Predicate; +import mage.game.Game; +import mage.game.stack.StackObject; +import mage.target.Target; +import mage.target.TargetStackObject; +import mage.target.Targets; + +/** + * + * @author LevelX2 + */ +public class SilverWyvern extends CardImpl { + + public SilverWyvern(UUID ownerId) { + super(ownerId, 68, "Silver Wyvern", Rarity.RARE, new CardType[]{CardType.CREATURE}, "{3}{U}{U}"); + this.expansionSetCode = "TPR"; + this.subtype.add("Drake"); + this.power = new MageInt(4); + this.toughness = new MageInt(3); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + // {U}: Change the target of target spell or ability that targets only Silver Wyvern. The new target must be a creature. + Effect effect = new ChooseNewTargetsTargetEffect(true, true); + effect.setText("Change the target of target spell or ability that targets only {this}. The new target must be a creature"); + Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, effect, new ManaCostsImpl("{U}")); + FilterStackObject filter = new FilterStackObject(); + filter.add(new TargetsOnlySourcePredicate(getId())); + ability.addTarget(new TargetStackObject(filter)); + this.addAbility(ability); + + } + + public SilverWyvern(final SilverWyvern card) { + super(card); + } + + @Override + public SilverWyvern copy() { + return new SilverWyvern(this); + } +} + +class TargetsOnlySourcePredicate implements Predicate { + + private final UUID sourceId; + + public TargetsOnlySourcePredicate(UUID sourceId) { + this.sourceId = sourceId; + } + + @Override + public boolean apply(MageObject input, Game game) { + StackObject stackObject = game.getStack().getStackObject(input.getId()); + if (stackObject != null) { + Targets spellTargets = stackObject.getStackAbility().getTargets(); + int numberOfTargets = 0; + for (Target target : spellTargets) { + if (target.getFirstTarget() == null || !target.getFirstTarget().toString().equals(sourceId.toString())) { // UUID != UUID does not work - it's always false + return false; + } + numberOfTargets += target.getTargets().size(); + } + if (numberOfTargets == 1) { + return true; + } + } + return false; + } + + @Override + public String toString() { + return "target spell or ability that targets only source"; + } +} diff --git a/Mage.Sets/src/mage/sets/timeshifted/Pandemonium.java b/Mage.Sets/src/mage/sets/timeshifted/Pandemonium.java new file mode 100644 index 00000000000..588ee3e077f --- /dev/null +++ b/Mage.Sets/src/mage/sets/timeshifted/Pandemonium.java @@ -0,0 +1,119 @@ +/* + * 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.timeshifted; + +import java.util.UUID; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldAllTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.Rarity; +import mage.constants.SetTargetPointer; +import mage.constants.Zone; +import mage.filter.common.FilterCreaturePermanent; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.common.TargetCreatureOrPlayer; + +/** + * + * @author LevelX2 + */ +public class Pandemonium extends CardImpl { + + public Pandemonium(UUID ownerId) { + super(ownerId, 68, "Pandemonium", Rarity.SPECIAL, new CardType[]{CardType.ENCHANTMENT}, "{3}{R}"); + this.expansionSetCode = "TSB"; + + // Whenever a creature enters the battlefield, that creature's controller may have it deal damage equal to its power to target creature or player of his or her choice. + Ability ability = new EntersBattlefieldAllTriggeredAbility(Zone.BATTLEFIELD, new PandemoniumEffect(), new FilterCreaturePermanent(), false, SetTargetPointer.PERMANENT, ""); + ability.addTarget(new TargetCreatureOrPlayer()); + this.addAbility(ability); + } + + public Pandemonium(final Pandemonium card) { + super(card); + } + + @Override + public void adjustTargets(Ability ability, Game game) { + if (ability instanceof EntersBattlefieldAllTriggeredAbility) { + UUID creatureId = ability.getEffects().get(0).getTargetPointer().getFirst(game, ability); + Permanent creature = game.getPermanent(creatureId); + if (creature != null) { + ability.getTargets().get(0).setTargetController(creature.getControllerId()); + } + } + } + + @Override + public Pandemonium copy() { + return new Pandemonium(this); + } +} + +class PandemoniumEffect extends OneShotEffect { + + public PandemoniumEffect() { + super(Outcome.Benefit); + this.staticText = "that creature's controller may have it deal damage equal to its power to target creature or player of his or her choice"; + } + + public PandemoniumEffect(final PandemoniumEffect effect) { + super(effect); + } + + @Override + public PandemoniumEffect copy() { + return new PandemoniumEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller != null) { + Permanent enteringCreature = game.getPermanentOrLKIBattlefield(getTargetPointer().getFirst(game, source)); + if (enteringCreature != null) { + Permanent targetPermanent = game.getPermanent(source.getTargets().getFirstTarget()); + if (targetPermanent != null) { + targetPermanent.damage(enteringCreature.getPower().getValue(), source.getSourceId(), game, false, true); + } else { + Player targetPlayer = game.getPlayer(source.getTargets().getFirstTarget()); + if (targetPlayer != null) { + targetPlayer.damage(enteringCreature.getPower().getValue(), source.getSourceId(), game, false, true); + } + } + return true; + } + } + return false; + } +} diff --git a/Mage/src/mage/abilities/effects/common/ChooseNewTargetsTargetEffect.java b/Mage/src/mage/abilities/effects/common/ChooseNewTargetsTargetEffect.java index 39a317cd59d..ee08b2d7417 100644 --- a/Mage/src/mage/abilities/effects/common/ChooseNewTargetsTargetEffect.java +++ b/Mage/src/mage/abilities/effects/common/ChooseNewTargetsTargetEffect.java @@ -32,8 +32,10 @@ import mage.constants.Outcome; import mage.abilities.Ability; import mage.abilities.Mode; import mage.abilities.effects.OneShotEffect; +import mage.filter.Filter; +import mage.filter.FilterPermanent; import mage.game.Game; -import mage.game.stack.Spell; +import mage.game.stack.StackObject; /** * @author BetaSteward_at_googlemail.com @@ -43,21 +45,27 @@ public class ChooseNewTargetsTargetEffect extends OneShotEffect { private boolean forceChange; private boolean onlyOneTarget; - + private FilterPermanent filterNewTarget; + public ChooseNewTargetsTargetEffect() { this(false, false); } + public ChooseNewTargetsTargetEffect(boolean forceChange, boolean onlyOneTarget) { + this(forceChange, onlyOneTarget, null); + } /** * * @param forceChange forces the user to choose another target (only targets with maxtargets = 1 supported) * @param onlyOneTarget only one target can be selected for the change + * @param filterNewTarget restriction to the new target */ - public ChooseNewTargetsTargetEffect(boolean forceChange, boolean onlyOneTarget) { + public ChooseNewTargetsTargetEffect(boolean forceChange, boolean onlyOneTarget, FilterPermanent filterNewTarget) { super(Outcome.Benefit); this.forceChange = forceChange; this.onlyOneTarget = onlyOneTarget; + this.filterNewTarget = filterNewTarget; } public ChooseNewTargetsTargetEffect(final ChooseNewTargetsTargetEffect effect) { @@ -68,9 +76,9 @@ public class ChooseNewTargetsTargetEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Spell spell = game.getStack().getSpell(source.getFirstTarget()); - if (spell != null) { - return spell.chooseNewTargets(game, source.getControllerId(), forceChange, onlyOneTarget); + StackObject stackObject = game.getStack().getStackObject(source.getFirstTarget()); + if (stackObject != null) { + return stackObject.chooseNewTargets(game, source.getControllerId(), forceChange, onlyOneTarget, filterNewTarget); } return false; } @@ -82,6 +90,9 @@ public class ChooseNewTargetsTargetEffect extends OneShotEffect { @Override public String getText(Mode mode) { + if (staticText != null && !staticText.isEmpty()) { + return staticText; + } StringBuilder sb = new StringBuilder(); if (forceChange) { sb.append("change the target of target "); diff --git a/Mage/src/mage/abilities/effects/common/counter/AddCountersTargetEffect.java b/Mage/src/mage/abilities/effects/common/counter/AddCountersTargetEffect.java index a0be206131f..5ae4cd6e314 100644 --- a/Mage/src/mage/abilities/effects/common/counter/AddCountersTargetEffect.java +++ b/Mage/src/mage/abilities/effects/common/counter/AddCountersTargetEffect.java @@ -95,9 +95,10 @@ public class AddCountersTargetEffect extends OneShotEffect { permanent.addCounters(newCounter, game); int numberAdded = permanent.getCounters().getCount(counter.getName()) - before; affectedTargets ++; - if (!game.isSimulation()) + if (!game.isSimulation()) { game.informPlayers(sourceObject.getLogName() +": "+ controller.getName()+ " puts " + numberAdded + " " + counter.getName().toLowerCase() + " counter on " + permanent.getLogName()); + } } } else { Player player = game.getPlayer(uuid); diff --git a/Mage/src/mage/abilities/keyword/ShadowAbility.java b/Mage/src/mage/abilities/keyword/ShadowAbility.java index ca95603cf34..57b41e1b348 100644 --- a/Mage/src/mage/abilities/keyword/ShadowAbility.java +++ b/Mage/src/mage/abilities/keyword/ShadowAbility.java @@ -31,7 +31,7 @@ public class ShadowAbility extends EvasionAbility implements MageSingleton { @Override public String getRule() { - return "Shadow"; + return "Shadow (This creature can block or be blocked by only creatures with shadow.)"; } @Override @@ -53,10 +53,7 @@ class ShadowEffect extends RestrictionEffect implements MageSingleton { @Override public boolean applies(Permanent permanent, Ability source, Game game) { - if (permanent.getAbilities().containsKey(ShadowAbility.getInstance().getId())) { - return true; - } - return false; + return permanent.getAbilities().containsKey(ShadowAbility.getInstance().getId()); } @Override @@ -66,11 +63,8 @@ class ShadowEffect extends RestrictionEffect implements MageSingleton { @Override public boolean canBeBlocked(Permanent attacker, Permanent blocker, Ability source, Game game) { - if (blocker.getAbilities().containsKey(ShadowAbility.getInstance().getId()) - || game.getContinuousEffects().asThough(blocker.getId(), AsThoughEffectType.BLOCK_SHADOW, source, blocker.getControllerId(), game)) { - return true; - } - return false; + return blocker.getAbilities().containsKey(ShadowAbility.getInstance().getId()) + || game.getContinuousEffects().asThough(blocker.getId(), AsThoughEffectType.BLOCK_SHADOW, source, blocker.getControllerId(), game); } @Override diff --git a/Mage/src/mage/game/events/GameEvent.java b/Mage/src/mage/game/events/GameEvent.java index 65948f54e37..0f515ccc77a 100644 --- a/Mage/src/mage/game/events/GameEvent.java +++ b/Mage/src/mage/game/events/GameEvent.java @@ -177,7 +177,16 @@ public class GameEvent { ADD_COUNTER, COUNTER_ADDED, ADD_COUNTERS, COUNTERS_ADDED, COUNTER_REMOVED, - LOSE_CONTROL, LOST_CONTROL, + LOSE_CONTROL, + + /* LOST_CONTROL + targetId id of the creature that lost control + sourceId id of the creature that lost control + playerId player that controlles the creature before + amount not used for this event + flag not used for this event + */ + LOST_CONTROL, GAIN_CONTROL, GAINED_CONTROL, CREATE_TOKEN, diff --git a/Mage/src/mage/game/stack/Spell.java b/Mage/src/mage/game/stack/Spell.java index 8b0536318d1..5c7dcc03b81 100644 --- a/Mage/src/mage/game/stack/Spell.java +++ b/Mage/src/mage/game/stack/Spell.java @@ -55,6 +55,7 @@ import mage.constants.SpellAbilityType; import mage.constants.Zone; import mage.counters.Counter; import mage.counters.Counters; +import mage.filter.FilterPermanent; import mage.game.Game; import mage.game.events.ZoneChangeEvent; import mage.game.permanent.Permanent; @@ -319,7 +320,7 @@ public class Spell implements StackObject, Card { * @return */ public boolean chooseNewTargets(Game game, UUID playerId) { - return chooseNewTargets(game, playerId, false, false); + return chooseNewTargets(game, playerId, false, false, null); } /** @@ -377,13 +378,13 @@ public class Spell implements StackObject, Card { * * @param game * @param playerId - player that can/has to change the taregt of the spell - * @param forceChange - does only work for targets with maximum of one - * targetId - * @param onlyOneTarget - 114.6b one target must be changed to another - * target + * @param forceChange - does only work for targets with maximum of one targetId + * @param onlyOneTarget - 114.6b one target must be changed to another target + * @param filterNewTarget restriction for the new target, if null nothing is cheched * @return */ - public boolean chooseNewTargets(Game game, UUID playerId, boolean forceChange, boolean onlyOneTarget) { + @Override + public boolean chooseNewTargets(Game game, UUID playerId, boolean forceChange, boolean onlyOneTarget, FilterPermanent filterNewTarget) { Player player = game.getPlayer(playerId); if (player != null) { StringBuilder newTargetDescription = new StringBuilder(); @@ -393,7 +394,7 @@ public class Spell implements StackObject, Card { for (UUID modeId : spellAbility.getModes().getSelectedModes()) { Mode mode = spellAbility.getModes().get(modeId); for (Target target : mode.getTargets()) { - Target newTarget = chooseNewTarget(player, spellAbility, mode, target, forceChange, game); + Target newTarget = chooseNewTarget(player, spellAbility, mode, target, forceChange, filterNewTarget, game); // clear the old target and copy all targets from new target target.clearChosen(); for (UUID targetId : newTarget.getTargets()) { @@ -424,7 +425,7 @@ public class Spell implements StackObject, Card { * @param game * @return */ - private Target chooseNewTarget(Player player, SpellAbility spellAbility, Mode mode, Target target, boolean forceChange, Game game) { + private Target chooseNewTarget(Player player, SpellAbility spellAbility, Mode mode, Target target, boolean forceChange, FilterPermanent filterNewTarget, Game game) { Target newTarget = target.copy(); newTarget.clearChosen(); for (UUID targetId : target.getTargets()) { @@ -443,6 +444,14 @@ public class Spell implements StackObject, Card { newTarget.clearChosen(); // TODO: Distinction between "spell controller" and "player that can change the target" - here player is used for both newTarget.chooseTarget(mode.getEffects().get(0).getOutcome(), player.getId(), spellAbility, game); + // check target restriction + if (newTarget.getFirstTarget() != null && filterNewTarget != null) { + Permanent newTargetPermanent = game.getPermanent(newTarget.getFirstTarget()); + if (newTargetPermanent == null || !filterNewTarget.match(newTargetPermanent, game)) { + game.informPlayer(player, "Target does not fullfil the target requirements (" + filterNewTarget.getMessage() +")"); + newTarget.clearChosen(); + } + } } while (player.isInGame() && (targetId.equals(newTarget.getFirstTarget()) || newTarget.getTargets().size() != 1)); // choose a new target } else { @@ -473,6 +482,12 @@ public class Spell implements StackObject, Card { } else { newTarget.addTarget(targetId, target.getTargetAmount(targetId), spellAbility, game, false); } + } else if (newTarget.getFirstTarget() != null && filterNewTarget != null) { + Permanent newTargetPermanent = game.getPermanent(newTarget.getFirstTarget()); + if (newTargetPermanent == null || !filterNewTarget.match(newTargetPermanent, game)) { + game.informPlayer(player, "This target does not fullfil the target requirements (" + filterNewTarget.getMessage() +")"); + again = true; + } } else { // valid target was selected, add it to the new target definition newTarget.addTarget(tempTarget.getFirstTarget(), target.getTargetAmount(targetId), spellAbility, game, false); diff --git a/Mage/src/mage/game/stack/StackAbility.java b/Mage/src/mage/game/stack/StackAbility.java index b7413d9d6e2..b9826bd2a33 100644 --- a/Mage/src/mage/game/stack/StackAbility.java +++ b/Mage/src/mage/game/stack/StackAbility.java @@ -54,10 +54,15 @@ import mage.target.Targets; import java.util.ArrayList; import java.util.List; import java.util.UUID; +import mage.abilities.dynamicvalue.common.StaticValue; import mage.cards.Card; import mage.constants.AbilityWord; +import mage.constants.Outcome; +import mage.filter.FilterPermanent; import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; import mage.players.Player; +import mage.target.TargetAmount; import mage.watchers.Watcher; /** @@ -535,4 +540,197 @@ public class StackAbility implements StackObject, Ability { throw new UnsupportedOperationException("Not supported."); } + /** + * 114.6. Some effects allow a player to change the target(s) of a spell or + * ability, and other effects allow a player to choose new targets for a + * spell or ability. + * + * 114.6a If an effect allows a player to "change the + * target(s)" of a spell or ability, each target can be changed only to + * another legal target. If a target can't be changed to another legal + * target, the original target is unchanged, even if the original target is + * itself illegal by then. If all the targets aren't changed to other legal + * targets, none of them are changed. + * + * 114.6b If an effect allows a player to "change a target" of a + * spell or ability, the process described in rule 114.6a + * is followed, except that only one of those targets may be changed + * (rather than all of them or none of them). + * + * 114.6c If an effect allows a + * player to "change any targets" of a spell or ability, the process + * described in rule 114.6a is followed, except that any number of those + * targets may be changed (rather than all of them or none of them). + * + * 114.6d If an effect allows a player to "choose new targets" for a spell or + * ability, the player may leave any number of the targets unchanged, even + * if those targets would be illegal. If the player chooses to change some + * or all of the targets, the new targets must be legal and must not cause + * any unchanged targets to become illegal. + * + * 114.6e When changing targets or + * choosing new targets for a spell or ability, only the final set of + * targets is evaluated to determine whether the change is legal. + * + * Example: Arc Trail is a sorcery that reads "Arc Trail deals 2 damage to + * target creature or player and 1 damage to another target creature or + * player." The current targets of Arc Trail are Runeclaw Bear and Llanowar + * Elves, in that order. You cast Redirect, an instant that reads "You may + * choose new targets for target spell," targeting Arc Trail. You can change + * the first target to Llanowar Elves and change the second target to + * Runeclaw Bear. + * + * 114.7. Modal spells and abilities may have different targeting + * requirements for each mode. An effect that allows a player to change the + * target(s) of a modal spell or ability, or to choose new targets for a + * modal spell or ability, doesn't allow that player to change its mode. + * (See rule 700.2.) + * + * 706.10c Some effects copy a spell or ability and state that its + * controller may choose new targets for the copy. The player may leave any + * number of the targets unchanged, even if those targets would be illegal. + * If the player chooses to change some or all of the targets, the new + * targets must be legal. Once the player has decided what the copy's + * targets will be, the copy is put onto the stack with those targets. + * + * @param game + * @param playerId - player that can/has to change the target of the ability + * @param forceChange - does only work for targets with maximum of one targetId + * @param onlyOneTarget - 114.6b one target must be changed to another target + * @param filterNewTarget restriction for the new target, if null nothing is cheched + * @return + */ + @Override + public boolean chooseNewTargets(Game game, UUID playerId, boolean forceChange, boolean onlyOneTarget, FilterPermanent filterNewTarget) { + Player player = game.getPlayer(playerId); + if (player != null) { + StringBuilder newTargetDescription = new StringBuilder(); + // Some abilities can have more than one mode + for (UUID modeId : ability.getModes().getSelectedModes()) { + Mode mode = ability.getModes().get(modeId); + for (Target target : mode.getTargets()) { + Target newTarget = chooseNewTarget(player, getStackAbility(), mode, target, forceChange, filterNewTarget, game); + // clear the old target and copy all targets from new target + target.clearChosen(); + for (UUID targetId : newTarget.getTargets()) { + target.addTarget(targetId, newTarget.getTargetAmount(targetId), ability, game, false); + } + + } + newTargetDescription.append(((AbilityImpl)ability).getTargetDescription(mode.getTargets(), game)); + } + + if (newTargetDescription.length() > 0 && !game.isSimulation()) { + game.informPlayers(this.getName() + " is now " + newTargetDescription.toString()); + } + return true; + } + return false; + } + + /** + * Handles the change of one target instance of a mode + * + * @param player - player that can choose the new target + * @param ability + * @param mode + * @param target + * @param forceChange + * @param game + * @return + */ + private Target chooseNewTarget(Player player, Ability ability, Mode mode, Target target, boolean forceChange, FilterPermanent filterNewTarget, Game game) { + Target newTarget = target.copy(); + newTarget.clearChosen(); + for (UUID targetId : target.getTargets()) { + String targetNames = getNamesOfTargets(targetId, game); + // change the target? + if (targetNames != null + && (forceChange || player.chooseUse(mode.getEffects().get(0).getOutcome(), "Change this target: " + targetNames + "?", game))) { + // choose exactly one other target + if (forceChange && target.possibleTargets(this.getSourceId(), getControllerId(), game).size() > 1) { // controller of ability must be used (e.g. TargetOpponent) + int iteration = 0; + do { + if (iteration > 0 && !game.isSimulation()) { + game.informPlayer(player, "You may only select exactly one target that must be different from the origin target!"); + } + iteration++; + newTarget.clearChosen(); + // TODO: Distinction between "ability controller" and "player that can change the target" - here player is used for both + newTarget.chooseTarget(mode.getEffects().get(0).getOutcome(), player.getId(), ability, game); + // check target restriction + if (newTarget.getFirstTarget() != null && filterNewTarget != null) { + Permanent newTargetPermanent = game.getPermanent(newTarget.getFirstTarget()); + if (newTargetPermanent == null || !filterNewTarget.match(newTargetPermanent, game)) { + game.informPlayer(player, "Target does not fullfil the target requirements (" + filterNewTarget.getMessage() +")"); + newTarget.clearChosen(); + } + } + } while (player.isInGame() && (targetId.equals(newTarget.getFirstTarget()) || newTarget.getTargets().size() != 1)); + // choose a new target + } else { + // build a target definition with exactly one possible target to select that replaces old target + Target tempTarget = target.copy(); + if (target instanceof TargetAmount) { + ((TargetAmount)tempTarget).setAmountDefinition(new StaticValue(target.getTargetAmount(targetId))); + } + tempTarget.setMinNumberOfTargets(1); + tempTarget.setMaxNumberOfTargets(1); + boolean again; + do { + again = false; + tempTarget.clearChosen(); + if (!tempTarget.chooseTarget(mode.getEffects().get(0).getOutcome(), player.getId(), ability, game)) { + if (player.chooseUse(Outcome.Benefit, "No target object selected. Reset to original target?", game)) { + // use previous target no target was selected + newTarget.addTarget(targetId, target.getTargetAmount(targetId), ability, game, false); + } else { + again = true; + } + } else { + // if possible add the alternate Target - it may not be included in the old definition nor in the already selected targets of the new definition + if (newTarget.getTargets().contains(tempTarget.getFirstTarget()) || target.getTargets().contains(tempTarget.getFirstTarget())) { + if (player.isHuman()) { + game.informPlayer(player, "This target was already selected from origin ability. You can only keep this target!"); + again = true; + } else { + newTarget.addTarget(targetId, target.getTargetAmount(targetId), ability, game, false); + } + } else if (newTarget.getFirstTarget() != null && filterNewTarget != null) { + Permanent newTargetPermanent = game.getPermanent(newTarget.getFirstTarget()); + if (newTargetPermanent == null || !filterNewTarget.match(newTargetPermanent, game)) { + game.informPlayer(player, "This target does not fullfil the target requirements (" + filterNewTarget.getMessage() +")"); + again = true; + } + } else { + // valid target was selected, add it to the new target definition + newTarget.addTarget(tempTarget.getFirstTarget(), target.getTargetAmount(targetId), ability, game, false); + } + } + } while (again && player.isInGame()); + } + } + // keep the target + else { + newTarget.addTarget(targetId, target.getTargetAmount(targetId), ability, game, false); + } + } + return newTarget; + } + + + private String getNamesOfTargets(UUID targetId, Game game) { + MageObject object = game.getObject(targetId); + String targetNames = null; + if (object == null) { + Player targetPlayer = game.getPlayer(targetId); + if (targetPlayer != null) { + targetNames = targetPlayer.getName(); + } + } else { + targetNames = object.getName(); + } + return targetNames; + } + } diff --git a/Mage/src/mage/game/stack/StackObject.java b/Mage/src/mage/game/stack/StackObject.java index 791cc6caaf4..ce408387aa5 100644 --- a/Mage/src/mage/game/stack/StackObject.java +++ b/Mage/src/mage/game/stack/StackObject.java @@ -31,6 +31,7 @@ package mage.game.stack; import java.util.UUID; import mage.MageObject; import mage.abilities.Ability; +import mage.filter.FilterPermanent; import mage.game.Controllable; import mage.game.Game; @@ -41,6 +42,7 @@ public interface StackObject extends MageObject, Controllable { void counter(UUID sourceId, Game game); Ability getStackAbility(); int getConvertedManaCost(); + boolean chooseNewTargets(Game game, UUID playerId, boolean forceChange, boolean onlyOneTarget, FilterPermanent filterNewTarget); @Override StackObject copy(); } diff --git a/Mage/src/mage/target/Target.java b/Mage/src/mage/target/Target.java index 4639f7702fd..c1c5ea41846 100644 --- a/Mage/src/mage/target/Target.java +++ b/Mage/src/mage/target/Target.java @@ -104,4 +104,8 @@ public interface Target extends Serializable { UUID getFirstTarget(); Target copy(); -} + + // some targets are choosen from players that are not the controller of the ability (e.g. Pandemonium) + void setTargetController(UUID playerId); + UUID getTargetController(); +} diff --git a/Mage/src/mage/target/TargetImpl.java b/Mage/src/mage/target/TargetImpl.java index f2c01dc5637..c6d46a94c66 100644 --- a/Mage/src/mage/target/TargetImpl.java +++ b/Mage/src/mage/target/TargetImpl.java @@ -60,6 +60,7 @@ public abstract class TargetImpl implements Target { // is the target handled as targeted spell/ability (notTarget = true is used for not targeted effects like e.g. sacrifice) protected boolean notTarget = false; protected boolean atRandom = false; + protected UUID targetController = null; // if null the ability controller is the targetController @Override public abstract TargetImpl copy(); @@ -84,6 +85,7 @@ public abstract class TargetImpl implements Target { this.zoneChangeCounters.putAll(target.zoneChangeCounters); this.atRandom = target.atRandom; this.notTarget = target.notTarget; + this.targetController = target.targetController; } @Override @@ -421,5 +423,15 @@ public abstract class TargetImpl implements Target { this.atRandom = atRandom; } + @Override + public void setTargetController(UUID playerId) { + this.targetController = playerId; + } + + @Override + public UUID getTargetController() { + return targetController; + } + } diff --git a/Mage/src/mage/target/Targets.java b/Mage/src/mage/target/Targets.java index 8544fa944a2..ad1f1c37fb5 100644 --- a/Mage/src/mage/target/Targets.java +++ b/Mage/src/mage/target/Targets.java @@ -97,7 +97,11 @@ public class Targets extends ArrayList { } while (!isChosen()) { Target target = this.getUnchosen().get(0); - if (!target.chooseTarget(outcome, playerId, source, game)) { + UUID targetController = playerId; + if (target.getTargetController() != null) { // some targets can have controller different than ability controller + targetController = target.getTargetController(); + } + if (!target.chooseTarget(outcome, targetController, source, game)) { return false; } }