1
0
Fork 0
mirror of https://github.com/correl/mage.git synced 2025-04-10 17:00:08 -09:00

* 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 ).

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
Mage.Tests/src/test/java/org/mage/test
Mage/src/main/java/mage

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;