[TPR] Added Pandemonium, Cannibalize, Silver Wyvern and Coffin Queen.

This commit is contained in:
LevelX2 2015-04-18 01:21:50 +02:00
parent 57d8cd6ff4
commit 7897485539
24 changed files with 1106 additions and 45 deletions

View file

@ -107,7 +107,7 @@ class CommandeerEffect extends OneShotEffect {
Spell spell = game.getStack().getSpell(targetPointer.getFirst(game, source)); Spell spell = game.getStack().getSpell(targetPointer.getFirst(game, source));
if (controller != null && spell != null) { if (controller != null && spell != null) {
spell.setControllerId(controller.getId()); spell.setControllerId(controller.getId());
spell.chooseNewTargets(game, controller.getId(), false, false); spell.chooseNewTargets(game, controller.getId(), false, false, null);
return true; return true;
} }
return false; return false;

View file

@ -142,7 +142,7 @@ class LightningStormAddCounterEffect extends OneShotEffect {
Spell spell = game.getStack().getSpell(source.getSourceId()); Spell spell = game.getStack().getSpell(source.getSourceId());
if (spell != null) { if (spell != null) {
spell.addCounters(CounterType.CHARGE.createInstance(2), game); 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; return false;
} }

View file

@ -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);
}
}

View file

@ -107,7 +107,7 @@ class QuicksilverDragonEffect extends OneShotEffect {
numTargets += target.getTargets().size(); numTargets += target.getTargets().size();
} }
if (numTargets == 1 && spell.getSpellAbility().getTargets().getFirstTarget().equals(source.getSourceId())) { 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; return true;
} }

View file

@ -87,9 +87,13 @@ class TheAbyssTriggeredAbility extends TriggeredAbilityImpl {
return new TheAbyssTriggeredAbility(this); return new TheAbyssTriggeredAbility(this);
} }
@Override
public boolean checkEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.UPKEEP_STEP_PRE;
}
@Override @Override
public boolean checkTrigger(GameEvent event, Game game) { public boolean checkTrigger(GameEvent event, Game game) {
if (event.getType() == GameEvent.EventType.UPKEEP_STEP_PRE) {
Player player = game.getPlayer(event.getPlayerId()); Player player = game.getPlayer(event.getPlayerId());
if (player != null) { if (player != null) {
FilterCreaturePermanent filter = new FilterCreaturePermanent("nonartifact creature you control"); FilterCreaturePermanent filter = new FilterCreaturePermanent("nonartifact creature you control");
@ -105,7 +109,6 @@ class TheAbyssTriggeredAbility extends TriggeredAbilityImpl {
return true; return true;
} }
} }
}
return false; return false;
} }

View file

@ -94,7 +94,7 @@ class DivertEffect extends OneShotEffect {
cost.clearPaid(); cost.clearPaid();
if (!cost.pay(source, game, spell.getControllerId(), if (!cost.pay(source, game, spell.getControllerId(),
spell.getControllerId(), false)) { spell.getControllerId(), false)) {
return spell.chooseNewTargets(game, source.getControllerId(), true, true); return spell.chooseNewTargets(game, source.getControllerId(), true, true, null);
} }
} }
} }

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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";
}
}

View file

@ -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);
}
}

View file

@ -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<MageObject> {
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";
}
}

View file

@ -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;
}
}

View file

@ -32,8 +32,10 @@ import mage.constants.Outcome;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.Mode; import mage.abilities.Mode;
import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.OneShotEffect;
import mage.filter.Filter;
import mage.filter.FilterPermanent;
import mage.game.Game; import mage.game.Game;
import mage.game.stack.Spell; import mage.game.stack.StackObject;
/** /**
* @author BetaSteward_at_googlemail.com * @author BetaSteward_at_googlemail.com
@ -43,21 +45,27 @@ public class ChooseNewTargetsTargetEffect extends OneShotEffect {
private boolean forceChange; private boolean forceChange;
private boolean onlyOneTarget; private boolean onlyOneTarget;
private FilterPermanent filterNewTarget;
public ChooseNewTargetsTargetEffect() { public ChooseNewTargetsTargetEffect() {
this(false, false); 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 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 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); super(Outcome.Benefit);
this.forceChange = forceChange; this.forceChange = forceChange;
this.onlyOneTarget = onlyOneTarget; this.onlyOneTarget = onlyOneTarget;
this.filterNewTarget = filterNewTarget;
} }
public ChooseNewTargetsTargetEffect(final ChooseNewTargetsTargetEffect effect) { public ChooseNewTargetsTargetEffect(final ChooseNewTargetsTargetEffect effect) {
@ -68,9 +76,9 @@ public class ChooseNewTargetsTargetEffect extends OneShotEffect {
@Override @Override
public boolean apply(Game game, Ability source) { public boolean apply(Game game, Ability source) {
Spell spell = game.getStack().getSpell(source.getFirstTarget()); StackObject stackObject = game.getStack().getStackObject(source.getFirstTarget());
if (spell != null) { if (stackObject != null) {
return spell.chooseNewTargets(game, source.getControllerId(), forceChange, onlyOneTarget); return stackObject.chooseNewTargets(game, source.getControllerId(), forceChange, onlyOneTarget, filterNewTarget);
} }
return false; return false;
} }
@ -82,6 +90,9 @@ public class ChooseNewTargetsTargetEffect extends OneShotEffect {
@Override @Override
public String getText(Mode mode) { public String getText(Mode mode) {
if (staticText != null && !staticText.isEmpty()) {
return staticText;
}
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
if (forceChange) { if (forceChange) {
sb.append("change the target of target "); sb.append("change the target of target ");

View file

@ -95,10 +95,11 @@ public class AddCountersTargetEffect extends OneShotEffect {
permanent.addCounters(newCounter, game); permanent.addCounters(newCounter, game);
int numberAdded = permanent.getCounters().getCount(counter.getName()) - before; int numberAdded = permanent.getCounters().getCount(counter.getName()) - before;
affectedTargets ++; affectedTargets ++;
if (!game.isSimulation()) if (!game.isSimulation()) {
game.informPlayers(sourceObject.getLogName() +": "+ controller.getName()+ " puts " + game.informPlayers(sourceObject.getLogName() +": "+ controller.getName()+ " puts " +
numberAdded + " " + counter.getName().toLowerCase() + " counter on " + permanent.getLogName()); numberAdded + " " + counter.getName().toLowerCase() + " counter on " + permanent.getLogName());
} }
}
} else { } else {
Player player = game.getPlayer(uuid); Player player = game.getPlayer(uuid);
if (player != null) { if (player != null) {

View file

@ -31,7 +31,7 @@ public class ShadowAbility extends EvasionAbility implements MageSingleton {
@Override @Override
public String getRule() { public String getRule() {
return "Shadow"; return "Shadow <i>(This creature can block or be blocked by only creatures with shadow.)</i>";
} }
@Override @Override
@ -53,10 +53,7 @@ class ShadowEffect extends RestrictionEffect implements MageSingleton {
@Override @Override
public boolean applies(Permanent permanent, Ability source, Game game) { public boolean applies(Permanent permanent, Ability source, Game game) {
if (permanent.getAbilities().containsKey(ShadowAbility.getInstance().getId())) { return permanent.getAbilities().containsKey(ShadowAbility.getInstance().getId());
return true;
}
return false;
} }
@Override @Override
@ -66,11 +63,8 @@ class ShadowEffect extends RestrictionEffect implements MageSingleton {
@Override @Override
public boolean canBeBlocked(Permanent attacker, Permanent blocker, Ability source, Game game) { public boolean canBeBlocked(Permanent attacker, Permanent blocker, Ability source, Game game) {
if (blocker.getAbilities().containsKey(ShadowAbility.getInstance().getId()) return blocker.getAbilities().containsKey(ShadowAbility.getInstance().getId())
|| game.getContinuousEffects().asThough(blocker.getId(), AsThoughEffectType.BLOCK_SHADOW, source, blocker.getControllerId(), game)) { || game.getContinuousEffects().asThough(blocker.getId(), AsThoughEffectType.BLOCK_SHADOW, source, blocker.getControllerId(), game);
return true;
}
return false;
} }
@Override @Override

View file

@ -177,7 +177,16 @@ public class GameEvent {
ADD_COUNTER, COUNTER_ADDED, ADD_COUNTER, COUNTER_ADDED,
ADD_COUNTERS, COUNTERS_ADDED, ADD_COUNTERS, COUNTERS_ADDED,
COUNTER_REMOVED, 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, GAIN_CONTROL, GAINED_CONTROL,
CREATE_TOKEN, CREATE_TOKEN,

View file

@ -55,6 +55,7 @@ import mage.constants.SpellAbilityType;
import mage.constants.Zone; import mage.constants.Zone;
import mage.counters.Counter; import mage.counters.Counter;
import mage.counters.Counters; import mage.counters.Counters;
import mage.filter.FilterPermanent;
import mage.game.Game; import mage.game.Game;
import mage.game.events.ZoneChangeEvent; import mage.game.events.ZoneChangeEvent;
import mage.game.permanent.Permanent; import mage.game.permanent.Permanent;
@ -319,7 +320,7 @@ public class Spell implements StackObject, Card {
* @return * @return
*/ */
public boolean chooseNewTargets(Game game, UUID playerId) { 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 game
* @param playerId - player that can/has to change the taregt of the spell * @param playerId - player that can/has to change the taregt of the spell
* @param forceChange - does only work for targets with maximum of one * @param forceChange - does only work for targets with maximum of one targetId
* targetId * @param onlyOneTarget - 114.6b one target must be changed to another target
* @param onlyOneTarget - 114.6b one target must be changed to another * @param filterNewTarget restriction for the new target, if null nothing is cheched
* target
* @return * @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); Player player = game.getPlayer(playerId);
if (player != null) { if (player != null) {
StringBuilder newTargetDescription = new StringBuilder(); StringBuilder newTargetDescription = new StringBuilder();
@ -393,7 +394,7 @@ public class Spell implements StackObject, Card {
for (UUID modeId : spellAbility.getModes().getSelectedModes()) { for (UUID modeId : spellAbility.getModes().getSelectedModes()) {
Mode mode = spellAbility.getModes().get(modeId); Mode mode = spellAbility.getModes().get(modeId);
for (Target target : mode.getTargets()) { 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 // clear the old target and copy all targets from new target
target.clearChosen(); target.clearChosen();
for (UUID targetId : newTarget.getTargets()) { for (UUID targetId : newTarget.getTargets()) {
@ -424,7 +425,7 @@ public class Spell implements StackObject, Card {
* @param game * @param game
* @return * @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(); Target newTarget = target.copy();
newTarget.clearChosen(); newTarget.clearChosen();
for (UUID targetId : target.getTargets()) { for (UUID targetId : target.getTargets()) {
@ -443,6 +444,14 @@ public class Spell implements StackObject, Card {
newTarget.clearChosen(); newTarget.clearChosen();
// TODO: Distinction between "spell controller" and "player that can change the target" - here player is used for both // 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); 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)); } while (player.isInGame() && (targetId.equals(newTarget.getFirstTarget()) || newTarget.getTargets().size() != 1));
// choose a new target // choose a new target
} else { } else {
@ -473,6 +482,12 @@ public class Spell implements StackObject, Card {
} else { } else {
newTarget.addTarget(targetId, target.getTargetAmount(targetId), spellAbility, game, false); 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 { } else {
// valid target was selected, add it to the new target definition // valid target was selected, add it to the new target definition
newTarget.addTarget(tempTarget.getFirstTarget(), target.getTargetAmount(targetId), spellAbility, game, false); newTarget.addTarget(tempTarget.getFirstTarget(), target.getTargetAmount(targetId), spellAbility, game, false);

View file

@ -54,10 +54,15 @@ import mage.target.Targets;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
import mage.abilities.dynamicvalue.common.StaticValue;
import mage.cards.Card; import mage.cards.Card;
import mage.constants.AbilityWord; import mage.constants.AbilityWord;
import mage.constants.Outcome;
import mage.filter.FilterPermanent;
import mage.game.events.GameEvent; import mage.game.events.GameEvent;
import mage.game.permanent.Permanent;
import mage.players.Player; import mage.players.Player;
import mage.target.TargetAmount;
import mage.watchers.Watcher; import mage.watchers.Watcher;
/** /**
@ -535,4 +540,197 @@ public class StackAbility implements StackObject, Ability {
throw new UnsupportedOperationException("Not supported."); 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;
}
} }

View file

@ -31,6 +31,7 @@ package mage.game.stack;
import java.util.UUID; import java.util.UUID;
import mage.MageObject; import mage.MageObject;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.filter.FilterPermanent;
import mage.game.Controllable; import mage.game.Controllable;
import mage.game.Game; import mage.game.Game;
@ -41,6 +42,7 @@ public interface StackObject extends MageObject, Controllable {
void counter(UUID sourceId, Game game); void counter(UUID sourceId, Game game);
Ability getStackAbility(); Ability getStackAbility();
int getConvertedManaCost(); int getConvertedManaCost();
boolean chooseNewTargets(Game game, UUID playerId, boolean forceChange, boolean onlyOneTarget, FilterPermanent filterNewTarget);
@Override @Override
StackObject copy(); StackObject copy();
} }

View file

@ -104,4 +104,8 @@ public interface Target extends Serializable {
UUID getFirstTarget(); UUID getFirstTarget();
Target copy(); 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();
} }

View file

@ -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) // 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 notTarget = false;
protected boolean atRandom = false; protected boolean atRandom = false;
protected UUID targetController = null; // if null the ability controller is the targetController
@Override @Override
public abstract TargetImpl copy(); public abstract TargetImpl copy();
@ -84,6 +85,7 @@ public abstract class TargetImpl implements Target {
this.zoneChangeCounters.putAll(target.zoneChangeCounters); this.zoneChangeCounters.putAll(target.zoneChangeCounters);
this.atRandom = target.atRandom; this.atRandom = target.atRandom;
this.notTarget = target.notTarget; this.notTarget = target.notTarget;
this.targetController = target.targetController;
} }
@Override @Override
@ -421,5 +423,15 @@ public abstract class TargetImpl implements Target {
this.atRandom = atRandom; this.atRandom = atRandom;
} }
@Override
public void setTargetController(UUID playerId) {
this.targetController = playerId;
}
@Override
public UUID getTargetController() {
return targetController;
}
} }

View file

@ -97,7 +97,11 @@ public class Targets extends ArrayList<Target> {
} }
while (!isChosen()) { while (!isChosen()) {
Target target = this.getUnchosen().get(0); 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; return false;
} }
} }