From 5d50179c8497a2f7a05745677fd83bffefc025d5 Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Fri, 23 Sep 2016 15:12:21 +0200 Subject: [PATCH 01/15] Added Test. --- .../mage/test/cards/modal/OneOrBothTest.java | 105 ++++++++++++++++++ Mage/src/main/java/mage/abilities/Modes.java | 55 +++++---- 2 files changed, 141 insertions(+), 19 deletions(-) create mode 100644 Mage.Tests/src/test/java/org/mage/test/cards/modal/OneOrBothTest.java diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/modal/OneOrBothTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/modal/OneOrBothTest.java new file mode 100644 index 00000000000..cee56e63951 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/modal/OneOrBothTest.java @@ -0,0 +1,105 @@ +/* + * 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.modal; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * + * @author LevelX2 + */ +public class OneOrBothTest extends CardTestPlayerBase { + + @Test + public void testSubtleStrikeFirstMode() { + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 2); + // Choose one or both — + // • Target creature gets -1/-1 until end of turn. + // • Put a +1/+1 counter on target creature. + addCard(Zone.HAND, playerA, "Subtle Strike"); // Instant {1}{B} + + addCard(Zone.BATTLEFIELD, playerA, "Silvercoat Lion"); + addCard(Zone.BATTLEFIELD, playerB, "Pillarfield Ox"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Subtle Strike", "Silvercoat Lion"); + setModeChoice(playerA, "1"); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertPowerToughness(playerA, "Silvercoat Lion", 2, 2); + assertPowerToughness(playerB, "Pillarfield Ox", 1, 3); + } + + @Test + public void testSubtleStrikeSecondMode() { + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 2); + // Choose one or both — + // • Target creature gets -1/-1 until end of turn. + // • Put a +1/+1 counter on target creature. + addCard(Zone.HAND, playerA, "Subtle Strike"); // Instant {1}{B} + + addCard(Zone.BATTLEFIELD, playerA, "Silvercoat Lion"); + addCard(Zone.BATTLEFIELD, playerB, "Pillarfield Ox"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Subtle Strike", "Pillarfield Ox"); + setModeChoice(playerA, "2"); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertPowerToughness(playerA, "Silvercoat Lion", 2, 2); + assertPowerToughness(playerB, "Pillarfield Ox", 3, 5); + } + + @Test + public void testSubtleStrikeBothModes() { + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 2); + // Choose one or both — + // • Target creature gets -1/-1 until end of turn. + // • Put a +1/+1 counter on target creature. + addCard(Zone.HAND, playerA, "Subtle Strike"); // Instant {1}{B} + + addCard(Zone.BATTLEFIELD, playerA, "Silvercoat Lion"); + addCard(Zone.BATTLEFIELD, playerB, "Pillarfield Ox"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Subtle Strike", "Pillarfield Ox"); + addTarget(playerA, "Silvercoat Lion"); + setModeChoice(playerA, "1"); + setModeChoice(playerA, "2"); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertPowerToughness(playerA, "Silvercoat Lion", 3, 3); + assertPowerToughness(playerB, "Pillarfield Ox", 1, 3); + } +} diff --git a/Mage/src/main/java/mage/abilities/Modes.java b/Mage/src/main/java/mage/abilities/Modes.java index 08a317f95cf..c8fa0d3cae8 100644 --- a/Mage/src/main/java/mage/abilities/Modes.java +++ b/Mage/src/main/java/mage/abilities/Modes.java @@ -49,7 +49,7 @@ import mage.util.CardUtil; */ public class Modes extends LinkedHashMap { - private Mode mode; // the current mode of the selected modes + private Mode currentMode; // the current mode of the selected modes private final ArrayList selectedModes = new ArrayList<>(); private int minModes; private int maxModes; @@ -58,11 +58,11 @@ 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() { - this.mode = new Mode(); - this.put(mode.getId(), mode); + this.currentMode = new Mode(); + this.put(currentMode.getId(), currentMode); this.minModes = 1; this.maxModes = 1; - this.selectedModes.add(mode); + this.selectedModes.add(currentMode); this.modeChooser = TargetController.YOU; this.eachModeOnlyOnce = false; this.eachModeMoreThanOnce = false; @@ -76,19 +76,17 @@ public class Modes extends LinkedHashMap { this.maxModes = modes.maxModes; if (modes.size() == 1) { - this.mode = values().iterator().next(); - this.selectedModes.add(mode); + this.currentMode = values().iterator().next(); + this.selectedModes.add(currentMode); } 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.currentMode = copiedMode; + } else if (selectedMode.equals(modes.getMode())) { + this.currentMode = copiedMode; } } } @@ -102,15 +100,15 @@ public class Modes extends LinkedHashMap { } public Mode getMode() { - return mode; + return currentMode; } public UUID getModeId(int index) { int idx = 0; - for (Mode currentMode : this.values()) { + for (Mode mode : this.values()) { idx++; if (idx == index) { - return currentMode.getId(); + return mode.getId(); } } return null; @@ -146,7 +144,7 @@ public class Modes extends LinkedHashMap { public void setActiveMode(Mode mode) { if (selectedModes.contains(mode)) { - this.mode = mode; + this.currentMode = mode; } } @@ -203,7 +201,7 @@ public class Modes extends LinkedHashMap { Player player = game.getPlayer(playerId); // player chooses modes manually - this.mode = null; + this.currentMode = null; while (this.selectedModes.size() < this.getMaxModes()) { Mode choice = player.chooseMode(this, source, game); if (choice == null) { @@ -213,8 +211,8 @@ public class Modes extends LinkedHashMap { return this.selectedModes.size() >= this.getMinModes(); } this.selectedModes.add(choice.copy()); - if (mode == null) { - mode = choice; + if (currentMode == null) { + currentMode = choice; } } if (isEachModeOnlyOnce()) { @@ -222,7 +220,7 @@ public class Modes extends LinkedHashMap { } return true; } - if (mode == null) { + if (currentMode == null) { this.selectedModes.clear(); Mode copiedMode = this.values().iterator().next().copy(); this.selectedModes.add(copiedMode); @@ -234,8 +232,16 @@ public class Modes extends LinkedHashMap { return true; } + /** + * Saves the already selected modes to the state value + * + * @param selectedModes + * @param source + * @param game + */ private void setAlreadySelectedModes(ArrayList selectedModes, Ability source, Game game) { String key = getKey(source, game); + @SuppressWarnings("unchecked") Set onceSelectedModes = (Set) game.getState().getValue(key); if (onceSelectedModes == null) { onceSelectedModes = new HashSet<>(); @@ -247,14 +253,25 @@ public class Modes extends LinkedHashMap { game.getState().setValue(key, onceSelectedModes); } + // The already once selected modes for a modal card are stored as a state value + // That's important for modal abilities with modes that can only selected once while the object stays in its zone + @SuppressWarnings("unchecked") private Set getAlreadySelectedModes(Ability source, Game game) { return (Set) game.getState().getValue(getKey(source, game)); } + // creates the key the selected modes are saved with to the state values private String getKey(Ability source, Game game) { return CardUtil.getObjectZoneString("selectedModes", source.getSourceId(), game, game.getState().getZoneChangeCounter(source.getSourceId()), false); } + /** + * Returns all (still) available modes of the ability + * + * @param source + * @param game + * @return + */ public List getAvailableModes(Ability source, Game game) { List availableModes = new ArrayList<>(); Set nonAvailableModes; From 0b118d074e25926cf3a64af28e357ae522ec51a5 Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Fri, 23 Sep 2016 19:10:25 +0200 Subject: [PATCH 02/15] * Demonic Pact - Fixed that the already chosen options were not correctly rollbacked. --- Mage/src/main/java/mage/abilities/Modes.java | 25 ++++++++++---------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/Mage/src/main/java/mage/abilities/Modes.java b/Mage/src/main/java/mage/abilities/Modes.java index c8fa0d3cae8..7ac07f93681 100644 --- a/Mage/src/main/java/mage/abilities/Modes.java +++ b/Mage/src/main/java/mage/abilities/Modes.java @@ -41,7 +41,6 @@ import mage.constants.TargetController; import mage.game.Game; import mage.players.Player; import mage.target.common.TargetOpponent; -import mage.util.CardUtil; /** * @@ -240,29 +239,29 @@ public class Modes extends LinkedHashMap { * @param game */ private void setAlreadySelectedModes(ArrayList selectedModes, Ability source, Game game) { - String key = getKey(source, game); - @SuppressWarnings("unchecked") - Set onceSelectedModes = (Set) game.getState().getValue(key); - if (onceSelectedModes == null) { - onceSelectedModes = new HashSet<>(); - } for (Mode mode : selectedModes) { - onceSelectedModes.add(mode.getId()); + String key = getKey(source, game, mode.getId()); + game.getState().setValue(key, true); } - - game.getState().setValue(key, onceSelectedModes); } // The already once selected modes for a modal card are stored as a state value // That's important for modal abilities with modes that can only selected once while the object stays in its zone @SuppressWarnings("unchecked") private Set getAlreadySelectedModes(Ability source, Game game) { - return (Set) game.getState().getValue(getKey(source, game)); + Set onceSelectedModes = new HashSet<>(); + for (UUID modeId : this.keySet()) { + Object exist = game.getState().getValue(getKey(source, game, modeId)); + if (exist != null) { + onceSelectedModes.add(modeId); + } + } + return onceSelectedModes; } // creates the key the selected modes are saved with to the state values - private String getKey(Ability source, Game game) { - return CardUtil.getObjectZoneString("selectedModes", source.getSourceId(), game, game.getState().getZoneChangeCounter(source.getSourceId()), false); + private String getKey(Ability source, Game game, UUID modeId) { + return source.getSourceId().toString() + game.getState().getZoneChangeCounter(source.getSourceId()) + modeId.toString(); } /** From c9bb0be016d1b13611d3ab25b34974bd669b6c78 Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Sat, 24 Sep 2016 01:12:01 +0200 Subject: [PATCH 03/15] Reworked selected modes handling. That fixed the Subtle Strike targeting problem. --- Mage.Common/src/mage/view/CardView.java | 23 ++++---- .../src/mage/view/StackAbilityView.java | 6 ++- .../java/mage/player/ai/ComputerPlayer.java | 3 +- .../src/mage/player/human/HumanPlayer.java | 3 +- .../battleforzendikar/ZadaHedronGrinder.java | 9 ++-- .../mage/sets/chronicles/GoblinArtisans.java | 54 +++++++++---------- .../sets/dragonsoftarkir/IcefallRegent.java | 3 +- .../sets/fatereforged/MonasterySiege.java | 3 +- .../sets/kaladesh/CapturedByTheConsulate.java | 3 +- .../sets/magicorigins/PsychicRebuttal.java | 3 +- .../sets/planechase2012/ElderwoodScion.java | 3 +- .../src/mage/sets/scourge/GripOfChaos.java | 7 +-- .../shadowsoverinnistrad/AccursedWitch.java | 3 +- .../shadowsoverinnistrad/InfectiousCurse.java | 3 +- .../sets/shardsofalara/HinderingLight.java | 3 +- .../src/mage/sets/venservskoth/Torchling.java | 6 +-- .../sets/vintagemasters/KaerveksTorch.java | 3 +- .../test/AI/basic/CastDestroySpellsTest.java | 6 +-- .../mage/test/cards/modal/OneOrBothTest.java | 4 +- .../cards/single/bfz/BrutalExpulsionTest.java | 24 +++++---- .../java/org/mage/test/player/TestPlayer.java | 3 +- .../main/java/mage/abilities/AbilityImpl.java | 6 ++- Mage/src/main/java/mage/abilities/Modes.java | 37 +++++-------- ...getOfTargetSpellAbilityToSourceEffect.java | 11 ++-- .../mage/abilities/keyword/HeroicAbility.java | 5 +- .../mageobject/NumberOfTargetsPredicate.java | 4 +- .../other/TargetsPermanentPredicate.java | 3 +- Mage/src/main/java/mage/game/GameState.java | 12 +++-- Mage/src/main/java/mage/game/stack/Spell.java | 9 ++-- .../java/mage/game/stack/StackObjImpl.java | 7 ++- .../main/java/mage/players/PlayerImpl.java | 2 +- .../common/TargetCreatureOrPlaneswalker.java | 19 ++++++- .../main/java/mage/util/TargetAddress.java | 4 +- 33 files changed, 163 insertions(+), 131 deletions(-) diff --git a/Mage.Common/src/mage/view/CardView.java b/Mage.Common/src/mage/view/CardView.java index 2e0414ecb8c..9accefe8ed5 100644 --- a/Mage.Common/src/mage/view/CardView.java +++ b/Mage.Common/src/mage/view/CardView.java @@ -30,7 +30,6 @@ package mage.view; import java.util.ArrayList; import java.util.List; import java.util.UUID; - import mage.MageObject; import mage.ObjectColor; import mage.abilities.Mode; @@ -55,7 +54,6 @@ import mage.game.stack.Spell; import mage.game.stack.StackAbility; import mage.target.Target; import mage.target.Targets; -import org.apache.log4j.Logger; /** * @author BetaSteward_at_googlemail.com @@ -81,7 +79,7 @@ public class CardView extends SimpleCardView { protected List manaCost; protected int convertedManaCost; protected Rarity rarity; - + protected MageObjectType mageObjectType = MageObjectType.NULL; protected boolean isAbility; @@ -323,7 +321,8 @@ public class CardView extends SimpleCardView { this.mageObjectType = MageObjectType.SPELL; Spell spell = (Spell) card; for (SpellAbility spellAbility : spell.getSpellAbilities()) { - for (Mode mode : spellAbility.getModes().getSelectedModes()) { + for (UUID modeId : spellAbility.getModes().getSelectedModes()) { + Mode mode = spellAbility.getModes().get(modeId); if (mode.getTargets().size() > 0) { setTargets(spellAbility.getTargets()); } @@ -331,18 +330,19 @@ public class CardView extends SimpleCardView { } // show for modal spell, which mode was choosen if (spell.getSpellAbility().isModal()) { - for (Mode mode : spell.getSpellAbility().getModes().getSelectedModes()) { + for (UUID modeId : spell.getSpellAbility().getModes().getSelectedModes()) { + Mode mode = spell.getSpellAbility().getModes().get(modeId); this.rules.add("Chosen mode: " + mode.getEffects().getText(mode) + ""); } } } - + // Frame color this.frameColor = card.getFrameColor(game); // Frame style this.frameStyle = card.getFrameStyle(); - + // Get starting loyalty this.startingLoyalty = "" + card.getStartingLoyalty(); } @@ -355,7 +355,7 @@ public class CardView extends SimpleCardView { this.mageObjectType = MageObjectType.PERMANENT; this.power = Integer.toString(object.getPower().getValue()); this.toughness = Integer.toString(object.getToughness().getValue()); - this.loyalty = Integer.toString(((Permanent) object).getCounters((Game)null).getCount(CounterType.LOYALTY)); + this.loyalty = Integer.toString(((Permanent) object).getCounters((Game) null).getCount(CounterType.LOYALTY)); } else { this.power = object.getPower().toString(); this.toughness = object.getToughness().toString(); @@ -488,7 +488,7 @@ public class CardView extends SimpleCardView { this.rarity = Rarity.NA; this.type = token.getTokenType(); this.tokenDescriptor = token.getTokenDescriptor(); - this.tokenSetCode = token.getOriginalExpansionSetCode(); + this.tokenSetCode = token.getOriginalExpansionSetCode(); } protected final void setTargets(Targets targets) { @@ -547,7 +547,7 @@ public class CardView extends SimpleCardView { public String getLoyalty() { return loyalty; } - + public String getStartingLoyalty() { return startingLoyalty; } @@ -567,7 +567,7 @@ public class CardView extends SimpleCardView { public ObjectColor getColor() { return color; } - + public ObjectColor getFrameColor() { return frameColor; } @@ -807,4 +807,3 @@ public class CardView extends SimpleCardView { } } - diff --git a/Mage.Common/src/mage/view/StackAbilityView.java b/Mage.Common/src/mage/view/StackAbilityView.java index 9b195d1b5bb..1c5c6914d52 100644 --- a/Mage.Common/src/mage/view/StackAbilityView.java +++ b/Mage.Common/src/mage/view/StackAbilityView.java @@ -99,7 +99,8 @@ public class StackAbilityView extends CardView { private void updateTargets(Game game, StackAbility ability) { List names = new ArrayList<>(); - for (Mode mode : ability.getModes().getSelectedModes()) { + for (UUID modeId : ability.getModes().getSelectedModes()) { + Mode mode = ability.getModes().get(modeId); if (mode.getTargets().size() > 0) { setTargets(mode.getTargets()); } else { @@ -132,7 +133,8 @@ public class StackAbilityView extends CardView { // show for modal ability, which mode was choosen if (ability.isModal()) { Modes modes = ability.getModes(); - for (Mode mode : modes.getSelectedModes()) { + for (UUID modeId : modes.getSelectedModes()) { + Mode mode = modes.get(modeId); 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 0d1a60d29dd..8f2d515fe35 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 @@ -1516,7 +1516,8 @@ public class ComputerPlayer extends PlayerImpl implements Player { //TODO: improve this; AvailableMode: for (Mode mode : modes.getAvailableModes(source, game)) { - for (Mode selectedMode : modes.getSelectedModes()) { + for (UUID selectedModeId : modes.getSelectedModes()) { + Mode selectedMode = modes.get(selectedModeId); if (selectedMode.getId().equals(mode.getId())) { continue AvailableMode; } 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 4b8bb57eece..03ca681e47f 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 @@ -1326,7 +1326,8 @@ public class HumanPlayer extends PlayerImpl { AvailableModes: for (Mode mode : modes.getAvailableModes(source, game)) { int timesSelected = 0; - for (Mode selectedMode : modes.getSelectedModes()) { + for (UUID selectedModeId : modes.getSelectedModes()) { + Mode selectedMode = modes.get(selectedModeId); if (mode.getId().equals(selectedMode.getId())) { if (modes.isEachModeMoreThanOnce()) { timesSelected++; diff --git a/Mage.Sets/src/mage/sets/battleforzendikar/ZadaHedronGrinder.java b/Mage.Sets/src/mage/sets/battleforzendikar/ZadaHedronGrinder.java index 129686fc335..557f05bd6f1 100644 --- a/Mage.Sets/src/mage/sets/battleforzendikar/ZadaHedronGrinder.java +++ b/Mage.Sets/src/mage/sets/battleforzendikar/ZadaHedronGrinder.java @@ -104,7 +104,8 @@ class ZadaHedronGrinderTriggeredAbility extends TriggeredAbilityImpl { if (isControlledInstantOrSorcery(spell)) { boolean targetsSource = false; for (Ability ability : spell.getSpellAbilities()) { - for (Mode mode : ability.getModes().getSelectedModes()) { + for (UUID modeId : ability.getModes().getSelectedModes()) { + Mode mode = ability.getModes().get(modeId); for (Target target : mode.getTargets()) { if (!target.isNotTarget()) { for (UUID targetId : target.getTargets()) { @@ -167,7 +168,8 @@ class ZadaHedronGrinderEffect extends OneShotEffect { Target usedTarget = null; setUsedTarget: for (Ability ability : spell.getSpellAbilities()) { - for (Mode mode : ability.getModes().getSelectedModes()) { + for (UUID modeId : ability.getModes().getSelectedModes()) { + Mode mode = ability.getModes().get(modeId); for (Target target : mode.getTargets()) { if (!target.isNotTarget() && target.getFirstTarget().equals(source.getSourceId())) { usedTarget = target.copy(); @@ -185,7 +187,8 @@ class ZadaHedronGrinderEffect extends OneShotEffect { Spell copy = spell.copySpell(source.getControllerId()); game.getStack().push(copy); setTarget: - for (Mode mode : copy.getSpellAbility().getModes().getSelectedModes()) { + for (UUID modeId : copy.getSpellAbility().getModes().getSelectedModes()) { + Mode mode = copy.getSpellAbility().getModes().get(modeId); for (Target target : mode.getTargets()) { if (target.getClass().equals(usedTarget.getClass())) { target.clearChosen(); // For targets with Max > 1 we need to clear before the text is comapred diff --git a/Mage.Sets/src/mage/sets/chronicles/GoblinArtisans.java b/Mage.Sets/src/mage/sets/chronicles/GoblinArtisans.java index a255033f825..271326e7b58 100644 --- a/Mage.Sets/src/mage/sets/chronicles/GoblinArtisans.java +++ b/Mage.Sets/src/mage/sets/chronicles/GoblinArtisans.java @@ -70,7 +70,7 @@ public class GoblinArtisans extends CardImpl { // {tap}: Flip a coin. If you win the flip, draw a card. If you lose the flip, counter target artifact spell you control that isn't the target of an ability from another creature named Goblin Artisans. this.addAbility(new SimpleActivatedAbility(Zone.BATTLEFIELD, new GoblinArtisansEffect(), new TapSourceCost())); - + } public GoblinArtisans(final GoblinArtisans card) { @@ -83,15 +83,14 @@ public class GoblinArtisans extends CardImpl { } } - class GoblinArtisansEffect extends OneShotEffect { private static final FilterPermanent filter = new FilterPermanent("permanent named Goblin Artisans"); - + static { filter.add(new NamePredicate("Goblin Artisans")); } - + public GoblinArtisansEffect() { super(Outcome.Damage); staticText = "Flip a coin. If you win the flip, draw a card. If you lose the flip, counter target artifact spell you control that isn't the target of an ability from another creature named Goblin Artisans."; @@ -109,37 +108,38 @@ class GoblinArtisansEffect extends OneShotEffect { controller.drawCards(1, game); } else { List artifacts = game.getBattlefield().getActivePermanents(new FilterControlledArtifactPermanent(), source.getControllerId(), game); - if (artifacts.isEmpty()){//Don't even bother if there is no artifact to 'counter'/sacrifice + if (artifacts.isEmpty()) {//Don't even bother if there is no artifact to 'counter'/sacrifice return true; - } - + } + filter.add(Predicates.not(new PermanentIdPredicate(source.getSourceId()))); - //removed the activating instance of Artisans, btw, wasn't that filter declared as static final? How come I can do this here? :) - List list = game.getBattlefield().getAllActivePermanents(filter, game); - for (Permanent perm : list){ // should I limit below for a particular kind of ability? Going for the most general, it's unlikely there'll be any other artisans anyway, so not concerned about efficiency :p - for (Ability abil : perm.getAbilities(game)){//below is copied from TargetsPermanentPredicate, but why only "selectedModes"? Shouldnt be more general as well? - for (Mode mode : abil.getModes().getSelectedModes()){ + //removed the activating instance of Artisans, btw, wasn't that filter declared as static final? How come I can do this here? :) + List list = game.getBattlefield().getAllActivePermanents(filter, game); + for (Permanent perm : list) { // should I limit below for a particular kind of ability? Going for the most general, it's unlikely there'll be any other artisans anyway, so not concerned about efficiency :p + for (Ability abil : perm.getAbilities(game)) {//below is copied from TargetsPermanentPredicate, but why only "selectedModes"? Shouldnt be more general as well? + for (UUID modeId : abil.getModes().getSelectedModes()) { + Mode mode = abil.getModes().get(modeId); for (Target target : mode.getTargets()) { - for (UUID targetId : target.getTargets()) { - artifacts.remove(game.getPermanentOrLKIBattlefield(targetId)); - }// we could - }// remove this - }//closing bracers - }// pyramid, if it's bothering anyone - } //they are all one-liners after all :) - if (!artifacts.isEmpty()){ - Cards cards=new CardsImpl(); - for (Permanent perm : artifacts){ + for (UUID targetId : target.getTargets()) { + artifacts.remove(game.getPermanentOrLKIBattlefield(targetId)); + }// we could + }// remove this + }//closing bracers + }// pyramid, if it's bothering anyone + } //they are all one-liners after all :) + if (!artifacts.isEmpty()) { + Cards cards = new CardsImpl(); + for (Permanent perm : artifacts) { cards.add(perm.getId()); } - TargetCard target = new TargetCard(Zone.BATTLEFIELD, new FilterCard()); + TargetCard target = new TargetCard(Zone.BATTLEFIELD, new FilterCard()); controller.choose(Outcome.Sacrifice, cards, target, game); - game.getPermanent(target.getFirstTarget()).sacrifice(source.getSourceId(), game); - } + game.getPermanent(target.getFirstTarget()).sacrifice(source.getSourceId(), game); + } return true; - } } - + } + return false; } diff --git a/Mage.Sets/src/mage/sets/dragonsoftarkir/IcefallRegent.java b/Mage.Sets/src/mage/sets/dragonsoftarkir/IcefallRegent.java index 7a5b1418363..dfac065c16f 100644 --- a/Mage.Sets/src/mage/sets/dragonsoftarkir/IcefallRegent.java +++ b/Mage.Sets/src/mage/sets/dragonsoftarkir/IcefallRegent.java @@ -227,7 +227,8 @@ class IcefallRegentCostIncreaseEffect extends CostModificationEffectImpl { public boolean applies(Ability abilityToModify, Ability source, Game game) { if (abilityToModify instanceof SpellAbility) { if (game.getOpponents(source.getControllerId()).contains(abilityToModify.getControllerId())) { - for (Mode mode : abilityToModify.getModes().getSelectedModes()) { + for (UUID modeId : abilityToModify.getModes().getSelectedModes()) { + Mode mode = abilityToModify.getModes().get(modeId); for (Target target : mode.getTargets()) { for (UUID targetUUID : target.getTargets()) { if (targetUUID.equals(source.getSourceId())) { diff --git a/Mage.Sets/src/mage/sets/fatereforged/MonasterySiege.java b/Mage.Sets/src/mage/sets/fatereforged/MonasterySiege.java index 77e9e768e4c..d3b6e04e238 100644 --- a/Mage.Sets/src/mage/sets/fatereforged/MonasterySiege.java +++ b/Mage.Sets/src/mage/sets/fatereforged/MonasterySiege.java @@ -109,7 +109,8 @@ class MonasterySiegeCostIncreaseEffect extends CostModificationEffectImpl { if (new ModeChoiceSourceCondition("Dragons").apply(game, source)) { if (abilityToModify instanceof SpellAbility) { if (game.getOpponents(source.getControllerId()).contains(abilityToModify.getControllerId())) { - for (Mode mode : abilityToModify.getModes().getSelectedModes()) { + for (UUID modeId : abilityToModify.getModes().getSelectedModes()) { + Mode mode = abilityToModify.getModes().get(modeId); for (Target target : mode.getTargets()) { for (UUID targetUUID : target.getTargets()) { if (targetUUID.equals(source.getControllerId())) { diff --git a/Mage.Sets/src/mage/sets/kaladesh/CapturedByTheConsulate.java b/Mage.Sets/src/mage/sets/kaladesh/CapturedByTheConsulate.java index 5de6ebe9bdc..bb0eb4cc8d2 100644 --- a/Mage.Sets/src/mage/sets/kaladesh/CapturedByTheConsulate.java +++ b/Mage.Sets/src/mage/sets/kaladesh/CapturedByTheConsulate.java @@ -141,7 +141,8 @@ class CapturedByTheConsulateTriggeredAbility extends TriggeredAbilityImpl { } if (stackObject != null) { int numberOfTargets = 0; - for (Mode mode : stackObject.getStackAbility().getModes().getSelectedModes()) { + for (UUID modeId : stackObject.getStackAbility().getModes().getSelectedModes()) { + Mode mode = stackObject.getStackAbility().getModes().get(modeId); for (Target target : mode.getTargets()) { numberOfTargets += target.getTargets().size(); } diff --git a/Mage.Sets/src/mage/sets/magicorigins/PsychicRebuttal.java b/Mage.Sets/src/mage/sets/magicorigins/PsychicRebuttal.java index 9aa89e0b2f2..533925a5786 100644 --- a/Mage.Sets/src/mage/sets/magicorigins/PsychicRebuttal.java +++ b/Mage.Sets/src/mage/sets/magicorigins/PsychicRebuttal.java @@ -135,7 +135,8 @@ class PsychicRebuttalPredicate implements ObjectPlayerPredicate possibleTargets = target.possibleTargets(source.getSourceId(), source.getControllerId(), game); diff --git a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/AccursedWitch.java b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/AccursedWitch.java index bc42294c7f1..6e0f6c19ae0 100644 --- a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/AccursedWitch.java +++ b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/AccursedWitch.java @@ -140,7 +140,8 @@ class AccursedWitchSpellsCostReductionEffect extends CostModificationEffectImpl public boolean applies(Ability abilityToModify, Ability source, Game game) { if (abilityToModify instanceof SpellAbility) { if (game.getOpponents(source.getControllerId()).contains(abilityToModify.getControllerId())) { - for (Mode mode : abilityToModify.getModes().getSelectedModes()) { + for (UUID modeId : abilityToModify.getModes().getSelectedModes()) { + Mode mode = abilityToModify.getModes().get(modeId); for (Target target : mode.getTargets()) { for (UUID targetUUID : target.getTargets()) { Permanent permanent = game.getPermanent(targetUUID); diff --git a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/InfectiousCurse.java b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/InfectiousCurse.java index 2cc858e4bb6..03e76ef9b6e 100644 --- a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/InfectiousCurse.java +++ b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/InfectiousCurse.java @@ -155,7 +155,8 @@ class InfectiousCurseCostReductionEffect extends CostModificationEffectImpl { public boolean applies(Ability abilityToModify, Ability source, Game game) { if (abilityToModify instanceof SpellAbility) { if (source.getControllerId().equals(abilityToModify.getControllerId())) { - for (Mode mode : abilityToModify.getModes().getSelectedModes()) { + for (UUID modeId : abilityToModify.getModes().getSelectedModes()) { + Mode mode = abilityToModify.getModes().get(modeId); for (Target target : mode.getTargets()) { for (UUID targetUUID : target.getTargets()) { Permanent enchantment = game.getPermanent(source.getSourceId()); diff --git a/Mage.Sets/src/mage/sets/shardsofalara/HinderingLight.java b/Mage.Sets/src/mage/sets/shardsofalara/HinderingLight.java index 7726f3ab806..6cfe4482004 100644 --- a/Mage.Sets/src/mage/sets/shardsofalara/HinderingLight.java +++ b/Mage.Sets/src/mage/sets/shardsofalara/HinderingLight.java @@ -84,7 +84,8 @@ class HinderingLightPredicate implements ObjectPlayerPredicate { if (spell != null) { int numberOfTargets = 0; for (SpellAbility spellAbility : spell.getSpellAbilities()) { - for (Mode mode : spellAbility.getModes().getSelectedModes()) { + for (UUID modeId : spellAbility.getModes().getSelectedModes()) { + Mode mode = spellAbility.getModes().get(modeId); for (Target target : mode.getTargets()) { for (UUID targetId : target.getTargets()) { if (!targetId.equals(sourceId)) { return false; - } - else { + } else { numberOfTargets++; } } diff --git a/Mage.Sets/src/mage/sets/vintagemasters/KaerveksTorch.java b/Mage.Sets/src/mage/sets/vintagemasters/KaerveksTorch.java index 2af317e2e49..14b5866ca5f 100644 --- a/Mage.Sets/src/mage/sets/vintagemasters/KaerveksTorch.java +++ b/Mage.Sets/src/mage/sets/vintagemasters/KaerveksTorch.java @@ -96,7 +96,8 @@ class KaerveksTorchCostIncreaseEffect extends CostModificationEffectImpl { @Override public boolean applies(Ability abilityToModify, Ability source, Game game) { if (abilityToModify instanceof SpellAbility || abilityToModify instanceof FlashbackAbility) { - for (Mode mode : abilityToModify.getModes().getSelectedModes()) { + for (UUID modeId : abilityToModify.getModes().getSelectedModes()) { + Mode mode = abilityToModify.getModes().get(modeId); for (Target target : mode.getTargets()) { for (UUID targetId : target.getTargets()) { if (targetId.equals(source.getSourceObject(game).getId())) { diff --git a/Mage.Tests/src/test/java/org/mage/test/AI/basic/CastDestroySpellsTest.java b/Mage.Tests/src/test/java/org/mage/test/AI/basic/CastDestroySpellsTest.java index 57f34cbc672..792284f65ab 100644 --- a/Mage.Tests/src/test/java/org/mage/test/AI/basic/CastDestroySpellsTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/AI/basic/CastDestroySpellsTest.java @@ -38,10 +38,6 @@ import org.mage.test.serverside.base.CardTestPlayerBaseAI; */ public class CastDestroySpellsTest extends CardTestPlayerBaseAI { - /** - * - * - */ @Test public void testOrzhovCharm() { // Choose one - @@ -58,6 +54,8 @@ public class CastDestroySpellsTest extends CardTestPlayerBaseAI { // Cycling abilities you activate cost you up to {2} less to activate. addCard(Zone.BATTLEFIELD, playerB, "Silvercoat Lion"); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Orzhov Charm", "Silvercoat Lion"); + setModeChoice(playerA, "2"); setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/modal/OneOrBothTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/modal/OneOrBothTest.java index cee56e63951..52aef300aaf 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/modal/OneOrBothTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/modal/OneOrBothTest.java @@ -49,8 +49,9 @@ public class OneOrBothTest extends CardTestPlayerBase { addCard(Zone.BATTLEFIELD, playerA, "Silvercoat Lion"); addCard(Zone.BATTLEFIELD, playerB, "Pillarfield Ox"); - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Subtle Strike", "Silvercoat Lion"); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Subtle Strike", "Pillarfield Ox"); setModeChoice(playerA, "1"); + setModeChoice(playerA, null); setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); @@ -72,6 +73,7 @@ public class OneOrBothTest extends CardTestPlayerBase { castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Subtle Strike", "Pillarfield Ox"); setModeChoice(playerA, "2"); + setModeChoice(playerA, null); setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/bfz/BrutalExpulsionTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/bfz/BrutalExpulsionTest.java index 195666c6097..727efe0738b 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/bfz/BrutalExpulsionTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/bfz/BrutalExpulsionTest.java @@ -39,20 +39,22 @@ import org.mage.test.serverside.base.CardTestPlayerBase; public class BrutalExpulsionTest extends CardTestPlayerBase { /** - * Brutal Expulsion targeting Gideon, Ally of Zendikar. Gideon has 3 loyalty. Brutal Expulsion resolves, - * leaves 1 loyalty. I attack Gideon for 1 with a Scion token, Gideon dies. Instead of going to graveyard, - * Expulsion sends Gideon to exile. However, in game Gideon went to graveyard. + * Brutal Expulsion targeting Gideon, Ally of Zendikar. Gideon has 3 + * loyalty. Brutal Expulsion resolves, leaves 1 loyalty. I attack Gideon for + * 1 with a Scion token, Gideon dies. Instead of going to graveyard, + * Expulsion sends Gideon to exile. However, in game Gideon went to + * graveyard. */ @Test public void testPlaneswalkerExile() { // 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. - addCard(Zone.HAND, playerA, "Brutal Expulsion"); + addCard(Zone.HAND, playerA, "Brutal Expulsion"); // {2}{U}{R} // Shock deals 2 damage to target creature or player. - addCard(Zone.HAND, playerA, "Shock"); - addCard(Zone.BATTLEFIELD, playerA, "Island", 4); - addCard(Zone.BATTLEFIELD, playerA, "Mountain", 5); + addCard(Zone.HAND, playerA, "Shock"); // {R} + addCard(Zone.BATTLEFIELD, playerA, "Island", 1); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 4); // Planeswalker with 4 loyalty. addCard(Zone.BATTLEFIELD, playerB, "Gideon, Ally of Zendikar"); @@ -60,12 +62,12 @@ public class BrutalExpulsionTest extends CardTestPlayerBase { castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Brutal Expulsion", playerB); setModeChoice(playerA, "2"); setModeChoice(playerA, null); - setChoice(playerA, "Yes"); + setChoice(playerA, "Yes"); // Redirect to planeswalker - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Shock", playerB); - setChoice(playerA, "Yes"); + castSpell(1, PhaseStep.BEGIN_COMBAT, playerA, "Shock", playerB); + setChoice(playerA, "Yes"); // Redirect to planeswalker - setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); + setStopAt(1, PhaseStep.END_COMBAT); execute(); assertPermanentCount(playerB, "Gideon, Ally of Zendikar", 0); diff --git a/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java b/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java index fd3b5ba7906..a8856084e0b 100644 --- a/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java +++ b/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java @@ -287,7 +287,8 @@ public class TestPlayer implements Player { } UUID modeId = ability.getModes().getModeId(modeNr); - for (Mode mode : ability.getModes().getSelectedModes()) { + for (UUID currentModeId : ability.getModes().getSelectedModes()) { + Mode mode = ability.getModes().get(currentModeId); if (mode.getId().equals(modeId)) { selectedMode = mode; ability.getModes().setActiveMode(mode); diff --git a/Mage/src/main/java/mage/abilities/AbilityImpl.java b/Mage/src/main/java/mage/abilities/AbilityImpl.java index 65f1095d378..c79e37f28bc 100644 --- a/Mage/src/main/java/mage/abilities/AbilityImpl.java +++ b/Mage/src/main/java/mage/abilities/AbilityImpl.java @@ -306,7 +306,8 @@ public abstract class AbilityImpl implements Ability { && game.replaceEvent(GameEvent.getEvent(GameEvent.EventType.CAST_SPELL_LATE, getId(), getSourceId(), getControllerId()), this)) { return false; } - for (Mode mode : this.getModes().getSelectedModes()) { + for (UUID modeId : this.getModes().getSelectedModes()) { + Mode mode = this.getModes().get(modeId); this.getModes().setActiveMode(mode); //20121001 - 601.2c // 601.2c The player announces his or her choice of an appropriate player, object, or zone for @@ -1060,7 +1061,8 @@ public abstract class AbilityImpl implements Ability { } } else if (object instanceof Spell && ((Spell) object).getSpellAbility().getModes().size() > 1) { Modes spellModes = ((Spell) object).getSpellAbility().getModes(); - for (Mode selectedMode : spellModes.getSelectedModes()) { + for (UUID selectedModeId : spellModes.getSelectedModes()) { + Mode selectedMode = spellModes.get(selectedModeId); int item = 0; for (Mode mode : spellModes.values()) { item++; diff --git a/Mage/src/main/java/mage/abilities/Modes.java b/Mage/src/main/java/mage/abilities/Modes.java index 7ac07f93681..6f1ea6502a3 100644 --- a/Mage/src/main/java/mage/abilities/Modes.java +++ b/Mage/src/main/java/mage/abilities/Modes.java @@ -49,7 +49,7 @@ import mage.target.common.TargetOpponent; public class Modes extends LinkedHashMap { private Mode currentMode; // the current mode of the selected modes - private final ArrayList selectedModes = new ArrayList<>(); + private final ArrayList selectedModes = new ArrayList<>(); private int minModes; private int maxModes; private TargetController modeChooser; @@ -61,7 +61,7 @@ public class Modes extends LinkedHashMap { this.put(currentMode.getId(), currentMode); this.minModes = 1; this.maxModes = 1; - this.selectedModes.add(currentMode); + this.selectedModes.add(currentMode.getId()); this.modeChooser = TargetController.YOU; this.eachModeOnlyOnce = false; this.eachModeMoreThanOnce = false; @@ -74,21 +74,8 @@ public class Modes extends LinkedHashMap { this.minModes = modes.minModes; this.maxModes = modes.maxModes; - if (modes.size() == 1) { - this.currentMode = values().iterator().next(); - this.selectedModes.add(currentMode); - } 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.currentMode = copiedMode; - } else if (selectedMode.equals(modes.getMode())) { - this.currentMode = copiedMode; - } - } - } + this.currentMode = values().iterator().next(); + selectedModes.addAll(modes.getSelectedModes()); this.modeChooser = modes.modeChooser; this.eachModeOnlyOnce = modes.eachModeOnlyOnce; this.eachModeMoreThanOnce = modes.eachModeMoreThanOnce; @@ -113,7 +100,7 @@ public class Modes extends LinkedHashMap { return null; } - public ArrayList getSelectedModes() { + public ArrayList getSelectedModes() { return selectedModes; } @@ -142,7 +129,7 @@ public class Modes extends LinkedHashMap { } public void setActiveMode(Mode mode) { - if (selectedModes.contains(mode)) { + if (selectedModes.contains(mode.getId())) { this.currentMode = mode; } } @@ -172,7 +159,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.copy()); + this.selectedModes.add(mode.getId()); } } if (isEachModeOnlyOnce()) { @@ -209,7 +196,7 @@ public class Modes extends LinkedHashMap { } return this.selectedModes.size() >= this.getMinModes(); } - this.selectedModes.add(choice.copy()); + this.selectedModes.add(choice.getId()); if (currentMode == null) { currentMode = choice; } @@ -222,7 +209,7 @@ public class Modes extends LinkedHashMap { if (currentMode == null) { this.selectedModes.clear(); Mode copiedMode = this.values().iterator().next().copy(); - this.selectedModes.add(copiedMode); + this.selectedModes.add(copiedMode.getId()); this.setActiveMode(copiedMode); } if (isEachModeOnlyOnce()) { @@ -238,9 +225,9 @@ public class Modes extends LinkedHashMap { * @param source * @param game */ - private void setAlreadySelectedModes(ArrayList selectedModes, Ability source, Game game) { - for (Mode mode : selectedModes) { - String key = getKey(source, game, mode.getId()); + private void setAlreadySelectedModes(ArrayList selectedModes, Ability source, Game game) { + for (UUID modeId : selectedModes) { + String key = getKey(source, game, modeId); game.getState().setValue(key, true); } } diff --git a/Mage/src/main/java/mage/abilities/effects/common/ChangeATargetOfTargetSpellAbilityToSourceEffect.java b/Mage/src/main/java/mage/abilities/effects/common/ChangeATargetOfTargetSpellAbilityToSourceEffect.java index 43dd5593277..8884c6c1414 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/ChangeATargetOfTargetSpellAbilityToSourceEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/ChangeATargetOfTargetSpellAbilityToSourceEffect.java @@ -51,7 +51,8 @@ public class ChangeATargetOfTargetSpellAbilityToSourceEffect extends OneShotEffe } else { return false; } - for (Mode mode : sourceAbility.getModes().getSelectedModes()) { + for (UUID modeId : sourceAbility.getModes().getSelectedModes()) { + Mode mode = sourceAbility.getModes().get(modeId); targets.addAll(mode.getTargets()); } @@ -102,12 +103,10 @@ public class ChangeATargetOfTargetSpellAbilityToSourceEffect extends OneShotEffe } if (oldTargetName != null) { game.informPlayers(sourceObject.getLogName() + ": Changed target of " + stackObject.getLogName() + " from " + oldTargetName + " to " + sourceObject.getLogName()); + } else if (twoTimesTarget) { + game.informPlayers(sourceObject.getLogName() + ": Target not changed to " + sourceObject.getLogName() + " because its not valid to target it twice for " + stackObject.getLogName()); } else { - if (twoTimesTarget) { - game.informPlayers(sourceObject.getLogName() + ": Target not changed to " + sourceObject.getLogName() + " because its not valid to target it twice for " + stackObject.getLogName()); - } else { - game.informPlayers(sourceObject.getLogName() + ": Target not changed to " + sourceObject.getLogName() + " because its no valid target for " + stackObject.getLogName()); - } + game.informPlayers(sourceObject.getLogName() + ": Target not changed to " + sourceObject.getLogName() + " because its no valid target for " + stackObject.getLogName()); } return true; } diff --git a/Mage/src/main/java/mage/abilities/keyword/HeroicAbility.java b/Mage/src/main/java/mage/abilities/keyword/HeroicAbility.java index bbd8ed567db..083a80484b7 100644 --- a/Mage/src/main/java/mage/abilities/keyword/HeroicAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/HeroicAbility.java @@ -1,4 +1,4 @@ - /* +/* * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, are @@ -82,7 +82,8 @@ public class HeroicAbility extends TriggeredAbilityImpl { private boolean checkSpell(Spell spell, Game game) { if (spell != null) { SpellAbility sa = spell.getSpellAbility(); - for (Mode mode : sa.getModes().getSelectedModes()) { + for (UUID modeId : sa.getModes().getSelectedModes()) { + Mode mode = sa.getModes().get(modeId); for (Target target : mode.getTargets()) { if (!target.isNotTarget() && target.getTargets().contains(this.getSourceId())) { return true; diff --git a/Mage/src/main/java/mage/filter/predicate/mageobject/NumberOfTargetsPredicate.java b/Mage/src/main/java/mage/filter/predicate/mageobject/NumberOfTargetsPredicate.java index 47bfd2ed07c..3653b268a78 100644 --- a/Mage/src/main/java/mage/filter/predicate/mageobject/NumberOfTargetsPredicate.java +++ b/Mage/src/main/java/mage/filter/predicate/mageobject/NumberOfTargetsPredicate.java @@ -27,6 +27,7 @@ */ package mage.filter.predicate.mageobject; +import java.util.UUID; import mage.MageObject; import mage.abilities.Mode; import mage.filter.predicate.Predicate; @@ -51,7 +52,8 @@ public class NumberOfTargetsPredicate implements Predicate { StackObject stackObject = game.getState().getStack().getStackObject(input.getId()); if (stackObject != null) { int numberOfTargets = 0; - for (Mode mode : stackObject.getStackAbility().getModes().getSelectedModes()) { + for (UUID modeId : stackObject.getStackAbility().getModes().getSelectedModes()) { + Mode mode = stackObject.getStackAbility().getModes().get(modeId); for (Target target : mode.getTargets()) { numberOfTargets += target.getTargets().size(); } diff --git a/Mage/src/main/java/mage/filter/predicate/other/TargetsPermanentPredicate.java b/Mage/src/main/java/mage/filter/predicate/other/TargetsPermanentPredicate.java index aad5bb551b6..7dc12d7984d 100644 --- a/Mage/src/main/java/mage/filter/predicate/other/TargetsPermanentPredicate.java +++ b/Mage/src/main/java/mage/filter/predicate/other/TargetsPermanentPredicate.java @@ -54,7 +54,8 @@ public class TargetsPermanentPredicate implements ObjectSourcePlayerPredicate input, Game game) { StackObject object = game.getStack().getStackObject(input.getObject().getId()); if (object != null) { - for (Mode mode : object.getStackAbility().getModes().getSelectedModes()) { + for (UUID modeId : object.getStackAbility().getModes().getSelectedModes()) { + Mode mode = object.getStackAbility().getModes().get(modeId); for (Target target : mode.getTargets()) { for (UUID targetId : target.getTargets()) { Permanent permanent = game.getPermanentOrLKIBattlefield(targetId); diff --git a/Mage/src/main/java/mage/game/GameState.java b/Mage/src/main/java/mage/game/GameState.java index 2e48c954ce0..eb553340685 100644 --- a/Mage/src/main/java/mage/game/GameState.java +++ b/Mage/src/main/java/mage/game/GameState.java @@ -314,7 +314,8 @@ 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().getSelectedModes()) { + for (UUID modeId : spell.getStackAbility().getModes().getSelectedModes()) { + Mode mode = spell.getStackAbility().getModes().get(modeId); if (!mode.getTargets().isEmpty()) { sb.append("targets"); for (Target target : mode.getTargets()) { @@ -366,7 +367,8 @@ 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().getSelectedModes()) { + for (UUID modeId : spell.getStackAbility().getModes().getSelectedModes()) { + Mode mode = spell.getStackAbility().getModes().get(modeId); if (!mode.getTargets().isEmpty()) { sb.append("targets"); for (Target target : mode.getTargets()) { @@ -784,7 +786,8 @@ public class GameState implements Serializable, Copyable { public void addAbility(Ability ability, MageObject attachedTo) { if (ability instanceof StaticAbility) { - for (Mode mode : ability.getModes().getSelectedModes()) { + for (UUID modeId : ability.getModes().getSelectedModes()) { + Mode mode = ability.getModes().get(modeId); for (Effect effect : mode.getEffects()) { if (effect instanceof ContinuousEffect) { addEffect((ContinuousEffect) effect, ability); @@ -806,7 +809,8 @@ public class GameState implements Serializable, Copyable { */ public void addAbility(Ability ability, UUID sourceId, Card attachedTo) { if (ability instanceof StaticAbility) { - for (Mode mode : ability.getModes().getSelectedModes()) { + for (UUID modeId : ability.getModes().getSelectedModes()) { + Mode mode = ability.getModes().get(modeId); for (Effect effect : mode.getEffects()) { if (effect instanceof ContinuousEffect) { addEffect((ContinuousEffect) effect, sourceId, ability); diff --git a/Mage/src/main/java/mage/game/stack/Spell.java b/Mage/src/main/java/mage/game/stack/Spell.java index b6bc9b98356..f587a9ed7ab 100644 --- a/Mage/src/main/java/mage/game/stack/Spell.java +++ b/Mage/src/main/java/mage/game/stack/Spell.java @@ -210,7 +210,8 @@ public class Spell extends StackObjImpl implements Card { if (notTargeted || legalParts) { for (SpellAbility spellAbility : this.spellAbilities) { if (spellAbilityHasLegalParts(spellAbility, game)) { - for (Mode mode : spellAbility.getModes().getSelectedModes()) { + for (UUID modeId : spellAbility.getModes().getSelectedModes()) { + Mode mode = spellAbility.getModes().get(modeId); spellAbility.getModes().setActiveMode(mode); if (mode.getTargets().stillLegal(spellAbility, game)) { if (!spellAbility.getSpellAbilityType().equals(SpellAbilityType.SPLICE)) { @@ -283,7 +284,8 @@ public class Spell extends StackObjImpl implements Card { private boolean hasTargets(SpellAbility spellAbility, Game game) { if (spellAbility.getModes().getSelectedModes().size() > 1) { - for (Mode mode : spellAbility.getModes().getSelectedModes()) { + for (UUID modeId : spellAbility.getModes().getSelectedModes()) { + Mode mode = spellAbility.getModes().get(modeId); if (!mode.getTargets().isEmpty()) { return true; } @@ -299,7 +301,8 @@ public class Spell extends StackObjImpl implements Card { if (spellAbility.getModes().getSelectedModes().size() > 1) { boolean targetedMode = false; boolean legalTargetedMode = false; - for (Mode mode : spellAbility.getModes().getSelectedModes()) { + for (UUID modeId : spellAbility.getModes().getSelectedModes()) { + Mode mode = spellAbility.getModes().get(modeId); if (mode.getTargets().size() > 0) { targetedMode = true; if (mode.getTargets().stillLegal(spellAbility, game)) { diff --git a/Mage/src/main/java/mage/game/stack/StackObjImpl.java b/Mage/src/main/java/mage/game/stack/StackObjImpl.java index 2286fd06ae5..d37d6f78a97 100644 --- a/Mage/src/main/java/mage/game/stack/StackObjImpl.java +++ b/Mage/src/main/java/mage/game/stack/StackObjImpl.java @@ -117,7 +117,8 @@ public abstract class StackObjImpl implements StackObject { } for (Ability ability : objectAbilities) { // Some spells can have more than one mode - for (Mode mode : ability.getModes().getSelectedModes()) { + for (UUID modeId : ability.getModes().getSelectedModes()) { + Mode mode = ability.getModes().get(modeId); ability.getModes().setActiveMode(mode); oldTargetDescription.append(ability.getTargetDescription(mode.getTargets(), game)); for (Target target : mode.getTargets()) { @@ -210,8 +211,7 @@ public abstract class StackObjImpl implements StackObject { again = true; } } else // if possible add the alternate Target - it may not be included in the old definition nor in the already selected targets of the new definition - { - if (newTarget.getTargets().contains(tempTarget.getFirstTarget()) || target.getTargets().contains(tempTarget.getFirstTarget())) { + if (newTarget.getTargets().contains(tempTarget.getFirstTarget()) || target.getTargets().contains(tempTarget.getFirstTarget())) { if (targetController.isHuman()) { if (targetController.chooseUse(Outcome.Benefit, "This target was already selected from origin spell. Reset to original target?", ability, game)) { // use previous target no target was selected @@ -240,7 +240,6 @@ public abstract class StackObjImpl implements StackObject { // valid target was selected, add it to the new target definition newTarget.addTarget(tempTarget.getFirstTarget(), target.getTargetAmount(targetId), ability, game, false); } - } } while (again && targetController.canRespond()); } } // keep the target diff --git a/Mage/src/main/java/mage/players/PlayerImpl.java b/Mage/src/main/java/mage/players/PlayerImpl.java index 6a045a8e48b..8b5ca4049d4 100644 --- a/Mage/src/main/java/mage/players/PlayerImpl.java +++ b/Mage/src/main/java/mage/players/PlayerImpl.java @@ -2870,7 +2870,7 @@ public abstract class PlayerImpl implements Player, Serializable { for (Mode mode : option.getModes().values()) { Ability newOption = option.copy(); newOption.getModes().getSelectedModes().clear(); - newOption.getModes().getSelectedModes().add(mode); + newOption.getModes().getSelectedModes().add(mode.getId()); newOption.getModes().setActiveMode(mode); if (newOption.getTargets().getUnchosen().size() > 0) { if (newOption.getManaCosts().getVariableCosts().size() > 0) { diff --git a/Mage/src/main/java/mage/target/common/TargetCreatureOrPlaneswalker.java b/Mage/src/main/java/mage/target/common/TargetCreatureOrPlaneswalker.java index 3e58a0523eb..c075c54d4fa 100644 --- a/Mage/src/main/java/mage/target/common/TargetCreatureOrPlaneswalker.java +++ b/Mage/src/main/java/mage/target/common/TargetCreatureOrPlaneswalker.java @@ -25,10 +25,13 @@ * authors and should not be interpreted as representing official policies, either expressed * or implied, of BetaSteward_at_googlemail.com. */ - package mage.target.common; +import java.util.UUID; +import mage.abilities.Ability; import mage.filter.common.FilterCreatureOrPlaneswalkerPermanent; +import mage.game.Game; +import mage.players.Player; import mage.target.TargetPermanent; /** @@ -38,7 +41,7 @@ import mage.target.TargetPermanent; public class TargetCreatureOrPlaneswalker extends TargetPermanent { public TargetCreatureOrPlaneswalker() { - this(1, 1 ,new FilterCreatureOrPlaneswalkerPermanent(), false); + this(1, 1, new FilterCreatureOrPlaneswalkerPermanent(), false); } public TargetCreatureOrPlaneswalker(int minNumTargets, int maxNumTargets, FilterCreatureOrPlaneswalkerPermanent filter, boolean notTarget) { @@ -55,4 +58,16 @@ public class TargetCreatureOrPlaneswalker extends TargetPermanent { return new TargetCreatureOrPlaneswalker(this); } + @Override + public boolean isLegal(Ability source, Game game) { + for (UUID playerId : targets.keySet()) { + Player targetPlayer = game.getPlayer(playerId); + if (targetPlayer != null) { + // there seems to be no possibility to add more predicates for theplayer so return here true + return true; + } + } + return super.isLegal(source, game); //To change body of generated methods, choose Tools | Templates. + } + } diff --git a/Mage/src/main/java/mage/util/TargetAddress.java b/Mage/src/main/java/mage/util/TargetAddress.java index 57bdddc23b4..80c5724676d 100644 --- a/Mage/src/main/java/mage/util/TargetAddress.java +++ b/Mage/src/main/java/mage/util/TargetAddress.java @@ -70,7 +70,7 @@ public class TargetAddress { 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; @@ -127,7 +127,7 @@ public class TargetAddress { } if (modeIterator != null && modeIterator.hasNext()) { - lastMode = modeIterator.next().getId(); + lastMode = modeIterator.next(); targetIterator = modes.get(lastMode).getTargets().iterator(); } else { lastMode = null; From d5d00451a84f760fa2ef15947a2efaed426ab37c Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Sat, 24 Sep 2016 01:13:13 +0200 Subject: [PATCH 04/15] Fixed a problem with static abilities for battlefield zone where the source was entering the battlefield but the ability did not work. --- .../org/mage/test/cards/abilities/keywords/UndyingTest.java | 2 +- Mage/src/main/java/mage/abilities/StaticAbility.java | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/UndyingTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/UndyingTest.java index 5a2fa4d5402..e591cae4831 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/UndyingTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/UndyingTest.java @@ -219,7 +219,7 @@ public class UndyingTest extends CardTestPlayerBase { } /** - * Tatterkite is getting counters on it, i have him in a edh deck with + * Tatterkite is getting counters on it, I have him in a edh deck with * Mikaeus, the Lunarch and when Tatterkite dies it triggers the undying and * he gets the +1/+1 counters */ diff --git a/Mage/src/main/java/mage/abilities/StaticAbility.java b/Mage/src/main/java/mage/abilities/StaticAbility.java index bbeee56b6a9..1930634dbe8 100644 --- a/Mage/src/main/java/mage/abilities/StaticAbility.java +++ b/Mage/src/main/java/mage/abilities/StaticAbility.java @@ -56,6 +56,9 @@ public abstract class StaticAbility extends AbilityImpl { if (game.getShortLivingLKI(getSourceId(), zone)) { return true; // maybe this can be a problem if effects removed the ability from the object } + if (game.getPermanentEntering(getSourceId()) != null && zone.equals(Zone.BATTLEFIELD)) { + return true; // abilities of permanents entering battlefield are countes as on battlefield + } return super.isInUseableZone(game, source, event); } From 5215a02181de333126c59df53b836f8c45cce9ed Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Sat, 24 Sep 2016 02:22:21 +0200 Subject: [PATCH 05/15] Minor change. --- .../src/mage/sets/commander2013/JelevaNephaliasScourge.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mage.Sets/src/mage/sets/commander2013/JelevaNephaliasScourge.java b/Mage.Sets/src/mage/sets/commander2013/JelevaNephaliasScourge.java index 40657270108..49573dd952c 100644 --- a/Mage.Sets/src/mage/sets/commander2013/JelevaNephaliasScourge.java +++ b/Mage.Sets/src/mage/sets/commander2013/JelevaNephaliasScourge.java @@ -120,7 +120,7 @@ class JelevaNephaliasScourgeEffect extends OneShotEffect { for (int i = 0; i < cardsToExile; i++) { Card card = player.getLibrary().removeFromTop(game); if (card != null) { - card.moveToExile(CardUtil.getCardExileZoneId(game, source), sourceCard.getName(), source.getSourceId(), game); + card.moveToExile(CardUtil.getCardExileZoneId(game, source), sourceCard.getIdName(), source.getSourceId(), game); } } } From b7f962669729bfe7c3d672ab139533e98bbc0c64 Mon Sep 17 00:00:00 2001 From: Dilnu Date: Fri, 23 Sep 2016 21:13:34 -0400 Subject: [PATCH 06/15] Small fixes to TriggeredAbilityImpl This fixes a type and removes a duplicate null check. --- Mage/src/main/java/mage/abilities/TriggeredAbilityImpl.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Mage/src/main/java/mage/abilities/TriggeredAbilityImpl.java b/Mage/src/main/java/mage/abilities/TriggeredAbilityImpl.java index 9cf9bf5719c..37e1b5482a2 100644 --- a/Mage/src/main/java/mage/abilities/TriggeredAbilityImpl.java +++ b/Mage/src/main/java/mage/abilities/TriggeredAbilityImpl.java @@ -88,7 +88,7 @@ public abstract class TriggeredAbilityImpl extends AbilityImpl implements Trigge MageObject object = game.getObject(getSourceId()); Player player = game.getPlayer(this.getControllerId()); if (player != null && object != null) { - if (!player.chooseUse(getEffects().get(0).getOutcome(), (object != null ? this.getRule(object.getLogName()) : this.getRule()), this, game)) { + if (!player.chooseUse(getEffects().get(0).getOutcome(), this.getRule(object.getLogName()), this, game)) { return false; } } else { @@ -216,7 +216,7 @@ public abstract class TriggeredAbilityImpl extends AbilityImpl implements Trigge /* 603.6c,603.6d - This has to be set, if the triggered ability has to check back in time if the permanent the ability is connected to had the ability on the battlefeild while the trigger is checked + This has to be set, if the triggered ability has to check back in time if the permanent the ability is connected to had the ability on the battlefield while the trigger is checked */ @Override public final void setLeavesTheBattlefieldTrigger(boolean leavesTheBattlefieldTrigger) { From 423c2bbf6c3237e3a0e8a3b638c792792598cfe1 Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Sat, 24 Sep 2016 03:13:59 +0200 Subject: [PATCH 07/15] Fixed a problem of Flashback and Buyback producing a loop. --- .../abilities/keywords/FlashbackTest.java | 25 +++++++++++++++++++ .../abilities/keyword/BuybackAbility.java | 2 +- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/FlashbackTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/FlashbackTest.java index b79f3ea19ae..12b941e7fac 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/FlashbackTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/FlashbackTest.java @@ -367,6 +367,31 @@ public class FlashbackTest extends CardTestPlayerBase { } + @Test + public void testSnapcasterMageWithBuyback() { + addCard(Zone.BATTLEFIELD, playerA, "Island", 8); + addCard(Zone.HAND, playerA, "Snapcaster Mage", 1); + + // Buyback {5}(You may pay an additional {5} as you cast this spell. If you do, put this card into your hand as it resolves.) + // Draw a card. + addCard(Zone.GRAVEYARD, playerA, "Whispers of the Muse", 1); // {U} + + // When Snapcaster Mage enters the battlefield, target instant or sorcery card in your graveyard gains flashback until end of turn. The flashback cost is equal to its mana cost. + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Snapcaster Mage"); + setChoice(playerA, "Whispers of the Muse"); + + activateAbility(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Flashback"); // Flashback Whispers of the Muse + setChoice(playerA, "Yes"); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertPermanentCount(playerA, "Snapcaster Mage", 1); + assertGraveyardCount(playerA, "Whispers of the Muse", 0); + assertHandCount(playerA, 1); + assertExileCount("Whispers of the Muse", 1); + + } + /** * Deep Analysis doesn't cost mana when flashbacked. */ diff --git a/Mage/src/main/java/mage/abilities/keyword/BuybackAbility.java b/Mage/src/main/java/mage/abilities/keyword/BuybackAbility.java index 8251c9fcdc7..98eb6e7752c 100644 --- a/Mage/src/main/java/mage/abilities/keyword/BuybackAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/BuybackAbility.java @@ -186,7 +186,7 @@ class BuybackEffect extends ReplacementEffectImpl { public boolean applies(GameEvent event, Ability source, Game game) { if (event.getTargetId().equals(source.getSourceId())) { ZoneChangeEvent zEvent = (ZoneChangeEvent) event; - if (zEvent.getFromZone() == Zone.STACK + if (zEvent.getFromZone() == Zone.STACK && zEvent.getToZone() == Zone.GRAVEYARD && source.getSourceId().equals(event.getSourceId())) { // if spell fizzled, the sourceId is null return true; } From 396f3b73b12d61b452f4f9a79b4b384a2a0f0d9e Mon Sep 17 00:00:00 2001 From: Dilnu Date: Fri, 23 Sep 2016 21:19:51 -0400 Subject: [PATCH 08/15] Propagate Applied Effects with the Flashback Replacement Effect MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This fixes a bug that allows replacement loops when other replacement effects conflict with Flashback’s attempts to exile Flashbacked spells. --- .../src/main/java/mage/abilities/keyword/FlashbackAbility.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Mage/src/main/java/mage/abilities/keyword/FlashbackAbility.java b/Mage/src/main/java/mage/abilities/keyword/FlashbackAbility.java index c32dc61b568..214d84e9c56 100644 --- a/Mage/src/main/java/mage/abilities/keyword/FlashbackAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/FlashbackAbility.java @@ -274,7 +274,8 @@ class FlashbackReplacementEffect extends ReplacementEffectImpl { if (controller != null) { Card card = game.getCard(event.getTargetId()); if (card != null) { - return controller.moveCards(card, Zone.EXILED, source, game); + return controller.moveCards( + card, Zone.EXILED, source, game, false, false, false, event.getAppliedEffects()); } } return false; From 1ccd2a1b04447937a724f74a2aa53977604eb611 Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Sat, 24 Sep 2016 03:36:18 +0200 Subject: [PATCH 09/15] Some fixes to prevent null pointer exceptions. --- .../src/mage/sets/iceage/ZursWeirding.java | 32 +++++++++---------- .../src/mage/sets/kaladesh/DeadlockTrap.java | 2 +- .../shadowsoverinnistrad/ForkInTheRoad.java | 6 ++-- 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/Mage.Sets/src/mage/sets/iceage/ZursWeirding.java b/Mage.Sets/src/mage/sets/iceage/ZursWeirding.java index a82e7091eda..0c102475bc1 100644 --- a/Mage.Sets/src/mage/sets/iceage/ZursWeirding.java +++ b/Mage.Sets/src/mage/sets/iceage/ZursWeirding.java @@ -45,7 +45,6 @@ import mage.constants.Zone; import mage.game.Game; import mage.game.events.GameEvent; import mage.players.Player; -import mage.players.PlayerList; /** * @@ -103,31 +102,30 @@ class ZursWeirdingReplacementEffect extends ReplacementEffectImpl { Card card = player.getLibrary().getFromTop(game); if (card != null) { // reveals it instead - player.revealCards(sourceObject.getIdName() + " next draw of " + player.getName() + " (" + game.getTurnNum()+"|"+game.getPhase().getType() +")", new CardsImpl(card), game); + player.revealCards(sourceObject.getIdName() + " next draw of " + player.getName() + " (" + game.getTurnNum() + "|" + game.getPhase().getType() + ")", new CardsImpl(card), game); // Then any other player may pay 2 life. If a player does, put that card into its owner's graveyard - PlayerList playerList = game.getPlayerList().copy(); - playerList.setCurrent(player.getId()); - Player currentPlayer = playerList.getNext(game); - String message = new StringBuilder("Pay 2 life to put ").append(card.getLogName()).append(" into graveyard?").toString(); - while (!currentPlayer.getId().equals(player.getId())) { - if (currentPlayer.canPayLifeCost() && - currentPlayer.getLife() >= 2 && - currentPlayer.chooseUse(Outcome.Benefit, message, source, game)) { - currentPlayer.loseLife(2, game); - player.moveCards(card, Zone.GRAVEYARD, source, game); -// game.getState().getRevealed().reset(); - return true; + String message = "Pay 2 life to put " + card.getLogName() + " into graveyard?"; + for (UUID playerId : game.getState().getPlayersInRange(player.getId(), game)) { + if (playerId.equals(player.getId())) { + continue; + } + Player otherPlayer = game.getPlayer(playerId); + if (otherPlayer.canPayLifeCost() + && otherPlayer.getLife() >= 2 + && otherPlayer.chooseUse(Outcome.Benefit, message, source, game)) { + otherPlayer.loseLife(2, game); + player.moveCards(card, Zone.GRAVEYARD, source, game); + break; } - - currentPlayer = playerList.getNext(game); } -// game.getState().getRevealed().reset(); } + return true; } return false; } + @Override public boolean checksEventType(GameEvent event, Game game) { return event.getType() == GameEvent.EventType.DRAW_CARD; diff --git a/Mage.Sets/src/mage/sets/kaladesh/DeadlockTrap.java b/Mage.Sets/src/mage/sets/kaladesh/DeadlockTrap.java index 1f4b221373d..2e7cef7fed2 100644 --- a/Mage.Sets/src/mage/sets/kaladesh/DeadlockTrap.java +++ b/Mage.Sets/src/mage/sets/kaladesh/DeadlockTrap.java @@ -93,7 +93,7 @@ class DeadlockTrapCantActivateEffect extends RestrictionEffect { @Override public boolean applies(Permanent permanent, Ability source, Game game) { - return getTargetPointer().getFirst(game, source).equals(permanent.getId()); + return permanent.getId().equals(getTargetPointer().getFirst(game, source)); } @Override diff --git a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/ForkInTheRoad.java b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/ForkInTheRoad.java index 7148db91471..a29c651c242 100644 --- a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/ForkInTheRoad.java +++ b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/ForkInTheRoad.java @@ -100,8 +100,10 @@ class ForkInTheRoadEffect extends OneShotEffect { if (target.getTargets().size() > 0) { Cards revealed = new CardsImpl(); for (UUID cardId : target.getTargets()) { - Card card = controller.getLibrary().getCard(cardId, game); - revealed.add(card); + Card card = game.getCard(cardId); + if (card != null) { + revealed.add(card); + } } controller.revealCards(sourceObject.getIdName(), revealed, game); if (target.getTargets().size() > 0) { From e81f26d5394200bbbdfc52f95f99b0fd7d699d8e Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Sat, 24 Sep 2016 03:48:48 +0200 Subject: [PATCH 10/15] Some fixes to prevent null pointer exceptions. --- Mage/src/main/java/mage/game/combat/Combat.java | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/Mage/src/main/java/mage/game/combat/Combat.java b/Mage/src/main/java/mage/game/combat/Combat.java index 0420af99270..6618cd85d7b 100644 --- a/Mage/src/main/java/mage/game/combat/Combat.java +++ b/Mage/src/main/java/mage/game/combat/Combat.java @@ -237,6 +237,9 @@ public class Combat implements Serializable, Copyable { possibleDefenders = new HashSet<>(defenders); } Player player = game.getPlayer(attackerId); + if (player == null) { + return false; + } if (possibleDefenders.size() == 1) { addAttackerToCombat(creatureId, possibleDefenders.iterator().next(), game); return true; @@ -866,7 +869,8 @@ public class Combat implements Serializable, Copyable { blockIsValid = true; break CombatGroups; } else // check if the blocker blocks a attacker that must be blocked at least by one and is the only blocker, this block is also valid - if (combatGroup.getBlockers().size() == 1) { + { + if (combatGroup.getBlockers().size() == 1) { if (mustBeBlockedByAtLeastOne.containsKey(forcingAttackerId)) { if (mustBeBlockedByAtLeastOne.get(forcingAttackerId).contains(creatureForcedToBlock.getId())) { blockIsValid = true; @@ -874,6 +878,7 @@ public class Combat implements Serializable, Copyable { } } } + } } } } @@ -921,8 +926,8 @@ public class Combat implements Serializable, Copyable { if (mustBeBlockedByAtLeastOne.containsKey(blockedAttackerId)) { // blocks a creature that has to be blocked by at least one if (combatGroupOfPossibleBlocker.getBlockers().size() == 1) { - Set blockedSet = mustBeBlockedByAtLeastOne.get(blockedAttackerId); - Set toBlockSet = mustBeBlockedByAtLeastOne.get(toBeBlockedCreatureId); + Set blockedSet = mustBeBlockedByAtLeastOne.get(blockedAttackerId); + Set toBlockSet = mustBeBlockedByAtLeastOne.get(toBeBlockedCreatureId); if (toBlockSet == null) { // This should never happen.
 return null; From 73a2ccda9b36552a09cb2b6a5aef37559866d7fc Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Sat, 24 Sep 2016 04:01:00 +0200 Subject: [PATCH 11/15] Xmage 1.4.15v5 --- Mage.Common/src/mage/utils/MageVersion.java | 2 +- Mage/src/main/java/mage/cards/repository/CardRepository.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Mage.Common/src/mage/utils/MageVersion.java b/Mage.Common/src/mage/utils/MageVersion.java index ad4b3c5ef3e..12dd7911f51 100644 --- a/Mage.Common/src/mage/utils/MageVersion.java +++ b/Mage.Common/src/mage/utils/MageVersion.java @@ -41,7 +41,7 @@ public class MageVersion implements Serializable, Comparable { public final static int MAGE_VERSION_MAJOR = 1; public final static int MAGE_VERSION_MINOR = 4; public final static int MAGE_VERSION_PATCH = 15; - public final static String MAGE_VERSION_MINOR_PATCH = "v4"; + public final static String MAGE_VERSION_MINOR_PATCH = "v5"; public final static String MAGE_VERSION_INFO = ""; private final int major; diff --git a/Mage/src/main/java/mage/cards/repository/CardRepository.java b/Mage/src/main/java/mage/cards/repository/CardRepository.java index a61dd314fdf..f86912203ad 100644 --- a/Mage/src/main/java/mage/cards/repository/CardRepository.java +++ b/Mage/src/main/java/mage/cards/repository/CardRepository.java @@ -63,7 +63,7 @@ public enum CardRepository { // raise this if db structure was changed private static final long CARD_DB_VERSION = 47; // raise this if new cards were added to the server - private static final long CARD_CONTENT_VERSION = 61; + private static final long CARD_CONTENT_VERSION = 62; private Dao cardDao; private Set classNames; From b85dc8c0cd27cc353a4dd144672c644be4d9bad0 Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Sat, 24 Sep 2016 14:55:09 +0200 Subject: [PATCH 12/15] Some minor changes. --- .../main/java/mage/client/util/GUISizeHelper.java | 10 +++------- .../mage/sets/worldwake/QuestForTheGoblinLord.java | 14 ++++++-------- 2 files changed, 9 insertions(+), 15 deletions(-) diff --git a/Mage.Client/src/main/java/mage/client/util/GUISizeHelper.java b/Mage.Client/src/main/java/mage/client/util/GUISizeHelper.java index 885d6daca90..ea774ec7039 100644 --- a/Mage.Client/src/main/java/mage/client/util/GUISizeHelper.java +++ b/Mage.Client/src/main/java/mage/client/util/GUISizeHelper.java @@ -13,8 +13,6 @@ import javax.swing.JMenuItem; import javax.swing.JPopupMenu; import mage.client.MageFrame; import mage.client.dialog.PreferencesDialog; -import mage.sets.avacynrestored.GuiseOfFire; -import org.apache.log4j.Logger; import org.mage.card.arcane.CardRenderer; /** @@ -145,12 +143,10 @@ public class GUISizeHelper { otherZonesCardDimension = new Dimension(CARD_IMAGE_WIDTH * otherZonesCardSize / 42, CARD_IMAGE_HEIGHT * otherZonesCardSize / 42); if (PreferencesDialog.getCachedValue(PreferencesDialog.KEY_CARD_RENDERING_FALLBACK, "false").equals("false")) { otherZonesCardVerticalOffset = CardRenderer.getCardTopHeight(otherZonesCardDimension.width); + } else if (otherZonesCardSize > 29) { + otherZonesCardVerticalOffset = otherZonesCardDimension.height / 8; } else { - if (otherZonesCardSize > 29) { - otherZonesCardVerticalOffset = otherZonesCardDimension.height / 8; - } else { - otherZonesCardVerticalOffset = otherZonesCardDimension.height / 10; - } + otherZonesCardVerticalOffset = otherZonesCardDimension.height / 10; } int battlefieldCardMinSize = PreferencesDialog.getCachedValue(PreferencesDialog.KEY_GUI_CARD_BATTLEFIELD_MIN_SIZE, 10); diff --git a/Mage.Sets/src/mage/sets/worldwake/QuestForTheGoblinLord.java b/Mage.Sets/src/mage/sets/worldwake/QuestForTheGoblinLord.java index b0d83912eac..a19993749d3 100644 --- a/Mage.Sets/src/mage/sets/worldwake/QuestForTheGoblinLord.java +++ b/Mage.Sets/src/mage/sets/worldwake/QuestForTheGoblinLord.java @@ -28,12 +28,6 @@ package mage.sets.worldwake; import java.util.UUID; - -import mage.constants.CardType; -import mage.constants.Rarity; -import mage.constants.Duration; -import mage.constants.TargetController; -import mage.constants.Zone; import mage.abilities.common.EntersBattlefieldControlledTriggeredAbility; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.condition.common.SourceHasCounterCondition; @@ -41,6 +35,11 @@ import mage.abilities.decorator.ConditionalContinuousEffect; import mage.abilities.effects.common.continuous.BoostAllEffect; import mage.abilities.effects.common.counter.AddCountersSourceEffect; import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Rarity; +import mage.constants.TargetController; +import mage.constants.Zone; import mage.counters.CounterType; import mage.filter.FilterPermanent; import mage.filter.common.FilterControlledCreaturePermanent; @@ -54,7 +53,7 @@ import mage.filter.predicate.permanent.ControllerPredicate; */ public class QuestForTheGoblinLord extends CardImpl { - private static final String rule = "As long as Quest for the Goblin Lord has five or more quest counters on it, creatures you control get +2/+0."; + private static final String rule = "As long as {this} has five or more quest counters on it, creatures you control get +2/+0"; private static final FilterCreaturePermanent filter = new FilterCreaturePermanent(); private static final FilterPermanent goblinFilter = new FilterControlledCreaturePermanent(); @@ -67,7 +66,6 @@ public class QuestForTheGoblinLord extends CardImpl { super(ownerId, 86, "Quest for the Goblin Lord", Rarity.UNCOMMON, new CardType[]{CardType.ENCHANTMENT}, "{R}"); this.expansionSetCode = "WWK"; - // Whenever a Goblin enters the battlefield under your control, you may put a quest counter on Quest for the Goblin Lord. this.addAbility(new EntersBattlefieldControlledTriggeredAbility(Zone.BATTLEFIELD, new AddCountersSourceEffect(CounterType.QUEST.createInstance()), goblinFilter, true)); From 101a1db6491c72e14e4e69ca01ea5454ff1f8319 Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Sat, 24 Sep 2016 14:55:18 +0200 Subject: [PATCH 13/15] Xmage 1.4.15v5 --- Utils/release/getting_implemented_cards.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Utils/release/getting_implemented_cards.txt b/Utils/release/getting_implemented_cards.txt index b73145bb9e8..e755097a8ee 100644 --- a/Utils/release/getting_implemented_cards.txt +++ b/Utils/release/getting_implemented_cards.txt @@ -102,6 +102,9 @@ git log 79f8617cd3c997d89770094d7a44294b0a48731f..head --diff-filter=A --name-st since 1.4.15v2 git log d9c804602ea116d80a74d53eaf07ee7a15cd7d81..head --diff-filter=A --name-status | sed -ne "s/^A[^u]Mage.Sets\/src\/mage\/sets\///p" | sort > added_cards.txt +since 1.4.15v5 +git log 73a2ccda9b36552a09cb2b6a5aef37559866d7fc..head --diff-filter=A --name-status | sed -ne "s/^A[^u]Mage.Sets\/src\/mage\/sets\///p" | sort > added_cards.txt + 3. Copy added_cards.txt to trunk\Utils folder 4. Run script: > perl extract_in_wiki_format.perl From 16bb17e5bb15b78fb287cff69826e686d45a97c6 Mon Sep 17 00:00:00 2001 From: spjspj Date: Sun, 25 Sep 2016 00:46:16 +1000 Subject: [PATCH 14/15] spjspj - Add option of 'Number of Seats'. This is for Tournaments so that you can draft say a 4 way draft and then have a 4 way game at the end of it. --- .../mage/client/dialog/NewTableDialog.java | 2 +- .../client/dialog/NewTournamentDialog.form | 30 +++--- .../client/dialog/NewTournamentDialog.java | 73 +++++++++++++- .../java/mage/client/table/TablesPanel.java | 2 +- .../tournament/TournamentController.java | 39 ++++++++ .../java/mage/game/events/TableEvent.java | 15 ++- .../mage/game/events/TableEventSource.java | 5 + .../java/mage/game/match/MatchOptions.java | 31 +++++- .../game/tournament/MultiplayerRound.java | 97 +++++++++++++++++++ .../mage/game/tournament/TournamentImpl.java | 10 ++ .../game/tournament/TournamentOptions.java | 5 +- .../tournament/TournamentSealedOptions.java | 4 +- .../TournamentSingleElimination.java | 24 +++-- .../mage/game/tournament/TournamentSwiss.java | 89 +++++++++++------ 14 files changed, 366 insertions(+), 60 deletions(-) create mode 100644 Mage/src/main/java/mage/game/tournament/MultiplayerRound.java diff --git a/Mage.Client/src/main/java/mage/client/dialog/NewTableDialog.java b/Mage.Client/src/main/java/mage/client/dialog/NewTableDialog.java index 7fa024953db..2e41a985139 100644 --- a/Mage.Client/src/main/java/mage/client/dialog/NewTableDialog.java +++ b/Mage.Client/src/main/java/mage/client/dialog/NewTableDialog.java @@ -378,7 +378,7 @@ public class NewTableDialog extends MageDialog { private void btnOKActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnOKActionPerformed GameTypeView gameType = (GameTypeView) cbGameType.getSelectedItem(); - MatchOptions options = new MatchOptions(this.txtName.getText(), gameType.getName()); + MatchOptions options = new MatchOptions(this.txtName.getText(), gameType.getName(), false, 2); options.getPlayerTypes().add("Human"); for (TablePlayerPanel player : players) { options.getPlayerTypes().add(player.getPlayerType()); diff --git a/Mage.Client/src/main/java/mage/client/dialog/NewTournamentDialog.form b/Mage.Client/src/main/java/mage/client/dialog/NewTournamentDialog.form index bcce7a2ac50..9268a0ec612 100644 --- a/Mage.Client/src/main/java/mage/client/dialog/NewTournamentDialog.form +++ b/Mage.Client/src/main/java/mage/client/dialog/NewTournamentDialog.form @@ -29,11 +29,8 @@ - - - - - + + @@ -175,6 +172,7 @@ + @@ -191,7 +189,12 @@ - + + + + + + @@ -200,6 +203,7 @@ + @@ -377,17 +381,19 @@ - - - - - + + - + + + + + + diff --git a/Mage.Client/src/main/java/mage/client/dialog/NewTournamentDialog.java b/Mage.Client/src/main/java/mage/client/dialog/NewTournamentDialog.java index 6ad35039fb0..90cc1b2bd37 100644 --- a/Mage.Client/src/main/java/mage/client/dialog/NewTournamentDialog.java +++ b/Mage.Client/src/main/java/mage/client/dialog/NewTournamentDialog.java @@ -170,6 +170,8 @@ public class NewTournamentDialog extends MageDialog { pnlPacks = new javax.swing.JPanel(); lblNbrPlayers = new javax.swing.JLabel(); spnNumPlayers = new javax.swing.JSpinner(); + lblNbrSeats = new javax.swing.JLabel(); + spnNumSeats = new javax.swing.JSpinner(); pnlDraftOptions = new javax.swing.JPanel(); jLabel6 = new javax.swing.JLabel(); cbDraftTiming = new javax.swing.JComboBox(); @@ -277,6 +279,14 @@ public class NewTournamentDialog extends MageDialog { } }); + lblNbrSeats.setText("Seats:"); + + spnNumSeats.addChangeListener(new javax.swing.event.ChangeListener() { + public void stateChanged(javax.swing.event.ChangeEvent evt) { + spnNumSeatsStateChanged(evt); + } + }); + jLabel6.setText("Timing:"); cbDraftTiming.setModel(new javax.swing.DefaultComboBoxModel(new String[] { "Item 1", "Item 2", "Item 3", "Item 4" })); @@ -358,6 +368,8 @@ public class NewTournamentDialog extends MageDialog { lblQuitRatio.setText("Allowed quit %:"); spnQuitRatio.setToolTipText("Players with quit % more than this value can't join this table"); + spnNumSeats.setToolTipText("The number of seats for each duel. If more than 2, will set number of wins to 1"); + spnNumPlayers.setToolTipText("The total number of players who will draft"); javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane()); getContentPane().setLayout(layout); @@ -373,7 +385,11 @@ public class NewTournamentDialog extends MageDialog { .addGroup(layout.createSequentialGroup() .addComponent(lblNbrPlayers) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(spnNumPlayers, javax.swing.GroupLayout.PREFERRED_SIZE, 46, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addComponent(spnNumPlayers, javax.swing.GroupLayout.PREFERRED_SIZE, 46, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(lblNbrSeats) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(spnNumSeats, javax.swing.GroupLayout.PREFERRED_SIZE, 46, javax.swing.GroupLayout.PREFERRED_SIZE)) .addComponent(lblPacks) .addComponent(lblPlayer1)) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) @@ -504,6 +520,8 @@ public class NewTournamentDialog extends MageDialog { .addComponent(lblNumRounds)) .addComponent(lblNbrPlayers, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .addComponent(spnNumPlayers) + .addComponent(lblNbrSeats, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(spnNumSeats) .addComponent(pnlDraftOptions, javax.swing.GroupLayout.PREFERRED_SIZE, 0, Short.MAX_VALUE)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(lblPlayer1, javax.swing.GroupLayout.PREFERRED_SIZE, 25, javax.swing.GroupLayout.PREFERRED_SIZE)) @@ -533,7 +551,8 @@ public class NewTournamentDialog extends MageDialog { private void btnOkActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnOkActionPerformed TournamentTypeView tournamentType = (TournamentTypeView) cbTournamentType.getSelectedItem(); - TournamentOptions tOptions = new TournamentOptions(this.txtName.getText()); + int numSeats = (Integer)this.spnNumSeats.getValue(); + TournamentOptions tOptions = new TournamentOptions(this.txtName.getText(), "", numSeats); tOptions.setTournamentType(tournamentType.getName()); tOptions.setPassword(txtPassword.getText()); tOptions.getPlayerTypes().add("Human"); @@ -653,13 +672,51 @@ public class NewTournamentDialog extends MageDialog { this.hideDialog(); }//GEN-LAST:event_btnCancelActionPerformed + private void updateNumSeats() { + int numPlayers = (Integer)this.spnNumPlayers.getValue(); + int numSeats = (Integer)this.spnNumSeats.getValue(); + + if (numSeats > 2) { + TournamentTypeView tournamentType = (TournamentTypeView) cbTournamentType.getSelectedItem(); + if (numSeats >= tournamentType.getMinPlayers()) { + createPlayers(numSeats - 1); + spnNumPlayers.setValue(numSeats); + } else { + numSeats = tournamentType.getMinPlayers(); + createPlayers(numSeats - 1); + spnNumPlayers.setValue(numSeats); + spnNumSeats.setValue(numSeats); + } + spnNumWins.setValue(1); + } + } + private void spnNumPlayersStateChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_spnNumPlayersStateChanged - int numPlayers = (Integer)this.spnNumPlayers.getValue() - 1; - createPlayers(numPlayers); + int numPlayers = (Integer)this.spnNumPlayers.getValue(); + createPlayers(numPlayers - 1); + int numSeats = (Integer)this.spnNumSeats.getValue(); + if (numSeats > 2 && numPlayers != numSeats) { + updateNumSeats(); + } }//GEN-LAST:event_spnNumPlayersStateChanged + private void spnNumSeatsStateChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_spnNumSeatsStateChanged + int numSeats = (Integer)this.spnNumSeats.getValue(); + if (numSeats > 2) { + this.spnNumPlayers.setEnabled(false); + } else { + this.spnNumPlayers.setEnabled(true); + } + updateNumSeats(); + }//GEN-LAST:event_spnNumSeatsStateChanged + + private void spnNumWinsnumPlayersChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_spnNumWinsnumPlayersChanged - // TODO add your handling code here: + int numSeats = (Integer)this.spnNumSeats.getValue(); + int numWins = (Integer)this.spnNumSeats.getValue(); + if (numSeats > 2) { + spnNumWins.setValue(1); + } }//GEN-LAST:event_spnNumWinsnumPlayersChanged private JFileChooser fcSelectDeck = null; @@ -726,6 +783,8 @@ public class NewTournamentDialog extends MageDialog { this.spnNumPlayers.setEnabled(tournamentType.getMinPlayers() != tournamentType.getMaxPlayers()); createPlayers((Integer) spnNumPlayers.getValue() - 1); + this.spnNumSeats.setModel(new SpinnerNumberModel(2, 2, tournamentType.getMaxPlayers(), 1)); + if (tournamentType.isLimited()) { this.isRandom = tournamentType.isRandom(); this.isRichMan = tournamentType.isRichMan(); @@ -914,6 +973,8 @@ public class NewTournamentDialog extends MageDialog { } + + private void drawPlayers() { this.pnlOtherPlayers.removeAll(); for (TournamentPlayerPanel panel: players) { @@ -1119,6 +1180,7 @@ public class NewTournamentDialog extends MageDialog { private javax.swing.JLabel lblGameType; private javax.swing.JLabel lblName; private javax.swing.JLabel lblNbrPlayers; + private javax.swing.JLabel lblNbrSeats; private javax.swing.JLabel lblNumRounds; private javax.swing.JLabel lblNumWins; private javax.swing.JLabel lblPacks; @@ -1135,6 +1197,7 @@ public class NewTournamentDialog extends MageDialog { private javax.swing.JSpinner spnConstructTime; private javax.swing.JSpinner spnFreeMulligans; private javax.swing.JSpinner spnNumPlayers; + private javax.swing.JSpinner spnNumSeats; private javax.swing.JSpinner spnNumRounds; private javax.swing.JSpinner spnNumWins; private javax.swing.JSpinner spnQuitRatio; diff --git a/Mage.Client/src/main/java/mage/client/table/TablesPanel.java b/Mage.Client/src/main/java/mage/client/table/TablesPanel.java index 253a3e08cc4..b0df188e1a2 100644 --- a/Mage.Client/src/main/java/mage/client/table/TablesPanel.java +++ b/Mage.Client/src/main/java/mage/client/table/TablesPanel.java @@ -1272,7 +1272,7 @@ public class TablesPanel extends javax.swing.JPanel { return; } - MatchOptions options = new MatchOptions("1", "Two Player Duel"); + MatchOptions options = new MatchOptions("1", "Two Player Duel", false, 2); options.getPlayerTypes().add("Human"); options.getPlayerTypes().add("Computer - mad"); options.setDeckType("Limited"); diff --git a/Mage.Server/src/main/java/mage/server/tournament/TournamentController.java b/Mage.Server/src/main/java/mage/server/tournament/TournamentController.java index 0d69cf257a7..c18b777224e 100644 --- a/Mage.Server/src/main/java/mage/server/tournament/TournamentController.java +++ b/Mage.Server/src/main/java/mage/server/tournament/TournamentController.java @@ -43,6 +43,7 @@ import mage.game.events.TableEvent; import mage.game.match.Match; import mage.game.match.MatchOptions; import mage.game.result.ResultProtos.TourneyQuitStatus; +import mage.game.tournament.MultiplayerRound; import mage.game.tournament.Tournament; import mage.game.tournament.TournamentPairing; import mage.game.tournament.TournamentPlayer; @@ -114,6 +115,19 @@ public class TournamentController { startMatch(event.getPair(), event.getMatchOptions()); } break; + case START_MULTIPLAYER_MATCH: + if (!isAbort()) { + initTournament(); // set state + MatchOptions matchOptions = event.getMatchOptions(); + if (matchOptions != null && event.getMultiplayerRound() != null) { + for (TournamentPlayer player : event.getMultiplayerRound().getAllPlayers()) { + matchOptions.getPlayerTypes().add(player.getPlayerType()); + } + } + + startMultiplayerMatch(event.getMultiplayerRound(), event.getMatchOptions()); + } + break; case END: endTournament(); break; @@ -264,6 +278,31 @@ public class TournamentController { logger.fatal("TournamentController startMatch error", ex); } } + + private void startMultiplayerMatch(MultiplayerRound round, MatchOptions matchOptions) { + try { + TableManager tableManager = TableManager.getInstance(); + Table table = tableManager.createTable(GamesRoomManager.getInstance().getMainRoomId(), matchOptions); + table.setTournamentSubTable(true); + table.setTournament(tournament); + table.setState(TableState.WAITING); + + for (TournamentPlayer player : round.getAllPlayers()) { + tableManager.addPlayer(getPlayerUserId(player.getPlayer().getId()), table.getId(), player.getPlayer(), player.getPlayerType(), player.getDeck()); + } + table.setState(TableState.STARTING); + tableManager.startTournamentSubMatch(null, table.getId()); + Match match = tableManager.getMatch(table.getId()); + match.setTableId(tableId); + round.setMatch(match); + round.setTableId(table.getId()); + for (TournamentPlayer player : round.getAllPlayers()) { + player.setState(TournamentPlayerState.DUELING); + } + } catch (GameException ex) { + logger.fatal("TournamentController startMatch error", ex); + } + } private void startDraft(Draft draft) { TableManager.getInstance().startDraft(tableId, draft); diff --git a/Mage/src/main/java/mage/game/events/TableEvent.java b/Mage/src/main/java/mage/game/events/TableEvent.java index f6409b770d9..2af79d52bb4 100644 --- a/Mage/src/main/java/mage/game/events/TableEvent.java +++ b/Mage/src/main/java/mage/game/events/TableEvent.java @@ -36,6 +36,7 @@ import mage.cards.decks.Deck; import mage.game.Game; import mage.game.draft.Draft; import mage.game.match.MatchOptions; +import mage.game.tournament.MultiplayerRound; import mage.game.tournament.TournamentPairing; /** @@ -46,7 +47,7 @@ public class TableEvent extends EventObject implements ExternalEvent, Serializab public enum EventType { UPDATE, INFO, STATUS, START_DRAFT, START_MATCH, SIDEBOARD, CONSTRUCT, SUBMIT_DECK, END, END_GAME_INFO, ERROR, - INIT_TIMER, RESUME_TIMER, PAUSE_TIMER, CHECK_STATE_PLAYERS + INIT_TIMER, RESUME_TIMER, PAUSE_TIMER, CHECK_STATE_PLAYERS, START_MULTIPLAYER_MATCH } private Game game; @@ -58,6 +59,7 @@ public class TableEvent extends EventObject implements ExternalEvent, Serializab private UUID playerId; private Deck deck; private TournamentPairing pair; + private MultiplayerRound round; private MatchOptions options; private int timeout; private boolean withTime; @@ -115,6 +117,13 @@ public class TableEvent extends EventObject implements ExternalEvent, Serializab this.options = options; this.eventType = eventType; } + + public TableEvent(EventType eventType, MultiplayerRound round, MatchOptions options) { + super(options); + this.round = round; + this.options = options; + this.eventType = eventType; + } public Game getGame() { return game; @@ -151,6 +160,10 @@ public class TableEvent extends EventObject implements ExternalEvent, Serializab public TournamentPairing getPair() { return pair; } + + public MultiplayerRound getMultiplayerRound() { + return round; + } public MatchOptions getMatchOptions() { return options; diff --git a/Mage/src/main/java/mage/game/events/TableEventSource.java b/Mage/src/main/java/mage/game/events/TableEventSource.java index 3e3f1faee87..af2657bec0b 100644 --- a/Mage/src/main/java/mage/game/events/TableEventSource.java +++ b/Mage/src/main/java/mage/game/events/TableEventSource.java @@ -38,6 +38,7 @@ import mage.game.tournament.TournamentPairing; import java.io.Serializable; import java.util.UUID; +import mage.game.tournament.MultiplayerRound; /** * @@ -93,4 +94,8 @@ public class TableEventSource implements EventSource, Serializable { public void fireTableEvent(EventType eventType, TournamentPairing pair, MatchOptions options) { dispatcher.fireEvent(new TableEvent(eventType, pair, options)); } + + public void fireTableEvent(EventType eventType, MultiplayerRound round, MatchOptions options) { + dispatcher.fireEvent(new TableEvent(eventType, round, options)); + } } diff --git a/Mage/src/main/java/mage/game/match/MatchOptions.java b/Mage/src/main/java/mage/game/match/MatchOptions.java index 930f88555fc..37901b91bea 100644 --- a/Mage/src/main/java/mage/game/match/MatchOptions.java +++ b/Mage/src/main/java/mage/game/match/MatchOptions.java @@ -52,21 +52,50 @@ public class MatchOptions implements Serializable { protected String deckType; protected boolean limited; protected List playerTypes = new ArrayList<>(); + protected boolean multiPlayer; + protected int numSeats; protected String password; protected SkillLevel skillLevel; protected boolean rollbackTurnsAllowed; protected int quitRatio; protected boolean rated; + protected int numSeatsForMatch; /** * Time each player has during the game to play using his\her priority. */ protected MatchTimeLimit matchTimeLimit; // 0 = no priorityTime handling - public MatchOptions(String name, String gameType) { + /*public MatchOptions(String name, String gameType) { this.name = name; this.gameType = gameType; this.password = ""; + this.multiPlayer = false; + this.numSeats = 2; + }*/ + + public MatchOptions(String name, String gameType, boolean multiPlayer, int numSeats ) { + this.name = name; + this.gameType = gameType; + this.password = ""; + this.multiPlayer = multiPlayer; + this.numSeats = numSeats; + } + + public void setNumSeats (int numSeats) { + this.numSeats = numSeats; + } + + public int getNumSeats () { + return numSeats; + } + + public void setMultiPlayer(boolean multiPlayer) { + this.multiPlayer = multiPlayer; + } + + public boolean getMultiPlayer() { + return multiPlayer; } public String getName() { diff --git a/Mage/src/main/java/mage/game/tournament/MultiplayerRound.java b/Mage/src/main/java/mage/game/tournament/MultiplayerRound.java new file mode 100644 index 00000000000..2e69299aa34 --- /dev/null +++ b/Mage/src/main/java/mage/game/tournament/MultiplayerRound.java @@ -0,0 +1,97 @@ +/* + * Copyright 2011 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.game.tournament; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import mage.game.match.Match; + +/** + * + * @author spjspj + */ +public class MultiplayerRound { + + private final int roundNum; + private final Tournament tournament; + private final int numSeats; + private final List allPlayers = new ArrayList<>(); + private Match match; + private UUID tableId; + + + public MultiplayerRound(int roundNum, Tournament tournament, int numSeats) { + this.roundNum = roundNum; + this.tournament = tournament; + this.numSeats = numSeats; + } + + public List getAllPlayers () { + return allPlayers; + } + + public TournamentPlayer getPlayer (int i) { + if (i >= 0 && i < numSeats && i < allPlayers.size()) { + return allPlayers.get(i); + } + return null; + } + + public void addPairing(TournamentPairing match) { + this.allPlayers.add(match.getPlayer1()); + this.allPlayers.add(match.getPlayer2()); + } + + public void addPlayer(TournamentPlayer player) { + this.allPlayers.add(player); + } + + public int getRoundNumber() { + return this.roundNum; + } + + public void setMatch (Match match) { + this.match = match; + } + + public void setTableId (UUID tableId) { + this.tableId = tableId; + } + + public boolean isRoundOver() { + boolean roundIsOver = true; + if (this.match != null) { + if (!this.match.hasEnded()) { + roundIsOver = false; + } + } + return roundIsOver; + } +} diff --git a/Mage/src/main/java/mage/game/tournament/TournamentImpl.java b/Mage/src/main/java/mage/game/tournament/TournamentImpl.java index ebc7faf589c..b01048d3b65 100644 --- a/Mage/src/main/java/mage/game/tournament/TournamentImpl.java +++ b/Mage/src/main/java/mage/game/tournament/TournamentImpl.java @@ -261,6 +261,12 @@ public abstract class TournamentImpl implements Tournament { } updateResults(); } + + protected void playMultiplayerRound(MultiplayerRound round) { + playMultiPlayerMatch(round); + + updateResults(); // show points from byes + } protected List getActivePlayers() { List activePlayers = new ArrayList<>(); @@ -456,6 +462,10 @@ public abstract class TournamentImpl implements Tournament { options.getMatchOptions().getPlayerTypes().add(pair.getPlayer2().getPlayerType()); tableEventSource.fireTableEvent(EventType.START_MATCH, pair, options.getMatchOptions()); } + + public void playMultiPlayerMatch(MultiplayerRound round) { + tableEventSource.fireTableEvent(EventType.START_MULTIPLAYER_MATCH, round, options.getMatchOptions()); + } public void end() { endTime = new Date(); diff --git a/Mage/src/main/java/mage/game/tournament/TournamentOptions.java b/Mage/src/main/java/mage/game/tournament/TournamentOptions.java index 4a111ebcd74..3160e2598ac 100644 --- a/Mage/src/main/java/mage/game/tournament/TournamentOptions.java +++ b/Mage/src/main/java/mage/game/tournament/TournamentOptions.java @@ -41,15 +41,16 @@ public class TournamentOptions implements Serializable { protected String name; protected String tournamentType; protected List playerTypes = new ArrayList<>(); - protected MatchOptions matchOptions = new MatchOptions("", "Two Player Duel"); + protected MatchOptions matchOptions; protected LimitedOptions limitedOptions; protected boolean watchingAllowed = true; protected int numberRounds; protected String password; protected int quitRatio; - public TournamentOptions(String name) { + public TournamentOptions(String name, String matchType, int numSeats) { this.name = name; + this.matchOptions = new MatchOptions("", matchType, numSeats > 2, numSeats); } public String getName() { diff --git a/Mage/src/main/java/mage/game/tournament/TournamentSealedOptions.java b/Mage/src/main/java/mage/game/tournament/TournamentSealedOptions.java index 586894a81c4..e3d1d0081f6 100644 --- a/Mage/src/main/java/mage/game/tournament/TournamentSealedOptions.java +++ b/Mage/src/main/java/mage/game/tournament/TournamentSealedOptions.java @@ -34,8 +34,8 @@ package mage.game.tournament; */ public class TournamentSealedOptions extends TournamentOptions { - public TournamentSealedOptions(String name) { - super(name); + public TournamentSealedOptions(String name, String matchType, int numSeats) { + super(name, matchType, numSeats); } } diff --git a/Mage/src/main/java/mage/game/tournament/TournamentSingleElimination.java b/Mage/src/main/java/mage/game/tournament/TournamentSingleElimination.java index 328d9c4ce1b..55ab5f8a0d5 100644 --- a/Mage/src/main/java/mage/game/tournament/TournamentSingleElimination.java +++ b/Mage/src/main/java/mage/game/tournament/TournamentSingleElimination.java @@ -28,6 +28,7 @@ package mage.game.tournament; +import java.util.List; import java.util.Map; import java.util.UUID; import mage.game.events.TableEvent; @@ -50,16 +51,25 @@ public abstract class TournamentSingleElimination extends TournamentImpl { entry.getValue().setResults("Auto Eliminated"); } } - while (this.getActivePlayers().size() > 1) { - // check if some player got killed / disconnected meanwhile and update their state - tableEventSource.fireTableEvent(TableEvent.EventType.CHECK_STATE_PLAYERS); - Round round = createRoundRandom(); - playRound(round); - eliminatePlayers(round); + if (options.matchOptions.getNumSeats() == 2) { + while (this.getActivePlayers().size() > 1) { + // check if some player got killed / disconnected meanwhile and update their state + tableEventSource.fireTableEvent(TableEvent.EventType.CHECK_STATE_PLAYERS); + Round round = createRoundRandom(); + playRound(round); + eliminatePlayers(round); + } + } else { + MultiplayerRound round = new MultiplayerRound(0, this, options.matchOptions.getNumSeats()); + for (TournamentPlayer player : getActivePlayers()) { + round.addPlayer(player); + } + playMultiplayerRound(round); } + nextStep(); } - + private void eliminatePlayers(Round round) { for (TournamentPairing pair: round.getPairs()) { pair.eliminatePlayers(); diff --git a/Mage/src/main/java/mage/game/tournament/TournamentSwiss.java b/Mage/src/main/java/mage/game/tournament/TournamentSwiss.java index 54337d71cc6..faf2ffc9285 100644 --- a/Mage/src/main/java/mage/game/tournament/TournamentSwiss.java +++ b/Mage/src/main/java/mage/game/tournament/TournamentSwiss.java @@ -56,13 +56,19 @@ public abstract class TournamentSwiss extends TournamentImpl { } } - while (this.getActivePlayers().size() > 1 && this.getNumberRounds() > this.getRounds().size()) { - // check if some player got killed / disconnected meanwhile and update their state - tableEventSource.fireTableEvent(TableEvent.EventType.CHECK_STATE_PLAYERS); - // Swiss pairing - Round round = createRoundSwiss(); - playRound(round); + if (options.matchOptions.getNumSeats() == 2) { + while (this.getActivePlayers().size() > 1 && this.getNumberRounds() > this.getRounds().size()) { + // check if some player got killed / disconnected meanwhile and update their state + tableEventSource.fireTableEvent(TableEvent.EventType.CHECK_STATE_PLAYERS); + // Swiss pairing + Round round = createRoundSwiss(); + playRound(round); + } + } else { + MultiplayerRound round = createMultiplayerRound(); + playMultiplayerRound(round); } + nextStep(); } @@ -70,33 +76,60 @@ public abstract class TournamentSwiss extends TournamentImpl { List roundPlayers = getActivePlayers(); boolean isLastRound = (rounds.size() + 1 == getNumberRounds()); - RoundPairings roundPairings; - if (roundPlayers.size() <= 16) { - SwissPairingMinimalWeightMatching swissPairing = new SwissPairingMinimalWeightMatching(roundPlayers, rounds, isLastRound); - roundPairings = swissPairing.getRoundPairings(); - } else { - SwissPairingSimple swissPairing = new SwissPairingSimple(roundPlayers, rounds); - roundPairings = swissPairing.getRoundPairings(); - } - - Round round = new Round(rounds.size() + 1, this); - rounds.add(round); - for (TournamentPairing pairing : roundPairings.getPairings()) { - round.addPairing(pairing); - } - for (TournamentPlayer playerBye : roundPairings.getPlayerByes()) { - // player free round - add to bye players of this round - round.getPlayerByes().add(playerBye); - if (isLastRound) { - playerBye.setState(TournamentPlayerState.FINISHED); + Round round = null; + if (options.matchOptions.getNumSeats() == 2) { + RoundPairings roundPairings; + if (roundPlayers.size() <= 16) { + SwissPairingMinimalWeightMatching swissPairing = new SwissPairingMinimalWeightMatching(roundPlayers, rounds, isLastRound); + roundPairings = swissPairing.getRoundPairings(); } else { - playerBye.setState(TournamentPlayerState.WAITING); + SwissPairingSimple swissPairing = new SwissPairingSimple(roundPlayers, rounds); + roundPairings = swissPairing.getRoundPairings(); + } + + round = new Round(rounds.size() + 1, this); + rounds.add(round); + for (TournamentPairing pairing : roundPairings.getPairings()) { + round.addPairing(pairing); + } + for (TournamentPlayer playerBye : roundPairings.getPlayerByes()) { + // player free round - add to bye players of this round + round.getPlayerByes().add(playerBye); + if (isLastRound) { + playerBye.setState(TournamentPlayerState.FINISHED); + } else { + playerBye.setState(TournamentPlayerState.WAITING); + } + playerBye.setStateInfo("Round Bye"); + updateResults(); } - playerBye.setStateInfo("Round Bye"); - updateResults(); } return round; } + public MultiplayerRound createMultiplayerRound() { + List roundPlayers = getActivePlayers(); + boolean isLastRound = (rounds.size() + 1 == getNumberRounds()); + + MultiplayerRound round = null; + if (options.matchOptions.getNumSeats() > 2) { + RoundPairings roundPairings; + if (roundPlayers.size() <= 16) { + SwissPairingMinimalWeightMatching swissPairing = new SwissPairingMinimalWeightMatching(roundPlayers, rounds, isLastRound); + roundPairings = swissPairing.getRoundPairings(); + } else { + SwissPairingSimple swissPairing = new SwissPairingSimple(roundPlayers, rounds); + roundPairings = swissPairing.getRoundPairings(); + } + + round = new MultiplayerRound(rounds.size() + 1, this, options.matchOptions.getNumSeats()); + for (TournamentPairing pairing : roundPairings.getPairings()) { + round.addPairing(pairing); + } + + } + return round; + + } } From 74503a1493ecbb199d4e95f4ce83e817872730c5 Mon Sep 17 00:00:00 2001 From: spjspj Date: Sun, 25 Sep 2016 00:48:41 +1000 Subject: [PATCH 15/15] spjspj - Add option of 'Number of Seats'. This is for Tournaments so that you can draft say a 4 way draft and then have a 4 way game at the end of it. --- Mage.Tests/src/test/java/org/mage/test/load/LoadTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mage.Tests/src/test/java/org/mage/test/load/LoadTest.java b/Mage.Tests/src/test/java/org/mage/test/load/LoadTest.java index 4687365d313..bdf0c032576 100644 --- a/Mage.Tests/src/test/java/org/mage/test/load/LoadTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/load/LoadTest.java @@ -267,7 +267,7 @@ public class LoadTest { * @return */ private MatchOptions createGameOptions(GameTypeView gameTypeView, Session session) { - MatchOptions options = new MatchOptions("Test game", gameTypeView.getName()); + MatchOptions options = new MatchOptions("Test game", gameTypeView.getName(), false, 2); options.getPlayerTypes().add("Human"); options.getPlayerTypes().add("Human");