Changed ability handling of modal spells to be able to select the same mode multiple times with different targets.

This commit is contained in:
LevelX2 2015-11-14 01:56:56 +01:00
parent b18cae5100
commit 4711e0cf99
40 changed files with 488 additions and 421 deletions

View file

@ -32,7 +32,7 @@ import java.util.List;
import java.util.UUID; import java.util.UUID;
import mage.MageObject; import mage.MageObject;
import mage.ObjectColor; import mage.ObjectColor;
import mage.abilities.Modes; import mage.abilities.Mode;
import mage.abilities.SpellAbility; import mage.abilities.SpellAbility;
import mage.abilities.costs.mana.ManaCosts; import mage.abilities.costs.mana.ManaCosts;
import mage.cards.Card; import mage.cards.Card;
@ -311,19 +311,16 @@ public class CardView extends SimpleCardView {
this.mageObjectType = MageObjectType.SPELL; this.mageObjectType = MageObjectType.SPELL;
Spell spell = (Spell) card; Spell spell = (Spell) card;
for (SpellAbility spellAbility : spell.getSpellAbilities()) { for (SpellAbility spellAbility : spell.getSpellAbilities()) {
for (UUID modeId : spellAbility.getModes().getSelectedModes()) { for (Mode mode : spellAbility.getModes().getSelectedModes()) {
spellAbility.getModes().setActiveMode(modeId); if (mode.getTargets().size() > 0) {
if (spellAbility.getTargets().size() > 0) {
setTargets(spellAbility.getTargets()); setTargets(spellAbility.getTargets());
} }
} }
} }
// show for modal spell, which mode was choosen // show for modal spell, which mode was choosen
if (spell.getSpellAbility().isModal()) { if (spell.getSpellAbility().isModal()) {
Modes modes = spell.getSpellAbility().getModes(); for (Mode mode : spell.getSpellAbility().getModes().getSelectedModes()) {
for (UUID modeId : modes.getSelectedModes()) { this.rules.add("<span color='green'><i>Chosen mode: " + mode.getEffects().getText(mode) + "</i></span>");
modes.setActiveMode(modeId);
this.rules.add("<span color='green'><i>Chosen mode: " + spell.getSpellAbility().getEffects().getText(modes.get(modeId)) + "</i></span>");
} }
} }
} }

View file

@ -31,6 +31,7 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
import mage.MageObject; import mage.MageObject;
import mage.abilities.Mode;
import mage.abilities.Modes; import mage.abilities.Modes;
import mage.abilities.effects.Effect; import mage.abilities.effects.Effect;
import mage.cards.Card; import mage.cards.Card;
@ -98,13 +99,12 @@ public class StackAbilityView extends CardView {
private void updateTargets(Game game, StackAbility ability) { private void updateTargets(Game game, StackAbility ability) {
List<String> names = new ArrayList<>(); List<String> names = new ArrayList<>();
for (UUID modeId : ability.getModes().getSelectedModes()) { for (Mode mode : ability.getModes().getSelectedModes()) {
ability.getModes().setActiveMode(modeId); if (mode.getTargets().size() > 0) {
if (ability.getTargets().size() > 0) { setTargets(mode.getTargets());
setTargets(ability.getTargets());
} else { } else {
List<UUID> targetList = new ArrayList<>(); List<UUID> targetList = new ArrayList<>();
for (Effect effect : ability.getEffects()) { for (Effect effect : mode.getEffects()) {
TargetPointer targetPointer = effect.getTargetPointer(); TargetPointer targetPointer = effect.getTargetPointer();
if (targetPointer instanceof FixedTarget) { if (targetPointer instanceof FixedTarget) {
targetList.add(((FixedTarget) targetPointer).getTarget()); targetList.add(((FixedTarget) targetPointer).getTarget());
@ -132,9 +132,8 @@ public class StackAbilityView extends CardView {
// show for modal ability, which mode was choosen // show for modal ability, which mode was choosen
if (ability.isModal()) { if (ability.isModal()) {
Modes modes = ability.getModes(); Modes modes = ability.getModes();
for (UUID modeId : modes.getSelectedModes()) { for (Mode mode : modes.getSelectedModes()) {
modes.setActiveMode(modeId); this.rules.add("<span color='green'><i>Chosen mode: " + mode.getEffects().getText(mode) + "</i></span>");
this.rules.add("<span color='green'><i>Chosen mode: " + ability.getEffects().getText(modes.get(modeId)) + "</i></span>");
} }
} }
} }

View file

@ -1568,9 +1568,14 @@ public class ComputerPlayer extends PlayerImpl implements Player {
return modes.getMode(); return modes.getMode();
} }
//TODO: improve this; //TODO: improve this;
AvailableMode:
for (Mode mode : modes.getAvailableModes(source, game)) { for (Mode mode : modes.getAvailableModes(source, game)) {
if (!modes.getSelectedModes().contains(mode.getId()) // select only modes not already selected for (Mode selectedMode : modes.getSelectedModes()) {
&& mode.getTargets().canChoose(source.getSourceId(), source.getControllerId(), game)) { // and where targets are available if (selectedMode.getId().equals(mode.getId())) {
continue AvailableMode;
}
}
if (mode.getTargets().canChoose(source.getSourceId(), source.getControllerId(), game)) { // and where targets are available
return mode; return mode;
} }
} }

View file

@ -64,7 +64,16 @@ import mage.constants.ManaType;
import mage.constants.Outcome; import mage.constants.Outcome;
import mage.constants.PhaseStep; import mage.constants.PhaseStep;
import mage.constants.PlayerAction; import mage.constants.PlayerAction;
import static mage.constants.PlayerAction.REQUEST_AUTO_ANSWER_ID_NO;
import static mage.constants.PlayerAction.REQUEST_AUTO_ANSWER_ID_YES;
import static mage.constants.PlayerAction.REQUEST_AUTO_ANSWER_RESET_ALL; import static mage.constants.PlayerAction.REQUEST_AUTO_ANSWER_RESET_ALL;
import static mage.constants.PlayerAction.REQUEST_AUTO_ANSWER_TEXT_NO;
import static mage.constants.PlayerAction.REQUEST_AUTO_ANSWER_TEXT_YES;
import static mage.constants.PlayerAction.RESET_AUTO_SELECT_REPLACEMENT_EFFECTS;
import static mage.constants.PlayerAction.TRIGGER_AUTO_ORDER_ABILITY_FIRST;
import static mage.constants.PlayerAction.TRIGGER_AUTO_ORDER_ABILITY_LAST;
import static mage.constants.PlayerAction.TRIGGER_AUTO_ORDER_NAME_FIRST;
import static mage.constants.PlayerAction.TRIGGER_AUTO_ORDER_NAME_LAST;
import static mage.constants.PlayerAction.TRIGGER_AUTO_ORDER_RESET_ALL; import static mage.constants.PlayerAction.TRIGGER_AUTO_ORDER_RESET_ALL;
import mage.constants.RangeOfInfluence; import mage.constants.RangeOfInfluence;
import mage.constants.Zone; import mage.constants.Zone;
@ -1284,20 +1293,24 @@ public class HumanPlayer extends PlayerImpl {
if (modes.size() > 1) { if (modes.size() > 1) {
MageObject obj = game.getObject(source.getSourceId()); MageObject obj = game.getObject(source.getSourceId());
Map<UUID, String> modeMap = new LinkedHashMap<>(); Map<UUID, String> modeMap = new LinkedHashMap<>();
AvailableModes:
for (Mode mode : modes.getAvailableModes(source, game)) { for (Mode mode : modes.getAvailableModes(source, game)) {
if ((!modes.getSelectedModes().contains(mode.getId()) || modes.isEachModeMoreThanOnce())// show only modes not already selected if more than once is not allowed int timesSelected = 0;
&& mode.getTargets().canChoose(source.getSourceId(), source.getControllerId(), game)) { // and needed targets have to be available for (Mode selectedMode : modes.getSelectedModes()) {
if (mode.getId().equals(selectedMode.getId())) {
if (modes.isEachModeMoreThanOnce()) {
timesSelected++;
} else {
continue AvailableModes;
}
}
}
if (mode.getTargets().canChoose(source.getSourceId(), source.getControllerId(), game)) { // and needed targets have to be available
String modeText = mode.getEffects().getText(mode); String modeText = mode.getEffects().getText(mode);
if (obj != null) { if (obj != null) {
modeText = modeText.replace("{source}", obj.getName()).replace("{this}", obj.getName()); modeText = modeText.replace("{source}", obj.getName()).replace("{this}", obj.getName());
} }
if (modes.isEachModeMoreThanOnce()) { if (modes.isEachModeMoreThanOnce()) {
int timesSelected = 0;
for (UUID selectedModeId : modes.getSelectedModes()) {
if (mode.getId().equals(selectedModeId)) {
timesSelected++;
}
}
if (timesSelected > 0) { if (timesSelected > 0) {
modeText = "(selected " + timesSelected + "x) " + modeText; modeText = "(selected " + timesSelected + "x) " + modeText;
} }
@ -1327,6 +1340,7 @@ public class HumanPlayer extends PlayerImpl {
} }
return null; return null;
} }
return modes.getMode(); return modes.getMode();
} }

View file

@ -30,6 +30,7 @@ package mage.sets.battleforzendikar;
import java.util.UUID; import java.util.UUID;
import mage.MageInt; import mage.MageInt;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.Mode;
import mage.abilities.TriggeredAbilityImpl; import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.OneShotEffect;
import mage.cards.CardImpl; import mage.cards.CardImpl;
@ -102,9 +103,8 @@ class ZadaHedronGrinderTriggeredAbility extends TriggeredAbilityImpl {
Spell spell = game.getStack().getSpell(event.getTargetId()); Spell spell = game.getStack().getSpell(event.getTargetId());
if (isControlledInstantOrSorcery(spell)) { if (isControlledInstantOrSorcery(spell)) {
boolean targetsSource = false; boolean targetsSource = false;
for (UUID modeId : spell.getSpellAbility().getModes().getSelectedModes()) { for (Mode mode : spell.getSpellAbility().getModes().getSelectedModes()) {
spell.getSpellAbility().getModes().setActiveMode(modeId); for (Target target : mode.getTargets()) {
for (Target target : spell.getSpellAbility().getTargets()) {
for (UUID targetId : target.getTargets()) { for (UUID targetId : target.getTargets()) {
if (targetId.equals(getSourceId())) { if (targetId.equals(getSourceId())) {
targetsSource = true; targetsSource = true;
@ -161,9 +161,8 @@ class ZadaHedronGrinderEffect extends OneShotEffect {
if (spell != null && controller != null) { if (spell != null && controller != null) {
Target usedTarget = null; Target usedTarget = null;
setUsedTarget: setUsedTarget:
for (UUID modeId : spell.getSpellAbility().getModes().getSelectedModes()) { for (Mode mode : spell.getSpellAbility().getModes().getSelectedModes()) {
spell.getSpellAbility().getModes().setActiveMode(modeId); for (Target target : mode.getTargets()) {
for (Target target : spell.getSpellAbility().getTargets()) {
if (target.getFirstTarget().equals(source.getSourceId())) { if (target.getFirstTarget().equals(source.getSourceId())) {
usedTarget = target.copy(); usedTarget = target.copy();
usedTarget.clearChosen(); usedTarget.clearChosen();
@ -178,9 +177,8 @@ class ZadaHedronGrinderEffect extends OneShotEffect {
if (!creature.getId().equals(source.getSourceId()) && usedTarget.canTarget(source.getControllerId(), creature.getId(), source, game)) { if (!creature.getId().equals(source.getSourceId()) && usedTarget.canTarget(source.getControllerId(), creature.getId(), source, game)) {
Spell copy = spell.copySpell(); Spell copy = spell.copySpell();
setTarget: setTarget:
for (UUID modeId : spell.getSpellAbility().getModes().getSelectedModes()) { for (Mode mode : spell.getSpellAbility().getModes().getSelectedModes()) {
copy.getSpellAbility().getModes().setActiveMode(modeId); for (Target target : mode.getTargets()) {
for (Target target : copy.getSpellAbility().getTargets()) {
if (target.getClass().equals(usedTarget.getClass()) && target.getMessage().equals(usedTarget.getMessage())) { if (target.getClass().equals(usedTarget.getClass()) && target.getMessage().equals(usedTarget.getMessage())) {
target.clearChosen(); target.clearChosen();
target.add(creature.getId(), game); target.add(creature.getId(), game);

View file

@ -95,6 +95,7 @@ public class MogisGodOfSlaughter extends CardImpl {
} }
class DoUnlessTargetPaysCost extends OneShotEffect { class DoUnlessTargetPaysCost extends OneShotEffect {
private final OneShotEffect executingEffect; private final OneShotEffect executingEffect;
private final Cost cost; private final Cost cost;
private final String userMessage; private final String userMessage;
@ -102,6 +103,7 @@ class DoUnlessTargetPaysCost extends OneShotEffect {
public DoUnlessTargetPaysCost(OneShotEffect effect, Cost cost) { public DoUnlessTargetPaysCost(OneShotEffect effect, Cost cost) {
this(effect, cost, null); this(effect, cost, null);
} }
public DoUnlessTargetPaysCost(OneShotEffect effect, Cost cost, String userMessage) { public DoUnlessTargetPaysCost(OneShotEffect effect, Cost cost, String userMessage) {
super(Outcome.Benefit); super(Outcome.Benefit);
this.executingEffect = effect; this.executingEffect = effect;
@ -123,7 +125,7 @@ class DoUnlessTargetPaysCost extends OneShotEffect {
if (player != null && mageObject != null) { if (player != null && mageObject != null) {
String message = userMessage; String message = userMessage;
if (message == null) { if (message == null) {
message = new StringBuilder(getCostText()).append(" to prevent ").append(executingEffect.getText(source.getModes().getMode())).append("?").toString(); message = getCostText() + " to prevent " + executingEffect.getText(source.getModes().getMode()) + "?";
} }
message = CardUtil.replaceSourceName(message, mageObject.getLogName()); message = CardUtil.replaceSourceName(message, mageObject.getLogName());
cost.clearPaid(); cost.clearPaid();
@ -153,8 +155,8 @@ class DoUnlessTargetPaysCost extends OneShotEffect {
private String getCostText() { private String getCostText() {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
String costText = cost.getText(); String costText = cost.getText();
if (costText != null && if (costText != null
!costText.toLowerCase().startsWith("discard") && !costText.toLowerCase().startsWith("discard")
&& !costText.toLowerCase().startsWith("sacrifice") && !costText.toLowerCase().startsWith("sacrifice")
&& !costText.toLowerCase().startsWith("remove")) { && !costText.toLowerCase().startsWith("remove")) {
sb.append("pay "); sb.append("pay ");

View file

@ -124,7 +124,7 @@ class JaceTelepathUnboundEffect extends OneShotEffect {
Card card = game.getCard(this.getTargetPointer().getFirst(game, source)); Card card = game.getCard(this.getTargetPointer().getFirst(game, source));
if (card != null) { if (card != null) {
ContinuousEffect effect = new JaceTelepathUnboundCastFromGraveyardEffect(); ContinuousEffect effect = new JaceTelepathUnboundCastFromGraveyardEffect();
effect.setTargetPointer(new FixedTarget(card.getId())); effect.setTargetPointer(new FixedTarget(card.getId(), card.getZoneChangeCounter(game)));
game.addEffect(effect, source); game.addEffect(effect, source);
effect = new JaceTelepathUnboundReplacementEffect(card.getId()); effect = new JaceTelepathUnboundReplacementEffect(card.getId());
game.addEffect(effect, source); game.addEffect(effect, source);

View file

@ -29,6 +29,7 @@ package mage.sets.magicorigins;
import java.util.UUID; import java.util.UUID;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.Mode;
import mage.abilities.condition.common.SpellMasteryCondition; import mage.abilities.condition.common.SpellMasteryCondition;
import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.OneShotEffect;
import mage.cards.CardImpl; import mage.cards.CardImpl;
@ -137,9 +138,8 @@ class PsychicRebuttalPredicate implements ObjectPlayerPredicate<ObjectPlayer<Sta
if (controllerId == null) { if (controllerId == null) {
return false; return false;
} }
for (UUID modeId : input.getObject().getStackAbility().getModes().getSelectedModes()) { for (Mode mode : input.getObject().getStackAbility().getModes().getSelectedModes()) {
input.getObject().getStackAbility().getModes().setActiveMode(modeId); for (Target target : mode.getTargets()) {
for (Target target : input.getObject().getStackAbility().getTargets()) {
for (UUID targetId : target.getTargets()) { for (UUID targetId : target.getTargets()) {
if (controllerId.equals(targetId)) { if (controllerId.equals(targetId)) {
return true; return true;

View file

@ -28,11 +28,12 @@
package mage.sets.shardsofalara; package mage.sets.shardsofalara;
import java.util.UUID; import java.util.UUID;
import mage.constants.CardType; import mage.abilities.Mode;
import mage.constants.Rarity;
import mage.abilities.effects.common.CounterTargetEffect; import mage.abilities.effects.common.CounterTargetEffect;
import mage.abilities.effects.common.DrawCardSourceControllerEffect; import mage.abilities.effects.common.DrawCardSourceControllerEffect;
import mage.cards.CardImpl; import mage.cards.CardImpl;
import mage.constants.CardType;
import mage.constants.Rarity;
import mage.filter.FilterSpell; import mage.filter.FilterSpell;
import mage.filter.predicate.ObjectPlayer; import mage.filter.predicate.ObjectPlayer;
import mage.filter.predicate.ObjectPlayerPredicate; import mage.filter.predicate.ObjectPlayerPredicate;
@ -58,7 +59,6 @@ public class HinderingLight extends CardImpl {
super(ownerId, 173, "Hindering Light", Rarity.COMMON, new CardType[]{CardType.INSTANT}, "{W}{U}"); super(ownerId, 173, "Hindering Light", Rarity.COMMON, new CardType[]{CardType.INSTANT}, "{W}{U}");
this.expansionSetCode = "ALA"; this.expansionSetCode = "ALA";
// Counter target spell that targets you or a permanent you control. // Counter target spell that targets you or a permanent you control.
this.getSpellAbility().addEffect(new CounterTargetEffect()); this.getSpellAbility().addEffect(new CounterTargetEffect());
this.getSpellAbility().addTarget(new TargetSpell(filter)); this.getSpellAbility().addTarget(new TargetSpell(filter));
@ -84,9 +84,8 @@ class HinderingLightPredicate implements ObjectPlayerPredicate<ObjectPlayer<Stac
if (controllerId == null) { if (controllerId == null) {
return false; return false;
} }
for (UUID modeId :input.getObject().getStackAbility().getModes().getSelectedModes()) { for (Mode mode : input.getObject().getStackAbility().getModes().getSelectedModes()) {
input.getObject().getStackAbility().getModes().setActiveMode(modeId); for (Target target : mode.getTargets()) {
for (Target target : input.getObject().getStackAbility().getTargets()) {
for (UUID targetId : target.getTargets()) { for (UUID targetId : target.getTargets()) {
if (controllerId.equals(targetId)) { if (controllerId.equals(targetId)) {
return true; return true;

View file

@ -29,6 +29,7 @@ package mage.sets.vintagemasters;
import java.util.UUID; import java.util.UUID;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.Mode;
import mage.abilities.SpellAbility; import mage.abilities.SpellAbility;
import mage.abilities.common.SimpleStaticAbility; import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.dynamicvalue.common.ManacostVariableValue; import mage.abilities.dynamicvalue.common.ManacostVariableValue;
@ -44,14 +45,13 @@ import mage.constants.Rarity;
import mage.constants.Zone; import mage.constants.Zone;
import mage.game.Game; import mage.game.Game;
import mage.target.Target; import mage.target.Target;
import mage.target.Targets;
import mage.target.common.TargetCreatureOrPlayer; import mage.target.common.TargetCreatureOrPlayer;
import mage.util.CardUtil; import mage.util.CardUtil;
/** /**
* *
* @author LoneFox * @author LoneFox
*
*/ */
public class KaerveksTorch extends CardImpl { public class KaerveksTorch extends CardImpl {
@ -95,13 +95,11 @@ class KaerveksTorchCostIncreaseEffect extends CostModificationEffectImpl {
@Override @Override
public boolean applies(Ability abilityToModify, Ability source, Game game) { public boolean applies(Ability abilityToModify, Ability source, Game game) {
if(abilityToModify instanceof SpellAbility || abilityToModify instanceof FlashbackAbility) if (abilityToModify instanceof SpellAbility || abilityToModify instanceof FlashbackAbility) {
{ for (Mode mode : abilityToModify.getModes().getSelectedModes()) {
for(UUID modeId: abilityToModify.getModes().getSelectedModes()) { for (Target target : mode.getTargets()) {
abilityToModify.getModes().setActiveMode(modeId); for (UUID targetId : target.getTargets()) {
for(Target target: abilityToModify.getTargets()) { if (targetId.equals(source.getSourceObject(game).getId())) {
for(UUID id: target.getTargets()) {
if(id.equals(source.getSourceObject(game).getId())) {
return true; return true;
} }
} }

View file

@ -1,43 +1,42 @@
/* /*
* Copyright 2010 maurer.it_at_googlemail.com. All rights reserved. * Copyright 2010 maurer.it_at_googlemail.com. All rights reserved.
* *
* Redistribution and use in source and binary forms, with or without modification, are * Redistribution and use in source and binary forms, with or without modification, are
* permitted provided that the following conditions are met: * permitted provided that the following conditions are met:
* *
* 1. Redistributions of source code must retain the above copyright notice, this list of * 1. Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer. * conditions and the following disclaimer.
* *
* 2. Redistributions in binary form must reproduce the above copyright notice, this list * 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 * of conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution. * provided with the distribution.
* *
* THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED * 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 * 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 * 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 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 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 * 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 * 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 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
* *
* The views and conclusions contained in the software and documentation are those of the * 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 * authors and should not be interpreted as representing official policies, either expressed
* or implied, of maurer.it_at_googlemail.com. * or implied, of maurer.it_at_googlemail.com.
*/ */
package mage.sets.zendikar; package mage.sets.zendikar;
import java.util.UUID; import java.util.UUID;
import mage.constants.CardType;
import mage.constants.Rarity;
import mage.constants.Zone;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.common.EntersBattlefieldTriggeredAbility;
import mage.abilities.common.LeavesBattlefieldTriggeredAbility; import mage.abilities.common.LeavesBattlefieldTriggeredAbility;
import mage.abilities.effects.common.ExileTargetForSourceEffect; import mage.abilities.effects.common.ExileTargetForSourceEffect;
import mage.abilities.effects.common.ReturnFromExileForSourceEffect; import mage.abilities.effects.common.ReturnFromExileForSourceEffect;
import mage.cards.CardImpl; import mage.cards.CardImpl;
import mage.constants.CardType;
import mage.constants.Rarity;
import mage.constants.Zone;
import mage.filter.common.FilterCreaturePermanent; import mage.filter.common.FilterCreaturePermanent;
import mage.filter.predicate.permanent.AnotherPredicate; import mage.filter.predicate.permanent.AnotherPredicate;
import mage.target.Target; import mage.target.Target;
@ -53,7 +52,6 @@ public class JourneyToNowhere extends CardImpl {
super(ownerId, 14, "Journey to Nowhere", Rarity.COMMON, new CardType[]{CardType.ENCHANTMENT}, "{1}{W}"); super(ownerId, 14, "Journey to Nowhere", Rarity.COMMON, new CardType[]{CardType.ENCHANTMENT}, "{1}{W}");
this.expansionSetCode = "ZEN"; this.expansionSetCode = "ZEN";
// When Journey to Nowhere enters the battlefield, exile target creature. // When Journey to Nowhere enters the battlefield, exile target creature.
FilterCreaturePermanent filter = new FilterCreaturePermanent(); FilterCreaturePermanent filter = new FilterCreaturePermanent();
filter.add(new AnotherPredicate()); filter.add(new AnotherPredicate());

View file

@ -45,11 +45,13 @@ public class CastDestroySpellsTest extends CardTestPlayerBaseAI {
@Test @Test
public void testOrzhovCharm() { public void testOrzhovCharm() {
// Choose one - // Choose one -
// Return target creature you control and all Auras you control attached to it to their owner's hand; // - Return target creature you control and all Auras you control attached to it to their owner's hand;
// or destroy target creature and you lose life equal to its toughness; // - Destroy target creature and you lose life equal to its toughness;
// or return target creature card with converted mana cost 1 or less from your graveyard to the battlefield. // - Return target creature card with converted mana cost 1 or less from your graveyard to the battlefield.
addCard(Zone.HAND, playerA, "Orzhov Charm"); // {W}{B} addCard(Zone.HAND, playerA, "Orzhov Charm"); // {W}{B}
// {T}: Add {1} to your mana pool.
// {T} {W/B}, {T}: Add {W}{W}, {W}{B}, or {B}{B} to your mana pool.
addCard(Zone.BATTLEFIELD, playerA, "Fetid Heath", 1); addCard(Zone.BATTLEFIELD, playerA, "Fetid Heath", 1);
addCard(Zone.BATTLEFIELD, playerA, "Plains", 1); addCard(Zone.BATTLEFIELD, playerA, "Plains", 1);

View file

@ -51,10 +51,11 @@ public class ReturnToHandTest extends CardTestPlayerBase {
addCard(Zone.BATTLEFIELD, playerB, "Swamp", 2); addCard(Zone.BATTLEFIELD, playerB, "Swamp", 2);
addCard(Zone.BATTLEFIELD, playerB, "Silvercoat Lion"); addCard(Zone.BATTLEFIELD, playerB, "Silvercoat Lion");
addCard(Zone.BATTLEFIELD, playerB, "Mountain", 3); addCard(Zone.BATTLEFIELD, playerB, "Mountain", 3);
addCard(Zone.HAND, playerB, "Bone Splinters");
// As an additional cost to cast Bone Splinters, sacrifice a creature. // As an additional cost to cast Bone Splinters, sacrifice a creature.
// Destroy target creature. // Destroy target creature.
addCard(Zone.HAND, playerB, "Bone Splinters");
castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Bone Splinters", "Pillarfield Ox"); castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Bone Splinters", "Pillarfield Ox");
setChoice(playerB, "Skarrgan Firebird"); setChoice(playerB, "Skarrgan Firebird");
@ -126,7 +127,7 @@ public class ReturnToHandTest extends CardTestPlayerBase {
// Devoid // Devoid
// Choose one or both // Choose one or both
// - Return target spell or creature to its owner's hand; // - Return target spell or creature to its owner's hand;
// or Brutal Expulsion deals 2 damage to target creature or planeswalker. If that permanent would be put into a graveyard this turn, exile it instead. // - Brutal Expulsion deals 2 damage to target creature or planeswalker. If that permanent would be put into a graveyard this turn, exile it instead.
addCard(Zone.HAND, playerA, "Brutal Expulsion"); // {2}{U}{R} addCard(Zone.HAND, playerA, "Brutal Expulsion"); // {2}{U}{R}
addCard(Zone.BATTLEFIELD, playerB, "Plains", 4); addCard(Zone.BATTLEFIELD, playerB, "Plains", 4);
@ -135,6 +136,8 @@ public class ReturnToHandTest extends CardTestPlayerBase {
castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Pillarfield Ox"); castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Pillarfield Ox");
castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerA, "Brutal Expulsion", "mode=1Pillarfield Ox^mode=2Silvercoat Lion", "Pillarfield Ox"); castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerA, "Brutal Expulsion", "mode=1Pillarfield Ox^mode=2Silvercoat Lion", "Pillarfield Ox");
setModeChoice(playerA, "1");
setModeChoice(playerA, "2");
setStopAt(2, PhaseStep.BEGIN_COMBAT); setStopAt(2, PhaseStep.BEGIN_COMBAT);
execute(); execute();

View file

@ -86,7 +86,7 @@ public class DeathtouchTest extends CardTestPlayerBase {
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Marath, Will of the Wild"); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Marath, Will of the Wild");
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Equip {2}", "Marath, Will of the Wild"); activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Equip {2}", "Marath, Will of the Wild", "Marath, Will of the Wild", StackClause.WHILE_NOT_ON_STACK);
activateAbility(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "{X},Remove X +1/+1 counters from Marath", "Archangel of Thune"); activateAbility(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "{X},Remove X +1/+1 counters from Marath", "Archangel of Thune");
setChoice(playerA, "X=3"); setChoice(playerA, "X=3");

View file

@ -36,15 +36,14 @@ import org.mage.test.serverside.base.CardTestPlayerBase;
* *
* @author LevelX2 * @author LevelX2
*/ */
public class HeroicTest extends CardTestPlayerBase { public class HeroicTest extends CardTestPlayerBase {
/** /**
* When casting Dromoka's Command targeting two of my own Heroic creatures, only one of them triggers. * When casting Dromoka's Command targeting two of my own Heroic creatures,
* It appears to be the one targeted with mode 4 (fight) rather than the one targeted with mode 3 (+1/+1 counter). * only one of them triggers. It appears to be the one targeted with mode 4
* (fight) rather than the one targeted with mode 3 (+1/+1 counter).
* Screenshot attached. Reproducible. * Screenshot attached. Reproducible.
*/ */
@Test @Test
public void testHeroicWithModal() { public void testHeroicWithModal() {
// Heroic - Whenever you cast a spell that targets Favored Hoplite, put a +1/+1 counter on Favored Hoplite and prevent all damage that would be dealt to it this turn. // Heroic - Whenever you cast a spell that targets Favored Hoplite, put a +1/+1 counter on Favored Hoplite and prevent all damage that would be dealt to it this turn.

View file

@ -1,31 +1,30 @@
/* /*
* Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved.
* *
* Redistribution and use in source and binary forms, with or without modification, are * Redistribution and use in source and binary forms, with or without modification, are
* permitted provided that the following conditions are met: * permitted provided that the following conditions are met:
* *
* 1. Redistributions of source code must retain the above copyright notice, this list of * 1. Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer. * conditions and the following disclaimer.
* *
* 2. Redistributions in binary form must reproduce the above copyright notice, this list * 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 * of conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution. * provided with the distribution.
* *
* THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED * 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 * 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 * 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 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 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 * 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 * 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 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
* *
* The views and conclusions contained in the software and documentation are those of the * 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 * authors and should not be interpreted as representing official policies, either expressed
* or implied, of BetaSteward_at_googlemail.com. * or implied, of BetaSteward_at_googlemail.com.
*/ */
package org.mage.test.cards.abilities.oneshot.counterspell; package org.mage.test.cards.abilities.oneshot.counterspell;
import mage.constants.PhaseStep; import mage.constants.PhaseStep;
@ -34,17 +33,17 @@ import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase; import org.mage.test.serverside.base.CardTestPlayerBase;
/** /**
* Cryptic Command * Cryptic Command Instant, 1UUU Choose two Counter target spell; or return
* Instant, 1UUU * target permanent to its owner's hand; or tap all creatures your opponents
* Choose two Counter target spell; or return target permanent to its owner's hand; or tap all creatures your opponents control; or draw a card. * control; or draw a card.
* *
* @author LevelX2 * @author LevelX2
*/ */
public class CrypticCommandTest extends CardTestPlayerBase { public class CrypticCommandTest extends CardTestPlayerBase {
/** /**
* Test that if command has only one target and that targets is not valid on resolution, Cryptic Command fizzeles * Test that if command has only one target and that targets is not valid on
* The player does not draw a card * resolution, Cryptic Command fizzeles The player does not draw a card
*/ */
@Test @Test
public void testCommand() { public void testCommand() {
@ -80,12 +79,14 @@ public class CrypticCommandTest extends CardTestPlayerBase {
assertHandCount(playerB, 0); // Because Cryptic Command has no legal target playerB does not draw a card and has 0 cards in hand assertHandCount(playerB, 0); // Because Cryptic Command has no legal target playerB does not draw a card and has 0 cards in hand
} }
/**
* Game is not letting me play Ricochet Trap targetting oponent's Cryptic Command, /**
* modes 1 and 4. It only has one target and should be allowed * Game is not letting me play Ricochet Trap targetting oponent's Cryptic
* Command, modes 1 and 4. It only has one target and should be allowed
*/ */
@Test @Test
public void testCommandChangeTarget() { public void testCommandChangeTarget() {
// Target player reveals his or her hand. You choose a nonland card from it. That player discards that card. You lose 2 life.
addCard(Zone.HAND, playerA, "Thoughtseize"); addCard(Zone.HAND, playerA, "Thoughtseize");
// Counter target spell. If that spell is countered this way, put it into its owner's hand instead of into that player's graveyard. // Counter target spell. If that spell is countered this way, put it into its owner's hand instead of into that player's graveyard.
// Draw a card. // Draw a card.
@ -99,11 +100,11 @@ public class CrypticCommandTest extends CardTestPlayerBase {
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Thoughtseize", playerB); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Thoughtseize", playerB);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", playerB); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", playerB);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Cryptic Command", "Thoughtseize"); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Cryptic Command", "mode=1Thoughtseize", "Thoughtseize", StackClause.WHILE_ON_STACK);
setModeChoice(playerB, "1"); // Counter target spell setModeChoice(playerB, "1"); // Counter target spell
setModeChoice(playerB, "4"); // Draw a card setModeChoice(playerB, "4"); // Draw a card
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Ricochet Trap", "Cryptic Command"); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Ricochet Trap", "Cryptic Command", "Cryptic Command", StackClause.WHILE_ON_STACK);
addTarget(playerA, "Lightning Bolt"); addTarget(playerA, "Lightning Bolt");
setStopAt(1, PhaseStep.CLEANUP); setStopAt(1, PhaseStep.CLEANUP);

View file

@ -63,6 +63,7 @@ public class NotOfThisWorldTest extends CardTestPlayerBase {
setStopAt(2, PhaseStep.PRECOMBAT_MAIN); setStopAt(2, PhaseStep.PRECOMBAT_MAIN);
execute(); execute();
assertHandCount(playerB, "Not of This World", 0);
assertGraveyardCount(playerB, "Not of This World", 1); assertGraveyardCount(playerB, "Not of This World", 1);
assertPermanentCount(playerB, "Ruhan of the Fomori", 1); assertPermanentCount(playerB, "Ruhan of the Fomori", 1);

View file

@ -22,6 +22,7 @@ public class GainControlTargetEffectTest extends CardTestPlayerBase {
*/ */
@Test @Test
public void testPermanentControlEffect() { public void testPermanentControlEffect() {
// When Smelt-Ward Gatekeepers enters the battlefield, if you control two or more Gates, gain control of target creature an opponent controls until end of turn. Untap that creature. That creature gains haste until end of turn.
addCard(Zone.HAND, playerA, "Smelt-Ward Gatekeepers", 1); addCard(Zone.HAND, playerA, "Smelt-Ward Gatekeepers", 1);
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 4); addCard(Zone.BATTLEFIELD, playerA, "Mountain", 4);
addCard(Zone.BATTLEFIELD, playerA, "Boros Guildgate", 2); addCard(Zone.BATTLEFIELD, playerA, "Boros Guildgate", 2);

View file

@ -13,11 +13,11 @@ import org.mage.test.serverside.base.CardTestPlayerBase;
* *
* @author jeffwadsworth * @author jeffwadsworth
*/ */
public class AbattoirGhoulTest extends CardTestPlayerBase{ public class AbattoirGhoulTest extends CardTestPlayerBase {
@Test @Test
public void testAbattoirGhoulEffect() { public void testAbattoirGhoulEffect() {
// Whenever a creature dealt damage by Abattoir Ghoul this turn dies, you gain life equal to that creature's toughness.
addCard(Zone.BATTLEFIELD, playerA, "Abattoir Ghoul", 1); addCard(Zone.BATTLEFIELD, playerA, "Abattoir Ghoul", 1);
addCard(Zone.BATTLEFIELD, playerB, "Memnite", 1); addCard(Zone.BATTLEFIELD, playerB, "Memnite", 1);
addCard(Zone.BATTLEFIELD, playerB, "Shivan Dragon", 1); addCard(Zone.BATTLEFIELD, playerB, "Shivan Dragon", 1);

View file

@ -36,18 +36,16 @@ import org.mage.test.serverside.base.CardTestPlayerBase;
* *
* @author LeveX2 * @author LeveX2
*/ */
public class JourneyToNowhereTest extends CardTestPlayerBase { public class JourneyToNowhereTest extends CardTestPlayerBase {
/* /*
Journey to Nowhere Enchantment {1}{W} Journey to Nowhere Enchantment {1}{W}
When Journey to Nowhere enters the battlefield, exile target creature. When Journey to Nowhere enters the battlefield, exile target creature.
When Journey to Nowhere leaves the battlefield, return the exiled card to the battlefield under its owner's control. When Journey to Nowhere leaves the battlefield, return the exiled card to the battlefield under its owner's control.
10/1/2009: If Journey to Nowhere leaves the battlefield before its first ability has resolved, its second ability will
trigger and do nothing. Then its first ability will resolve and exile the targeted creature forever.
*/
10/1/2009: If Journey to Nowhere leaves the battlefield before its first ability has resolved, its second ability will
trigger and do nothing. Then its first ability will resolve and exile the targeted creature forever.
*/
@Test @Test
public void testTargetGetsExiled() { public void testTargetGetsExiled() {
addCard(Zone.HAND, playerA, "Journey to Nowhere"); addCard(Zone.HAND, playerA, "Journey to Nowhere");
@ -64,7 +62,6 @@ public class JourneyToNowhereTest extends CardTestPlayerBase {
assertExileCount("Silvercoat Lion", 1); assertExileCount("Silvercoat Lion", 1);
} }
@Test @Test
public void testTargetGetsExiledAndReturns() { public void testTargetGetsExiledAndReturns() {
addCard(Zone.HAND, playerA, "Journey to Nowhere"); addCard(Zone.HAND, playerA, "Journey to Nowhere");
@ -88,9 +85,9 @@ public class JourneyToNowhereTest extends CardTestPlayerBase {
} }
/* /*
10/1/2009: If Journey to Nowhere leaves the battlefield before its first ability has resolved, its second ability will 10/1/2009: If Journey to Nowhere leaves the battlefield before its first ability has resolved, its second ability will
trigger and do nothing. Then its first ability will resolve and exile the targeted creature forever. trigger and do nothing. Then its first ability will resolve and exile the targeted creature forever.
*/ */
@Test @Test
public void testTargetGetsExiledAndDoesNeverReturn() { public void testTargetGetsExiledAndDoesNeverReturn() {
addCard(Zone.HAND, playerA, "Journey to Nowhere"); addCard(Zone.HAND, playerA, "Journey to Nowhere");
@ -113,12 +110,12 @@ public class JourneyToNowhereTest extends CardTestPlayerBase {
} }
/* /*
Journey is played and targets the creature as it enters the battlefield. Journey is played and targets the creature as it enters the battlefield.
The Journey will be returned to hand before the ability resolves. The Journey will be returned to hand before the ability resolves.
The Journey will be played again targeting another creature. The Journey will be played again targeting another creature.
The Journey will be disenchanted later, so only the second creature has to return to battlefield. The Journey will be disenchanted later, so only the second creature has to return to battlefield.
*/ */
@Test @Test
public void testTargetGetsExiledAndDoesNeverReturnAndJourneyPlayedAgain() { public void testTargetGetsExiledAndDoesNeverReturnAndJourneyPlayedAgain() {
addCard(Zone.HAND, playerA, "Journey to Nowhere"); addCard(Zone.HAND, playerA, "Journey to Nowhere");
@ -133,7 +130,7 @@ public class JourneyToNowhereTest extends CardTestPlayerBase {
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Journey to Nowhere"); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Journey to Nowhere");
addTarget(playerA, "Silvercoat Lion"); addTarget(playerA, "Silvercoat Lion");
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Boomerang", "Journey to Nowhere"); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Boomerang", "Journey to Nowhere", "Journey to Nowhere", StackClause.WHILE_NOT_ON_STACK);
castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Journey to Nowhere"); castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Journey to Nowhere");
addTarget(playerA, "Pillarfield Ox"); addTarget(playerA, "Pillarfield Ox");

View file

@ -94,12 +94,12 @@ public class ReturnToHandEffectsTest extends CardTestPlayerBase {
execute(); execute();
assertPermanentCount(playerA, "Stormfront Riders", 1); assertPermanentCount(playerA, "Stormfront Riders", 1);
assertPermanentCount(playerA, "Rat", 0);
assertHandCount(playerA, "Silvercoat Lion", 2); assertHandCount(playerA, "Silvercoat Lion", 2);
assertGraveyardCount(playerA, "Lab Rats", 1); assertGraveyardCount(playerA, "Lab Rats", 1);
assertGraveyardCount(playerB, "Boomerang", 1); assertGraveyardCount(playerB, "Boomerang", 1);
assertPermanentCount(playerA, "Soldier", 3); assertPermanentCount(playerA, "Soldier", 3);
assertPermanentCount(playerA, "Rat", 0);
} }

View file

@ -123,10 +123,10 @@ public class SpellskiteTest extends CardTestPlayerBase {
public void testSpellskite() { public void testSpellskite() {
addCard(Zone.BATTLEFIELD, playerA, "Island", 4); addCard(Zone.BATTLEFIELD, playerA, "Island", 4);
// Choose two - // Choose two -
// Counter target spell; // - Counter target spell;
// or return target permanent to its owner's hand; // - return target permanent to its owner's hand;
// or tap all creatures your opponents control; // - tap all creatures your opponents control;
// or draw a card. // - draw a card.
addCard(Zone.HAND, playerA, "Cryptic Command"); addCard(Zone.HAND, playerA, "Cryptic Command");
addCard(Zone.BATTLEFIELD, playerB, "Spellskite", 1); addCard(Zone.BATTLEFIELD, playerB, "Spellskite", 1);
@ -141,7 +141,7 @@ public class SpellskiteTest extends CardTestPlayerBase {
setModeChoice(playerA, "1"); // Counter target spell setModeChoice(playerA, "1"); // Counter target spell
setModeChoice(playerA, "2"); // return target permanent to its owner's hand setModeChoice(playerA, "2"); // return target permanent to its owner's hand
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerB, "{UP}: Change a target of target spell or ability to {this}.", "Cryptic Command"); activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerB, "{UP}: Change a target of target spell or ability to {this}.", "Cryptic Command", "Cryptic Command");
setStopAt(1, PhaseStep.BEGIN_COMBAT); setStopAt(1, PhaseStep.BEGIN_COMBAT);
execute(); execute();

View file

@ -3,7 +3,6 @@
* To change this template file, choose Tools | Templates * To change this template file, choose Tools | Templates
* and open the template in the editor. * and open the template in the editor.
*/ */
package org.mage.test.lki; package org.mage.test.lki;
import mage.constants.PhaseStep; import mage.constants.PhaseStep;
@ -21,9 +20,9 @@ public class LastKnownInformationTest extends CardTestPlayerBase {
* see here for more information * see here for more information
* http://www.slightlymagic.net/forum/viewtopic.php?f=116&t=14516 * http://www.slightlymagic.net/forum/viewtopic.php?f=116&t=14516
* *
* Tests Safehold Elite with persist returns to battlefield with -1/-1 counter * Tests Safehold Elite with persist returns to battlefield with -1/-1
* Murder Investigation has to put 2 tokens onto battlefield because enchanted Safehold Elite * counter Murder Investigation has to put 2 tokens onto battlefield because
* was 2/2 * enchanted Safehold Elite was 2/2
* *
* @author LevelX * @author LevelX
*/ */
@ -38,9 +37,9 @@ public class LastKnownInformationTest extends CardTestPlayerBase {
// {1}{W} // {1}{W}
// Enchant creature you control // Enchant creature you control
// When enchanted creature dies, put X 1/1 white Soldier creature tokens onto the battlefield, where X is its power. // When enchanted creature dies, put X 1/1 white Soldier creature tokens onto the battlefield, where X is its power.
addCard(Zone.HAND, playerA, "Murder Investigation",1); addCard(Zone.HAND, playerA, "Murder Investigation", 1);
addCard(Zone.HAND, playerB, "Lightning Bolt",2); addCard(Zone.HAND, playerB, "Lightning Bolt", 2);
addCard(Zone.BATTLEFIELD, playerB, "Mountain", 2); addCard(Zone.BATTLEFIELD, playerB, "Mountain", 2);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Murder Investigation", "Safehold Elite"); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Murder Investigation", "Safehold Elite");
@ -64,11 +63,14 @@ public class LastKnownInformationTest extends CardTestPlayerBase {
} }
/** /**
* Here we test that Trostani's first ability checks the toughness on resolve. * Here we test that Trostani's first ability checks the toughness on
* resolve.
* *
*/ */
@Test @Test
public void testTrostaniSelesnyasVoice1() { public void testTrostaniSelesnyasVoice1() {
// Whenever another creature enters the battlefield under your control, you gain life equal to that creature's toughness.
// {1}{G}{W}, {T}: Populate. (Put a token onto the battlefield that's a copy of a creature token you control.)
addCard(Zone.BATTLEFIELD, playerA, "Trostani, Selesnya's Voice"); addCard(Zone.BATTLEFIELD, playerA, "Trostani, Selesnya's Voice");
addCard(Zone.BATTLEFIELD, playerA, "Forest", 3); addCard(Zone.BATTLEFIELD, playerA, "Forest", 3);
@ -76,20 +78,21 @@ public class LastKnownInformationTest extends CardTestPlayerBase {
addCard(Zone.HAND, playerA, "Grizzly Bears", 1); addCard(Zone.HAND, playerA, "Grizzly Bears", 1);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Grizzly Bears"); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Grizzly Bears");
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Giant Growth", "Grizzly Bears"); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Giant Growth", "Grizzly Bears", "Grizzly Bears", StackClause.WHILE_NOT_ON_STACK);
setStopAt(1, PhaseStep.END_TURN); setStopAt(1, PhaseStep.END_TURN);
execute(); execute();
assertGraveyardCount(playerA, "Giant Growth", 1);
assertPermanentCount(playerA, "Grizzly Bears", 1); assertPermanentCount(playerA, "Grizzly Bears", 1);
assertLife(playerA, 25); assertLife(playerA, 25);
} }
/** /**
* Here we test correct spell interaction by playing Cloudshift BEFORE Giant Growth resolves. * Here we test correct spell interaction by playing Cloudshift BEFORE Giant
* Cloudshift will remove 2/2 creature and it will return as 2/2. * Growth resolves. Cloudshift will remove 2/2 creature and it will return
* Giant Growth will be fizzled. * as 2/2. Giant Growth will be fizzled. That means that player should gain
* That means that player should gain 2 + 2 life. * 2 + 2 life.
*/ */
@Test @Test
public void testTrostaniSelesnyasVoice2() { public void testTrostaniSelesnyasVoice2() {
@ -102,7 +105,7 @@ public class LastKnownInformationTest extends CardTestPlayerBase {
addCard(Zone.HAND, playerA, "Grizzly Bears", 1); addCard(Zone.HAND, playerA, "Grizzly Bears", 1);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Grizzly Bears"); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Grizzly Bears");
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Giant Growth", "Grizzly Bears"); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Giant Growth", "Grizzly Bears", "Grizzly Bears", StackClause.WHILE_NOT_ON_STACK);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cloudshift", "Grizzly Bears", "Giant Growth", castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cloudshift", "Grizzly Bears", "Giant Growth",
StackClause.WHILE_ON_STACK); StackClause.WHILE_ON_STACK);
@ -114,8 +117,8 @@ public class LastKnownInformationTest extends CardTestPlayerBase {
} }
/** /**
* Here we test actual use of LKI by playing Cloudshift AFTER Giant Growth resolves. * Here we test actual use of LKI by playing Cloudshift AFTER Giant Growth
* Cloudshift will remove 5/5 creature and it will return as 2/2. * resolves. Cloudshift will remove 5/5 creature and it will return as 2/2.
* That means that player should gain 5 + 2 life. * That means that player should gain 5 + 2 life.
* *
*/ */
@ -130,7 +133,7 @@ public class LastKnownInformationTest extends CardTestPlayerBase {
addCard(Zone.HAND, playerA, "Grizzly Bears", 1); addCard(Zone.HAND, playerA, "Grizzly Bears", 1);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Grizzly Bears"); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Grizzly Bears");
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Giant Growth", "Grizzly Bears"); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Giant Growth", "Grizzly Bears", "Grizzly Bears", StackClause.WHILE_NOT_ON_STACK);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cloudshift", "Grizzly Bears", "Giant Growth", castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cloudshift", "Grizzly Bears", "Giant Growth",
StackClause.WHILE_NOT_ON_STACK); StackClause.WHILE_NOT_ON_STACK);
@ -142,5 +145,4 @@ public class LastKnownInformationTest extends CardTestPlayerBase {
} }
} }

View file

@ -118,6 +118,8 @@ public class TestPlayer implements Player {
private final ComputerPlayer computerPlayer; private final ComputerPlayer computerPlayer;
private String[] groupsForTargetHandling = null;
public TestPlayer(ComputerPlayer computerPlayer) { public TestPlayer(ComputerPlayer computerPlayer) {
this.computerPlayer = computerPlayer; this.computerPlayer = computerPlayer;
AIPlayer = false; AIPlayer = false;
@ -204,23 +206,14 @@ public class TestPlayer implements Player {
return true; return true;
} }
// private boolean checkSpellOnTopOfStackCondition(String[] groups, Game game) { @Override
// if (groups.length > 2 && groups[2].startsWith("spellOnTopOfStack=")) { public boolean addTargets(Ability ability, Game game) {
// String spellOnTopOFStack = groups[2].substring(18); if (groupsForTargetHandling == null) {
// if (game.getStack().size() > 0) { return true;
// StackObject stackObject = game.getStack().getFirst(); }
// if (stackObject != null && stackObject.getStackAbility().toString().contains(spellOnTopOFStack)) {
// return true;
// }
// }
// return false;
// }
// return true;
// }
private boolean addTargets(Ability ability, String[] groups, Game game) {
boolean result = true; boolean result = true;
for (int i = 1; i < groups.length; i++) { for (int i = 1; i < groupsForTargetHandling.length; i++) {
String group = groups[i]; String group = groupsForTargetHandling[i];
if (group.startsWith("spellOnStack") || group.startsWith("spellOnTopOfStack") || group.startsWith("!spellOnStack") || group.startsWith("target=null") || group.startsWith("manaInPool=")) { if (group.startsWith("spellOnStack") || group.startsWith("spellOnTopOfStack") || group.startsWith("!spellOnStack") || group.startsWith("target=null") || group.startsWith("manaInPool=")) {
break; break;
} }
@ -277,29 +270,36 @@ public class TestPlayer implements Player {
int index = 0; int index = 0;
int targetsSet = 0; int targetsSet = 0;
for (String targetName : targetList) { for (String targetName : targetList) {
Mode selectedMode = null;
if (targetName.startsWith("mode=")) { if (targetName.startsWith("mode=")) {
int modeNr = Integer.parseInt(targetName.substring(5, 6)); int modeNr = Integer.parseInt(targetName.substring(5, 6));
if (modeNr == 0 || modeNr > ability.getModes().size()) { if (modeNr == 0 || modeNr > ability.getModes().size()) {
throw new UnsupportedOperationException("Given mode number (" + modeNr + ") not available for " + ability.toString()); throw new UnsupportedOperationException("Given mode number (" + modeNr + ") not available for " + ability.toString());
} }
int modeCounter = 1; UUID modeId = ability.getModes().getModeId(modeNr);
for (Mode mode : ability.getModes().values()) {
if (modeCounter == modeNr) { for (Mode mode : ability.getModes().getSelectedModes()) {
ability.getModes().setMode(mode); if (mode.getId().equals(modeId)) {
selectedMode = mode;
ability.getModes().setActiveMode(mode);
index = 0; // reset target index if mode changes index = 0; // reset target index if mode changes
break; break;
} }
modeCounter++;
} }
targetName = targetName.substring(6); targetName = targetName.substring(6);
} else {
selectedMode = ability.getModes().getMode();
} }
if (ability.getTargets().size() == 0) { if (selectedMode == null) {
throw new UnsupportedOperationException("Mode not available for " + ability.toString());
}
if (selectedMode.getTargets().size() == 0) {
throw new AssertionError("Ability has no targets. " + ability.toString()); throw new AssertionError("Ability has no targets. " + ability.toString());
} }
if (index >= ability.getTargets().size()) { if (index >= selectedMode.getTargets().size()) {
break; // this can happen if targets should be set but can't be used because of hexproof e.g. break; // this can happen if targets should be set but can't be used because of hexproof e.g.
} }
Target currentTarget = ability.getTargets().get(index); Target currentTarget = selectedMode.getTargets().get(index);
if (targetName.startsWith("targetPlayer=")) { if (targetName.startsWith("targetPlayer=")) {
target = targetName.substring(targetName.indexOf("targetPlayer=") + 13); target = targetName.substring(targetName.indexOf("targetPlayer=") + 13);
for (Player player : game.getPlayers().values()) { for (Player player : game.getPlayers().values()) {
@ -362,6 +362,7 @@ public class TestPlayer implements Player {
if (action.getAction().startsWith("activate:")) { if (action.getAction().startsWith("activate:")) {
String command = action.getAction(); String command = action.getAction();
command = command.substring(command.indexOf("activate:") + 9); command = command.substring(command.indexOf("activate:") + 9);
groupsForTargetHandling = null;
String[] groups = command.split("\\$"); String[] groups = command.split("\\$");
if (groups.length > 2 && !checkExecuteCondition(groups, game)) { if (groups.length > 2 && !checkExecuteCondition(groups, game)) {
break; break;
@ -371,13 +372,11 @@ public class TestPlayer implements Player {
int bookmark = game.bookmarkState(); int bookmark = game.bookmarkState();
Ability newAbility = ability.copy(); Ability newAbility = ability.copy();
if (groups.length > 1 && !groups[1].equals("target=NO_TARGET")) { if (groups.length > 1 && !groups[1].equals("target=NO_TARGET")) {
if (!addTargets(newAbility, groups, game)) { groupsForTargetHandling = groups;
// targets could not be set -> try next priority
break;
}
} }
if (computerPlayer.activateAbility((ActivatedAbility) newAbility, game)) { if (computerPlayer.activateAbility((ActivatedAbility) newAbility, game)) {
actions.remove(action); actions.remove(action);
groupsForTargetHandling = null;
return true; return true;
} else { } else {
game.restoreState(bookmark, ability.getRule()); game.restoreState(bookmark, ability.getRule());
@ -1913,6 +1912,7 @@ public class TestPlayer implements Player {
@Override @Override
public boolean playMana(Ability ability, ManaCost unpaid, String promptText, Game game) { public boolean playMana(Ability ability, ManaCost unpaid, String promptText, Game game) {
groupsForTargetHandling = null;
return computerPlayer.playMana(ability, unpaid, promptText, game); return computerPlayer.playMana(ability, unpaid, promptText, game);
} }

View file

@ -155,7 +155,7 @@ public abstract class AbilityImpl implements Ability {
subAbilities.add(subAbility.copy()); subAbilities.add(subAbility.copy());
} }
} }
this.modes = ability.modes.copy(); this.modes = ability.getModes().copy();
this.ruleAtTheTop = ability.ruleAtTheTop; this.ruleAtTheTop = ability.ruleAtTheTop;
this.ruleVisible = ability.ruleVisible; this.ruleVisible = ability.ruleVisible;
this.ruleAdditionalCostsVisible = ability.ruleAdditionalCostsVisible; this.ruleAdditionalCostsVisible = ability.ruleAdditionalCostsVisible;
@ -196,6 +196,7 @@ public abstract class AbilityImpl implements Ability {
boolean result = true; boolean result = true;
//20100716 - 117.12 //20100716 - 117.12
if (checkIfClause(game)) { if (checkIfClause(game)) {
for (Effect effect : getEffects()) { for (Effect effect : getEffects()) {
if (effect instanceof OneShotEffect) { if (effect instanceof OneShotEffect) {
boolean effectResult = effect.apply(game, this); boolean effectResult = effect.apply(game, this);
@ -255,9 +256,14 @@ public abstract class AbilityImpl implements Ability {
/* 20130201 - 601.2b /* 20130201 - 601.2b
* If the spell is modal the player announces the mode choice (see rule 700.2). * If the spell is modal the player announces the mode choice (see rule 700.2).
*/ */
if (!modes.choose(game, this)) { if (!getModes().choose(game, this)) {
return false; return false;
} }
if (controller.isTestMode()) {
if (!controller.addTargets(this, game)) {
return false;
}
}
getSourceObject(game); getSourceObject(game);
@ -274,9 +280,8 @@ public abstract class AbilityImpl implements Ability {
} }
// TODO: Because all (non targeted) choices have to be done during resolution // TODO: Because all (non targeted) choices have to be done during resolution
// this has to be removed, if all using effects are changed // this has to be removed, if all using effects are changed
for (UUID modeId : this.getModes().getSelectedModes()) { for (Mode mode : this.getModes().getSelectedModes()) {
this.getModes().setActiveMode(modeId); if (mode.getChoices().size() > 0 && mode.getChoices().choose(game, this) == false) {
if (getChoices().size() > 0 && getChoices().choose(game, this) == false) {
logger.debug("activate failed - choice"); logger.debug("activate failed - choice");
return false; return false;
} }
@ -313,8 +318,8 @@ public abstract class AbilityImpl implements Ability {
VariableManaCost variableManaCost = handleManaXCosts(game, noMana, controller); VariableManaCost variableManaCost = handleManaXCosts(game, noMana, controller);
String announceString = handleOtherXCosts(game, controller); String announceString = handleOtherXCosts(game, controller);
for (UUID modeId : this.getModes().getSelectedModes()) { for (Mode mode : this.getModes().getSelectedModes()) {
this.getModes().setActiveMode(modeId); this.getModes().setActiveMode(mode);
//20121001 - 601.2c //20121001 - 601.2c
// 601.2c The player announces his or her choice of an appropriate player, object, or zone for // 601.2c The player announces his or her choice of an appropriate player, object, or zone for
// each target the spell requires. A spell may require some targets only if an alternative or // each target the spell requires. A spell may require some targets only if an alternative or
@ -335,7 +340,7 @@ public abstract class AbilityImpl implements Ability {
if (sourceObject != null && !this.getAbilityType().equals(AbilityType.TRIGGERED)) { // triggered abilities check this already in playerImpl.triggerAbility if (sourceObject != null && !this.getAbilityType().equals(AbilityType.TRIGGERED)) { // triggered abilities check this already in playerImpl.triggerAbility
sourceObject.adjustTargets(this, game); sourceObject.adjustTargets(this, game);
} }
if (getTargets().size() > 0 && getTargets().chooseTargets(getEffects().get(0).getOutcome(), this.controllerId, this, noMana, game) == false) { if (mode.getTargets().size() > 0 && mode.getTargets().chooseTargets(getEffects().get(0).getOutcome(), this.controllerId, this, noMana, game) == false) {
if ((variableManaCost != null || announceString != null) && !game.isSimulation()) { if ((variableManaCost != null || announceString != null) && !game.isSimulation()) {
game.informPlayer(controller, (sourceObject != null ? sourceObject.getIdName() : "") + ": no valid targets with this value of X"); game.informPlayer(controller, (sourceObject != null ? sourceObject.getIdName() : "") + ": no valid targets with this value of X");
} }
@ -408,7 +413,7 @@ public abstract class AbilityImpl implements Ability {
} }
if (variableManaCost != null) { if (variableManaCost != null) {
int xValue = getManaCostsToPay().getX(); int xValue = getManaCostsToPay().getX();
game.informPlayers(new StringBuilder(controller.getLogName()).append(" announces a value of ").append(xValue).append(" for ").append(variableManaCost.getText()).toString()); game.informPlayers(controller.getLogName() + " announces a value of " + xValue + " for " + variableManaCost.getText());
} }
} }
activated = true; activated = true;
@ -681,7 +686,7 @@ public abstract class AbilityImpl implements Ability {
@Override @Override
public Effects getEffects() { public Effects getEffects() {
return modes.getMode().getEffects(); return getModes().getMode().getEffects();
} }
@Override @Override
@ -706,7 +711,7 @@ public abstract class AbilityImpl implements Ability {
@Override @Override
public Choices getChoices() { public Choices getChoices() {
return modes.getMode().getChoices(); return getModes().getMode().getChoices();
} }
@Override @Override
@ -781,7 +786,7 @@ public abstract class AbilityImpl implements Ability {
} }
String ruleStart = sbRule.toString(); String ruleStart = sbRule.toString();
String text = modes.getText(); String text = getModes().getText();
String rule; String rule;
if (!text.isEmpty()) { if (!text.isEmpty()) {
if (ruleStart.length() > 1) { if (ruleStart.length() > 1) {
@ -873,7 +878,7 @@ public abstract class AbilityImpl implements Ability {
@Override @Override
public Targets getTargets() { public Targets getTargets() {
return modes.getMode().getTargets(); return getModes().getMode().getTargets();
} }
@Override @Override
@ -883,12 +888,12 @@ public abstract class AbilityImpl implements Ability {
@Override @Override
public boolean isModal() { public boolean isModal() {
return this.modes.size() > 1; return getModes().size() > 1;
} }
@Override @Override
public void addMode(Mode mode) { public void addMode(Mode mode) {
this.modes.addMode(mode); getModes().addMode(mode);
} }
@Override @Override
@ -899,10 +904,10 @@ public abstract class AbilityImpl implements Ability {
@Override @Override
public boolean canChooseTarget(Game game) { public boolean canChooseTarget(Game game) {
int found = 0; int found = 0;
for (Mode mode : modes.values()) { for (Mode mode : getModes().values()) {
if (mode.getTargets().canChoose(sourceId, controllerId, game)) { if (mode.getTargets().canChoose(sourceId, controllerId, game)) {
found++; found++;
if (modes.isEachModeMoreThanOnce()) { if (getModes().isEachModeMoreThanOnce()) {
return true; return true;
} }
if (found >= getModes().getMinModes()) { if (found >= getModes().getMinModes()) {
@ -1037,7 +1042,7 @@ public abstract class AbilityImpl implements Ability {
logger.warn("Could get no object: " + this.toString()); logger.warn("Could get no object: " + this.toString());
} }
return new StringBuilder(" activates: ") return new StringBuilder(" activates: ")
.append(object != null ? this.formatRule(modes.getText(), object.getLogName()) : modes.getText()) .append(object != null ? this.formatRule(getModes().getText(), object.getLogName()) : getModes().getText())
.append(" from ") .append(" from ")
.append(getMessageText(game)).toString(); .append(getMessageText(game)).toString();
} }
@ -1106,14 +1111,13 @@ public abstract class AbilityImpl implements Ability {
} }
} else if (object instanceof Spell && ((Spell) object).getSpellAbility().getModes().size() > 1) { } else if (object instanceof Spell && ((Spell) object).getSpellAbility().getModes().size() > 1) {
Modes spellModes = ((Spell) object).getSpellAbility().getModes(); Modes spellModes = ((Spell) object).getSpellAbility().getModes();
for (UUID modeId : spellModes.getSelectedModes()) { for (Mode selectedMode : spellModes.getSelectedModes()) {
int item = 0; int item = 0;
for (Mode mode : spellModes.values()) { for (Mode mode : spellModes.values()) {
item++; item++;
if (mode.getId().equals(modeId)) { if (mode.getId().equals(selectedMode.getId())) {
spellModes.setActiveMode(mode.getId());
sb.append(" (mode ").append(item).append(")"); sb.append(" (mode ").append(item).append(")");
sb.append(getTargetDescriptionForLog(getTargets(), game)); sb.append(getTargetDescriptionForLog(selectedMode.getTargets(), game));
break; break;
} }
} }

View file

@ -49,8 +49,8 @@ import mage.util.CardUtil;
*/ */
public class Modes extends LinkedHashMap<UUID, Mode> { public class Modes extends LinkedHashMap<UUID, Mode> {
private UUID modeId; private Mode mode; // the current mode of the selected modes
private final ArrayList<UUID> selectedModes = new ArrayList<>(); private final ArrayList<Mode> selectedModes = new ArrayList<>();
private int minModes; private int minModes;
private int maxModes; private int maxModes;
private TargetController modeChooser; private TargetController modeChooser;
@ -58,25 +58,40 @@ public class Modes extends LinkedHashMap<UUID, Mode> {
private boolean eachModeOnlyOnce; // state if each mode can be chosen only once as long as the source object exists private boolean eachModeOnlyOnce; // state if each mode can be chosen only once as long as the source object exists
public Modes() { public Modes() {
Mode mode = new Mode(); this.mode = new Mode();
this.put(mode.getId(), mode); this.put(mode.getId(), mode);
this.modeId = mode.getId();
this.minModes = 1; this.minModes = 1;
this.maxModes = 1; this.maxModes = 1;
this.selectedModes.add(modeId); this.selectedModes.add(mode);
this.modeChooser = TargetController.YOU; this.modeChooser = TargetController.YOU;
this.eachModeOnlyOnce = false; this.eachModeOnlyOnce = false;
this.eachModeMoreThanOnce = false; this.eachModeMoreThanOnce = false;
} }
public Modes(final Modes modes) { public Modes(final Modes modes) {
this.modeId = modes.modeId;
for (Map.Entry<UUID, Mode> entry : modes.entrySet()) { for (Map.Entry<UUID, Mode> entry : modes.entrySet()) {
this.put(entry.getKey(), entry.getValue().copy()); this.put(entry.getKey(), entry.getValue().copy());
} }
this.minModes = modes.minModes; this.minModes = modes.minModes;
this.maxModes = modes.maxModes; this.maxModes = modes.maxModes;
this.selectedModes.addAll(modes.selectedModes);
if (modes.size() == 1) {
this.mode = values().iterator().next();
this.selectedModes.add(mode);
} else {
// probably there is still a problem with copying modes with the same mode selected multiple times.
for (Mode selectedMode : modes.getSelectedModes()) {
Mode copiedMode = selectedMode.copy();
this.selectedModes.add(copiedMode);
if (modes.getSelectedModes().size() == 1) {
this.mode = copiedMode;
} else {
if (selectedMode.equals(modes.getMode())) {
this.mode = copiedMode;
}
}
}
}
this.modeChooser = modes.modeChooser; this.modeChooser = modes.modeChooser;
this.eachModeOnlyOnce = modes.eachModeOnlyOnce; this.eachModeOnlyOnce = modes.eachModeOnlyOnce;
this.eachModeMoreThanOnce = modes.eachModeMoreThanOnce; this.eachModeMoreThanOnce = modes.eachModeMoreThanOnce;
@ -87,10 +102,21 @@ public class Modes extends LinkedHashMap<UUID, Mode> {
} }
public Mode getMode() { public Mode getMode() {
return get(modeId); return mode;
} }
public ArrayList<UUID> getSelectedModes() { public UUID getModeId(int index) {
int idx = 0;
for (Mode currentMode : this.values()) {
idx++;
if (idx == index) {
return currentMode.getId();
}
}
return null;
}
public ArrayList<Mode> getSelectedModes() {
return selectedModes; return selectedModes;
} }
@ -118,16 +144,9 @@ public class Modes extends LinkedHashMap<UUID, Mode> {
return this.modeChooser; return this.modeChooser;
} }
public void setActiveMode(UUID modeId) { public void setActiveMode(Mode mode) {
if (selectedModes.contains(modeId)) { if (selectedModes.contains(mode)) {
this.modeId = modeId; this.mode = mode;
}
}
public void setMode(Mode mode) {
if (this.containsKey(mode.getId())) {
this.modeId = mode.getId();
this.selectedModes.add(mode.getId());
} }
} }
@ -156,7 +175,7 @@ public class Modes extends LinkedHashMap<UUID, Mode> {
for (Mode mode : this.values()) { for (Mode mode : this.values()) {
if ((!isEachModeOnlyOnce() || onceSelectedModes == null || !onceSelectedModes.contains(mode.getId())) if ((!isEachModeOnlyOnce() || onceSelectedModes == null || !onceSelectedModes.contains(mode.getId()))
&& mode.getTargets().canChoose(source.getSourceId(), source.getControllerId(), game)) { && mode.getTargets().canChoose(source.getSourceId(), source.getControllerId(), game)) {
this.selectedModes.add(mode.getId()); this.selectedModes.add(mode.copy());
} }
} }
if (isEachModeOnlyOnce()) { if (isEachModeOnlyOnce()) {
@ -184,6 +203,7 @@ public class Modes extends LinkedHashMap<UUID, Mode> {
Player player = game.getPlayer(playerId); Player player = game.getPlayer(playerId);
// player chooses modes manually // player chooses modes manually
this.mode = null;
while (this.selectedModes.size() < this.getMaxModes()) { while (this.selectedModes.size() < this.getMaxModes()) {
Mode choice = player.chooseMode(this, source, game); Mode choice = player.chooseMode(this, source, game);
if (choice == null) { if (choice == null) {
@ -192,29 +212,38 @@ public class Modes extends LinkedHashMap<UUID, Mode> {
} }
return this.selectedModes.size() >= this.getMinModes(); return this.selectedModes.size() >= this.getMinModes();
} }
setMode(choice); this.selectedModes.add(choice.copy());
if (mode == null) {
mode = choice;
}
} }
if (isEachModeOnlyOnce()) { if (isEachModeOnlyOnce()) {
setAlreadySelectedModes(selectedModes, source, game); setAlreadySelectedModes(selectedModes, source, game);
} }
return true; return true;
} }
this.modeId = this.values().iterator().next().getId(); if (mode == null) {
this.selectedModes.clear(); this.selectedModes.clear();
this.selectedModes.add(modeId); Mode copiedMode = this.values().iterator().next().copy();
this.selectedModes.add(copiedMode);
this.setActiveMode(copiedMode);
}
if (isEachModeOnlyOnce()) { if (isEachModeOnlyOnce()) {
setAlreadySelectedModes(selectedModes, source, game); setAlreadySelectedModes(selectedModes, source, game);
} }
return true; return true;
} }
private void setAlreadySelectedModes(ArrayList<UUID> selectedModes, Ability source, Game game) { private void setAlreadySelectedModes(ArrayList<Mode> selectedModes, Ability source, Game game) {
String key = getKey(source, game); String key = getKey(source, game);
Set<UUID> onceSelectedModes = (Set<UUID>) game.getState().getValue(key); Set<UUID> onceSelectedModes = (Set<UUID>) game.getState().getValue(key);
if (onceSelectedModes == null) { if (onceSelectedModes == null) {
onceSelectedModes = new HashSet<>(); onceSelectedModes = new HashSet<>();
} }
onceSelectedModes.addAll(selectedModes); for (Mode mode : selectedModes) {
onceSelectedModes.add(mode.getId());
}
game.getState().setValue(key, onceSelectedModes); game.getState().setValue(key, onceSelectedModes);
} }

View file

@ -27,9 +27,9 @@
*/ */
package mage.abilities.common; package mage.abilities.common;
import mage.constants.Zone;
import mage.abilities.TriggeredAbilityImpl; import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.effects.Effect; import mage.abilities.effects.Effect;
import mage.constants.Zone;
import mage.game.Game; import mage.game.Game;
import mage.game.events.GameEvent; import mage.game.events.GameEvent;
@ -73,7 +73,7 @@ public class EntersBattlefieldTriggeredAbility extends TriggeredAbilityImpl {
@Override @Override
public boolean checkTrigger(GameEvent event, Game game) { public boolean checkTrigger(GameEvent event, Game game) {
return event.getTargetId().equals(getSourceId()); return event.getTargetId().equals(getSourceId());
} }
@Override @Override
@ -81,7 +81,7 @@ public class EntersBattlefieldTriggeredAbility extends TriggeredAbilityImpl {
if (noRule) { if (noRule) {
return super.getRule(); return super.getRule();
} }
return (rulePrefix != null ? rulePrefix : "") + "When {this} enters the battlefield, "+ super.getRule(); return (rulePrefix != null ? rulePrefix : "") + "When {this} enters the battlefield, " + super.getRule();
} }
@Override @Override

View file

@ -8,6 +8,7 @@ package mage.abilities.effects.common;
import java.util.UUID; import java.util.UUID;
import mage.MageObject; import mage.MageObject;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.Mode;
import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.OneShotEffect;
import mage.constants.Outcome; import mage.constants.Outcome;
import mage.game.Game; import mage.game.Game;
@ -50,9 +51,8 @@ public class ChangeATargetOfTargetSpellAbilityToSourceEffect extends OneShotEffe
} else { } else {
return false; return false;
} }
for (UUID modeId : sourceAbility.getModes().getSelectedModes()) { for (Mode mode : sourceAbility.getModes().getSelectedModes()) {
sourceAbility.getModes().setActiveMode(modeId); targets.addAll(mode.getTargets());
targets.addAll(sourceAbility.getTargets());
} }
boolean twoTimesTarget = false; boolean twoTimesTarget = false;

View file

@ -1,43 +1,39 @@
/* /*
* Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved.
* *
* Redistribution and use in source and binary forms, with or without modification, are * Redistribution and use in source and binary forms, with or without modification, are
* permitted provided that the following conditions are met: * permitted provided that the following conditions are met:
* *
* 1. Redistributions of source code must retain the above copyright notice, this list of * 1. Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer. * conditions and the following disclaimer.
* *
* 2. Redistributions in binary form must reproduce the above copyright notice, this list * 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 * of conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution. * provided with the distribution.
* *
* THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED * 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 * 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 * 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 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 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 * 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 * 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 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
* *
* The views and conclusions contained in the software and documentation are those of the * 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 * authors and should not be interpreted as representing official policies, either expressed
* or implied, of BetaSteward_at_googlemail.com. * or implied, of BetaSteward_at_googlemail.com.
*/ */
package mage.abilities.effects.common; package mage.abilities.effects.common;
import java.util.UUID; import java.util.UUID;
import mage.MageObject; import mage.MageObject;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.Mode; import mage.abilities.Mode;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.OneShotEffect;
import mage.cards.Card; import mage.cards.Card;
import mage.constants.AbilityType;
import mage.constants.Outcome; import mage.constants.Outcome;
import mage.constants.Zone;
import mage.game.Game; import mage.game.Game;
import mage.game.permanent.Permanent; import mage.game.permanent.Permanent;
import mage.players.Player; import mage.players.Player;
@ -49,7 +45,6 @@ import mage.util.CardUtil;
*/ */
public class ExileTargetForSourceEffect extends OneShotEffect { public class ExileTargetForSourceEffect extends OneShotEffect {
public ExileTargetForSourceEffect() { public ExileTargetForSourceEffect() {
super(Outcome.Exile); super(Outcome.Exile);
} }
@ -71,11 +66,11 @@ public class ExileTargetForSourceEffect extends OneShotEffect {
Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source));
UUID exileId = CardUtil.getExileZoneId(game, source.getSourceId(), source.getSourceObjectZoneChangeCounter()); UUID exileId = CardUtil.getExileZoneId(game, source.getSourceId(), source.getSourceObjectZoneChangeCounter());
if (permanent != null) { if (permanent != null) {
return controller.moveCardToExileWithInfo(permanent, exileId, sourceObject.getIdName(), source.getSourceId(), game, Zone.BATTLEFIELD, true); return controller.moveCardsToExile(permanent, source, game, true, exileId, sourceObject.getIdName());
} else { } else {
Card card = game.getCard(getTargetPointer().getFirst(game, source)); Card card = game.getCard(getTargetPointer().getFirst(game, source));
if (card != null) { if (card != null) {
return controller.moveCardToExileWithInfo(card, exileId, sourceObject.getIdName(), source.getSourceId(), game, game.getState().getZone(card.getId()), true); return controller.moveCardsToExile(card, source, game, true, exileId, sourceObject.getIdName());
} }
} }
} }
@ -84,14 +79,14 @@ public class ExileTargetForSourceEffect extends OneShotEffect {
@Override @Override
public String getText(Mode mode) { public String getText(Mode mode) {
if(staticText != null && !staticText.isEmpty()) { if (staticText != null && !staticText.isEmpty()) {
return staticText; return staticText;
} }
if (mode.getTargets().isEmpty()) { if (mode.getTargets().isEmpty()) {
return "Exile it"; return "exile it";
} else { } else {
return "Exile target " + mode.getTargets().get(0).getTargetName(); return "exile target " + mode.getTargets().get(0).getTargetName();
} }
} }
} }

View file

@ -27,10 +27,14 @@
*/ */
package mage.abilities.effects.common; package mage.abilities.effects.common;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.UUID;
import mage.MageObject;
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.cards.CardsImpl; import mage.cards.Card;
import mage.constants.Outcome; import mage.constants.Outcome;
import mage.constants.Zone; import mage.constants.Zone;
import mage.game.Game; import mage.game.Game;
@ -70,7 +74,14 @@ public class ReturnToHandTargetEffect extends OneShotEffect {
if (controller == null) { if (controller == null) {
return false; return false;
} }
return controller.moveCards(new CardsImpl(targetPointer.getTargets(game, source)), null, Zone.HAND, source, game); Set<Card> cards = new LinkedHashSet<>();
for (UUID targetId : targetPointer.getTargets(game, source)) {
MageObject mageObject = game.getObject(targetId);
if (mageObject instanceof Card) {
cards.add((Card) mageObject);
}
}
return controller.moveCards(cards, Zone.HAND, source, game);
} }
@Override @Override

View file

@ -1,34 +1,34 @@
/* /*
* Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved.
* *
* Redistribution and use in source and binary forms, with or without modification, are * Redistribution and use in source and binary forms, with or without modification, are
* permitted provided that the following conditions are met: * permitted provided that the following conditions are met:
* *
* 1. Redistributions of source code must retain the above copyright notice, this list of * 1. Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer. * conditions and the following disclaimer.
* *
* 2. Redistributions in binary form must reproduce the above copyright notice, this list * 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 * of conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution. * provided with the distribution.
* *
* THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED * 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 * 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 * 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 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 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 * 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 * 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 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
* *
* The views and conclusions contained in the software and documentation are those of the * 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 * authors and should not be interpreted as representing official policies, either expressed
* or implied, of BetaSteward_at_googlemail.com. * or implied, of BetaSteward_at_googlemail.com.
*/ */
package mage.abilities.keyword; package mage.abilities.keyword;
import java.util.UUID; import java.util.UUID;
import mage.abilities.Mode;
import mage.abilities.SpellAbility; import mage.abilities.SpellAbility;
import mage.abilities.TriggeredAbilityImpl; import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.effects.Effect; import mage.abilities.effects.Effect;
@ -44,7 +44,6 @@ import mage.target.Target;
* *
* @author LevelX2 * @author LevelX2
*/ */
public class HeroicAbility extends TriggeredAbilityImpl { public class HeroicAbility extends TriggeredAbilityImpl {
public HeroicAbility(Effect effect) { public HeroicAbility(Effect effect) {
@ -83,13 +82,13 @@ public class HeroicAbility extends TriggeredAbilityImpl {
private boolean checkSpell(Spell spell, Game game) { private boolean checkSpell(Spell spell, Game game) {
if (spell != null) { if (spell != null) {
SpellAbility sa = spell.getSpellAbility(); SpellAbility sa = spell.getSpellAbility();
for(UUID modeId :sa.getModes().getSelectedModes()) { for (Mode mode : sa.getModes().getSelectedModes()) {
for (Target target : sa.getModes().get(modeId).getTargets()) { for (Target target : mode.getTargets()) {
if (!target.isNotTarget() && target.getTargets().contains(this.getSourceId())) { if (!target.isNotTarget() && target.getTargets().contains(this.getSourceId())) {
return true; return true;
} }
} }
for (Effect effect : sa.getModes().get(modeId).getEffects()) { for (Effect effect : mode.getEffects()) {
for (UUID targetId : effect.getTargetPointer().getTargets(game, sa)) { for (UUID targetId : effect.getTargetPointer().getTargets(game, sa)) {
if (targetId.equals(this.getSourceId())) { if (targetId.equals(this.getSourceId())) {
return true; return true;

View file

@ -27,7 +27,6 @@
*/ */
package mage.filter.predicate.mageobject; package mage.filter.predicate.mageobject;
import java.util.UUID;
import mage.MageObject; import mage.MageObject;
import mage.abilities.Mode; import mage.abilities.Mode;
import mage.filter.predicate.Predicate; import mage.filter.predicate.Predicate;
@ -52,8 +51,7 @@ public class NumberOfTargetsPredicate implements Predicate<MageObject> {
Spell spell = game.getStack().getSpell(input.getId()); Spell spell = game.getStack().getSpell(input.getId());
if (spell != null) { if (spell != null) {
int numberOfTargets = 0; int numberOfTargets = 0;
for (UUID modeId : spell.getSpellAbility().getModes().getSelectedModes()) { for (Mode mode : spell.getSpellAbility().getModes().getSelectedModes()) {
Mode mode = spell.getSpellAbility().getModes().get(modeId);
for (Target target : mode.getTargets()) { for (Target target : mode.getTargets()) {
numberOfTargets += target.getTargets().size(); numberOfTargets += target.getTargets().size();
} }

View file

@ -53,13 +53,12 @@ public class TargetsPermanentPredicate implements ObjectSourcePlayerPredicate<Ob
@Override @Override
public boolean apply(ObjectSourcePlayer<MageObject> input, Game game) { public boolean apply(ObjectSourcePlayer<MageObject> input, Game game) {
StackObject object = game.getStack().getStackObject(input.getObject().getId()); StackObject object = game.getStack().getStackObject(input.getObject().getId());
if(object != null) { if (object != null) {
for(UUID modeId : object.getStackAbility().getModes().getSelectedModes()) { for (Mode mode : object.getStackAbility().getModes().getSelectedModes()) {
Mode mode = object.getStackAbility().getModes().get(modeId); for (Target target : mode.getTargets()) {
for(Target target : mode.getTargets()) { for (UUID targetId : target.getTargets()) {
for(UUID targetId : target.getTargets()) {
Permanent permanent = game.getPermanentOrLKIBattlefield(targetId); Permanent permanent = game.getPermanentOrLKIBattlefield(targetId);
if(permanent != null && targetFilter.match(permanent, input.getSourceId(), input.getPlayerId(), game)) { if (permanent != null && targetFilter.match(permanent, input.getSourceId(), input.getPlayerId(), game)) {
return true; return true;
} }
} }

View file

@ -309,7 +309,7 @@ public class GameState implements Serializable, Copyable<GameState> {
for (StackObject spell : stack) { for (StackObject spell : stack) {
sb.append(spell.getControllerId()).append(spell.getName()); sb.append(spell.getControllerId()).append(spell.getName());
sb.append(spell.getStackAbility().toString()); sb.append(spell.getStackAbility().toString());
for (Mode mode : spell.getStackAbility().getModes().values()) { for (Mode mode : spell.getStackAbility().getModes().getSelectedModes()) {
if (!mode.getTargets().isEmpty()) { if (!mode.getTargets().isEmpty()) {
sb.append("targets"); sb.append("targets");
for (Target target : mode.getTargets()) { for (Target target : mode.getTargets()) {
@ -367,7 +367,7 @@ public class GameState implements Serializable, Copyable<GameState> {
for (StackObject spell : stack) { for (StackObject spell : stack) {
sb.append(spell.getControllerId()).append(spell.getName()); sb.append(spell.getControllerId()).append(spell.getName());
sb.append(spell.getStackAbility().toString()); sb.append(spell.getStackAbility().toString());
for (Mode mode : spell.getStackAbility().getModes().values()) { for (Mode mode : spell.getStackAbility().getModes().getSelectedModes()) {
if (!mode.getTargets().isEmpty()) { if (!mode.getTargets().isEmpty()) {
sb.append("targets"); sb.append("targets");
for (Target target : mode.getTargets()) { for (Target target : mode.getTargets()) {
@ -709,7 +709,7 @@ public class GameState implements Serializable, Copyable<GameState> {
public void addAbility(Ability ability, MageObject attachedTo) { public void addAbility(Ability ability, MageObject attachedTo) {
if (ability instanceof StaticAbility) { if (ability instanceof StaticAbility) {
for (Mode mode : ability.getModes().values()) { for (Mode mode : ability.getModes().getSelectedModes()) {
for (Effect effect : mode.getEffects()) { for (Effect effect : mode.getEffects()) {
if (effect instanceof ContinuousEffect) { if (effect instanceof ContinuousEffect) {
addEffect((ContinuousEffect) effect, ability); addEffect((ContinuousEffect) effect, ability);
@ -731,7 +731,7 @@ public class GameState implements Serializable, Copyable<GameState> {
*/ */
public void addAbility(Ability ability, UUID sourceId, Card attachedTo) { public void addAbility(Ability ability, UUID sourceId, Card attachedTo) {
if (ability instanceof StaticAbility) { if (ability instanceof StaticAbility) {
for (Mode mode : ability.getModes().values()) { for (Mode mode : ability.getModes().getSelectedModes()) {
for (Effect effect : mode.getEffects()) { for (Effect effect : mode.getEffects()) {
if (effect instanceof ContinuousEffect) { if (effect instanceof ContinuousEffect) {
addEffect((ContinuousEffect) effect, sourceId, ability); addEffect((ContinuousEffect) effect, sourceId, ability);

View file

@ -36,6 +36,7 @@ import mage.Mana;
import mage.ObjectColor; import mage.ObjectColor;
import mage.abilities.Abilities; import mage.abilities.Abilities;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.Mode;
import mage.abilities.SpellAbility; import mage.abilities.SpellAbility;
import mage.abilities.costs.AlternativeSourceCosts; import mage.abilities.costs.AlternativeSourceCosts;
import mage.abilities.costs.Cost; import mage.abilities.costs.Cost;
@ -195,9 +196,9 @@ public class Spell extends StackObjImpl implements Card {
if (notTargeted || legalParts) { if (notTargeted || legalParts) {
for (SpellAbility spellAbility : this.spellAbilities) { for (SpellAbility spellAbility : this.spellAbilities) {
if (spellAbilityHasLegalParts(spellAbility, game)) { if (spellAbilityHasLegalParts(spellAbility, game)) {
for (UUID modeId : spellAbility.getModes().getSelectedModes()) { for (Mode mode : spellAbility.getModes().getSelectedModes()) {
spellAbility.getModes().setActiveMode(modeId); spellAbility.getModes().setActiveMode(mode);
if (spellAbility.getTargets().stillLegal(spellAbility, game)) { if (mode.getTargets().stillLegal(spellAbility, game)) {
if (!spellAbility.getSpellAbilityType().equals(SpellAbilityType.SPLICE)) { if (!spellAbility.getSpellAbilityType().equals(SpellAbilityType.SPLICE)) {
updateOptionalCosts(index); updateOptionalCosts(index);
} }
@ -268,9 +269,8 @@ public class Spell extends StackObjImpl implements Card {
private boolean hasTargets(SpellAbility spellAbility, Game game) { private boolean hasTargets(SpellAbility spellAbility, Game game) {
if (spellAbility.getModes().getSelectedModes().size() > 1) { if (spellAbility.getModes().getSelectedModes().size() > 1) {
for (UUID modeId : spellAbility.getModes().getSelectedModes()) { for (Mode mode : spellAbility.getModes().getSelectedModes()) {
spellAbility.getModes().setActiveMode(modeId); if (!mode.getTargets().isEmpty()) {
if (!spellAbility.getTargets().isEmpty()) {
return true; return true;
} }
@ -285,11 +285,10 @@ public class Spell extends StackObjImpl implements Card {
if (spellAbility.getModes().getSelectedModes().size() > 1) { if (spellAbility.getModes().getSelectedModes().size() > 1) {
boolean targetedMode = false; boolean targetedMode = false;
boolean legalTargetedMode = false; boolean legalTargetedMode = false;
for (UUID modeId : spellAbility.getModes().getSelectedModes()) { for (Mode mode : spellAbility.getModes().getSelectedModes()) {
spellAbility.getModes().setActiveMode(modeId); if (mode.getTargets().size() > 0) {
if (spellAbility.getTargets().size() > 0) {
targetedMode = true; targetedMode = true;
if (spellAbility.getTargets().stillLegal(spellAbility, game)) { if (mode.getTargets().stillLegal(spellAbility, game)) {
legalTargetedMode = true; legalTargetedMode = true;
} }
} }

View file

@ -86,14 +86,14 @@ public class StackAbility extends StackObjImpl implements Ability {
public StackAbility(Ability ability, UUID controllerId) { public StackAbility(Ability ability, UUID controllerId) {
this.ability = ability; this.ability = ability;
this.controllerId = controllerId; this.controllerId = controllerId;
this.name = new StringBuilder("stack ability (").append(ability.getRule()).append(")").toString(); this.name = "stack ability (" + ability.getRule() + ")";
} }
public StackAbility(final StackAbility spell) { public StackAbility(final StackAbility stackAbility) {
this.ability = spell.ability.copy(); this.ability = stackAbility.ability.copy();
this.controllerId = spell.controllerId; this.controllerId = stackAbility.controllerId;
this.name = spell.name; this.name = stackAbility.name;
this.expansionSetCode = spell.expansionSetCode; this.expansionSetCode = stackAbility.expansionSetCode;
} }
@Override @Override

View file

@ -117,8 +117,8 @@ public abstract class StackObjImpl implements StackObject {
} }
for (Ability ability : objectAbilities) { for (Ability ability : objectAbilities) {
// Some spells can have more than one mode // Some spells can have more than one mode
for (UUID modeId : ability.getModes().getSelectedModes()) { for (Mode mode : ability.getModes().getSelectedModes()) {
Mode mode = ability.getModes().get(modeId); ability.getModes().setActiveMode(mode);
oldTargetDescription.append(ability.getTargetDescription(mode.getTargets(), game)); oldTargetDescription.append(ability.getTargetDescription(mode.getTargets(), game));
for (Target target : mode.getTargets()) { for (Target target : mode.getTargets()) {
Target newTarget = chooseNewTarget(targetController, ability, mode, target, forceChange, filterNewTarget, game); Target newTarget = chooseNewTarget(targetController, ability, mode, target, forceChange, filterNewTarget, game);

View file

@ -787,4 +787,13 @@ public interface Player extends MageItem, Copyable<Player> {
MatchPlayer getMatchPlayer(); MatchPlayer getMatchPlayer();
boolean scry(int value, Ability source, Game game); boolean scry(int value, Ability source, Game game);
/**
* Only used for test player for pre-setting targets
*
* @param ability
* @param game
* @return
*/
boolean addTargets(Ability ability, Game game);
} }

View file

@ -2706,7 +2706,8 @@ public abstract class PlayerImpl implements Player, Serializable {
for (Mode mode : option.getModes().values()) { for (Mode mode : option.getModes().values()) {
Ability newOption = option.copy(); Ability newOption = option.copy();
newOption.getModes().getSelectedModes().clear(); newOption.getModes().getSelectedModes().clear();
newOption.getModes().setMode(mode); newOption.getModes().getSelectedModes().add(mode);
newOption.getModes().setActiveMode(mode);
if (newOption.getTargets().getUnchosen().size() > 0) { if (newOption.getTargets().getUnchosen().size() > 0) {
if (newOption.getManaCosts().getVariableCosts().size() > 0) { if (newOption.getManaCosts().getVariableCosts().size() > 0) {
addVariableXOptions(options, newOption, 0, game); addVariableXOptions(options, newOption, 0, game);
@ -3448,4 +3449,10 @@ public abstract class PlayerImpl implements Player, Serializable {
return true; return true;
} }
@Override
public boolean addTargets(Ability ability, Game game) {
// only used for TestPlayer to preSet Targets
return true;
}
} }

View file

@ -36,11 +36,11 @@ import mage.cards.Card;
import mage.game.stack.Spell; import mage.game.stack.Spell;
import mage.target.Target; import mage.target.Target;
/** /**
* @author duncant * @author duncant
*/ */
public class TargetAddress { public class TargetAddress {
protected int spellAbilityIndex; protected int spellAbilityIndex;
protected UUID mode; protected UUID mode;
protected int targetIndex; protected int targetIndex;
@ -52,6 +52,7 @@ public class TargetAddress {
} }
protected static class TargetAddressIterable implements Iterable<TargetAddress> { protected static class TargetAddressIterable implements Iterable<TargetAddress> {
protected final Card card; protected final Card card;
public TargetAddressIterable(Card card) { public TargetAddressIterable(Card card) {
@ -64,9 +65,10 @@ public class TargetAddress {
} }
protected static class TargetAddressIterator implements Iterator<TargetAddress> { protected static class TargetAddressIterator implements Iterator<TargetAddress> {
protected Iterator<SpellAbility> spellAbilityIterator; protected Iterator<SpellAbility> spellAbilityIterator;
protected Integer lastSpellAbilityIndex = null; protected Integer lastSpellAbilityIndex = null;
protected Iterator<UUID> modeIterator = null; protected Iterator<Mode> modeIterator = null;
protected Modes modes = null; protected Modes modes = null;
protected UUID lastMode = null; protected UUID lastMode = null;
protected Iterator<Target> targetIterator = null; protected Iterator<Target> targetIterator = null;
@ -91,8 +93,8 @@ public class TargetAddress {
public TargetAddress next() { public TargetAddress next() {
TargetAddress ret = new TargetAddress(lastSpellAbilityIndex, TargetAddress ret = new TargetAddress(lastSpellAbilityIndex,
lastMode, lastMode,
lastTargetIndex); lastTargetIndex);
calcNext(); calcNext();
return ret; return ret;
@ -120,7 +122,7 @@ public class TargetAddress {
} }
if (modeIterator != null && modeIterator.hasNext()) { if (modeIterator != null && modeIterator.hasNext()) {
lastMode = modeIterator.next(); lastMode = modeIterator.next().getId();
targetIterator = modes.get(lastMode).getTargets().iterator(); targetIterator = modes.get(lastMode).getTargets().iterator();
} else { } else {
lastMode = null; lastMode = null;
@ -145,7 +147,6 @@ public class TargetAddress {
} }
} }
public static Iterable<TargetAddress> walk(Card card) { public static Iterable<TargetAddress> walk(Card card) {
return new TargetAddressIterable(card); return new TargetAddressIterable(card);
} }
@ -179,8 +180,8 @@ public class TargetAddress {
public boolean equals(TargetAddress other) { public boolean equals(TargetAddress other) {
return spellAbilityIndex == other.spellAbilityIndex return spellAbilityIndex == other.spellAbilityIndex
&& mode.equals(other.mode) && mode.equals(other.mode)
&& targetIndex == other.targetIndex; && targetIndex == other.targetIndex;
} }
@Override @Override