mirror of
https://github.com/magefree/mage.git
synced 2025-12-23 12:02:01 -08:00
* No more continuous effects stay on battlefield after player leave the game;
Test framework: added real time check for player in game or not;
This commit is contained in:
parent
00633ce055
commit
9ef2e0bda7
5 changed files with 244 additions and 25 deletions
|
|
@ -0,0 +1,132 @@
|
|||
package org.mage.test.cards.continuous;
|
||||
|
||||
import mage.abilities.common.SimpleStaticAbility;
|
||||
import mage.abilities.effects.common.continuous.BoostAllEffect;
|
||||
import mage.constants.Duration;
|
||||
import mage.constants.PhaseStep;
|
||||
import mage.constants.Zone;
|
||||
import org.junit.Test;
|
||||
import org.mage.test.serverside.base.CardTestMultiPlayerBaseWithRangeAll;
|
||||
|
||||
/**
|
||||
* @author JayDi85
|
||||
*/
|
||||
public class PlayerLeavesGameTest extends CardTestMultiPlayerBaseWithRangeAll {
|
||||
|
||||
/*
|
||||
800.4a When a player leaves the game, all objects (see rule 109) owned by that player leave the game and any effects
|
||||
which give that player control of any objects or players end. Then, if that player controlled any objects on the stack
|
||||
not represented by cards, those objects cease to exist. Then, if there are any objects still controlled by that player,
|
||||
those objects are exiled. This is not a state-based action. It happens as soon as the player leaves the game.
|
||||
If the player who left the game had priority at the time he or she left, priority passes to the next player in turn
|
||||
order who’s still in the game.
|
||||
*/
|
||||
|
||||
String cardBear2 = "Balduvian Bears"; // 2/2
|
||||
|
||||
@Test
|
||||
public void test_PlayerLeaveGame() {
|
||||
// Player order: A -> D -> C -> B
|
||||
|
||||
// B must checks A for online status
|
||||
checkPlayerInGame("turn 1", 1, PhaseStep.PRECOMBAT_MAIN, playerD, playerA, true);
|
||||
checkPlayerInGame("turn 2", 2, PhaseStep.PRECOMBAT_MAIN, playerD, playerA, true);
|
||||
checkPlayerInGame("turn 3 before", 3, PhaseStep.PRECOMBAT_MAIN, playerD, playerA, true);
|
||||
|
||||
concede(3, PhaseStep.POSTCOMBAT_MAIN, playerA);
|
||||
|
||||
checkPlayerInGame("turn 3 after", 3, PhaseStep.END_TURN, playerD, playerA, false);
|
||||
checkPlayerInGame("turn 4", 4, PhaseStep.PRECOMBAT_MAIN, playerD, playerA, false);
|
||||
|
||||
setStopAt(4, PhaseStep.CLEANUP);
|
||||
setStrictChooseMode(true);
|
||||
execute();
|
||||
assertAllCommandsUsed();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_PlayerLeaveGameWithOwnPermanent() {
|
||||
// Player order: A -> D -> C -> B
|
||||
|
||||
addCard(Zone.BATTLEFIELD, playerA, cardBear2, 1);
|
||||
|
||||
// B must checks A for online status
|
||||
checkPlayerInGame("turn 1", 1, PhaseStep.PRECOMBAT_MAIN, playerD, playerA, true);
|
||||
checkPermanentCount("turn 1", 1, PhaseStep.PRECOMBAT_MAIN, playerD, playerA, cardBear2, 1);
|
||||
checkPlayerInGame("turn 2", 2, PhaseStep.PRECOMBAT_MAIN, playerD, playerA, true);
|
||||
checkPermanentCount("turn 2", 2, PhaseStep.PRECOMBAT_MAIN, playerD, playerA, cardBear2, 1);
|
||||
checkPlayerInGame("turn 3 before", 3, PhaseStep.PRECOMBAT_MAIN, playerD, playerA, true);
|
||||
checkPermanentCount("turn 3 before", 3, PhaseStep.PRECOMBAT_MAIN, playerD, playerA, cardBear2, 1);
|
||||
|
||||
concede(3, PhaseStep.POSTCOMBAT_MAIN, playerA);
|
||||
|
||||
checkPlayerInGame("turn 3 after", 3, PhaseStep.END_TURN, playerD, playerA, false);
|
||||
checkPermanentCount("turn 3 after", 3, PhaseStep.END_TURN, playerD, playerA, cardBear2, 0);
|
||||
checkPlayerInGame("turn 4", 4, PhaseStep.PRECOMBAT_MAIN, playerD, playerA, false);
|
||||
checkPermanentCount("turn 4", 4, PhaseStep.PRECOMBAT_MAIN, playerD, playerA, cardBear2, 0);
|
||||
|
||||
setStopAt(4, PhaseStep.CLEANUP);
|
||||
setStrictChooseMode(true);
|
||||
execute();
|
||||
assertAllCommandsUsed();
|
||||
}
|
||||
|
||||
private void prepareAndRunLeaveGameWithEffectTest(Duration duration) {
|
||||
// Player order: A -> D -> C -> B
|
||||
addCard(Zone.BATTLEFIELD, playerA, cardBear2, 1);
|
||||
addCard(Zone.BATTLEFIELD, playerD, cardBear2, 1);
|
||||
addCustomCardWithAbility("effect", playerA, new SimpleStaticAbility(new BoostAllEffect(1, 1, duration)));
|
||||
|
||||
// B must checks A for online status
|
||||
checkPlayerInGame(duration.toString() + " - turn 1", 1, PhaseStep.PRECOMBAT_MAIN, playerD, playerA, true);
|
||||
checkPermanentCount(duration.name() + " - turn 1", 1, PhaseStep.PRECOMBAT_MAIN, playerD, playerA, cardBear2, 1);
|
||||
checkPT(duration.name() + " - turn 1", 1, PhaseStep.PRECOMBAT_MAIN, playerD, cardBear2, 3, 3);
|
||||
//
|
||||
checkPlayerInGame(duration.name() + " - turn 2", 2, PhaseStep.PRECOMBAT_MAIN, playerD, playerA, true);
|
||||
checkPermanentCount(duration.name() + " - turn 2", 2, PhaseStep.PRECOMBAT_MAIN, playerD, playerA, cardBear2, 1);
|
||||
checkPT(duration.name() + " - turn 2", 2, PhaseStep.PRECOMBAT_MAIN, playerD, cardBear2, 3, 3);
|
||||
//
|
||||
checkPlayerInGame(duration.name() + " - turn 3 before", 3, PhaseStep.PRECOMBAT_MAIN, playerD, playerA, true);
|
||||
checkPermanentCount(duration.name() + " - turn 3 before", 3, PhaseStep.PRECOMBAT_MAIN, playerD, playerA, cardBear2, 1);
|
||||
checkPT(duration.name() + " - turn 3 before", 3, PhaseStep.PRECOMBAT_MAIN, playerD, cardBear2, 3, 3);
|
||||
//
|
||||
concede(3, PhaseStep.POSTCOMBAT_MAIN, playerA);
|
||||
//
|
||||
checkPlayerInGame(duration.name() + " - turn 3 after", 3, PhaseStep.END_TURN, playerD, playerA, false);
|
||||
checkPermanentCount(duration.name() + " - turn 3 after", 3, PhaseStep.END_TURN, playerD, playerA, cardBear2, 0);
|
||||
checkPT(duration.name() + " - turn 3 after", 3, PhaseStep.END_TURN, playerD, cardBear2, 2, 2);
|
||||
//
|
||||
checkPlayerInGame(duration.name() + " - turn 4", 4, PhaseStep.PRECOMBAT_MAIN, playerD, playerA, false);
|
||||
checkPermanentCount(duration.name() + " - turn 4", 4, PhaseStep.PRECOMBAT_MAIN, playerD, playerA, cardBear2, 0);
|
||||
checkPT(duration.name() + " - turn 4", 4, PhaseStep.PRECOMBAT_MAIN, playerD, cardBear2, 2, 2);
|
||||
|
||||
setStopAt(4, PhaseStep.CLEANUP);
|
||||
setStrictChooseMode(true);
|
||||
execute();
|
||||
assertAllCommandsUsed();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_PlayerLeaveGameWithOwnPermanentAndCustomEffect() {
|
||||
prepareAndRunLeaveGameWithEffectTest(Duration.Custom);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_PlayerLeaveGameWithOwnPermanentAndWhileOnBattlefieldEffect() {
|
||||
prepareAndRunLeaveGameWithEffectTest(Duration.WhileOnBattlefield);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_PlayerLeaveGameWithOwnPermanentAndEndOfGameEffect() {
|
||||
prepareAndRunLeaveGameWithEffectTest(Duration.EndOfGame);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_PlayerLeaveGameWithOwnPermanentAndUntilSourceLeavesBattlefielEffect() {
|
||||
prepareAndRunLeaveGameWithEffectTest(Duration.UntilSourceLeavesBattlefield);
|
||||
}
|
||||
|
||||
// TODO: add leave tests for end of step
|
||||
// TODO: add leave tests for end of turn
|
||||
// TODO: add leave tests for end of your turn
|
||||
}
|
||||
|
|
@ -1,8 +1,6 @@
|
|||
|
||||
package org.mage.test.commander.duel;
|
||||
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import mage.constants.PhaseStep;
|
||||
import mage.constants.Zone;
|
||||
import mage.game.Game;
|
||||
|
|
@ -11,8 +9,9 @@ import org.junit.Assert;
|
|||
import org.junit.Test;
|
||||
import org.mage.test.serverside.base.CardTestCommanderDuelBase;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author LevelX2
|
||||
*/
|
||||
|
||||
|
|
@ -36,11 +35,13 @@ public class TeferiMageOfZhalfirTest extends CardTestCommanderDuelBase {
|
|||
execute();
|
||||
|
||||
assertPermanentCount(playerA, "Daxos of Meletis", 1);
|
||||
|
||||
assertAllCommandsUsed();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCommanderDamage() {
|
||||
setLife(playerA, 20);
|
||||
setLife(playerB, 20);
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Plains", 6);
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Island", 1);
|
||||
// Enchant creature
|
||||
|
|
@ -51,22 +52,28 @@ public class TeferiMageOfZhalfirTest extends CardTestCommanderDuelBase {
|
|||
addCard(Zone.BATTLEFIELD, playerA, "Teferi, Mage of Zhalfir");
|
||||
|
||||
// Daxos of Meletis can't be blocked by creatures with power 3 or greater.
|
||||
// Whenever Daxos of Meletis deals combat damage to a player, exile the top card of that player's library. You gain life equal to that card's converted mana cost. Until end of turn, you may cast that card and you may spend mana as though it were mana of any color to cast it.
|
||||
// Whenever Daxos of Meletis deals combat damage to a player, exile the top card of that player's library.
|
||||
// You gain life equal to that card's converted mana cost. Until end of turn, you may cast that card
|
||||
// and you may spend mana as though it were mana of any color to cast it.
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Daxos of Meletis");
|
||||
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Angelic Destiny", "Daxos of Meletis");
|
||||
|
||||
attack(3, playerA, "Daxos of Meletis");
|
||||
attack(5, playerA, "Daxos of Meletis");
|
||||
attack(7, playerA, "Daxos of Meletis");
|
||||
attack(9, playerA, "Daxos of Meletis");
|
||||
checkPT("before lost", 9, PhaseStep.PRECOMBAT_MAIN, playerA, "Daxos of Meletis", 6, 6);
|
||||
|
||||
setStrictChooseMode(true);
|
||||
setStopAt(9, PhaseStep.POSTCOMBAT_MAIN);
|
||||
execute();
|
||||
assertAllCommandsUsed();
|
||||
|
||||
assertPermanentCount(playerA, "Daxos of Meletis", 1);
|
||||
assertPowerToughness(playerA, "Daxos of Meletis", 6, 6);
|
||||
assertPowerToughness(playerA, "Daxos of Meletis", 6, 6); // no effects removes after game over -- users and tests can get last game state with all affected effects
|
||||
|
||||
Assert.assertEquals("Player A has won because of commander damage", true, playerA.hasWon());
|
||||
Assert.assertEquals("Player A has lost because of commander damage", true, playerB.hasLost());
|
||||
Assert.assertEquals("Player B has lost because of commander damage", true, playerB.hasLost());
|
||||
}
|
||||
}
|
||||
|
|
@ -626,6 +626,13 @@ public class TestPlayer implements Player {
|
|||
wasProccessed = true;
|
||||
}
|
||||
|
||||
// check player in game: target player, must be in game
|
||||
if (params[0].equals(CHECK_COMMAND_PLAYER_IN_GAME) && params.length == 3) {
|
||||
assertPlayerInGame(action, game, game.getPlayer(UUID.fromString(params[1])), Boolean.parseBoolean(params[2]));
|
||||
actions.remove(action);
|
||||
wasProccessed = true;
|
||||
}
|
||||
|
||||
// check ability: card name, ability class, must have
|
||||
if (params[0].equals(CHECK_COMMAND_ABILITY) && params.length == 4) {
|
||||
assertAbility(action, game, computerPlayer, params[1], params[2], Boolean.parseBoolean(params[3]));
|
||||
|
|
@ -633,9 +640,9 @@ public class TestPlayer implements Player {
|
|||
wasProccessed = true;
|
||||
}
|
||||
|
||||
// check battlefield count: card name, count
|
||||
if (params[0].equals(CHECK_COMMAND_PERMANENT_COUNT) && params.length == 3) {
|
||||
assertPermanentCount(action, game, computerPlayer, params[1], Integer.parseInt(params[2]));
|
||||
// check battlefield count: target player, card name, count
|
||||
if (params[0].equals(CHECK_COMMAND_PERMANENT_COUNT) && params.length == 4) {
|
||||
assertPermanentCount(action, game, game.getPlayer(UUID.fromString(params[1])), params[2], Integer.parseInt(params[3]));
|
||||
actions.remove(action);
|
||||
wasProccessed = true;
|
||||
}
|
||||
|
|
@ -928,6 +935,18 @@ public class TestPlayer implements Player {
|
|||
Life, player.getLife());
|
||||
}
|
||||
|
||||
private void assertPlayerInGame(PlayerAction action, Game game, Player targetPlayer, boolean mustBeInGame) {
|
||||
Assert.assertNotNull("Can't find target player", targetPlayer);
|
||||
|
||||
if (targetPlayer.isInGame() && !mustBeInGame) {
|
||||
Assert.fail(action.getActionName() + " - player " + targetPlayer.getName() + " must NOT be in game");
|
||||
}
|
||||
|
||||
if (!targetPlayer.isInGame() && mustBeInGame) {
|
||||
Assert.fail(action.getActionName() + " - player " + targetPlayer.getName() + " must be in game");
|
||||
}
|
||||
}
|
||||
|
||||
private void assertAbility(PlayerAction action, Game game, Player player, String permanentName, String abilityClass, boolean mustHave) {
|
||||
Permanent perm = findPermanentWithAssert(action, game, player, permanentName);
|
||||
|
||||
|
|
|
|||
|
|
@ -64,6 +64,7 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement
|
|||
public static final String CHECK_COMMAND_SUBTYPE = "SUBTYPE";
|
||||
public static final String CHECK_COMMAND_MANA_POOL = "MANA_POOL";
|
||||
public static final String CHECK_COMMAND_ALIAS_ZONE = "ALIAS_ZONE";
|
||||
public static final String CHECK_COMMAND_PLAYER_IN_GAME = "PLAYER_IN_GAME";
|
||||
|
||||
// TODO: add target player param to commands
|
||||
public static final String SHOW_COMMAND_LIBRARY = "LIBRARY";
|
||||
|
|
@ -300,6 +301,10 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement
|
|||
check(checkName, turnNum, step, player, CHECK_COMMAND_LIFE, life.toString());
|
||||
}
|
||||
|
||||
public void checkPlayerInGame(String checkName, int turnNum, PhaseStep step, TestPlayer player, TestPlayer targetPlayer, Boolean mustBeInGame) {
|
||||
check(checkName, turnNum, step, player, CHECK_COMMAND_PLAYER_IN_GAME, targetPlayer.getId().toString(), mustBeInGame.toString());
|
||||
}
|
||||
|
||||
public void checkAbility(String checkName, int turnNum, PhaseStep step, TestPlayer player, String permanentName, Class<?> abilityClass, Boolean mustHave) {
|
||||
//Assert.assertNotEquals("", permanentName);
|
||||
check(checkName, turnNum, step, player, CHECK_COMMAND_ABILITY, permanentName, abilityClass.getName(), mustHave.toString());
|
||||
|
|
@ -307,7 +312,12 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement
|
|||
|
||||
public void checkPermanentCount(String checkName, int turnNum, PhaseStep step, TestPlayer player, String permanentName, Integer count) {
|
||||
//Assert.assertNotEquals("", permanentName);
|
||||
check(checkName, turnNum, step, player, CHECK_COMMAND_PERMANENT_COUNT, permanentName, count.toString());
|
||||
checkPermanentCount(checkName, turnNum, step, player, player, permanentName, count);
|
||||
}
|
||||
|
||||
public void checkPermanentCount(String checkName, int turnNum, PhaseStep step, TestPlayer player, TestPlayer targetPlayer, String permanentName, Integer count) {
|
||||
//Assert.assertNotEquals("", permanentName);
|
||||
check(checkName, turnNum, step, player, CHECK_COMMAND_PERMANENT_COUNT, targetPlayer.getId().toString(), permanentName, count.toString());
|
||||
}
|
||||
|
||||
public void checkPermanentCounters(String checkName, int turnNum, PhaseStep step, TestPlayer player, String permanentName, CounterType counterType, Integer count) {
|
||||
|
|
|
|||
|
|
@ -1,10 +1,13 @@
|
|||
package mage.abilities.effects;
|
||||
|
||||
import mage.MageObject;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.MageSingleton;
|
||||
import mage.cards.Card;
|
||||
import mage.constants.Duration;
|
||||
import mage.constants.Zone;
|
||||
import mage.game.Game;
|
||||
import mage.players.Player;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import java.util.*;
|
||||
|
|
@ -93,6 +96,22 @@ public class ContinuousEffectsList<T extends ContinuousEffect> extends ArrayList
|
|||
}
|
||||
|
||||
private boolean isInactive(T effect, Game game) {
|
||||
// ends all inactive effects -- calls on player leave or apply new effect
|
||||
if (game.getState().isGameOver()) {
|
||||
// no need to remove effects after end -- users and tests must see last game state
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
800.4a When a player leaves the game, all objects (see rule 109) owned by that player leave the game and any effects
|
||||
which give that player control of any objects or players end. Then, if that player controlled any objects on the stack
|
||||
not represented by cards, those objects cease to exist. Then, if there are any objects still controlled by that player,
|
||||
those objects are exiled. This is not a state-based action. It happens as soon as the player leaves the game.
|
||||
If the player who left the game had priority at the time he or she left, priority passes to the next player in turn
|
||||
order who’s still in the game.
|
||||
*/
|
||||
// objects removes doing in player.leave() call... effects removes is here
|
||||
|
||||
Set<Ability> set = effectAbilityMap.get(effect.getId());
|
||||
if (set == null) {
|
||||
logger.debug("No abilities for effect found: " + effect.toString());
|
||||
|
|
@ -108,30 +127,62 @@ public class ContinuousEffectsList<T extends ContinuousEffect> extends ArrayList
|
|||
} else if (effect.isDiscarded()) {
|
||||
it.remove();
|
||||
} else {
|
||||
// 800.4k When a player leaves the game, any continuous effects with durations that last until that
|
||||
// player’s next turn or until a specific point in that turn will last until that turn would have begun.
|
||||
// They neither expire immediately nor last indefinitely.
|
||||
MageObject object = game.getObject(ability.getSourceId());
|
||||
boolean isObjectInGame = ability.getSourceId() == null || object != null; // Commander effects have no sourceId
|
||||
boolean isOwnerLeaveGame = false;
|
||||
if (object instanceof Card) {
|
||||
Player owner = game.getPlayer(((Card) object).getOwnerId());
|
||||
isOwnerLeaveGame = !owner.isInGame();
|
||||
}
|
||||
|
||||
switch (effect.getDuration()) {
|
||||
//
|
||||
case WhileOnBattlefield:
|
||||
case WhileInGraveyard:
|
||||
case WhileOnStack:
|
||||
if (ability.getSourceId() != null && game.getObject(ability.getSourceId()) == null) { // Commander effects have no sourceId
|
||||
it.remove(); // if the related source object does no longer exist in game - the effect has to be removed
|
||||
case EndOfStep:
|
||||
case EndOfCombat:
|
||||
case EndOfGame:
|
||||
// if the related source object does no longer exist in game - the effect has to be removed
|
||||
if (isOwnerLeaveGame || !isObjectInGame) {
|
||||
it.remove();
|
||||
}
|
||||
break;
|
||||
case OneUse:
|
||||
if (effect.isUsed()) {
|
||||
if (isOwnerLeaveGame || effect.isUsed()) {
|
||||
it.remove();
|
||||
}
|
||||
break;
|
||||
case Custom:
|
||||
// custom effects must process it's own inactive method (override), but can'be missied by devs
|
||||
if (isOwnerLeaveGame || effect.isInactive(ability, game)) {
|
||||
it.remove();
|
||||
}
|
||||
break;
|
||||
case EndOfTurn:
|
||||
// end of turn discards on cleanup steps
|
||||
// 514.2
|
||||
break;
|
||||
case UntilYourNextTurn:
|
||||
case UntilEndOfYourNextTurn:
|
||||
// until your turn effects continue until real turn reached, their used it's own inactive method
|
||||
// 514.2 Second, the following actions happen simultaneously: all damage marked on permanents
|
||||
// (including phased-out permanents) is removed and all "until end of turn" and "this turn" effects end.
|
||||
// This turn-based action doesn’t use the stack.
|
||||
if (effect.isInactive(ability, game)) {
|
||||
it.remove();
|
||||
}
|
||||
break;
|
||||
case UntilSourceLeavesBattlefield:
|
||||
if (Zone.BATTLEFIELD != game.getState().getZone(ability.getSourceId())) {
|
||||
if (isOwnerLeaveGame || Zone.BATTLEFIELD != game.getState().getZone(ability.getSourceId())) {
|
||||
it.remove();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException("Effects gets unknown duration " + effect.getDuration() + ", effect: " + effect.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue