From 4711e0cf99d4e01356188f646f881f98b4ebe17e Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Sat, 14 Nov 2015 01:56:56 +0100 Subject: [PATCH] Changed ability handling of modal spells to be able to select the same mode multiple times with different targets. --- Mage.Common/src/mage/view/CardView.java | 13 +-- .../src/mage/view/StackAbilityView.java | 15 ++- .../java/mage/player/ai/ComputerPlayer.java | 9 +- .../src/mage/player/human/HumanPlayer.java | 30 ++++-- .../battleforzendikar/ZadaHedronGrinder.java | 16 ++- .../bornofthegods/MogisGodOfSlaughter.java | 18 ++-- .../magicorigins/JaceTelepathUnbound.java | 2 +- .../sets/magicorigins/PsychicRebuttal.java | 6 +- .../sets/shardsofalara/HinderingLight.java | 13 ++- .../sets/vintagemasters/KaerveksTorch.java | 16 ++- .../mage/sets/zendikar/JourneyToNowhere.java | 62 ++++++------ .../test/AI/basic/CastDestroySpellsTest.java | 8 +- .../abilities/activated/ReturnToHandTest.java | 7 +- .../abilities/keywords/DeathtouchTest.java | 2 +- .../cards/abilities/keywords/HeroicTest.java | 19 ++-- .../counterspell/CrypticCommandTest.java | 97 ++++++++++--------- .../counterspell/NotOfThisWorldTest.java | 1 + .../control/GainControlTargetEffectTest.java | 1 + .../cards/triggers/AbattoirGhoulTest.java | 16 +-- .../cards/triggers/JourneyToNowhereTest.java | 59 ++++++----- .../triggers/ReturnToHandEffectsTest.java | 2 +- .../test/cards/triggers/SpellskiteTest.java | 10 +- .../test/lki/LastKnownInformationTest.java | 44 +++++---- .../java/org/mage/test/player/TestPlayer.java | 56 +++++------ Mage/src/mage/abilities/AbilityImpl.java | 48 ++++----- Mage/src/mage/abilities/Modes.java | 81 +++++++++++----- .../EntersBattlefieldTriggeredAbility.java | 6 +- ...getOfTargetSpellAbilityToSourceEffect.java | 6 +- .../common/ExileTargetForSourceEffect.java | 67 ++++++------- .../common/ReturnToHandTargetEffect.java | 15 ++- .../mage/abilities/keyword/HeroicAbility.java | 63 ++++++------ .../mageobject/NumberOfTargetsPredicate.java | 4 +- .../other/TargetsPermanentPredicate.java | 11 +-- Mage/src/mage/game/GameState.java | 8 +- Mage/src/mage/game/stack/Spell.java | 19 ++-- Mage/src/mage/game/stack/StackAbility.java | 12 +-- Mage/src/mage/game/stack/StackObjImpl.java | 4 +- Mage/src/mage/players/Player.java | 9 ++ Mage/src/mage/players/PlayerImpl.java | 9 +- Mage/src/mage/util/TargetAddress.java | 25 ++--- 40 files changed, 488 insertions(+), 421 deletions(-) diff --git a/Mage.Common/src/mage/view/CardView.java b/Mage.Common/src/mage/view/CardView.java index 7216ba5c49..5be823cbe3 100644 --- a/Mage.Common/src/mage/view/CardView.java +++ b/Mage.Common/src/mage/view/CardView.java @@ -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("Chosen mode: " + spell.getSpellAbility().getEffects().getText(modes.get(modeId)) + ""); + for (Mode mode : spell.getSpellAbility().getModes().getSelectedModes()) { + this.rules.add("Chosen mode: " + mode.getEffects().getText(mode) + ""); } } } diff --git a/Mage.Common/src/mage/view/StackAbilityView.java b/Mage.Common/src/mage/view/StackAbilityView.java index 6fb4d3c486..815c0ec13d 100644 --- a/Mage.Common/src/mage/view/StackAbilityView.java +++ b/Mage.Common/src/mage/view/StackAbilityView.java @@ -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 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 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("Chosen mode: " + ability.getEffects().getText(modes.get(modeId)) + ""); + for (Mode mode : modes.getSelectedModes()) { + this.rules.add("Chosen mode: " + mode.getEffects().getText(mode) + ""); } } } diff --git a/Mage.Server.Plugins/Mage.Player.AI/src/main/java/mage/player/ai/ComputerPlayer.java b/Mage.Server.Plugins/Mage.Player.AI/src/main/java/mage/player/ai/ComputerPlayer.java index d967799a0d..4fff0604db 100644 --- a/Mage.Server.Plugins/Mage.Player.AI/src/main/java/mage/player/ai/ComputerPlayer.java +++ b/Mage.Server.Plugins/Mage.Player.AI/src/main/java/mage/player/ai/ComputerPlayer.java @@ -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; } } diff --git a/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/HumanPlayer.java b/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/HumanPlayer.java index dd3830375a..a9c4cb6c6b 100644 --- a/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/HumanPlayer.java +++ b/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/HumanPlayer.java @@ -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 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(); } diff --git a/Mage.Sets/src/mage/sets/battleforzendikar/ZadaHedronGrinder.java b/Mage.Sets/src/mage/sets/battleforzendikar/ZadaHedronGrinder.java index e34ac40666..48c1a77a9a 100644 --- a/Mage.Sets/src/mage/sets/battleforzendikar/ZadaHedronGrinder.java +++ b/Mage.Sets/src/mage/sets/battleforzendikar/ZadaHedronGrinder.java @@ -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); diff --git a/Mage.Sets/src/mage/sets/bornofthegods/MogisGodOfSlaughter.java b/Mage.Sets/src/mage/sets/bornofthegods/MogisGodOfSlaughter.java index 85fb229bf7..d5bd22552a 100644 --- a/Mage.Sets/src/mage/sets/bornofthegods/MogisGodOfSlaughter.java +++ b/Mage.Sets/src/mage/sets/bornofthegods/MogisGodOfSlaughter.java @@ -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 "); diff --git a/Mage.Sets/src/mage/sets/magicorigins/JaceTelepathUnbound.java b/Mage.Sets/src/mage/sets/magicorigins/JaceTelepathUnbound.java index 7e87bf53d0..3e55cfbfbd 100644 --- a/Mage.Sets/src/mage/sets/magicorigins/JaceTelepathUnbound.java +++ b/Mage.Sets/src/mage/sets/magicorigins/JaceTelepathUnbound.java @@ -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); diff --git a/Mage.Sets/src/mage/sets/magicorigins/PsychicRebuttal.java b/Mage.Sets/src/mage/sets/magicorigins/PsychicRebuttal.java index cdc1ecbfb1..84aca36df0 100644 --- a/Mage.Sets/src/mage/sets/magicorigins/PsychicRebuttal.java +++ b/Mage.Sets/src/mage/sets/magicorigins/PsychicRebuttal.java @@ -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 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); } diff --git a/Mage/src/mage/abilities/AbilityImpl.java b/Mage/src/mage/abilities/AbilityImpl.java index a595b7478f..4efdb77684 100644 --- a/Mage/src/mage/abilities/AbilityImpl.java +++ b/Mage/src/mage/abilities/AbilityImpl.java @@ -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; } } diff --git a/Mage/src/mage/abilities/Modes.java b/Mage/src/mage/abilities/Modes.java index f17c787cb0..08a317f95c 100644 --- a/Mage/src/mage/abilities/Modes.java +++ b/Mage/src/mage/abilities/Modes.java @@ -49,8 +49,8 @@ import mage.util.CardUtil; */ public class Modes extends LinkedHashMap { - private UUID modeId; - private final ArrayList selectedModes = new ArrayList<>(); + private Mode mode; // the current mode of the selected modes + private final ArrayList selectedModes = new ArrayList<>(); private int minModes; private int maxModes; private TargetController modeChooser; @@ -58,25 +58,40 @@ public class Modes extends LinkedHashMap { 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 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 { } public Mode getMode() { - return get(modeId); + return mode; } - public ArrayList 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 getSelectedModes() { return selectedModes; } @@ -118,16 +144,9 @@ public class Modes extends LinkedHashMap { 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 { 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 { 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 { } 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 selectedModes, Ability source, Game game) { + private void setAlreadySelectedModes(ArrayList selectedModes, Ability source, Game game) { String key = getKey(source, game); Set onceSelectedModes = (Set) 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); } diff --git a/Mage/src/mage/abilities/common/EntersBattlefieldTriggeredAbility.java b/Mage/src/mage/abilities/common/EntersBattlefieldTriggeredAbility.java index ddaf6be2b8..528a8c65c1 100644 --- a/Mage/src/mage/abilities/common/EntersBattlefieldTriggeredAbility.java +++ b/Mage/src/mage/abilities/common/EntersBattlefieldTriggeredAbility.java @@ -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 diff --git a/Mage/src/mage/abilities/effects/common/ChangeATargetOfTargetSpellAbilityToSourceEffect.java b/Mage/src/mage/abilities/effects/common/ChangeATargetOfTargetSpellAbilityToSourceEffect.java index e660cd1967..d332b7ac6c 100644 --- a/Mage/src/mage/abilities/effects/common/ChangeATargetOfTargetSpellAbilityToSourceEffect.java +++ b/Mage/src/mage/abilities/effects/common/ChangeATargetOfTargetSpellAbilityToSourceEffect.java @@ -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; diff --git a/Mage/src/mage/abilities/effects/common/ExileTargetForSourceEffect.java b/Mage/src/mage/abilities/effects/common/ExileTargetForSourceEffect.java index f909d0828e..697c07435a 100644 --- a/Mage/src/mage/abilities/effects/common/ExileTargetForSourceEffect.java +++ b/Mage/src/mage/abilities/effects/common/ExileTargetForSourceEffect.java @@ -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(); } } } diff --git a/Mage/src/mage/abilities/effects/common/ReturnToHandTargetEffect.java b/Mage/src/mage/abilities/effects/common/ReturnToHandTargetEffect.java index 7e6d25c147..f4af1e29b0 100644 --- a/Mage/src/mage/abilities/effects/common/ReturnToHandTargetEffect.java +++ b/Mage/src/mage/abilities/effects/common/ReturnToHandTargetEffect.java @@ -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 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 diff --git a/Mage/src/mage/abilities/keyword/HeroicAbility.java b/Mage/src/mage/abilities/keyword/HeroicAbility.java index 5b89f5d65b..2df1ea081c 100644 --- a/Mage/src/mage/abilities/keyword/HeroicAbility.java +++ b/Mage/src/mage/abilities/keyword/HeroicAbility.java @@ -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; diff --git a/Mage/src/mage/filter/predicate/mageobject/NumberOfTargetsPredicate.java b/Mage/src/mage/filter/predicate/mageobject/NumberOfTargetsPredicate.java index 7284bd1943..6d40519920 100644 --- a/Mage/src/mage/filter/predicate/mageobject/NumberOfTargetsPredicate.java +++ b/Mage/src/mage/filter/predicate/mageobject/NumberOfTargetsPredicate.java @@ -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 { 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(); } diff --git a/Mage/src/mage/filter/predicate/other/TargetsPermanentPredicate.java b/Mage/src/mage/filter/predicate/other/TargetsPermanentPredicate.java index 5471eae280..aad5bb551b 100644 --- a/Mage/src/mage/filter/predicate/other/TargetsPermanentPredicate.java +++ b/Mage/src/mage/filter/predicate/other/TargetsPermanentPredicate.java @@ -53,13 +53,12 @@ public class TargetsPermanentPredicate implements ObjectSourcePlayerPredicate 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; } } diff --git a/Mage/src/mage/game/GameState.java b/Mage/src/mage/game/GameState.java index 68f312ad7a..d2af936c84 100644 --- a/Mage/src/mage/game/GameState.java +++ b/Mage/src/mage/game/GameState.java @@ -309,7 +309,7 @@ public class GameState implements Serializable, Copyable { 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 { 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 { 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 { */ 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); diff --git a/Mage/src/mage/game/stack/Spell.java b/Mage/src/mage/game/stack/Spell.java index 79092da68a..d2efaaaf15 100644 --- a/Mage/src/mage/game/stack/Spell.java +++ b/Mage/src/mage/game/stack/Spell.java @@ -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; } } diff --git a/Mage/src/mage/game/stack/StackAbility.java b/Mage/src/mage/game/stack/StackAbility.java index 6b689529d9..7215000ee4 100644 --- a/Mage/src/mage/game/stack/StackAbility.java +++ b/Mage/src/mage/game/stack/StackAbility.java @@ -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 diff --git a/Mage/src/mage/game/stack/StackObjImpl.java b/Mage/src/mage/game/stack/StackObjImpl.java index 6cf33e65ea..447b1a00c5 100644 --- a/Mage/src/mage/game/stack/StackObjImpl.java +++ b/Mage/src/mage/game/stack/StackObjImpl.java @@ -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); diff --git a/Mage/src/mage/players/Player.java b/Mage/src/mage/players/Player.java index 7cfa444437..3dae03761b 100644 --- a/Mage/src/mage/players/Player.java +++ b/Mage/src/mage/players/Player.java @@ -787,4 +787,13 @@ public interface Player extends MageItem, Copyable { 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); } diff --git a/Mage/src/mage/players/PlayerImpl.java b/Mage/src/mage/players/PlayerImpl.java index 4ad9cd64dd..d736afc3c7 100644 --- a/Mage/src/mage/players/PlayerImpl.java +++ b/Mage/src/mage/players/PlayerImpl.java @@ -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; + } + } diff --git a/Mage/src/mage/util/TargetAddress.java b/Mage/src/mage/util/TargetAddress.java index 4774fe21e1..e4e89d1832 100644 --- a/Mage/src/mage/util/TargetAddress.java +++ b/Mage/src/mage/util/TargetAddress.java @@ -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 { + protected final Card card; - + public TargetAddressIterable(Card card) { this.card = card; } @@ -64,9 +65,10 @@ public class TargetAddress { } protected static class TargetAddressIterator implements Iterator { + protected Iterator spellAbilityIterator; protected Integer lastSpellAbilityIndex = null; - protected Iterator modeIterator = null; + protected Iterator modeIterator = null; protected Modes modes = null; protected UUID lastMode = null; protected Iterator 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 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