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

View file

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

View file

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

View file

@ -64,7 +64,16 @@ import mage.constants.ManaType;
import mage.constants.Outcome;
import mage.constants.PhaseStep;
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_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 mage.constants.RangeOfInfluence;
import mage.constants.Zone;
@ -1284,20 +1293,24 @@ public class HumanPlayer extends PlayerImpl {
if (modes.size() > 1) {
MageObject obj = game.getObject(source.getSourceId());
Map<UUID, String> modeMap = new LinkedHashMap<>();
AvailableModes:
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
&& mode.getTargets().canChoose(source.getSourceId(), source.getControllerId(), game)) { // and needed targets have to be available
int timesSelected = 0;
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);
if (obj != null) {
modeText = modeText.replace("{source}", obj.getName()).replace("{this}", obj.getName());
}
if (modes.isEachModeMoreThanOnce()) {
int timesSelected = 0;
for (UUID selectedModeId : modes.getSelectedModes()) {
if (mode.getId().equals(selectedModeId)) {
timesSelected++;
}
}
if (timesSelected > 0) {
modeText = "(selected " + timesSelected + "x) " + modeText;
}
@ -1327,6 +1340,7 @@ public class HumanPlayer extends PlayerImpl {
}
return null;
}
return modes.getMode();
}

View file

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

View file

@ -74,9 +74,9 @@ public class MogisGodOfSlaughter extends CardImpl {
// As long as your devotion to black and red is less than seven, Mogis isn't a creature.
Effect effect = new LoseCreatureTypeSourceEffect(new DevotionCount(ColoredManaSymbol.B, ColoredManaSymbol.R), 7);
effect.setText("As long as your devotion to black and red is less than seven, Mogis isn't a creature");
this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, effect));
// At the beginning of each opponent's upkeep, Mogis deals 2 damage to that player unless he or she sacrifices a creature.
this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, effect));
// At the beginning of each opponent's upkeep, Mogis deals 2 damage to that player unless he or she sacrifices a creature.
effect = new DoUnlessTargetPaysCost(new DamageTargetEffect(2, false, "that player"), new SacrificeTargetCost(new TargetControlledCreaturePermanent()),
"Sacrifice a creature? (otherwise you get 2 damage)");
effect.setText("Mogis deals 2 damage to that player unless he or she sacrifices a creature");
@ -95,6 +95,7 @@ public class MogisGodOfSlaughter extends CardImpl {
}
class DoUnlessTargetPaysCost extends OneShotEffect {
private final OneShotEffect executingEffect;
private final Cost cost;
private final String userMessage;
@ -102,6 +103,7 @@ class DoUnlessTargetPaysCost extends OneShotEffect {
public DoUnlessTargetPaysCost(OneShotEffect effect, Cost cost) {
this(effect, cost, null);
}
public DoUnlessTargetPaysCost(OneShotEffect effect, Cost cost, String userMessage) {
super(Outcome.Benefit);
this.executingEffect = effect;
@ -123,7 +125,7 @@ class DoUnlessTargetPaysCost extends OneShotEffect {
if (player != null && mageObject != null) {
String message = userMessage;
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());
cost.clearPaid();
@ -132,8 +134,8 @@ class DoUnlessTargetPaysCost extends OneShotEffect {
}
if (!cost.isPaid()) {
executingEffect.setTargetPointer(this.targetPointer);
return executingEffect.apply(game, source);
}
return executingEffect.apply(game, source);
}
return true;
}
return false;
@ -153,8 +155,8 @@ class DoUnlessTargetPaysCost extends OneShotEffect {
private String getCostText() {
StringBuilder sb = new StringBuilder();
String costText = cost.getText();
if (costText != null &&
!costText.toLowerCase().startsWith("discard")
if (costText != null
&& !costText.toLowerCase().startsWith("discard")
&& !costText.toLowerCase().startsWith("sacrifice")
&& !costText.toLowerCase().startsWith("remove")) {
sb.append("pay ");

View file

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

View file

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

View file

@ -28,11 +28,12 @@
package mage.sets.shardsofalara;
import java.util.UUID;
import mage.constants.CardType;
import mage.constants.Rarity;
import mage.abilities.Mode;
import mage.abilities.effects.common.CounterTargetEffect;
import mage.abilities.effects.common.DrawCardSourceControllerEffect;
import mage.cards.CardImpl;
import mage.constants.CardType;
import mage.constants.Rarity;
import mage.filter.FilterSpell;
import mage.filter.predicate.ObjectPlayer;
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}");
this.expansionSetCode = "ALA";
// Counter target spell that targets you or a permanent you control.
this.getSpellAbility().addEffect(new CounterTargetEffect());
this.getSpellAbility().addTarget(new TargetSpell(filter));
@ -84,9 +84,8 @@ class HinderingLightPredicate implements ObjectPlayerPredicate<ObjectPlayer<Stac
if (controllerId == null) {
return false;
}
for (UUID modeId :input.getObject().getStackAbility().getModes().getSelectedModes()) {
input.getObject().getStackAbility().getModes().setActiveMode(modeId);
for (Target target : input.getObject().getStackAbility().getTargets()) {
for (Mode mode : input.getObject().getStackAbility().getModes().getSelectedModes()) {
for (Target target : mode.getTargets()) {
for (UUID targetId : target.getTargets()) {
if (controllerId.equals(targetId)) {
return true;
@ -96,7 +95,7 @@ class HinderingLightPredicate implements ObjectPlayerPredicate<ObjectPlayer<Stac
return true;
}
}
}
}
}
return false;
}

View file

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

View file

@ -1,43 +1,42 @@
/*
* Copyright 2010 maurer.it_at_googlemail.com. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are
* permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice, this list
* of conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* The views and conclusions contained in the software and documentation are those of the
* authors and should not be interpreted as representing official policies, either expressed
* or implied, of maurer.it_at_googlemail.com.
*/
* Copyright 2010 maurer.it_at_googlemail.com. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are
* permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice, this list
* of conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* The views and conclusions contained in the software and documentation are those of the
* authors and should not be interpreted as representing official policies, either expressed
* or implied, of maurer.it_at_googlemail.com.
*/
package mage.sets.zendikar;
import java.util.UUID;
import mage.constants.CardType;
import mage.constants.Rarity;
import mage.constants.Zone;
import mage.abilities.Ability;
import mage.abilities.common.EntersBattlefieldTriggeredAbility;
import mage.abilities.common.LeavesBattlefieldTriggeredAbility;
import mage.abilities.effects.common.ExileTargetForSourceEffect;
import mage.abilities.effects.common.ReturnFromExileForSourceEffect;
import mage.cards.CardImpl;
import mage.constants.CardType;
import mage.constants.Rarity;
import mage.constants.Zone;
import mage.filter.common.FilterCreaturePermanent;
import mage.filter.predicate.permanent.AnotherPredicate;
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}");
this.expansionSetCode = "ZEN";
// When Journey to Nowhere enters the battlefield, exile target creature.
FilterCreaturePermanent filter = new FilterCreaturePermanent();
filter.add(new AnotherPredicate());
@ -61,7 +59,7 @@ public class JourneyToNowhere extends CardImpl {
Target target = new TargetPermanent(filter);
ability1.addTarget(target);
this.addAbility(ability1);
// When Journey to Nowhere leaves the battlefield, return the exiled card to the battlefield under its owner's control.
Ability ability2 = new LeavesBattlefieldTriggeredAbility(new ReturnFromExileForSourceEffect(Zone.BATTLEFIELD), false);
this.addAbility(ability2);

View file

@ -45,11 +45,13 @@ public class CastDestroySpellsTest extends CardTestPlayerBaseAI {
@Test
public void testOrzhovCharm() {
// Choose one -
// 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;
// or return target creature card with converted mana cost 1 or less from your graveyard to the battlefield.
// - Return target creature you control and all Auras you control attached to it to their owner's hand;
// - Destroy target creature and you lose life equal to its toughness;
// - 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}
// {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, "Plains", 1);

View file

@ -51,10 +51,11 @@ public class ReturnToHandTest extends CardTestPlayerBase {
addCard(Zone.BATTLEFIELD, playerB, "Swamp", 2);
addCard(Zone.BATTLEFIELD, playerB, "Silvercoat Lion");
addCard(Zone.BATTLEFIELD, playerB, "Mountain", 3);
addCard(Zone.HAND, playerB, "Bone Splinters");
// As an additional cost to cast Bone Splinters, sacrifice a creature.
// Destroy target creature.
addCard(Zone.HAND, playerB, "Bone Splinters");
castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Bone Splinters", "Pillarfield Ox");
setChoice(playerB, "Skarrgan Firebird");
@ -126,7 +127,7 @@ public class ReturnToHandTest extends CardTestPlayerBase {
// Devoid
// Choose one or both
// - 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.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, playerA, "Brutal Expulsion", "mode=1Pillarfield Ox^mode=2Silvercoat Lion", "Pillarfield Ox");
setModeChoice(playerA, "1");
setModeChoice(playerA, "2");
setStopAt(2, PhaseStep.BEGIN_COMBAT);
execute();

View file

@ -86,7 +86,7 @@ public class DeathtouchTest extends CardTestPlayerBase {
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");
setChoice(playerA, "X=3");

View file

@ -36,30 +36,29 @@ import org.mage.test.serverside.base.CardTestPlayerBase;
*
* @author LevelX2
*/
public class HeroicTest extends CardTestPlayerBase {
/**
* When casting Dromoka's Command targeting two of my own Heroic creatures, 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).
* When casting Dromoka's Command targeting two of my own Heroic creatures,
* 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.
*/
@Test
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.
addCard(Zone.BATTLEFIELD, playerA, "Favored Hoplite", 1); // 1/2
// Heroic Whenever you cast a spell that targets Lagonna-Band Trailblazer, put a +1/+1 counter on Lagonna-Band Trailblazer.
addCard(Zone.BATTLEFIELD, playerA, "Lagonna-Band Trailblazer"); // 0/4
// Mode 3 = Put a +1/+1 counter on target creature
// Mode 4 = Target creature you control fights target creature you don't control
addCard(Zone.HAND, playerA, "Dromoka's Command", 1); // {G}{W}
addCard(Zone.BATTLEFIELD, playerA, "Forest", 1);
addCard(Zone.BATTLEFIELD, playerA, "Plains", 1);
addCard(Zone.BATTLEFIELD, playerB, "Silvercoat Lion");
addCard(Zone.BATTLEFIELD, playerB, "Silvercoat Lion");
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Dromoka's Command", "mode=3Lagonna-Band Trailblazer^mode=4Favored Hoplite^Silvercoat Lion");
// Silvercoat lion will be set by AI as only possible target
setModeChoice(playerA, "3");
@ -69,11 +68,11 @@ public class HeroicTest extends CardTestPlayerBase {
execute();
assertGraveyardCount(playerA, "Dromoka's Command", 1);
assertPowerToughness(playerA, "Favored Hoplite", 2, 3);
assertGraveyardCount(playerB, "Silvercoat Lion", 1);
assertPowerToughness(playerA, "Lagonna-Band Trailblazer", 2, 6);
}
}

View file

@ -1,31 +1,30 @@
/*
* Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are
* permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice, this list
* of conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* The views and conclusions contained in the software and documentation are those of the
* authors and should not be interpreted as representing official policies, either expressed
* or implied, of BetaSteward_at_googlemail.com.
*/
* Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are
* permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice, this list
* of conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* The views and conclusions contained in the software and documentation are those of the
* authors and should not be interpreted as representing official policies, either expressed
* or implied, of BetaSteward_at_googlemail.com.
*/
package org.mage.test.cards.abilities.oneshot.counterspell;
import mage.constants.PhaseStep;
@ -34,17 +33,17 @@ import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase;
/**
* Cryptic Command
* Instant, 1UUU
* 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.
* Cryptic Command Instant, 1UUU 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.
*
* @author LevelX2
*/
public class CrypticCommandTest extends CardTestPlayerBase {
/**
* Test that if command has only one target and that targets is not valid on resolution, Cryptic Command fizzeles
* The player does not draw a card
* Test that if command has only one target and that targets is not valid on
* resolution, Cryptic Command fizzeles The player does not draw a card
*/
@Test
public void testCommand() {
@ -54,16 +53,16 @@ public class CrypticCommandTest extends CardTestPlayerBase {
addCard(Zone.HAND, playerA, "Remand");
addCard(Zone.BATTLEFIELD, playerA, "Swamp", 1);
addCard(Zone.BATTLEFIELD, playerA, "Island", 2);
addCard(Zone.HAND, playerB, "Cryptic Command");
addCard(Zone.BATTLEFIELD, playerB, "Island", 4);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Thoughtseize", playerB);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Cryptic Command", "Thoughtseize");
setModeChoice(playerB, "1"); // Counter target spell
setModeChoice(playerB, "4"); // Draw a card
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Remand", "Thoughtseize", "Cast Cryptic Command");
setStopAt(1, PhaseStep.CLEANUP);
@ -78,14 +77,16 @@ public class CrypticCommandTest extends CardTestPlayerBase {
assertGraveyardCount(playerB, 1);
assertHandCount(playerA, 2); // Thoughtsize + card drawn from Remand
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
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");
// 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.
@ -93,19 +94,19 @@ public class CrypticCommandTest extends CardTestPlayerBase {
addCard(Zone.HAND, playerA, "Lightning Bolt");
addCard(Zone.BATTLEFIELD, playerA, "Swamp", 1);
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 5);
addCard(Zone.HAND, playerB, "Cryptic Command");
addCard(Zone.BATTLEFIELD, playerB, "Island", 4);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Thoughtseize", playerB);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", playerB);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Cryptic Command", "Thoughtseize");
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", playerB);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Cryptic Command", "mode=1Thoughtseize", "Thoughtseize", StackClause.WHILE_ON_STACK);
setModeChoice(playerB, "1"); // Counter target spell
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");
setStopAt(1, PhaseStep.CLEANUP);
execute();
@ -118,6 +119,6 @@ public class CrypticCommandTest extends CardTestPlayerBase {
assertGraveyardCount(playerA, "Lightning Bolt", 1);
assertHandCount(playerB, 1); // card drawn from Cryptic Command
}
}

View file

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

View file

@ -22,6 +22,7 @@ public class GainControlTargetEffectTest extends CardTestPlayerBase {
*/
@Test
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.BATTLEFIELD, playerA, "Mountain", 4);
addCard(Zone.BATTLEFIELD, playerA, "Boros Guildgate", 2);

View file

@ -13,25 +13,25 @@ import org.mage.test.serverside.base.CardTestPlayerBase;
*
* @author jeffwadsworth
*/
public class AbattoirGhoulTest extends CardTestPlayerBase{
public class AbattoirGhoulTest extends CardTestPlayerBase {
@Test
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, playerB, "Memnite", 1);
addCard(Zone.BATTLEFIELD, playerB, "Shivan Dragon", 1);
attack(1, playerA, "Abattoir Ghoul");
block(1, playerB, "Memnite", "Abattoir Ghoul");
block(1, playerB, "Shivan Dragon", "Abattoir Ghoul");
setStopAt(1, PhaseStep.END_TURN);
execute();
assertLife(playerA, 21);
assertLife(playerB, 20);
}
}

View file

@ -36,23 +36,21 @@ import org.mage.test.serverside.base.CardTestPlayerBase;
*
* @author LeveX2
*/
public class JourneyToNowhereTest extends CardTestPlayerBase {
/*
Journey to Nowhere Enchantment {1}{W}
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.
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.
*/
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.
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
public void testTargetGetsExiled() {
addCard(Zone.HAND, playerA, "Journey to Nowhere");
addCard(Zone.BATTLEFIELD, playerA, "Plains", 2);
addCard(Zone.BATTLEFIELD, playerB, "Silvercoat Lion", 1);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Journey to Nowhere");
@ -63,13 +61,12 @@ public class JourneyToNowhereTest extends CardTestPlayerBase {
assertPermanentCount(playerA, "Journey to Nowhere", 1);
assertExileCount("Silvercoat Lion", 1);
}
@Test
public void testTargetGetsExiledAndReturns() {
addCard(Zone.HAND, playerA, "Journey to Nowhere");
addCard(Zone.BATTLEFIELD, playerA, "Plains", 2);
addCard(Zone.HAND, playerB, "Disenchant", 1);
addCard(Zone.BATTLEFIELD, playerB, "Silvercoat Lion", 1);
addCard(Zone.BATTLEFIELD, playerB, "Plains", 2);
@ -78,7 +75,7 @@ public class JourneyToNowhereTest extends CardTestPlayerBase {
addTarget(playerA, "Silvercoat Lion");
castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerB, "Disenchant", "Journey to Nowhere");
setStopAt(1, PhaseStep.END_TURN);
execute();
@ -88,14 +85,14 @@ 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
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
public void testTargetGetsExiledAndDoesNeverReturn() {
addCard(Zone.HAND, playerA, "Journey to Nowhere");
addCard(Zone.BATTLEFIELD, playerA, "Plains", 2);
addCard(Zone.HAND, playerB, "Disenchant", 1);
addCard(Zone.BATTLEFIELD, playerB, "Silvercoat Lion", 1);
addCard(Zone.BATTLEFIELD, playerB, "Plains", 2);
@ -103,29 +100,29 @@ public class JourneyToNowhereTest extends CardTestPlayerBase {
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Journey to Nowhere");
addTarget(playerA, "Silvercoat Lion");
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Disenchant", "Journey to Nowhere");
setStopAt(1, PhaseStep.BEGIN_COMBAT);
execute();
assertGraveyardCount(playerA, "Journey to Nowhere", 1);
assertGraveyardCount(playerB, "Disenchant", 1);
assertExileCount("Silvercoat Lion", 1);
}
}
/*
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 played again targeting another creature.
The Journey will be disenchanted later, so only the second creature has to return to 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 played again targeting another creature.
The Journey will be disenchanted later, so only the second creature has to return to battlefield.
*/
@Test
public void testTargetGetsExiledAndDoesNeverReturnAndJourneyPlayedAgain() {
addCard(Zone.HAND, playerA, "Journey to Nowhere");
addCard(Zone.HAND, playerA, "Boomerang");
addCard(Zone.BATTLEFIELD, playerA, "Plains", 4);
addCard(Zone.BATTLEFIELD, playerA, "Island", 4);
addCard(Zone.HAND, playerB, "Disenchant", 1);
addCard(Zone.BATTLEFIELD, playerB, "Silvercoat Lion", 1);
addCard(Zone.BATTLEFIELD, playerB, "Pillarfield Ox", 1);
@ -133,11 +130,11 @@ public class JourneyToNowhereTest extends CardTestPlayerBase {
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Journey to Nowhere");
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");
addTarget(playerA, "Pillarfield Ox");
castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Disenchant", "Journey to Nowhere");
setStopAt(2, PhaseStep.BEGIN_COMBAT);
@ -145,11 +142,11 @@ public class JourneyToNowhereTest extends CardTestPlayerBase {
assertGraveyardCount(playerA, "Boomerang", 1);
assertGraveyardCount(playerA, "Journey to Nowhere", 1);
assertGraveyardCount(playerB, "Disenchant", 1);
assertGraveyardCount(playerB, "Disenchant", 1);
assertPermanentCount(playerB, "Pillarfield Ox", 1);
assertPermanentCount(playerB, "Silvercoat Lion", 0);
assertExileCount("Silvercoat Lion", 1);
}
}
}

View file

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

View file

@ -123,10 +123,10 @@ public class SpellskiteTest extends CardTestPlayerBase {
public void testSpellskite() {
addCard(Zone.BATTLEFIELD, playerA, "Island", 4);
// 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.
// - Counter target spell;
// - return target permanent to its owner's hand;
// - tap all creatures your opponents control;
// - draw a card.
addCard(Zone.HAND, playerA, "Cryptic Command");
addCard(Zone.BATTLEFIELD, playerB, "Spellskite", 1);
@ -141,7 +141,7 @@ public class SpellskiteTest extends CardTestPlayerBase {
setModeChoice(playerA, "1"); // Counter target spell
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);
execute();

View file

@ -3,7 +3,6 @@
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package org.mage.test.lki;
import mage.constants.PhaseStep;
@ -20,10 +19,10 @@ public class LastKnownInformationTest extends CardTestPlayerBase {
/**
* see here for more information
* http://www.slightlymagic.net/forum/viewtopic.php?f=116&t=14516
*
* Tests Safehold Elite with persist returns to battlefield with -1/-1 counter
* Murder Investigation has to put 2 tokens onto battlefield because enchanted Safehold Elite
* was 2/2
*
* Tests Safehold Elite with persist returns to battlefield with -1/-1
* counter Murder Investigation has to put 2 tokens onto battlefield because
* enchanted Safehold Elite was 2/2
*
* @author LevelX
*/
@ -38,9 +37,9 @@ public class LastKnownInformationTest extends CardTestPlayerBase {
// {1}{W}
// Enchant creature you control
// 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);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Murder Investigation", "Safehold Elite");
@ -58,17 +57,20 @@ public class LastKnownInformationTest extends CardTestPlayerBase {
// because enchanted Safehold Elite's P/T was 2/2, Murder Investigation has to put 2 Soldier onto the battlefield
assertPermanentCount(playerA, "Soldier", 2);
assertGraveyardCount(playerB, "Lightning Bolt", 2);
assertActionCount(playerB, 0);
assertActionCount(playerB, 0);
}
/**
* 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
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, "Forest", 3);
@ -76,20 +78,21 @@ public class LastKnownInformationTest extends CardTestPlayerBase {
addCard(Zone.HAND, playerA, "Grizzly Bears", 1);
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);
execute();
assertGraveyardCount(playerA, "Giant Growth", 1);
assertPermanentCount(playerA, "Grizzly Bears", 1);
assertLife(playerA, 25);
}
/**
* Here we test correct spell interaction by playing Cloudshift BEFORE Giant Growth resolves.
* Cloudshift will remove 2/2 creature and it will return as 2/2.
* Giant Growth will be fizzled.
* That means that player should gain 2 + 2 life.
* Here we test correct spell interaction by playing Cloudshift BEFORE Giant
* Growth resolves. Cloudshift will remove 2/2 creature and it will return
* as 2/2. Giant Growth will be fizzled. That means that player should gain
* 2 + 2 life.
*/
@Test
public void testTrostaniSelesnyasVoice2() {
@ -102,7 +105,7 @@ public class LastKnownInformationTest extends CardTestPlayerBase {
addCard(Zone.HAND, playerA, "Grizzly Bears", 1);
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",
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.
* Cloudshift will remove 5/5 creature and it will return as 2/2.
* Here we test actual use of LKI by playing Cloudshift AFTER Giant Growth
* resolves. Cloudshift will remove 5/5 creature and it will return as 2/2.
* 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);
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",
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 String[] groupsForTargetHandling = null;
public TestPlayer(ComputerPlayer computerPlayer) {
this.computerPlayer = computerPlayer;
AIPlayer = false;
@ -204,23 +206,14 @@ public class TestPlayer implements Player {
return true;
}
// private boolean checkSpellOnTopOfStackCondition(String[] groups, Game game) {
// if (groups.length > 2 && groups[2].startsWith("spellOnTopOfStack=")) {
// String spellOnTopOFStack = groups[2].substring(18);
// if (game.getStack().size() > 0) {
// 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) {
@Override
public boolean addTargets(Ability ability, Game game) {
if (groupsForTargetHandling == null) {
return true;
}
boolean result = true;
for (int i = 1; i < groups.length; i++) {
String group = groups[i];
for (int i = 1; i < groupsForTargetHandling.length; i++) {
String group = groupsForTargetHandling[i];
if (group.startsWith("spellOnStack") || group.startsWith("spellOnTopOfStack") || group.startsWith("!spellOnStack") || group.startsWith("target=null") || group.startsWith("manaInPool=")) {
break;
}
@ -277,29 +270,36 @@ public class TestPlayer implements Player {
int index = 0;
int targetsSet = 0;
for (String targetName : targetList) {
Mode selectedMode = null;
if (targetName.startsWith("mode=")) {
int modeNr = Integer.parseInt(targetName.substring(5, 6));
if (modeNr == 0 || modeNr > ability.getModes().size()) {
throw new UnsupportedOperationException("Given mode number (" + modeNr + ") not available for " + ability.toString());
}
int modeCounter = 1;
for (Mode mode : ability.getModes().values()) {
if (modeCounter == modeNr) {
ability.getModes().setMode(mode);
UUID modeId = ability.getModes().getModeId(modeNr);
for (Mode mode : ability.getModes().getSelectedModes()) {
if (mode.getId().equals(modeId)) {
selectedMode = mode;
ability.getModes().setActiveMode(mode);
index = 0; // reset target index if mode changes
break;
}
modeCounter++;
}
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());
}
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.
}
Target currentTarget = ability.getTargets().get(index);
Target currentTarget = selectedMode.getTargets().get(index);
if (targetName.startsWith("targetPlayer=")) {
target = targetName.substring(targetName.indexOf("targetPlayer=") + 13);
for (Player player : game.getPlayers().values()) {
@ -362,6 +362,7 @@ public class TestPlayer implements Player {
if (action.getAction().startsWith("activate:")) {
String command = action.getAction();
command = command.substring(command.indexOf("activate:") + 9);
groupsForTargetHandling = null;
String[] groups = command.split("\\$");
if (groups.length > 2 && !checkExecuteCondition(groups, game)) {
break;
@ -371,13 +372,11 @@ public class TestPlayer implements Player {
int bookmark = game.bookmarkState();
Ability newAbility = ability.copy();
if (groups.length > 1 && !groups[1].equals("target=NO_TARGET")) {
if (!addTargets(newAbility, groups, game)) {
// targets could not be set -> try next priority
break;
}
groupsForTargetHandling = groups;
}
if (computerPlayer.activateAbility((ActivatedAbility) newAbility, game)) {
actions.remove(action);
groupsForTargetHandling = null;
return true;
} else {
game.restoreState(bookmark, ability.getRule());
@ -1913,6 +1912,7 @@ public class TestPlayer implements Player {
@Override
public boolean playMana(Ability ability, ManaCost unpaid, String promptText, Game game) {
groupsForTargetHandling = null;
return computerPlayer.playMana(ability, unpaid, promptText, game);
}

View file

@ -155,7 +155,7 @@ public abstract class AbilityImpl implements Ability {
subAbilities.add(subAbility.copy());
}
}
this.modes = ability.modes.copy();
this.modes = ability.getModes().copy();
this.ruleAtTheTop = ability.ruleAtTheTop;
this.ruleVisible = ability.ruleVisible;
this.ruleAdditionalCostsVisible = ability.ruleAdditionalCostsVisible;
@ -196,6 +196,7 @@ public abstract class AbilityImpl implements Ability {
boolean result = true;
//20100716 - 117.12
if (checkIfClause(game)) {
for (Effect effect : getEffects()) {
if (effect instanceof OneShotEffect) {
boolean effectResult = effect.apply(game, this);
@ -255,9 +256,14 @@ public abstract class AbilityImpl implements Ability {
/* 20130201 - 601.2b
* 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;
}
if (controller.isTestMode()) {
if (!controller.addTargets(this, game)) {
return false;
}
}
getSourceObject(game);
@ -274,9 +280,8 @@ public abstract class AbilityImpl implements Ability {
}
// TODO: Because all (non targeted) choices have to be done during resolution
// this has to be removed, if all using effects are changed
for (UUID modeId : this.getModes().getSelectedModes()) {
this.getModes().setActiveMode(modeId);
if (getChoices().size() > 0 && getChoices().choose(game, this) == false) {
for (Mode mode : this.getModes().getSelectedModes()) {
if (mode.getChoices().size() > 0 && mode.getChoices().choose(game, this) == false) {
logger.debug("activate failed - choice");
return false;
}
@ -313,8 +318,8 @@ public abstract class AbilityImpl implements Ability {
VariableManaCost variableManaCost = handleManaXCosts(game, noMana, controller);
String announceString = handleOtherXCosts(game, controller);
for (UUID modeId : this.getModes().getSelectedModes()) {
this.getModes().setActiveMode(modeId);
for (Mode mode : this.getModes().getSelectedModes()) {
this.getModes().setActiveMode(mode);
//20121001 - 601.2c
// 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
@ -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
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()) {
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) {
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;
@ -681,7 +686,7 @@ public abstract class AbilityImpl implements Ability {
@Override
public Effects getEffects() {
return modes.getMode().getEffects();
return getModes().getMode().getEffects();
}
@Override
@ -706,7 +711,7 @@ public abstract class AbilityImpl implements Ability {
@Override
public Choices getChoices() {
return modes.getMode().getChoices();
return getModes().getMode().getChoices();
}
@Override
@ -781,7 +786,7 @@ public abstract class AbilityImpl implements Ability {
}
String ruleStart = sbRule.toString();
String text = modes.getText();
String text = getModes().getText();
String rule;
if (!text.isEmpty()) {
if (ruleStart.length() > 1) {
@ -873,7 +878,7 @@ public abstract class AbilityImpl implements Ability {
@Override
public Targets getTargets() {
return modes.getMode().getTargets();
return getModes().getMode().getTargets();
}
@Override
@ -883,12 +888,12 @@ public abstract class AbilityImpl implements Ability {
@Override
public boolean isModal() {
return this.modes.size() > 1;
return getModes().size() > 1;
}
@Override
public void addMode(Mode mode) {
this.modes.addMode(mode);
getModes().addMode(mode);
}
@Override
@ -899,10 +904,10 @@ public abstract class AbilityImpl implements Ability {
@Override
public boolean canChooseTarget(Game game) {
int found = 0;
for (Mode mode : modes.values()) {
for (Mode mode : getModes().values()) {
if (mode.getTargets().canChoose(sourceId, controllerId, game)) {
found++;
if (modes.isEachModeMoreThanOnce()) {
if (getModes().isEachModeMoreThanOnce()) {
return true;
}
if (found >= getModes().getMinModes()) {
@ -1037,7 +1042,7 @@ public abstract class AbilityImpl implements Ability {
logger.warn("Could get no object: " + this.toString());
}
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(getMessageText(game)).toString();
}
@ -1106,14 +1111,13 @@ public abstract class AbilityImpl implements Ability {
}
} else if (object instanceof Spell && ((Spell) object).getSpellAbility().getModes().size() > 1) {
Modes spellModes = ((Spell) object).getSpellAbility().getModes();
for (UUID modeId : spellModes.getSelectedModes()) {
for (Mode selectedMode : spellModes.getSelectedModes()) {
int item = 0;
for (Mode mode : spellModes.values()) {
item++;
if (mode.getId().equals(modeId)) {
spellModes.setActiveMode(mode.getId());
if (mode.getId().equals(selectedMode.getId())) {
sb.append(" (mode ").append(item).append(")");
sb.append(getTargetDescriptionForLog(getTargets(), game));
sb.append(getTargetDescriptionForLog(selectedMode.getTargets(), game));
break;
}
}

View file

@ -49,8 +49,8 @@ import mage.util.CardUtil;
*/
public class Modes extends LinkedHashMap<UUID, Mode> {
private UUID modeId;
private final ArrayList<UUID> selectedModes = new ArrayList<>();
private Mode mode; // the current mode of the selected modes
private final ArrayList<Mode> selectedModes = new ArrayList<>();
private int minModes;
private int maxModes;
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
public Modes() {
Mode mode = new Mode();
this.mode = new Mode();
this.put(mode.getId(), mode);
this.modeId = mode.getId();
this.minModes = 1;
this.maxModes = 1;
this.selectedModes.add(modeId);
this.selectedModes.add(mode);
this.modeChooser = TargetController.YOU;
this.eachModeOnlyOnce = false;
this.eachModeMoreThanOnce = false;
}
public Modes(final Modes modes) {
this.modeId = modes.modeId;
for (Map.Entry<UUID, Mode> entry : modes.entrySet()) {
this.put(entry.getKey(), entry.getValue().copy());
}
this.minModes = modes.minModes;
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.eachModeOnlyOnce = modes.eachModeOnlyOnce;
this.eachModeMoreThanOnce = modes.eachModeMoreThanOnce;
@ -87,10 +102,21 @@ public class Modes extends LinkedHashMap<UUID, Mode> {
}
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;
}
@ -118,16 +144,9 @@ public class Modes extends LinkedHashMap<UUID, Mode> {
return this.modeChooser;
}
public void setActiveMode(UUID modeId) {
if (selectedModes.contains(modeId)) {
this.modeId = modeId;
}
}
public void setMode(Mode mode) {
if (this.containsKey(mode.getId())) {
this.modeId = mode.getId();
this.selectedModes.add(mode.getId());
public void setActiveMode(Mode mode) {
if (selectedModes.contains(mode)) {
this.mode = mode;
}
}
@ -156,7 +175,7 @@ public class Modes extends LinkedHashMap<UUID, Mode> {
for (Mode mode : this.values()) {
if ((!isEachModeOnlyOnce() || onceSelectedModes == null || !onceSelectedModes.contains(mode.getId()))
&& mode.getTargets().canChoose(source.getSourceId(), source.getControllerId(), game)) {
this.selectedModes.add(mode.getId());
this.selectedModes.add(mode.copy());
}
}
if (isEachModeOnlyOnce()) {
@ -184,6 +203,7 @@ public class Modes extends LinkedHashMap<UUID, Mode> {
Player player = game.getPlayer(playerId);
// player chooses modes manually
this.mode = null;
while (this.selectedModes.size() < this.getMaxModes()) {
Mode choice = player.chooseMode(this, source, game);
if (choice == null) {
@ -192,29 +212,38 @@ public class Modes extends LinkedHashMap<UUID, Mode> {
}
return this.selectedModes.size() >= this.getMinModes();
}
setMode(choice);
this.selectedModes.add(choice.copy());
if (mode == null) {
mode = choice;
}
}
if (isEachModeOnlyOnce()) {
setAlreadySelectedModes(selectedModes, source, game);
}
return true;
}
this.modeId = this.values().iterator().next().getId();
this.selectedModes.clear();
this.selectedModes.add(modeId);
if (mode == null) {
this.selectedModes.clear();
Mode copiedMode = this.values().iterator().next().copy();
this.selectedModes.add(copiedMode);
this.setActiveMode(copiedMode);
}
if (isEachModeOnlyOnce()) {
setAlreadySelectedModes(selectedModes, source, game);
}
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);
Set<UUID> onceSelectedModes = (Set<UUID>) game.getState().getValue(key);
if (onceSelectedModes == null) {
onceSelectedModes = new HashSet<>();
}
onceSelectedModes.addAll(selectedModes);
for (Mode mode : selectedModes) {
onceSelectedModes.add(mode.getId());
}
game.getState().setValue(key, onceSelectedModes);
}

View file

@ -27,9 +27,9 @@
*/
package mage.abilities.common;
import mage.constants.Zone;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.effects.Effect;
import mage.constants.Zone;
import mage.game.Game;
import mage.game.events.GameEvent;
@ -73,7 +73,7 @@ public class EntersBattlefieldTriggeredAbility extends TriggeredAbilityImpl {
@Override
public boolean checkTrigger(GameEvent event, Game game) {
return event.getTargetId().equals(getSourceId());
return event.getTargetId().equals(getSourceId());
}
@Override
@ -81,7 +81,7 @@ public class EntersBattlefieldTriggeredAbility extends TriggeredAbilityImpl {
if (noRule) {
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

View file

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

View file

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

View file

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

View file

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

View file

@ -53,13 +53,12 @@ public class TargetsPermanentPredicate implements ObjectSourcePlayerPredicate<Ob
@Override
public boolean apply(ObjectSourcePlayer<MageObject> input, Game game) {
StackObject object = game.getStack().getStackObject(input.getObject().getId());
if(object != null) {
for(UUID modeId : object.getStackAbility().getModes().getSelectedModes()) {
Mode mode = object.getStackAbility().getModes().get(modeId);
for(Target target : mode.getTargets()) {
for(UUID targetId : target.getTargets()) {
if (object != null) {
for (Mode mode : object.getStackAbility().getModes().getSelectedModes()) {
for (Target target : mode.getTargets()) {
for (UUID targetId : target.getTargets()) {
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;
}
}

View file

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

View file

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

View file

@ -86,14 +86,14 @@ public class StackAbility extends StackObjImpl implements Ability {
public StackAbility(Ability ability, UUID controllerId) {
this.ability = ability;
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) {
this.ability = spell.ability.copy();
this.controllerId = spell.controllerId;
this.name = spell.name;
this.expansionSetCode = spell.expansionSetCode;
public StackAbility(final StackAbility stackAbility) {
this.ability = stackAbility.ability.copy();
this.controllerId = stackAbility.controllerId;
this.name = stackAbility.name;
this.expansionSetCode = stackAbility.expansionSetCode;
}
@Override

View file

@ -117,8 +117,8 @@ public abstract class StackObjImpl implements StackObject {
}
for (Ability ability : objectAbilities) {
// Some spells can have more than one mode
for (UUID modeId : ability.getModes().getSelectedModes()) {
Mode mode = ability.getModes().get(modeId);
for (Mode mode : ability.getModes().getSelectedModes()) {
ability.getModes().setActiveMode(mode);
oldTargetDescription.append(ability.getTargetDescription(mode.getTargets(), game));
for (Target target : mode.getTargets()) {
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();
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()) {
Ability newOption = option.copy();
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.getManaCosts().getVariableCosts().size() > 0) {
addVariableXOptions(options, newOption, 0, game);
@ -3448,4 +3449,10 @@ public abstract class PlayerImpl implements Player, Serializable {
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.target.Target;
/**
* @author duncant
*/
public class TargetAddress {
protected int spellAbilityIndex;
protected UUID mode;
protected int targetIndex;
@ -52,8 +52,9 @@ public class TargetAddress {
}
protected static class TargetAddressIterable implements Iterable<TargetAddress> {
protected final Card card;
public TargetAddressIterable(Card card) {
this.card = card;
}
@ -64,9 +65,10 @@ public class TargetAddress {
}
protected static class TargetAddressIterator implements Iterator<TargetAddress> {
protected Iterator<SpellAbility> spellAbilityIterator;
protected Integer lastSpellAbilityIndex = null;
protected Iterator<UUID> modeIterator = null;
protected Iterator<Mode> modeIterator = null;
protected Modes modes = null;
protected UUID lastMode = null;
protected Iterator<Target> targetIterator = null;
@ -88,14 +90,14 @@ public class TargetAddress {
public boolean hasNext() {
return lastTargetIndex != null;
}
public TargetAddress next() {
TargetAddress ret = new TargetAddress(lastSpellAbilityIndex,
lastMode,
lastTargetIndex);
lastMode,
lastTargetIndex);
calcNext();
return ret;
}
public void remove() {
@ -118,9 +120,9 @@ public class TargetAddress {
return;
}
}
if (modeIterator != null && modeIterator.hasNext()) {
lastMode = modeIterator.next();
lastMode = modeIterator.next().getId();
targetIterator = modes.get(lastMode).getTargets().iterator();
} else {
lastMode = null;
@ -145,7 +147,6 @@ public class TargetAddress {
}
}
public static Iterable<TargetAddress> walk(Card card) {
return new TargetAddressIterable(card);
}
@ -179,8 +180,8 @@ public class TargetAddress {
public boolean equals(TargetAddress other) {
return spellAbilityIndex == other.spellAbilityIndex
&& mode.equals(other.mode)
&& targetIndex == other.targetIndex;
&& mode.equals(other.mode)
&& targetIndex == other.targetIndex;
}
@Override