From 9dd6e616cdb40254bb6b7470be6e5d4c7d737303 Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Sun, 26 Jul 2015 00:38:56 +0200 Subject: [PATCH] * Fixed that commander mana replacement effect did wrong mana replacements if additional mana was produced from abilities of opponents (fixes #1146). --- .../duel/CommanderManaReplacmentTest.java | 82 +++++++++++++ Mage/src/mage/game/events/GameEvent.java | 15 ++- Mage/src/mage/players/ManaPool.java | 108 +++++++++--------- 3 files changed, 149 insertions(+), 56 deletions(-) create mode 100644 Mage.Tests/src/test/java/org/mage/test/commander/duel/CommanderManaReplacmentTest.java diff --git a/Mage.Tests/src/test/java/org/mage/test/commander/duel/CommanderManaReplacmentTest.java b/Mage.Tests/src/test/java/org/mage/test/commander/duel/CommanderManaReplacmentTest.java new file mode 100644 index 00000000000..84ee71449bd --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/commander/duel/CommanderManaReplacmentTest.java @@ -0,0 +1,82 @@ +/* + * 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.commander.duel; + +import java.io.FileNotFoundException; +import mage.constants.PhaseStep; +import mage.constants.Zone; +import mage.game.Game; +import mage.game.GameException; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestCommanderDuelBase; + +/** + * + * @author LevelX2 + */ +public class CommanderManaReplacmentTest extends CardTestCommanderDuelBase { + + @Override + protected Game createNewGameAndPlayers() throws GameException, FileNotFoundException { + setDecknamePlayerA("CommanderDuel_UW.dck"); // Commander = Daxos of Meletis + return super.createNewGameAndPlayers(); + } + + /** + * This issue appears to arise in both Commander Two Player Duel and + * Commander Free For All. Whenever a player (call her player 1) controls a + * permanent with a mana doubling continuous effect (e.g. Mana Flare) and + * another player (call her player 2) taps an affected permanent for mana + * that is outside of player 1's color identity, player 2 gets an additional + * colorless mana rather than the correct color of mana. I suspect the + * reason for this is that Xmage is treating Mana Flare as producing mana of + * the appropriate color and adding it to another player's mana pool, which + * is both incorrect in the game rules and would cause this problem. + * + * For example, I am playing a mono red deck and control a Mana Flare. You + * are playing a mono green deck and you tap a Forest for mana. You should + * add GG to your mana pool, but instead, Xmage shows you adding 1G to your + * mana pool. + */ + @Test + public void castCommanderWithFlash() { + addCard(Zone.BATTLEFIELD, playerA, "Island", 1); + addCard(Zone.HAND, playerA, "Vedalken Mastermind", 1); // {U}{U} + + addCard(Zone.BATTLEFIELD, playerB, "Mana Flare"); + + activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {U}"); // should add {U}{U} + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Vedalken Mastermind"); + setStopAt(1, PhaseStep.PRECOMBAT_MAIN); + execute(); + + assertPermanentCount(playerA, "Vedalken Mastermind", 1); + + } + +} diff --git a/Mage/src/mage/game/events/GameEvent.java b/Mage/src/mage/game/events/GameEvent.java index 433ad158c39..fcabb52c61a 100644 --- a/Mage/src/mage/game/events/GameEvent.java +++ b/Mage/src/mage/game/events/GameEvent.java @@ -119,7 +119,20 @@ public class GameEvent implements Serializable { */ SPELL_CAST, ACTIVATE_ABILITY, ACTIVATED_ABILITY, - ADD_MANA, MANA_ADDED, + /* ADD_MANA + targetId id of the ability that added the mana + sourceId sourceId of the ability that added the mana + playerId player the mana is added to the mana pool for + mana the mana added + */ + ADD_MANA, + /* MANA_ADDED + targetId id of the ability that added the mana + sourceId sourceId of the ability that added the mana + playerId player the mana is added to the mana pool for + mana the mana added + */ + MANA_ADDED, /* MANA_PAYED targetId id if the ability the mana was paid for (not the sourceId) sourceId sourceId of the mana source diff --git a/Mage/src/mage/players/ManaPool.java b/Mage/src/mage/players/ManaPool.java index 200faae6b79..f4745ac20ca 100644 --- a/Mage/src/mage/players/ManaPool.java +++ b/Mage/src/mage/players/ManaPool.java @@ -1,31 +1,30 @@ /* -* Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. -* -* Redistribution and use in source and binary forms, with or without modification, are -* permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, this list of -* conditions and the following disclaimer. -* -* 2. Redistributions in binary form must reproduce the above copyright notice, this list -* of conditions and the following disclaimer in the documentation and/or other materials -* provided with the distribution. -* -* THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED -* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND -* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR -* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF -* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -* -* The views and conclusions contained in the software and documentation are those of the -* authors and should not be interpreted as representing official policies, either expressed -* or implied, of BetaSteward_at_googlemail.com. -*/ - + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ package mage.players; import java.io.Serializable; @@ -63,7 +62,7 @@ public class ManaPool implements Serializable { private boolean autoPayment; // auto payment from mana pool: true - mode is active private boolean autoPaymentRestricted; // auto payment from mana pool: true - if auto Payment is on, it will only pay if one kind of mana is in the pool private ManaType unlockedManaType; // type of mana that was selected to pay manually - + private final Set doNotEmptyManaTypes = new HashSet<>(); public ManaPool(UUID playerId) { @@ -75,7 +74,7 @@ public class ManaPool implements Serializable { public ManaPool(final ManaPool pool) { this.playerId = pool.playerId; - for (ManaPoolItem item: pool.manaItems) { + for (ManaPoolItem item : pool.manaItems) { manaItems.add(item.copy()); } this.autoPayment = pool.autoPayment; @@ -105,24 +104,24 @@ public class ManaPool implements Serializable { } /** - * + * * @param manaType the mana type that should be paid * @param ability * @param filter * @param game - * @return + * @return */ public boolean pay(ManaType manaType, Ability ability, Filter filter, Game game) { if (!autoPayment && !manaType.equals(unlockedManaType)) { // if manual payment and the needed mana type was not unlocked, nothing will be paid return false; } - if (autoPayment && autoPaymentRestricted && !wasManaAddedBeyondStock() && !manaType.equals(unlockedManaType)) { + if (autoPayment && autoPaymentRestricted && !wasManaAddedBeyondStock() && !manaType.equals(unlockedManaType)) { // if automatic restricted payment and there is laready mana in the pool // and the needed mana type was not unlocked, nothing will be paid return false; } - + if (getConditional(manaType, ability, filter, game) > 0) { removeConditional(manaType, ability, game); lockManaType(); // pay only one mana if mana payment is set to manually @@ -134,7 +133,7 @@ public class ManaPool implements Serializable { if (!filter.match(sourceObject, game)) { continue; } - } + } if (!manaType.equals(unlockedManaType) && autoPayment && autoPaymentRestricted && mana.count() == mana.getStock()) { // no mana added beyond the stock so don't auto pay this continue; @@ -202,7 +201,7 @@ public class ManaPool implements Serializable { public void clearEmptyManaPoolRules() { doNotEmptyManaTypes.clear(); } - + public void addDoNotEmptyManaType(ManaType manaType) { doNotEmptyManaTypes.add(manaType); } @@ -217,7 +216,7 @@ public class ManaPool implements Serializable { if (!doNotEmptyManaTypes.contains(manaType)) { if (item.get(manaType) > 0) { if (!item.getDuration().equals(Duration.EndOfTurn) || game.getPhase().getType().equals(TurnPhase.END)) { - if (game.replaceEvent(new GameEvent(GameEvent.EventType.EMPTY_MANA_POOL, playerId, null, playerId))) { + if (game.replaceEvent(new GameEvent(GameEvent.EventType.EMPTY_MANA_POOL, playerId, null, playerId))) { int amount = item.get(manaType); item.clear(manaType); item.add(ManaType.COLORLESS, amount); @@ -230,7 +229,7 @@ public class ManaPool implements Serializable { if (conditionalItem != null) { if (conditionalItem.get(manaType) > 0) { if (!item.getDuration().equals(Duration.EndOfTurn) || game.getPhase().getType().equals(TurnPhase.END)) { - if (game.replaceEvent(new GameEvent(GameEvent.EventType.EMPTY_MANA_POOL, playerId, null, playerId))) { + if (game.replaceEvent(new GameEvent(GameEvent.EventType.EMPTY_MANA_POOL, playerId, null, playerId))) { int amount = conditionalItem.get(manaType); conditionalItem.clear(manaType); conditionalItem.add(ManaType.COLORLESS, amount); @@ -246,8 +245,8 @@ public class ManaPool implements Serializable { if (item.count() == 0) { it.remove(); } - } - return total; + } + return total; } private int payX(Ability ability, Game game) { @@ -261,8 +260,7 @@ public class ManaPool implements Serializable { total += item.count(); it.remove(); } - } - else { + } else { total += item.count(); it.remove(); } @@ -272,10 +270,11 @@ public class ManaPool implements Serializable { /** * remove all mana from pool that applies and that matches filter + * * @param ability * @param game * @param filter - * @return + * @return */ public int payX(Ability ability, Game game, FilterMana filter) { if (filter == null) { @@ -297,8 +296,7 @@ public class ManaPool implements Serializable { } } } - } - else { + } else { if (filter.isBlack()) { total += item.getBlack(); item.removeBlack(); @@ -333,7 +331,7 @@ public class ManaPool implements Serializable { public Mana getMana() { Mana m = new Mana(); - for (ManaPoolItem item: manaItems) { + for (ManaPoolItem item : manaItems) { m.add(item.getMana()); } return m; @@ -375,12 +373,12 @@ public class ManaPool implements Serializable { public void addMana(Mana manaToAdd, Game game, Ability source) { addMana(manaToAdd, game, source, false); } - + public void addMana(Mana manaToAdd, Game game, Ability source, boolean emptyOnTurnsEnd) { Mana mana = manaToAdd.copy(); - if (!game.replaceEvent(new ManaEvent(EventType.ADD_MANA, source.getId(), source.getSourceId(), source.getControllerId(), mana))) { + if (!game.replaceEvent(new ManaEvent(EventType.ADD_MANA, source.getId(), source.getSourceId(), playerId, mana))) { if (mana instanceof ConditionalMana) { - ManaPoolItem item = new ManaPoolItem((ConditionalMana)mana, source.getSourceId(), source.getOriginalId()); + ManaPoolItem item = new ManaPoolItem((ConditionalMana) mana, source.getSourceId(), source.getOriginalId()); if (emptyOnTurnsEnd) { item.setDuration(Duration.EndOfTurn); } @@ -389,18 +387,18 @@ public class ManaPool implements Serializable { ManaPoolItem item = new ManaPoolItem(mana.getRed(), mana.getGreen(), mana.getBlue(), mana.getWhite(), mana.getBlack(), mana.getColorless(), source.getSourceId(), source.getOriginalId(), mana.getFlag()); if (emptyOnTurnsEnd) { item.setDuration(Duration.EndOfTurn); - } + } this.manaItems.add(item); } - GameEvent event = GameEvent.getEvent(GameEvent.EventType.MANA_ADDED, source.getId(), source.getSourceId(), source.getControllerId()); - event.setData(mana.toString()); - game.fireEvent(event); + ManaEvent manaEvent = new ManaEvent(EventType.MANA_ADDED, source.getId(), source.getSourceId(), playerId, mana); + manaEvent.setData(mana.toString()); + game.fireEvent(manaEvent); } } public List getConditionalMana() { List conditionalMana = new ArrayList<>(); - for (ManaPoolItem item: manaItems) { + for (ManaPoolItem item : manaItems) { if (item.isConditional()) { conditionalMana.add(item.getConditionalMana()); } @@ -410,7 +408,7 @@ public class ManaPool implements Serializable { public int count() { int x = 0; - for (ManaPoolItem item: manaItems) { + for (ManaPoolItem item : manaItems) { x += item.count(); } return x; @@ -465,7 +463,7 @@ public class ManaPool implements Serializable { mana.setStock(mana.count()); } } - + private boolean wasManaAddedBeyondStock() { for (ManaPoolItem mana : manaItems) { if (mana.getStock() < mana.count()) {