* Fixed that permanents under non owner control sine they are on the battlefield were no exiled if the controller left the game (e.g. Captive Audience) (fixes #5593).

This commit is contained in:
LevelX2 2020-06-27 23:47:04 +02:00
parent 2c745109e4
commit d2d892a7cb
7 changed files with 308 additions and 235 deletions

View file

@ -1,5 +1,6 @@
package org.mage.test.multiplayer; package org.mage.test.multiplayer;
import java.io.FileNotFoundException;
import mage.constants.MultiplayerAttackOption; import mage.constants.MultiplayerAttackOption;
import mage.constants.PhaseStep; import mage.constants.PhaseStep;
import mage.constants.RangeOfInfluence; import mage.constants.RangeOfInfluence;
@ -14,8 +15,6 @@ import org.junit.Assert;
import org.junit.Test; import org.junit.Test;
import org.mage.test.serverside.base.CardTestMultiPlayerBase; import org.mage.test.serverside.base.CardTestMultiPlayerBase;
import java.io.FileNotFoundException;
/** /**
* @author LevelX2 * @author LevelX2
*/ */
@ -344,4 +343,54 @@ public class PlayerLeftGameRange1Test extends CardTestMultiPlayerBase {
Assert.assertTrue("Staff of player B could be used", staffPlayerB.isTapped()); Assert.assertTrue("Staff of player B could be used", staffPlayerB.isTapped());
} }
/**
* Captive Audience doesn't work correctly in multiplayer #5593
*
* Currently, if the controller of Captive Audience leaves the game, Captive
* Audience returns to its owner instead of being exiled.
*/
@Test
public void TestCaptiveAudienceGoesToExile() {
addCard(Zone.BATTLEFIELD, playerA, "Swamp", 4);
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3);
// Captive Audience enters the battlefield under the control of an opponent of your choice.
// At the beginning of your upkeep, choose one that hasn't been chosen
// Your life total becomes 4.
// Discard your hand.
// Each opponent creates five 2/2 black Zombie creature tokens.
addCard(Zone.HAND, playerA, "Captive Audience"); // Enchantment {5}{B}{R}
addCard(Zone.BATTLEFIELD, playerA, "Silvercoat Lion", 1);
addCard(Zone.BATTLEFIELD, playerA, "Pillarfield Ox", 1);
setChoice(playerA, "PlayerA"); // Starting Player
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Captive Audience");
setChoice(playerA, "PlayerD");
setModeChoice(playerD, "1");
attack(5, playerA, "Silvercoat Lion", playerD);
attack(5, playerA, "Pillarfield Ox", playerD);
setStopAt(5, PhaseStep.POSTCOMBAT_MAIN);
setStrictChooseMode(true);
execute();
assertAllCommandsUsed();
assertLife(playerA, 2);
Assert.assertFalse("Player D is no longer in the game", playerD.isInGame());
assertPermanentCount(playerD, 0);
assertPermanentCount(playerA, "Captive Audience", 0);
assertGraveyardCount(playerA, "Captive Audience", 0);
assertExileCount(playerA, "Captive Audience", 1);
}
} }

View file

@ -1,5 +1,13 @@
package org.mage.test.serverside.base.impl; package org.mage.test.serverside.base.impl;
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import mage.MageObject; import mage.MageObject;
import mage.Mana; import mage.Mana;
import mage.ObjectColor; import mage.ObjectColor;
@ -35,15 +43,6 @@ import org.mage.test.player.TestPlayer;
import org.mage.test.serverside.base.CardTestAPI; import org.mage.test.serverside.base.CardTestAPI;
import org.mage.test.serverside.base.MageTestPlayerBase; import org.mage.test.serverside.base.MageTestPlayerBase;
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
/** /**
* API for test initialization and asserting the test results. * API for test initialization and asserting the test results.
* *
@ -275,7 +274,7 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement
} }
} }
Assert.assertFalse("Wrong stop command on " + this.stopOnTurn + " / " + this.stopAtStep + " (" + this.stopAtStep.getIndex() + ")" Assert.assertFalse("Wrong stop command on " + this.stopOnTurn + " / " + this.stopAtStep + " (" + this.stopAtStep.getIndex() + ")"
+ " (found actions after stop on " + maxTurn + " / " + maxPhase + ")", + " (found actions after stop on " + maxTurn + " / " + maxPhase + ")",
(maxTurn > this.stopOnTurn) || (maxTurn == this.stopOnTurn && maxPhase > this.stopAtStep.getIndex())); (maxTurn > this.stopOnTurn) || (maxTurn == this.stopOnTurn && maxPhase > this.stopAtStep.getIndex()));
for (Player player : currentGame.getPlayers().values()) { for (Player player : currentGame.getPlayers().values()) {
@ -508,8 +507,8 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement
* Add a card to specified zone of specified player. * Add a card to specified zone of specified player.
* *
* @param gameZone {@link mage.constants.Zone} to add cards to. * @param gameZone {@link mage.constants.Zone} to add cards to.
* @param player {@link Player} to add cards for. Use either playerA or * @param player {@link Player} to add cards for. Use either playerA or
* playerB. * playerB.
* @param cardName Card name in string format. * @param cardName Card name in string format.
*/ */
@Override @Override
@ -521,10 +520,10 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement
* Add any amount of cards to specified zone of specified player. * Add any amount of cards to specified zone of specified player.
* *
* @param gameZone {@link mage.constants.Zone} to add cards to. * @param gameZone {@link mage.constants.Zone} to add cards to.
* @param player {@link Player} to add cards for. Use either playerA or * @param player {@link Player} to add cards for. Use either playerA or
* playerB. * playerB.
* @param cardName Card name in string format. * @param cardName Card name in string format.
* @param count Amount of cards to be added. * @param count Amount of cards to be added.
*/ */
@Override @Override
public void addCard(Zone gameZone, TestPlayer player, String cardName, int count) { public void addCard(Zone gameZone, TestPlayer player, String cardName, int count) {
@ -535,13 +534,13 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement
* Add any amount of cards to specified zone of specified player. * Add any amount of cards to specified zone of specified player.
* *
* @param gameZone {@link mage.constants.Zone} to add cards to. * @param gameZone {@link mage.constants.Zone} to add cards to.
* @param player {@link Player} to add cards for. Use either playerA or * @param player {@link Player} to add cards for. Use either playerA or
* playerB. * playerB.
* @param cardName Card name in string format. * @param cardName Card name in string format.
* @param count Amount of cards to be added. * @param count Amount of cards to be added.
* @param tapped In case gameZone is Battlefield, determines whether * @param tapped In case gameZone is Battlefield, determines whether
* permanent should be tapped. In case gameZone is other than Battlefield, * permanent should be tapped. In case gameZone is other than Battlefield,
* {@link IllegalArgumentException} is thrown * {@link IllegalArgumentException} is thrown
*/ */
@Override @Override
public void addCard(Zone gameZone, TestPlayer player, String cardName, int count, boolean tapped) { public void addCard(Zone gameZone, TestPlayer player, String cardName, int count, boolean tapped) {
@ -622,7 +621,7 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement
* Set player's initial life count. * Set player's initial life count.
* *
* @param player {@link Player} to set life count for. * @param player {@link Player} to set life count for.
* @param life Life count to set. * @param life Life count to set.
*/ */
@Override @Override
public void setLife(TestPlayer player, int life) { public void setLife(TestPlayer player, int life) {
@ -699,7 +698,7 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement
* Assert player's life count after test execution. * Assert player's life count after test execution.
* *
* @param player {@link Player} to get life for comparison. * @param player {@link Player} to get life for comparison.
* @param life Expected player's life to compare with. * @param life Expected player's life to compare with.
*/ */
@Override @Override
public void assertLife(Player player, int life) throws AssertionError { public void assertLife(Player player, int life) throws AssertionError {
@ -716,14 +715,14 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement
* params 3b. all: there is at least one creature with the cardName with the * params 3b. all: there is at least one creature with the cardName with the
* different p\t params * different p\t params
* *
* @param player {@link Player} to get creatures for comparison. * @param player {@link Player} to get creatures for comparison.
* @param cardName Card name to compare with. * @param cardName Card name to compare with.
* @param power Expected power to compare with. * @param power Expected power to compare with.
* @param toughness Expected toughness to compare with. * @param toughness Expected toughness to compare with.
* @param scope {@link mage.filter.Filter.ComparisonScope} Use ANY, if you * @param scope {@link mage.filter.Filter.ComparisonScope} Use ANY, if you
* want "at least one creature with given name should have specified p\t" * want "at least one creature with given name should have specified p\t"
* Use ALL, if you want "all creature with gived name should have specified * Use ALL, if you want "all creature with gived name should have specified
* p\t" * p\t"
*/ */
@Override @Override
public void assertPowerToughness(Player player, String cardName, int power, int toughness, Filter.ComparisonScope scope) public void assertPowerToughness(Player player, String cardName, int power, int toughness, Filter.ComparisonScope scope)
@ -813,8 +812,8 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement
* @param cardName * @param cardName
* @param ability * @param ability
* @param mustHave true if creature should contain ability, false if it * @param mustHave true if creature should contain ability, false if it
* should NOT contain it instead * should NOT contain it instead
* @param count number of permanents with that ability * @param count number of permanents with that ability
* @throws AssertionError * @throws AssertionError
*/ */
public void assertAbility(Player player, String cardName, Ability ability, boolean mustHave, int count) throws AssertionError { public void assertAbility(Player player, String cardName, Ability ability, boolean mustHave, int count) throws AssertionError {
@ -847,7 +846,7 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement
* Assert permanent count under player's control. * Assert permanent count under player's control.
* *
* @param player {@link Player} which permanents should be counted. * @param player {@link Player} which permanents should be counted.
* @param count Expected count. * @param count Expected count.
*/ */
@Override @Override
public void assertPermanentCount(Player player, int count) throws AssertionError { public void assertPermanentCount(Player player, int count) throws AssertionError {
@ -863,9 +862,9 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement
/** /**
* Assert permanent count under player's control. * Assert permanent count under player's control.
* *
* @param player {@link Player} which permanents should be counted. * @param player {@link Player} which permanents should be counted.
* @param cardName Name of the cards that should be counted. * @param cardName Name of the cards that should be counted.
* @param count Expected count. * @param count Expected count.
*/ */
@Override @Override
public void assertPermanentCount(Player player, String cardName, int count) throws AssertionError { public void assertPermanentCount(Player player, String cardName, int count) throws AssertionError {
@ -915,8 +914,8 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement
* Assert counter count on a permanent * Assert counter count on a permanent
* *
* @param cardName Name of the cards that should be counted. * @param cardName Name of the cards that should be counted.
* @param type Type of the counter that should be counted. * @param type Type of the counter that should be counted.
* @param count Expected count. * @param count Expected count.
*/ */
public void assertCounterCount(String cardName, CounterType type, int count) throws AssertionError { public void assertCounterCount(String cardName, CounterType type, int count) throws AssertionError {
this.assertCounterCount(null, cardName, type, count); this.assertCounterCount(null, cardName, type, count);
@ -939,8 +938,8 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement
* Assert counter count on a card in exile * Assert counter count on a card in exile
* *
* @param cardName Name of the cards that should be counted. * @param cardName Name of the cards that should be counted.
* @param type Type of the counter that should be counted. * @param type Type of the counter that should be counted.
* @param count Expected count. * @param count Expected count.
*/ */
public void assertCounterOnExiledCardCount(String cardName, CounterType type, int count) throws AssertionError { public void assertCounterOnExiledCardCount(String cardName, CounterType type, int count) throws AssertionError {
//Assert.assertNotEquals("", cardName); //Assert.assertNotEquals("", cardName);
@ -963,8 +962,8 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement
* Assert counter count on a player * Assert counter count on a player
* *
* @param player The player whos counters should be counted. * @param player The player whos counters should be counted.
* @param type Type of the counter that should be counted. * @param type Type of the counter that should be counted.
* @param count Expected count. * @param count Expected count.
*/ */
public void assertCounterCount(Player player, CounterType type, int count) throws AssertionError { public void assertCounterCount(Player player, CounterType type, int count) throws AssertionError {
Assert.assertEquals("(Battlefield) Counter counts are not equal (" + player.getName() + ':' + type + ')', count, player.getCounters().getCount(type)); Assert.assertEquals("(Battlefield) Counter counts are not equal (" + player.getName() + ':' + type + ')', count, player.getCounters().getCount(type));
@ -974,7 +973,7 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement
* Assert whether a permanent is a specified type or not * Assert whether a permanent is a specified type or not
* *
* @param cardName Name of the permanent that should be checked. * @param cardName Name of the permanent that should be checked.
* @param type A type to test for * @param type A type to test for
* @param mustHave true if creature should have type, false if it should not * @param mustHave true if creature should have type, false if it should not
*/ */
public void assertType(String cardName, CardType type, boolean mustHave) throws AssertionError { public void assertType(String cardName, CardType type, boolean mustHave) throws AssertionError {
@ -999,8 +998,8 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement
* Assert whether a permanent is a specified type * Assert whether a permanent is a specified type
* *
* @param cardName Name of the permanent that should be checked. * @param cardName Name of the permanent that should be checked.
* @param type A type to test for * @param type A type to test for
* @param subType a subtype to test for * @param subType a subtype to test for
*/ */
public void assertType(String cardName, CardType type, SubType subType) throws AssertionError { public void assertType(String cardName, CardType type, SubType subType) throws AssertionError {
//Assert.assertNotEquals("", cardName); //Assert.assertNotEquals("", cardName);
@ -1015,7 +1014,7 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement
* Assert whether a permanent is not a specified type * Assert whether a permanent is not a specified type
* *
* @param cardName Name of the permanent that should be checked. * @param cardName Name of the permanent that should be checked.
* @param type A type to test for * @param type A type to test for
*/ */
public void assertNotType(String cardName, CardType type) throws AssertionError { public void assertNotType(String cardName, CardType type) throws AssertionError {
//Assert.assertNotEquals("", cardName); //Assert.assertNotEquals("", cardName);
@ -1027,7 +1026,7 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement
* Assert whether a permanent is not a specified subtype * Assert whether a permanent is not a specified subtype
* *
* @param cardName Name of the permanent that should be checked. * @param cardName Name of the permanent that should be checked.
* @param subType a subtype to test for * @param subType a subtype to test for
*/ */
public void assertNotSubtype(String cardName, SubType subType) throws AssertionError { public void assertNotSubtype(String cardName, SubType subType) throws AssertionError {
//Assert.assertNotEquals("", cardName); //Assert.assertNotEquals("", cardName);
@ -1040,10 +1039,10 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement
/** /**
* Assert permanent color * Assert permanent color
* *
* @param player player to check * @param player player to check
* @param cardName card name on battlefield from player * @param cardName card name on battlefield from player
* @param searchColors colors list with searchable values * @param searchColors colors list with searchable values
* @param mustHave must or not must have that colors * @param mustHave must or not must have that colors
*/ */
public void assertColor(Player player, String cardName, ObjectColor searchColors, boolean mustHave) { public void assertColor(Player player, String cardName, ObjectColor searchColors, boolean mustHave) {
//Assert.assertNotEquals("", cardName); //Assert.assertNotEquals("", cardName);
@ -1078,7 +1077,7 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement
* Assert whether a permanent is tapped or not * Assert whether a permanent is tapped or not
* *
* @param cardName Name of the permanent that should be checked. * @param cardName Name of the permanent that should be checked.
* @param tapped Whether the permanent is tapped or not * @param tapped Whether the permanent is tapped or not
*/ */
public void assertTapped(String cardName, boolean tapped) throws AssertionError { public void assertTapped(String cardName, boolean tapped) throws AssertionError {
//Assert.assertNotEquals("", cardName); //Assert.assertNotEquals("", cardName);
@ -1105,8 +1104,8 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement
* Assert whether X permanents of the same name are tapped or not. * Assert whether X permanents of the same name are tapped or not.
* *
* @param cardName Name of the permanent that should be checked. * @param cardName Name of the permanent that should be checked.
* @param tapped Whether the permanent is tapped or not * @param tapped Whether the permanent is tapped or not
* @param count The amount of this permanents that should be tapped * @param count The amount of this permanents that should be tapped
*/ */
public void assertTappedCount(String cardName, boolean tapped, int count) throws AssertionError { public void assertTappedCount(String cardName, boolean tapped, int count) throws AssertionError {
//Assert.assertNotEquals("", cardName); //Assert.assertNotEquals("", cardName);
@ -1128,7 +1127,7 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement
/** /**
* Assert whether a permanent is attacking or not * Assert whether a permanent is attacking or not
* *
* @param cardName Name of the permanent that should be checked. * @param cardName Name of the permanent that should be checked.
* @param attacking Whether the permanent is attacking or not * @param attacking Whether the permanent is attacking or not
*/ */
public void assertAttacking(String cardName, boolean attacking) throws AssertionError { public void assertAttacking(String cardName, boolean attacking) throws AssertionError {
@ -1150,7 +1149,7 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement
* Assert card count in player's hand. * Assert card count in player's hand.
* *
* @param player {@link Player} who's hand should be counted. * @param player {@link Player} who's hand should be counted.
* @param count Expected count. * @param count Expected count.
*/ */
public void assertHandCount(Player player, int count) throws AssertionError { public void assertHandCount(Player player, int count) throws AssertionError {
int actual = currentGame.getPlayer(player.getId()).getHand().size(); int actual = currentGame.getPlayer(player.getId()).getHand().size();
@ -1160,9 +1159,9 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement
/** /**
* Assert card count in player's hand. * Assert card count in player's hand.
* *
* @param player {@link Player} who's hand should be counted. * @param player {@link Player} who's hand should be counted.
* @param cardName Name of the cards that should be counted. * @param cardName Name of the cards that should be counted.
* @param count Expected count. * @param count Expected count.
*/ */
public void assertHandCount(Player player, String cardName, int count) throws AssertionError { public void assertHandCount(Player player, String cardName, int count) throws AssertionError {
//Assert.assertNotEquals("", cardName); //Assert.assertNotEquals("", cardName);
@ -1210,7 +1209,7 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement
* Assert card count in player's graveyard. * Assert card count in player's graveyard.
* *
* @param player {@link Player} who's graveyard should be counted. * @param player {@link Player} who's graveyard should be counted.
* @param count Expected count. * @param count Expected count.
*/ */
public void assertGraveyardCount(Player player, int count) throws AssertionError { public void assertGraveyardCount(Player player, int count) throws AssertionError {
int actual = currentGame.getPlayer(player.getId()).getGraveyard().size(); int actual = currentGame.getPlayer(player.getId()).getGraveyard().size();
@ -1221,7 +1220,7 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement
* Assert card count in exile. * Assert card count in exile.
* *
* @param cardName Name of the cards that should be counted. * @param cardName Name of the cards that should be counted.
* @param count Expected count. * @param count Expected count.
*/ */
public void assertExileCount(String cardName, int count) throws AssertionError { public void assertExileCount(String cardName, int count) throws AssertionError {
//Assert.assertNotEquals("", cardName); //Assert.assertNotEquals("", cardName);
@ -1259,9 +1258,9 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement
/** /**
* Assert card count in player's exile. * Assert card count in player's exile.
* *
* @param owner {@link Player} who's exile should be counted. * @param owner {@link Player} who's exile should be counted.
* @param cardName Name of the cards that should be counted. * @param cardName Name of the cards that should be counted.
* @param count Expected count. * @param count Expected count.
*/ */
public void assertExileCount(Player owner, String cardName, int count) throws AssertionError { public void assertExileCount(Player owner, String cardName, int count) throws AssertionError {
//Assert.assertNotEquals("", cardName); //Assert.assertNotEquals("", cardName);
@ -1280,9 +1279,9 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement
/** /**
* Assert card count in player's graveyard. * Assert card count in player's graveyard.
* *
* @param player {@link Player} who's graveyard should be counted. * @param player {@link Player} who's graveyard should be counted.
* @param cardName Name of the cards that should be counted. * @param cardName Name of the cards that should be counted.
* @param count Expected count. * @param count Expected count.
*/ */
public void assertGraveyardCount(Player player, String cardName, int count) throws AssertionError { public void assertGraveyardCount(Player player, String cardName, int count) throws AssertionError {
assertAliaseSupportInActivateCommand(cardName, true); assertAliaseSupportInActivateCommand(cardName, true);
@ -1301,7 +1300,7 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement
* Assert library card count. * Assert library card count.
* *
* @param player {@link Player} who's library should be counted. * @param player {@link Player} who's library should be counted.
* @param count Expected count. * @param count Expected count.
*/ */
public void assertLibraryCount(Player player, int count) throws AssertionError { public void assertLibraryCount(Player player, int count) throws AssertionError {
List<Card> libraryList = player.getLibrary().getCards(currentGame); List<Card> libraryList = player.getLibrary().getCards(currentGame);
@ -1312,9 +1311,9 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement
/** /**
* Assert specific card count in player's library. * Assert specific card count in player's library.
* *
* @param player {@link Player} who's library should be counted. * @param player {@link Player} who's library should be counted.
* @param cardName Name of the cards that should be counted. * @param cardName Name of the cards that should be counted.
* @param count Expected count. * @param count Expected count.
*/ */
public void assertLibraryCount(Player player, String cardName, int count) throws AssertionError { public void assertLibraryCount(Player player, String cardName, int count) throws AssertionError {
//Assert.assertNotEquals("", cardName); //Assert.assertNotEquals("", cardName);
@ -1353,8 +1352,8 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement
} }
/** /**
* Raise error on any unused commands, choices or targets * Raise error on any unused commands, choices or targets If you want to
* If you want to test that ability can't be activated then use call checkPlayableAbility() * test that ability can't be activated then use call checkPlayableAbility()
* *
* @throws AssertionError * @throws AssertionError
*/ */
@ -1501,8 +1500,8 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement
* @param player * @param player
* @param cardName * @param cardName
* @param targetName for modes you can add "mode=3" before target name, * @param targetName for modes you can add "mode=3" before target name,
* multiple targets can be seperated by ^, not target marks as * multiple targets can be seperated by ^, not target marks as
* TestPlayer.NO_TARGET * TestPlayer.NO_TARGET
*/ */
public void castSpell(int turnNum, PhaseStep step, TestPlayer player, String cardName, String targetName) { public void castSpell(int turnNum, PhaseStep step, TestPlayer player, String cardName, String targetName) {
//Assert.assertNotEquals("", cardName); //Assert.assertNotEquals("", cardName);
@ -1525,8 +1524,8 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement
* @param step * @param step
* @param player * @param player
* @param cardName * @param cardName
* @param targetName for modal spells add the mode to the name e.g. * @param targetName for modal spells add the mode to the name e.g.
* "mode=2SilvercoatLion^mode3=PillarfieldOx" * "mode=2SilvercoatLion^mode3=PillarfieldOx"
* @param spellOnStack * @param spellOnStack
*/ */
public void castSpell(int turnNum, PhaseStep step, TestPlayer player, String cardName, String targetName, String spellOnStack) { public void castSpell(int turnNum, PhaseStep step, TestPlayer player, String cardName, String targetName, String spellOnStack) {
@ -1613,7 +1612,7 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement
* @param step * @param step
* @param player * @param player
* @param ability * @param ability
* @param targetName use NO_TARGET if there is no target to set * @param targetName use NO_TARGET if there is no target to set
* @param spellOnStack * @param spellOnStack
*/ */
public void activateAbility(int turnNum, PhaseStep step, TestPlayer player, String ability, String targetName, String spellOnStack) { public void activateAbility(int turnNum, PhaseStep step, TestPlayer player, String ability, String targetName, String spellOnStack) {
@ -1626,8 +1625,8 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement
* @param step * @param step
* @param player * @param player
* @param ability * @param ability
* @param targetName if not target has to be defined use the constant * @param targetName if not target has to be defined use the constant
* NO_TARGET * NO_TARGET
* @param spellOnStack * @param spellOnStack
* @param clause * @param clause
*/ */
@ -1691,9 +1690,12 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement
} }
/** /**
* For use choices set "Yes" or "No" the the choice string. For X values set * For use choices set "Yes" or "No" the the choice string.<br>
* "X=[xValue]" example: for X=3 set choice string to "X=3". * For X values set "X=[xValue]" example: for X=3 set choice string to
* <br>For ColorChoice use "Red", "Green", "Blue", "Black" or "White" * "X=3".<br>
* For ColorChoice use "Red", "Green", "Blue", "Black" or "White"<br>
* use command setModeChoice if you have to set a mode from modal
* ability<br>
* *
* @param player * @param player
* @param choice * @param choice
@ -1713,10 +1715,10 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement
* *
* @param player * @param player
* @param choice starting with "1" for mode 1, "2" for mode 2 and so on (to * @param choice starting with "1" for mode 1, "2" for mode 2 and so on (to
* set multiple modes call the command multiple times). If a spell mode can * set multiple modes call the command multiple times). If a spell mode can
* be used only once like Demonic Pact, the value has to be set to the * be used only once like Demonic Pact, the value has to be set to the
* number of the remaining modes (e.g. if only 2 are left the number need to * number of the remaining modes (e.g. if only 2 are left the number need to
* be 1 or 2). * be 1 or 2).
*/ */
public void setModeChoice(TestPlayer player, String choice) { public void setModeChoice(TestPlayer player, String choice) {
player.addModeChoice(choice); player.addModeChoice(choice);
@ -1727,12 +1729,12 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement
* *
* @param player * @param player
* @param target you can add multiple targets by separating them by the "^" * @param target you can add multiple targets by separating them by the "^"
* character e.g. "creatureName1^creatureName2" you can qualify the target * character e.g. "creatureName1^creatureName2" you can qualify the target
* additional by setcode e.g. "creatureName-M15" you can add [no copy] to * additional by setcode e.g. "creatureName-M15" you can add [no copy] to
* the end of the target name to prohibit targets that are copied you can * the end of the target name to prohibit targets that are copied you can
* add [only copy] to the end of the target name to allow only targets that * add [only copy] to the end of the target name to allow only targets that
* are copies. For modal spells use a prefix with the mode number: * are copies. For modal spells use a prefix with the mode number:
* mode=1Lightning Bolt^mode=2Silvercoat Lion * mode=1Lightning Bolt^mode=2Silvercoat Lion
*/ */
// TODO: mode options doesn't work here (see BrutalExpulsionTest) // TODO: mode options doesn't work here (see BrutalExpulsionTest)
public void addTarget(TestPlayer player, String target) { public void addTarget(TestPlayer player, String target) {
@ -1764,7 +1766,7 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement
/** /**
* @param player * @param player
* @param target use TestPlayer.TARGET_SKIP to 0 targets selects or to stop * @param target use TestPlayer.TARGET_SKIP to 0 targets selects or to stop
* "up two xxx" selection * "up two xxx" selection
* @param amount * @param amount
*/ */
public void addTargetAmount(TestPlayer player, String target, int amount) { public void addTargetAmount(TestPlayer player, String target, int amount) {

View file

@ -1,72 +1,73 @@
/* /*
* To change this license header, choose License Headers in Project Properties. * To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates * To change this template file, choose Tools | Templates
* and open the template in the editor. * and open the template in the editor.
*/ */
package mage.abilities.effects.common; package mage.abilities.effects.common;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.effects.ContinuousEffect; import mage.abilities.effects.ContinuousEffect;
import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.continuous.GainControlTargetEffect; import mage.abilities.effects.common.continuous.GainControlTargetEffect;
import mage.constants.Duration; import mage.constants.Duration;
import mage.constants.Outcome; import mage.constants.Outcome;
import static mage.constants.Outcome.Benefit; import static mage.constants.Outcome.Benefit;
import mage.game.Game; import mage.game.Game;
import mage.game.permanent.Permanent; import mage.game.permanent.Permanent;
import mage.players.Player; import mage.players.Player;
import mage.target.Target; import mage.target.Target;
import mage.target.common.TargetOpponent; import mage.target.common.TargetOpponent;
import mage.target.targetpointer.FixedTarget; import mage.target.targetpointer.FixedTarget;
/** /**
* Use this effect only with EntersBattlefieldAbility like abilities * Use this effect only with EntersBattlefieldAbility like abilities
* *
* @author LevelX2 * @author LevelX2
*/ */
public class EntersBattlefieldUnderControlOfOpponentOfChoiceEffect extends OneShotEffect {
public class EntersBattlefieldUnderControlOfOpponentOfChoiceEffect extends OneShotEffect {
public EntersBattlefieldUnderControlOfOpponentOfChoiceEffect() {
public EntersBattlefieldUnderControlOfOpponentOfChoiceEffect() { super(Benefit);
super(Benefit); staticText = "under the control of an opponent of your choice";
staticText = "under the control of an opponent of your choice"; }
}
private EntersBattlefieldUnderControlOfOpponentOfChoiceEffect(final EntersBattlefieldUnderControlOfOpponentOfChoiceEffect effect) {
private EntersBattlefieldUnderControlOfOpponentOfChoiceEffect(final EntersBattlefieldUnderControlOfOpponentOfChoiceEffect effect) { super(effect);
super(effect); }
}
@Override
@Override public EntersBattlefieldUnderControlOfOpponentOfChoiceEffect copy() {
public EntersBattlefieldUnderControlOfOpponentOfChoiceEffect copy() { return new EntersBattlefieldUnderControlOfOpponentOfChoiceEffect(this);
return new EntersBattlefieldUnderControlOfOpponentOfChoiceEffect(this); }
}
@Override
@Override public boolean apply(Game game, Ability source) {
public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId());
Player controller = game.getPlayer(source.getControllerId()); if (controller == null) {
if (controller == null) { return false;
return false; }
} Target target = new TargetOpponent();
Target target = new TargetOpponent(); target.setNotTarget(true);
target.setNotTarget(true); if (!controller.choose(Outcome.Benefit, target, source.getSourceId(), game)) {
if (!controller.choose(Outcome.Benefit, target, source.getSourceId(), game)) { return false;
return false; }
} Player opponent = game.getPlayer(target.getFirstTarget());
Player opponent = game.getPlayer(target.getFirstTarget()); if (opponent == null) {
if (opponent == null) { return false;
return false; }
} Permanent permanent = game.getPermanentEntering(source.getSourceId());
Permanent permanent = game.getPermanentEntering(source.getSourceId()); if (permanent != null) {
if (permanent != null) { permanent.setOriginalControllerId(opponent.getId()); // permanent was controlled by this player since the existance of this object so original controller has to be set to the first controller
game.informPlayers(permanent.getLogName() + " enters the battlefield under the control of " + opponent.getLogName()); permanent.setControllerId(opponent.getId()); // neccessary to set already here because spell caster never controlled the permanent (important for rule 800.4a)
} game.informPlayers(permanent.getLogName() + " enters the battlefield under the control of " + opponent.getLogName());
ContinuousEffect continuousEffect = new GainControlTargetEffect( }
Duration.Custom, true, opponent.getId() ContinuousEffect continuousEffect = new GainControlTargetEffect(
); Duration.Custom, true, opponent.getId()
continuousEffect.setTargetPointer(new FixedTarget( );
source.getSourceId(), source.getSourceObjectZoneChangeCounter() continuousEffect.setTargetPointer(new FixedTarget(
)); source.getSourceId(), source.getSourceObjectZoneChangeCounter()
game.addEffect(continuousEffect, source); ));
return true; game.addEffect(continuousEffect, source);
} return true;
} }
}

View file

@ -1,5 +1,9 @@
package mage.game; package mage.game;
import java.io.IOException;
import java.io.Serializable;
import java.util.*;
import java.util.Map.Entry;
import mage.MageException; import mage.MageException;
import mage.MageObject; import mage.MageObject;
import mage.abilities.*; import mage.abilities.*;
@ -67,11 +71,6 @@ import mage.util.functions.ApplyToPermanent;
import mage.watchers.common.*; import mage.watchers.common.*;
import org.apache.log4j.Logger; import org.apache.log4j.Logger;
import java.io.IOException;
import java.io.Serializable;
import java.util.*;
import java.util.Map.Entry;
public abstract class GameImpl implements Game, Serializable { public abstract class GameImpl implements Game, Serializable {
private static final int ROLLBACK_TURNS_MAX = 4; private static final int ROLLBACK_TURNS_MAX = 4;
@ -562,7 +561,7 @@ public abstract class GameImpl implements Game, Serializable {
@Override @Override
public void saveState(boolean bookmark) { public void saveState(boolean bookmark) {
if (!simulation && gameStates != null) { if (!simulation && gameStates != null) {
if (bookmark || saveGame) { if (bookmark || saveGame) {
gameStates.save(state); gameStates.save(state);
} }
@ -1549,7 +1548,7 @@ public abstract class GameImpl implements Game, Serializable {
/** /**
* @param emblem * @param emblem
* @param sourceObject * @param sourceObject
* @param toPlayerId controller and owner of the emblem * @param toPlayerId controller and owner of the emblem
*/ */
@Override @Override
public void addEmblem(Emblem emblem, MageObject sourceObject, UUID toPlayerId) { public void addEmblem(Emblem emblem, MageObject sourceObject, UUID toPlayerId) {
@ -1567,8 +1566,8 @@ public abstract class GameImpl implements Game, Serializable {
/** /**
* @param plane * @param plane
* @param sourceObject * @param sourceObject
* @param toPlayerId controller and owner of the plane (may only be one per * @param toPlayerId controller and owner of the plane (may only be one per
* game..) * game..)
* @return boolean - whether the plane was added successfully or not * @return boolean - whether the plane was added successfully or not
*/ */
@Override @Override
@ -1642,7 +1641,7 @@ public abstract class GameImpl implements Game, Serializable {
newBluePrint.reset(this); newBluePrint.reset(this);
//getState().addCard(permanent); //getState().addCard(permanent);
if (copyFromPermanent.isMorphed() || copyFromPermanent.isManifested() if (copyFromPermanent.isMorphed() || copyFromPermanent.isManifested()
|| copyFromPermanent.isFaceDown(this)) { || copyFromPermanent.isFaceDown(this)) {
MorphAbility.setPermanentToFaceDownCreature(newBluePrint); MorphAbility.setPermanentToFaceDownCreature(newBluePrint);
} }
@ -1805,7 +1804,7 @@ public abstract class GameImpl implements Game, Serializable {
break; break;
} }
// triggered abilities that don't use the stack have to be executed first (e.g. Banisher Priest Return exiled creature // triggered abilities that don't use the stack have to be executed first (e.g. Banisher Priest Return exiled creature
for (Iterator<TriggeredAbility> it = abilities.iterator(); it.hasNext(); ) { for (Iterator<TriggeredAbility> it = abilities.iterator(); it.hasNext();) {
TriggeredAbility triggeredAbility = it.next(); TriggeredAbility triggeredAbility = it.next();
if (!triggeredAbility.isUsesStack()) { if (!triggeredAbility.isUsesStack()) {
state.removeTriggeredAbility(triggeredAbility); state.removeTriggeredAbility(triggeredAbility);
@ -1880,7 +1879,7 @@ public abstract class GameImpl implements Game, Serializable {
Zone currentZone = this.getState().getZone(card.getId()); Zone currentZone = this.getState().getZone(card.getId());
String currentZoneInfo = (currentZone == null ? "(error)" : "(" + currentZone.name() + ")"); String currentZoneInfo = (currentZone == null ? "(error)" : "(" + currentZone.name() + ")");
if (player.chooseUse(Outcome.Benefit, "Move " + card.getIdName() if (player.chooseUse(Outcome.Benefit, "Move " + card.getIdName()
+ " to the command zone or leave it in current zone " + currentZoneInfo + "?", "You can only make this choice once per object", + " to the command zone or leave it in current zone " + currentZoneInfo + "?", "You can only make this choice once per object",
"Move to command", "Leave in current zone " + currentZoneInfo, null, this)) { "Move to command", "Leave in current zone " + currentZoneInfo, null, this)) {
toMove.add(card); toMove.add(card);
} else { } else {
@ -2596,7 +2595,7 @@ public abstract class GameImpl implements Game, Serializable {
} }
//20100423 - 800.4a //20100423 - 800.4a
Set<Card> toOutside = new HashSet<>(); Set<Card> toOutside = new HashSet<>();
for (Iterator<Permanent> it = getBattlefield().getAllPermanents().iterator(); it.hasNext(); ) { for (Iterator<Permanent> it = getBattlefield().getAllPermanents().iterator(); it.hasNext();) {
Permanent perm = it.next(); Permanent perm = it.next();
if (perm.isOwnedBy(playerId)) { if (perm.isOwnedBy(playerId)) {
if (perm.getAttachedTo() != null) { if (perm.getAttachedTo() != null) {
@ -2621,6 +2620,10 @@ public abstract class GameImpl implements Game, Serializable {
for (ContinuousEffect effect : getContinuousEffects().getLayeredEffects(this)) { for (ContinuousEffect effect : getContinuousEffects().getLayeredEffects(this)) {
if (effect.hasLayer(Layer.ControlChangingEffects_2)) { if (effect.hasLayer(Layer.ControlChangingEffects_2)) {
for (Ability ability : getContinuousEffects().getLayeredEffectAbilities(effect)) { for (Ability ability : getContinuousEffects().getLayeredEffectAbilities(effect)) {
if (effect.getTargetPointer().getTargets(this, ability).contains(perm.getId())) {
effect.discard();
continue Effects;
}
for (Target target : ability.getTargets()) { for (Target target : ability.getTargets()) {
for (UUID targetId : target.getTargets()) { for (UUID targetId : target.getTargets()) {
if (targetId.equals(perm.getId())) { if (targetId.equals(perm.getId())) {
@ -2630,6 +2633,7 @@ public abstract class GameImpl implements Game, Serializable {
} }
} }
} }
} }
} }
} }
@ -2641,7 +2645,7 @@ public abstract class GameImpl implements Game, Serializable {
player.moveCards(toOutside, Zone.OUTSIDE, null, this); player.moveCards(toOutside, Zone.OUTSIDE, null, this);
// triggered abilities that don't use the stack have to be executed // triggered abilities that don't use the stack have to be executed
List<TriggeredAbility> abilities = state.getTriggered(player.getId()); List<TriggeredAbility> abilities = state.getTriggered(player.getId());
for (Iterator<TriggeredAbility> it = abilities.iterator(); it.hasNext(); ) { for (Iterator<TriggeredAbility> it = abilities.iterator(); it.hasNext();) {
TriggeredAbility triggeredAbility = it.next(); TriggeredAbility triggeredAbility = it.next();
if (!triggeredAbility.isUsesStack()) { if (!triggeredAbility.isUsesStack()) {
state.removeTriggeredAbility(triggeredAbility); state.removeTriggeredAbility(triggeredAbility);
@ -2661,7 +2665,7 @@ public abstract class GameImpl implements Game, Serializable {
// Remove cards from the player in all exile zones // Remove cards from the player in all exile zones
for (ExileZone exile : this.getExile().getExileZones()) { for (ExileZone exile : this.getExile().getExileZones()) {
for (Iterator<UUID> it = exile.iterator(); it.hasNext(); ) { for (Iterator<UUID> it = exile.iterator(); it.hasNext();) {
Card card = this.getCard(it.next()); Card card = this.getCard(it.next());
if (card != null && card.isOwnedBy(playerId)) { if (card != null && card.isOwnedBy(playerId)) {
it.remove(); it.remove();
@ -2671,7 +2675,7 @@ public abstract class GameImpl implements Game, Serializable {
//Remove all commander/emblems/plane the player controls //Remove all commander/emblems/plane the player controls
boolean addPlaneAgain = false; boolean addPlaneAgain = false;
for (Iterator<CommandObject> it = this.getState().getCommand().iterator(); it.hasNext(); ) { for (Iterator<CommandObject> it = this.getState().getCommand().iterator(); it.hasNext();) {
CommandObject obj = it.next(); CommandObject obj = it.next();
if (obj.isControlledBy(playerId)) { if (obj.isControlledBy(playerId)) {
if (obj instanceof Emblem) { if (obj instanceof Emblem) {

View file

@ -1,5 +1,8 @@
package mage.game.permanent; package mage.game.permanent;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import mage.MageObject; import mage.MageObject;
import mage.MageObjectReference; import mage.MageObjectReference;
import mage.abilities.Ability; import mage.abilities.Ability;
@ -10,12 +13,10 @@ import mage.game.Controllable;
import mage.game.Game; import mage.game.Game;
import mage.game.GameState; import mage.game.GameState;
import java.util.List;
import java.util.Set;
import java.util.UUID;
public interface Permanent extends Card, Controllable { public interface Permanent extends Card, Controllable {
void setOriginalControllerId(UUID controllerId);
void setControllerId(UUID controllerId); void setControllerId(UUID controllerId);
boolean isTapped(); boolean isTapped();
@ -103,7 +104,8 @@ public interface Permanent extends Card, Controllable {
/** /**
* @param source * @param source
* @param game * @param game
* @param silentMode - use it to ignore warning message for users (e.g. for checking only) * @param silentMode - use it to ignore warning message for users (e.g. for
* checking only)
* @return * @return
*/ */
boolean cantBeAttachedBy(MageObject source, Game game, boolean silentMode); boolean cantBeAttachedBy(MageObject source, Game game, boolean silentMode);
@ -217,7 +219,7 @@ public interface Permanent extends Card, Controllable {
/** /**
* @param defenderId id of planeswalker or player to attack - can be empty * @param defenderId id of planeswalker or player to attack - can be empty
* to check generally * to check generally
* @param game * @param game
* @return * @return
*/ */

View file

@ -1,5 +1,7 @@
package mage.game.permanent; package mage.game.permanent;
import java.io.Serializable;
import java.util.*;
import mage.MageObject; import mage.MageObject;
import mage.MageObjectReference; import mage.MageObjectReference;
import mage.ObjectColor; import mage.ObjectColor;
@ -38,9 +40,6 @@ import mage.util.GameLog;
import mage.util.ThreadLocalStringBuilder; import mage.util.ThreadLocalStringBuilder;
import org.apache.log4j.Logger; import org.apache.log4j.Logger;
import java.io.Serializable;
import java.util.*;
/** /**
* @author BetaSteward_at_googlemail.com * @author BetaSteward_at_googlemail.com
*/ */
@ -184,6 +183,11 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
abilities.setControllerId(controllerId); abilities.setControllerId(controllerId);
} }
@Override
public void setOriginalControllerId(UUID originalControllerId) {
this.originalControllerId = originalControllerId;
}
/** /**
* Called before each applyEffects or if after a permanent was copied for * Called before each applyEffects or if after a permanent was copied for
* the copied object * the copied object
@ -793,7 +797,7 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
this.attachedTo = attachToObjectId; this.attachedTo = attachToObjectId;
this.attachedToZoneChangeCounter = game.getState().getZoneChangeCounter(attachToObjectId); this.attachedToZoneChangeCounter = game.getState().getZoneChangeCounter(attachToObjectId);
for (Ability ability : this.getAbilities()) { for (Ability ability : this.getAbilities()) {
for (Iterator<Effect> ite = ability.getEffects(game, EffectType.CONTINUOUS).iterator(); ite.hasNext(); ) { for (Iterator<Effect> ite = ability.getEffects(game, EffectType.CONTINUOUS).iterator(); ite.hasNext();) {
ContinuousEffect effect = (ContinuousEffect) ite.next(); ContinuousEffect effect = (ContinuousEffect) ite.next();
game.getContinuousEffects().setOrder(effect); game.getContinuousEffects().setOrder(effect);
// It's important to update the timestamp of the copied effect in ContinuousEffects because it does the action // It's important to update the timestamp of the copied effect in ContinuousEffects because it does the action
@ -848,8 +852,8 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
* @param game * @param game
* @param preventable * @param preventable
* @param combat * @param combat
* @param markDamage If true, damage will be dealt later in applyDamage * @param markDamage If true, damage will be dealt later in applyDamage
* method * method
* @return * @return
*/ */
private int damage(int damageAmount, UUID sourceId, Game game, boolean preventable, boolean combat, boolean markDamage, List<UUID> appliedEffects) { private int damage(int damageAmount, UUID sourceId, Game game, boolean preventable, boolean combat, boolean markDamage, List<UUID> appliedEffects) {
@ -1066,9 +1070,9 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
if (game.getPlayer(this.getControllerId()).hasOpponent(sourceControllerId, game) if (game.getPlayer(this.getControllerId()).hasOpponent(sourceControllerId, game)
&& game.getContinuousEffects().asThough(this.getId(), AsThoughEffectType.HEXPROOF, null, sourceControllerId, game) == null && game.getContinuousEffects().asThough(this.getId(), AsThoughEffectType.HEXPROOF, null, sourceControllerId, game) == null
&& abilities.stream() && abilities.stream()
.filter(HexproofBaseAbility.class::isInstance) .filter(HexproofBaseAbility.class::isInstance)
.map(HexproofBaseAbility.class::cast) .map(HexproofBaseAbility.class::cast)
.anyMatch(ability -> ability.checkObject(source, game))) { .anyMatch(ability -> ability.checkObject(source, game))) {
return false; return false;
} }
@ -1619,9 +1623,9 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
public boolean moveToExile(UUID exileId, String name, UUID sourceId, Game game, List<UUID> appliedEffects) { public boolean moveToExile(UUID exileId, String name, UUID sourceId, Game game, List<UUID> appliedEffects) {
Zone fromZone = game.getState().getZone(objectId); Zone fromZone = game.getState().getZone(objectId);
ZoneChangeEvent event = new ZoneChangeEvent(this, sourceId, ownerId, fromZone, Zone.EXILED, appliedEffects); ZoneChangeEvent event = new ZoneChangeEvent(this, sourceId, ownerId, fromZone, Zone.EXILED, appliedEffects);
ZoneChangeInfo.Exile info = new ZoneChangeInfo.Exile(event, exileId, name); ZoneChangeInfo.Exile zcInfo = new ZoneChangeInfo.Exile(event, exileId, name);
boolean successfullyMoved = ZonesHandler.moveCard(info, game); boolean successfullyMoved = ZonesHandler.moveCard(zcInfo, game);
//20180810 - 701.3d //20180810 - 701.3d
detachAllAttachments(game); detachAllAttachments(game);
return successfullyMoved; return successfullyMoved;

View file

@ -1,6 +1,10 @@
package mage.players; package mage.players;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import java.io.Serializable;
import java.util.*;
import java.util.Map.Entry;
import java.util.stream.Collectors;
import mage.ConditionalMana; import mage.ConditionalMana;
import mage.MageObject; import mage.MageObject;
import mage.MageObjectReference; import mage.MageObjectReference;
@ -66,11 +70,6 @@ import mage.util.GameLog;
import mage.util.RandomUtil; import mage.util.RandomUtil;
import org.apache.log4j.Logger; import org.apache.log4j.Logger;
import java.io.Serializable;
import java.util.*;
import java.util.Map.Entry;
import java.util.stream.Collectors;
public abstract class PlayerImpl implements Player, Serializable { public abstract class PlayerImpl implements Player, Serializable {
private static final Logger logger = Logger.getLogger(PlayerImpl.class); private static final Logger logger = Logger.getLogger(PlayerImpl.class);
@ -614,9 +613,9 @@ public abstract class PlayerImpl implements Player, Serializable {
&& this.hasOpponent(sourceControllerId, game) && this.hasOpponent(sourceControllerId, game)
&& game.getContinuousEffects().asThough(this.getId(), AsThoughEffectType.HEXPROOF, null, sourceControllerId, game) == null && game.getContinuousEffects().asThough(this.getId(), AsThoughEffectType.HEXPROOF, null, sourceControllerId, game) == null
&& abilities.stream() && abilities.stream()
.filter(HexproofBaseAbility.class::isInstance) .filter(HexproofBaseAbility.class::isInstance)
.map(HexproofBaseAbility.class::cast) .map(HexproofBaseAbility.class::cast)
.anyMatch(ability -> ability.checkObject(source, game))) { .anyMatch(ability -> ability.checkObject(source, game))) {
return false; return false;
} }
@ -656,7 +655,7 @@ public abstract class PlayerImpl implements Player, Serializable {
game.informPlayers(getLogName() + " discards down to " game.informPlayers(getLogName() + " discards down to "
+ this.maxHandSize + this.maxHandSize
+ (this.maxHandSize == 1 + (this.maxHandSize == 1
? " hand card" : " hand cards")); ? " hand card" : " hand cards"));
} }
discard(hand.size() - this.maxHandSize, false, null, game); discard(hand.size() - this.maxHandSize, false, null, game);
} }
@ -805,7 +804,7 @@ public abstract class PlayerImpl implements Player, Serializable {
} }
GameEvent gameEvent = GameEvent.getEvent(GameEvent.EventType.DISCARD_CARD, GameEvent gameEvent = GameEvent.getEvent(GameEvent.EventType.DISCARD_CARD,
card.getId(), source == null card.getId(), source == null
? null : source.getSourceId(), playerId); ? null : source.getSourceId(), playerId);
gameEvent.setFlag(source != null); // event from effect or from cost (source == null) gameEvent.setFlag(source != null); // event from effect or from cost (source == null)
if (game.replaceEvent(gameEvent, source)) { if (game.replaceEvent(gameEvent, source)) {
return false; return false;
@ -1842,9 +1841,9 @@ public abstract class PlayerImpl implements Player, Serializable {
} }
private List<Permanent> getPermanentsThatCanBeUntapped(Game game, private List<Permanent> getPermanentsThatCanBeUntapped(Game game,
List<Permanent> canBeUntapped, List<Permanent> canBeUntapped,
RestrictionUntapNotMoreThanEffect handledEffect, RestrictionUntapNotMoreThanEffect handledEffect,
Map<Entry<RestrictionUntapNotMoreThanEffect, Set<Ability>>, Integer> notMoreThanEffectsUsage) { Map<Entry<RestrictionUntapNotMoreThanEffect, Set<Ability>>, Integer> notMoreThanEffectsUsage) {
List<Permanent> leftForUntap = new ArrayList<>(); List<Permanent> leftForUntap = new ArrayList<>();
// select permanents that can still be untapped // select permanents that can still be untapped
for (Permanent permanent : canBeUntapped) { for (Permanent permanent : canBeUntapped) {
@ -2553,7 +2552,7 @@ public abstract class PlayerImpl implements Player, Serializable {
@Override @Override
public boolean searchLibrary(TargetCardInLibrary target, Ability source, Game game, UUID targetPlayerId, public boolean searchLibrary(TargetCardInLibrary target, Ability source, Game game, UUID targetPlayerId,
boolean triggerEvents) { boolean triggerEvents) {
//20091005 - 701.14c //20091005 - 701.14c
Library searchedLibrary = null; Library searchedLibrary = null;
String searchInfo = null; String searchInfo = null;
@ -2755,7 +2754,7 @@ public abstract class PlayerImpl implements Player, Serializable {
/** /**
* @param game * @param game
* @param appliedEffects * @param appliedEffects
* @param numSides Number of sides the dice has * @param numSides Number of sides the dice has
* @return the number that the player rolled * @return the number that the player rolled
*/ */
@Override @Override
@ -2792,16 +2791,16 @@ public abstract class PlayerImpl implements Player, Serializable {
/** /**
* @param game * @param game
* @param appliedEffects * @param appliedEffects
* @param numberChaosSides The number of chaos sides the planar die * @param numberChaosSides The number of chaos sides the planar die
* currently has (normally 1 but can be 5) * currently has (normally 1 but can be 5)
* @param numberPlanarSides The number of chaos sides the planar die * @param numberPlanarSides The number of chaos sides the planar die
* currently has (normally 1) * currently has (normally 1)
* @return the outcome that the player rolled. Either ChaosRoll, PlanarRoll * @return the outcome that the player rolled. Either ChaosRoll, PlanarRoll
* or NilRoll * or NilRoll
*/ */
@Override @Override
public PlanarDieRoll rollPlanarDie(Game game, List<UUID> appliedEffects, int numberChaosSides, public PlanarDieRoll rollPlanarDie(Game game, List<UUID> appliedEffects, int numberChaosSides,
int numberPlanarSides) { int numberPlanarSides) {
int result = RandomUtil.nextInt(9) + 1; int result = RandomUtil.nextInt(9) + 1;
PlanarDieRoll roll = PlanarDieRoll.NIL_ROLL; PlanarDieRoll roll = PlanarDieRoll.NIL_ROLL;
if (numberChaosSides + numberPlanarSides > 9) { if (numberChaosSides + numberPlanarSides > 9) {
@ -2958,7 +2957,8 @@ public abstract class PlayerImpl implements Player, Serializable {
/** /**
* @param ability * @param ability
* @param availableMana if null, it won't be checked if enough mana is available * @param availableMana if null, it won't be checked if enough mana is
* available
* @param sourceObject * @param sourceObject
* @param game * @param game
* @return * @return
@ -3290,6 +3290,17 @@ public abstract class PlayerImpl implements Player, Serializable {
return getPlayable(game, hidden, Zone.ALL, true); return getPlayable(game, hidden, Zone.ALL, true);
} }
/**
* Returns a list of all available spells and abilities the player can
* currently cast/activate with his available ressources
*
* @param game
* @param hidden also from hidden objects (e.g. turned face down cards ?)
* @param fromZone of objects from which zone (ALL = from all zones)
* @param hideDuplicatedAbilities if equal abilities exist return only the
* first instance
* @return
*/
public List<ActivatedAbility> getPlayable(Game game, boolean hidden, Zone fromZone, boolean hideDuplicatedAbilities) { public List<ActivatedAbility> getPlayable(Game game, boolean hidden, Zone fromZone, boolean hideDuplicatedAbilities) {
List<ActivatedAbility> playable = new ArrayList<>(); List<ActivatedAbility> playable = new ArrayList<>();
if (shouldSkipGettingPlayable(game)) { if (shouldSkipGettingPlayable(game)) {
@ -3655,7 +3666,7 @@ public abstract class PlayerImpl implements Player, Serializable {
@Override @Override
public boolean canPaySacrificeCost(Permanent permanent, UUID sourceId, public boolean canPaySacrificeCost(Permanent permanent, UUID sourceId,
UUID controllerId, Game game UUID controllerId, Game game
) { ) {
return sacrificeCostFilter == null || !sacrificeCostFilter.match(permanent, sourceId, controllerId, game); return sacrificeCostFilter == null || !sacrificeCostFilter.match(permanent, sourceId, controllerId, game);
} }
@ -3808,8 +3819,8 @@ public abstract class PlayerImpl implements Player, Serializable {
@Override @Override
public boolean moveCards(Card card, Zone toZone, public boolean moveCards(Card card, Zone toZone,
Ability source, Game game, Ability source, Game game,
boolean tapped, boolean faceDown, boolean byOwner, List<UUID> appliedEffects boolean tapped, boolean faceDown, boolean byOwner, List<UUID> appliedEffects
) { ) {
Set<Card> cardList = new HashSet<>(); Set<Card> cardList = new HashSet<>();
if (card != null) { if (card != null) {
@ -3820,22 +3831,22 @@ public abstract class PlayerImpl implements Player, Serializable {
@Override @Override
public boolean moveCards(Cards cards, Zone toZone, public boolean moveCards(Cards cards, Zone toZone,
Ability source, Game game Ability source, Game game
) { ) {
return moveCards(cards.getCards(game), toZone, source, game); return moveCards(cards.getCards(game), toZone, source, game);
} }
@Override @Override
public boolean moveCards(Set<Card> cards, Zone toZone, public boolean moveCards(Set<Card> cards, Zone toZone,
Ability source, Game game Ability source, Game game
) { ) {
return moveCards(cards, toZone, source, game, false, false, false, null); return moveCards(cards, toZone, source, game, false, false, false, null);
} }
@Override @Override
public boolean moveCards(Set<Card> cards, Zone toZone, public boolean moveCards(Set<Card> cards, Zone toZone,
Ability source, Game game, Ability source, Game game,
boolean tapped, boolean faceDown, boolean byOwner, List<UUID> appliedEffects boolean tapped, boolean faceDown, boolean byOwner, List<UUID> appliedEffects
) { ) {
if (cards.isEmpty()) { if (cards.isEmpty()) {
return true; return true;
@ -3937,8 +3948,8 @@ public abstract class PlayerImpl implements Player, Serializable {
@Override @Override
public boolean moveCardsToExile(Card card, Ability source, public boolean moveCardsToExile(Card card, Ability source,
Game game, boolean withName, UUID exileId, Game game, boolean withName, UUID exileId,
String exileZoneName String exileZoneName
) { ) {
Set<Card> cards = new HashSet<>(); Set<Card> cards = new HashSet<>();
cards.add(card); cards.add(card);
@ -3947,8 +3958,8 @@ public abstract class PlayerImpl implements Player, Serializable {
@Override @Override
public boolean moveCardsToExile(Set<Card> cards, Ability source, public boolean moveCardsToExile(Set<Card> cards, Ability source,
Game game, boolean withName, UUID exileId, Game game, boolean withName, UUID exileId,
String exileZoneName String exileZoneName
) { ) {
if (cards.isEmpty()) { if (cards.isEmpty()) {
return true; return true;
@ -3964,14 +3975,14 @@ public abstract class PlayerImpl implements Player, Serializable {
@Override @Override
public boolean moveCardToHandWithInfo(Card card, UUID sourceId, public boolean moveCardToHandWithInfo(Card card, UUID sourceId,
Game game Game game
) { ) {
return this.moveCardToHandWithInfo(card, sourceId, game, true); return this.moveCardToHandWithInfo(card, sourceId, game, true);
} }
@Override @Override
public boolean moveCardToHandWithInfo(Card card, UUID sourceId, public boolean moveCardToHandWithInfo(Card card, UUID sourceId,
Game game, boolean withName Game game, boolean withName
) { ) {
boolean result = false; boolean result = false;
Zone fromZone = game.getState().getZone(card.getId()); Zone fromZone = game.getState().getZone(card.getId());
@ -3996,7 +4007,7 @@ public abstract class PlayerImpl implements Player, Serializable {
@Override @Override
public Set<Card> moveCardsToGraveyardWithInfo(Set<Card> allCards, Ability source, public Set<Card> moveCardsToGraveyardWithInfo(Set<Card> allCards, Ability source,
Game game, Zone fromZone Game game, Zone fromZone
) { ) {
UUID sourceId = source == null ? null : source.getSourceId(); UUID sourceId = source == null ? null : source.getSourceId();
Set<Card> movedCards = new LinkedHashSet<>(); Set<Card> movedCards = new LinkedHashSet<>();
@ -4004,7 +4015,7 @@ public abstract class PlayerImpl implements Player, Serializable {
// identify cards from one owner // identify cards from one owner
Cards cards = new CardsImpl(); Cards cards = new CardsImpl();
UUID ownerId = null; UUID ownerId = null;
for (Iterator<Card> it = allCards.iterator(); it.hasNext(); ) { for (Iterator<Card> it = allCards.iterator(); it.hasNext();) {
Card card = it.next(); Card card = it.next();
if (cards.isEmpty()) { if (cards.isEmpty()) {
ownerId = card.getOwnerId(); ownerId = card.getOwnerId();
@ -4067,7 +4078,7 @@ public abstract class PlayerImpl implements Player, Serializable {
@Override @Override
public boolean moveCardToGraveyardWithInfo(Card card, UUID sourceId, public boolean moveCardToGraveyardWithInfo(Card card, UUID sourceId,
Game game, Zone fromZone Game game, Zone fromZone
) { ) {
if (card == null) { if (card == null) {
return false; return false;
@ -4096,8 +4107,8 @@ public abstract class PlayerImpl implements Player, Serializable {
@Override @Override
public boolean moveCardToLibraryWithInfo(Card card, UUID sourceId, public boolean moveCardToLibraryWithInfo(Card card, UUID sourceId,
Game game, Zone fromZone, Game game, Zone fromZone,
boolean toTop, boolean withName boolean toTop, boolean withName
) { ) {
if (card == null) { if (card == null) {
return false; return false;
@ -4162,7 +4173,7 @@ public abstract class PlayerImpl implements Player, Serializable {
@Override @Override
public boolean moveCardToExileWithInfo(Card card, UUID exileId, String exileName, UUID sourceId, public boolean moveCardToExileWithInfo(Card card, UUID exileId, String exileName, UUID sourceId,
Game game, Zone fromZone, boolean withName) { Game game, Zone fromZone, boolean withName) {
if (card == null) { if (card == null) {
return false; return false;
} }
@ -4185,7 +4196,7 @@ public abstract class PlayerImpl implements Player, Serializable {
game.informPlayers(this.getLogName() + " moves " + (withName ? card.getLogName() game.informPlayers(this.getLogName() + " moves " + (withName ? card.getLogName()
+ (card.isCopy() ? " (Copy)" : "") : "a card face down") + ' ' + (card.isCopy() ? " (Copy)" : "") : "a card face down") + ' '
+ (fromZone != null ? "from " + fromZone.toString().toLowerCase(Locale.ENGLISH) + (fromZone != null ? "from " + fromZone.toString().toLowerCase(Locale.ENGLISH)
+ ' ' : "") + "to the exile zone"); + ' ' : "") + "to the exile zone");
} }
result = true; result = true;