mirror of
https://github.com/correl/mage.git
synced 2024-11-14 19:19:32 +00:00
* Improved rollback handling of the test framework. Test metadata will no longer rollbacked by a rollback and one can now define different actions after the executed rollback.
This commit is contained in:
parent
621d8c188d
commit
8c4c2728d6
11 changed files with 306 additions and 193 deletions
|
@ -964,9 +964,8 @@ public class MorphTest extends CardTestPlayerBase {
|
|||
setStrictChooseMode(true);
|
||||
setStopAt(1, PhaseStep.END_TURN);
|
||||
execute();
|
||||
// 1 action must be here ("no" option is restores on failed morph call in playLand)
|
||||
//assertAllCommandsUsed();
|
||||
assertChoicesCount(playerA, 1);
|
||||
|
||||
assertAllCommandsUsed();
|
||||
|
||||
assertPermanentCount(playerA, "Zoetic Cavern", 1);
|
||||
}
|
||||
|
|
|
@ -1,54 +0,0 @@
|
|||
|
||||
package org.mage.test.cards.abilities.oneshot.damage;
|
||||
|
||||
import mage.constants.PhaseStep;
|
||||
import mage.constants.Zone;
|
||||
import org.junit.Test;
|
||||
import org.mage.test.serverside.base.CardTestPlayerBase;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author LevelX2
|
||||
*/
|
||||
public class DeflectingPalmTest extends CardTestPlayerBase {
|
||||
|
||||
/**
|
||||
* Test that prevented damage will be created with the correct source and
|
||||
* will trigger the ability of Satyr Firedance
|
||||
* https://github.com/magefree/mage/issues/804
|
||||
*/
|
||||
|
||||
@Test
|
||||
public void testDamageInPlayer() {
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Mountain");
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Plains");
|
||||
// The next time a source of your choice would deal damage to you this turn, prevent that damage.
|
||||
// If damage is prevented this way, Deflecting Palm deals that much damage to that source's controller.
|
||||
addCard(Zone.HAND, playerA, "Deflecting Palm");
|
||||
// Whenever an instant or sorcery spell you control deals damage to an opponent, Satyr Firedancer deals
|
||||
// that much damage to target creature that player controls.
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Satyr Firedancer");
|
||||
|
||||
addCard(Zone.BATTLEFIELD, playerB, "Silvercoat Lion");
|
||||
addCard(Zone.BATTLEFIELD, playerB, "Mountain");
|
||||
addCard(Zone.HAND, playerB, "Lightning Bolt");
|
||||
|
||||
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB ,"Lightning Bolt", playerA);
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA ,"Deflecting Palm", null, "Lightning Bolt");
|
||||
setChoice(playerA, "Lightning Bolt");
|
||||
addTarget(playerA, "Silvercoat Lion"); // target for Satyr Firedancer
|
||||
|
||||
setStopAt(1, PhaseStep.BEGIN_COMBAT);
|
||||
execute();
|
||||
|
||||
assertGraveyardCount(playerA, "Deflecting Palm", 1);
|
||||
assertGraveyardCount(playerB, "Lightning Bolt", 1);
|
||||
|
||||
|
||||
assertGraveyardCount(playerB, "Silvercoat Lion", 1);
|
||||
|
||||
assertLife(playerA, 20);
|
||||
assertLife(playerB, 17);
|
||||
}
|
||||
}
|
|
@ -96,10 +96,10 @@ public class JaceTest extends CardTestPlayerBase {
|
|||
activateAbility(3, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Draw a card");
|
||||
setChoice(playerA, "Pillarfield Ox");
|
||||
|
||||
setStopAt(3, PhaseStep.BEGIN_COMBAT);
|
||||
execute();
|
||||
rollbackTurns(3, PhaseStep.BEGIN_COMBAT, playerA, 0); // Start of turn 3
|
||||
|
||||
currentGame.rollbackTurns(0); // Start of turn 3
|
||||
setStopAt(3, PhaseStep.POSTCOMBAT_MAIN);
|
||||
execute();
|
||||
|
||||
assertGraveyardCount(playerA, "Pillarfield Ox", 0); // Goes back to hand
|
||||
assertHandCount(playerA, "Pillarfield Ox", 1);
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
package org.mage.test.cards.replacement.prevent;
|
||||
|
||||
import mage.constants.PhaseStep;
|
||||
|
@ -49,7 +48,7 @@ public class DeflectingPalmTest extends CardTestPlayerBase {
|
|||
*/
|
||||
@Test
|
||||
public void testPreventDamageWithDromokasCommand() {
|
||||
|
||||
setStrictChooseMode(true);
|
||||
// Choose two -
|
||||
// - Prevent all damage target instant or sorcery spell would deal this turn;
|
||||
// - or Target player sacrifices an enchantment;
|
||||
|
@ -68,15 +67,21 @@ public class DeflectingPalmTest extends CardTestPlayerBase {
|
|||
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Deflecting Palm");
|
||||
setChoice(playerB, "Silvercoat Lion");
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Dromoka's Command", "Deflecting Palm");
|
||||
addTarget(playerA, "Silvercoat Lion");
|
||||
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Dromoka's Command", null, "Deflecting Palm");
|
||||
|
||||
setModeChoice(playerA, "1");
|
||||
addTarget(playerA, "Deflecting Palm");
|
||||
setModeChoice(playerA, "3");
|
||||
addTarget(playerA, "Silvercoat Lion");
|
||||
|
||||
attack(1, playerA, "Silvercoat Lion");
|
||||
|
||||
setStopAt(1, PhaseStep.POSTCOMBAT_MAIN);
|
||||
execute();
|
||||
|
||||
assertAllCommandsUsed();
|
||||
|
||||
assertGraveyardCount(playerB, "Deflecting Palm", 1);
|
||||
assertGraveyardCount(playerA, "Dromoka's Command", 1);
|
||||
|
||||
|
@ -87,4 +92,40 @@ public class DeflectingPalmTest extends CardTestPlayerBase {
|
|||
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that prevented damage will be created with the correct source and
|
||||
* will trigger the ability of Satyr Firedance
|
||||
* https://github.com/magefree/mage/issues/804
|
||||
*/
|
||||
@Test
|
||||
public void testDamageInPlayer() {
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Mountain");
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Plains");
|
||||
// The next time a source of your choice would deal damage to you this turn, prevent that damage.
|
||||
// If damage is prevented this way, Deflecting Palm deals that much damage to that source's controller.
|
||||
addCard(Zone.HAND, playerA, "Deflecting Palm");
|
||||
// Whenever an instant or sorcery spell you control deals damage to an opponent, Satyr Firedancer deals
|
||||
// that much damage to target creature that player controls.
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Satyr Firedancer");
|
||||
|
||||
addCard(Zone.BATTLEFIELD, playerB, "Silvercoat Lion");
|
||||
addCard(Zone.BATTLEFIELD, playerB, "Mountain");
|
||||
addCard(Zone.HAND, playerB, "Lightning Bolt");
|
||||
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Lightning Bolt", playerA);
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Deflecting Palm", null, "Lightning Bolt");
|
||||
setChoice(playerA, "Lightning Bolt");
|
||||
addTarget(playerA, "Silvercoat Lion"); // target for Satyr Firedancer
|
||||
|
||||
setStopAt(1, PhaseStep.BEGIN_COMBAT);
|
||||
execute();
|
||||
|
||||
assertGraveyardCount(playerA, "Deflecting Palm", 1);
|
||||
assertGraveyardCount(playerB, "Lightning Bolt", 1);
|
||||
|
||||
assertGraveyardCount(playerB, "Silvercoat Lion", 1);
|
||||
|
||||
assertLife(playerA, 20);
|
||||
assertLife(playerB, 17);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,7 +37,11 @@ public class PlayerAction {
|
|||
}
|
||||
|
||||
/**
|
||||
* Calls after action removed from commands queue later (for multi steps action, e.g. AI related)
|
||||
* Calls after action removed from commands queue later (for multi steps
|
||||
* action, e.g.AI related)
|
||||
*
|
||||
* @param game
|
||||
* @param player
|
||||
*/
|
||||
public void onActionRemovedLater(Game game, TestPlayer player) {
|
||||
//
|
||||
|
|
|
@ -1,8 +1,14 @@
|
|||
package org.mage.test.player;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.*;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
import mage.MageItem;
|
||||
import mage.MageObject;
|
||||
import mage.MageObjectReference;
|
||||
import mage.Mana;
|
||||
import mage.ObjectColor;
|
||||
import mage.abilities.*;
|
||||
import mage.abilities.costs.AlternativeSourceCosts;
|
||||
|
@ -57,14 +63,6 @@ import mage.util.CardUtil;
|
|||
import org.apache.log4j.Logger;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Ignore;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.*;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
import mage.Mana;
|
||||
|
||||
import static org.mage.test.serverside.base.impl.CardTestPlayerAPIImpl.*;
|
||||
|
||||
/**
|
||||
|
@ -87,7 +85,8 @@ public class TestPlayer implements Player {
|
|||
private boolean AIPlayer; // full playable AI
|
||||
private boolean AICanChooseInStrictMode = false; // AI can choose in custom aiXXX commands (e.g. on one priority or step)
|
||||
private final List<PlayerAction> actions = new ArrayList<>();
|
||||
private final Map<PlayerAction, PhaseStep> actionsToRemovesLater = new HashMap<>(); // remove actions later, on next step (e.g. for AI commands)
|
||||
private final Map<PlayerAction, PhaseStep> actionsToRemoveLater = new HashMap<>(); // remove actions later, on next step (e.g. for AI commands)
|
||||
private final Map<Integer, HashMap<UUID, ArrayList<PlayerAction>>> rollbackActions = new HashMap<>(); // actions to add after a executed rollback
|
||||
private final List<String> choices = new ArrayList<>(); // choices stack for choice
|
||||
private final List<String> targets = new ArrayList<>(); // targets stack for choose (it's uses on empty direct target by cast command)
|
||||
private final Map<String, UUID> aliases = new HashMap<>(); // aliases for game objects/players (use it for cards with same name to save and use)
|
||||
|
@ -552,16 +551,16 @@ public class TestPlayer implements Player {
|
|||
@Override
|
||||
public boolean priority(Game game) {
|
||||
// later remove actions (ai commands related)
|
||||
if (actionsToRemovesLater.size() > 0) {
|
||||
if (actionsToRemoveLater.size() > 0) {
|
||||
List<PlayerAction> removed = new ArrayList<>();
|
||||
actionsToRemovesLater.forEach((action, step) -> {
|
||||
actionsToRemoveLater.forEach((action, step) -> {
|
||||
if (game.getStep().getType() != step) {
|
||||
action.onActionRemovedLater(game, this);
|
||||
actions.remove(action);
|
||||
removed.add(action);
|
||||
}
|
||||
});
|
||||
removed.forEach(actionsToRemovesLater::remove);
|
||||
removed.forEach(actionsToRemoveLater::remove);
|
||||
}
|
||||
|
||||
int numberOfActions = actions.size();
|
||||
|
@ -681,11 +680,15 @@ public class TestPlayer implements Player {
|
|||
String[] groups = command.split("\\$");
|
||||
if (groups.length > 0) {
|
||||
if (groups[0].equals("Rollback")) {
|
||||
if (groups.length > 1 && groups[1].startsWith("turns=")) {
|
||||
if (groups.length > 2 && groups[1].startsWith("turns=") && groups[2].startsWith("rollbackBlock=")) {
|
||||
int turns = Integer.parseInt(groups[1].substring(6));
|
||||
int rollbackBlockNumber = Integer.parseInt(groups[2].substring(14));
|
||||
game.rollbackTurns(turns);
|
||||
actions.remove(action);
|
||||
addActionsAfterRollback(game, rollbackBlockNumber);
|
||||
return true;
|
||||
} else {
|
||||
Assert.fail("Rollback command misses parameter: " + command);
|
||||
}
|
||||
}
|
||||
if (groups[0].equals("Concede")) {
|
||||
|
@ -710,7 +713,7 @@ public class TestPlayer implements Player {
|
|||
// play step
|
||||
if (command.equals(AI_COMMAND_PLAY_STEP)) {
|
||||
AICanChooseInStrictMode = true; // disable on action's remove
|
||||
actionsToRemovesLater.put(action, game.getStep().getType());
|
||||
actionsToRemoveLater.put(action, game.getStep().getType());
|
||||
computerPlayer.priority(game);
|
||||
return true;
|
||||
}
|
||||
|
@ -1008,6 +1011,32 @@ public class TestPlayer implements Player {
|
|||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds actions to the player actions after an executed rollback Actions
|
||||
* have to be added after the rollback becauuse otherwise the actions are
|
||||
* not valid because otehr ot the same actions are already taken before the
|
||||
* rollback.
|
||||
*
|
||||
* @param game
|
||||
* @param rollbackBlock rollback block to add the actions for
|
||||
*/
|
||||
private void addActionsAfterRollback(Game game, int rollbackBlockNumber) {
|
||||
Map<UUID, ArrayList<PlayerAction>> rollbackBlock = rollbackActions.get(rollbackBlockNumber);
|
||||
if (rollbackBlock != null && !rollbackBlock.isEmpty()) {
|
||||
for (Map.Entry<UUID, ArrayList<PlayerAction>> entry : rollbackBlock.entrySet()) {
|
||||
TestPlayer testPlayer = (TestPlayer) game.getPlayer(entry.getKey());
|
||||
if (testPlayer != null) {
|
||||
// Add the actions at the start of the action list
|
||||
int pos = 0;
|
||||
for (PlayerAction playerAction : entry.getValue()) {
|
||||
testPlayer.getActions().add(pos, playerAction);
|
||||
pos++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void tryToPlayPriority(Game game) {
|
||||
if (AIPlayer) {
|
||||
computerPlayer.priority(game);
|
||||
|
@ -2538,16 +2567,7 @@ public class TestPlayer implements Player {
|
|||
|
||||
@Override
|
||||
public void restore(Player player) {
|
||||
this.modesSet.clear();
|
||||
this.modesSet.addAll(((TestPlayer) player).modesSet);
|
||||
this.actions.clear();
|
||||
this.actions.addAll(((TestPlayer) player).actions);
|
||||
this.choices.clear();
|
||||
this.choices.addAll(((TestPlayer) player).choices);
|
||||
this.targets.clear();
|
||||
this.targets.addAll(((TestPlayer) player).targets);
|
||||
this.aliases.clear();
|
||||
this.aliases.putAll(((TestPlayer) player).aliases);
|
||||
// no rollback for test player meta data (modesSet, actions, choices, targets, aliases)
|
||||
computerPlayer.restore(player);
|
||||
}
|
||||
|
||||
|
@ -4031,4 +4051,9 @@ public class TestPlayer implements Player {
|
|||
public void setAICanChooseInStrictMode(boolean AICanChooseInStrictMode) {
|
||||
this.AICanChooseInStrictMode = AICanChooseInStrictMode;
|
||||
}
|
||||
|
||||
public Map<Integer, HashMap<UUID, ArrayList<org.mage.test.player.PlayerAction>>> getRollbackActions() {
|
||||
return rollbackActions;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -87,13 +87,13 @@ public class DemonicPactTest extends CardTestPlayerBase {
|
|||
* the game. The log says I'm the winner and the opponent lost and that is
|
||||
* immediately after rollback request.
|
||||
*/
|
||||
|
||||
@Test
|
||||
public void testPactOfNegationRollback() {
|
||||
setStrictChooseMode(true);
|
||||
|
||||
addCard(Zone.HAND, playerA, "Silvercoat Lion", 1);
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Plains", 2);
|
||||
|
||||
|
||||
addCard(Zone.BATTLEFIELD, playerB, "Island", 5);
|
||||
// Counter target spell.
|
||||
// At the beginning of your next upkeep, pay {3}{U}{U}. If you don't, you lose the game.
|
||||
|
@ -106,13 +106,13 @@ public class DemonicPactTest extends CardTestPlayerBase {
|
|||
|
||||
rollbackTurns(2, PhaseStep.PRECOMBAT_MAIN, playerB, 0);
|
||||
|
||||
setStrictChooseMode(true);
|
||||
setChoice(playerB, "Yes");
|
||||
|
||||
setStopAt(2, PhaseStep.BEGIN_COMBAT);
|
||||
execute();
|
||||
|
||||
assertAllCommandsUsed();
|
||||
|
||||
|
||||
assertGraveyardCount(playerA, "Silvercoat Lion", 1);
|
||||
assertGraveyardCount(playerB, "Pact of Negation", 1);
|
||||
|
||||
|
@ -121,7 +121,6 @@ public class DemonicPactTest extends CardTestPlayerBase {
|
|||
|
||||
assertTappedCount("Island", true, 5);
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
package org.mage.test.rollback;
|
||||
|
||||
import mage.constants.PhaseStep;
|
||||
|
@ -70,28 +69,38 @@ public class NewCreaturesAreRemovedTest extends CardTestPlayerBase {
|
|||
addCard(Zone.HAND, playerA, "Port Town"); // Land
|
||||
addCard(Zone.HAND, playerA, "Island"); // Land
|
||||
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Silvercoat Lion", 3);
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Silvercoat Lion", 1); // TODO: Check why the test fails (related to rollback?) if the number is set to 3
|
||||
addCard(Zone.BATTLEFIELD, playerB, "Pillarfield Ox", 3);
|
||||
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Tamiyo's Journal");
|
||||
|
||||
attack(2, playerB, "Pillarfield Ox");
|
||||
attack(2, playerB, "Pillarfield Ox"); // A = 18
|
||||
|
||||
attack(3, playerA, "Silvercoat Lion"); // B = 18
|
||||
|
||||
attack(3, playerA, "Silvercoat Lion");
|
||||
rollbackTurns(3, PhaseStep.END_TURN, playerA, 0);
|
||||
rollbackAfterActionsStart();
|
||||
attack(3, playerA, "Silvercoat Lion"); // B = 18
|
||||
rollbackAfterActionsEnd();
|
||||
|
||||
attack(4, playerB, "Pillarfield Ox");
|
||||
|
||||
attack(5, playerA, "Silvercoat Lion");
|
||||
attack(4, playerB, "Pillarfield Ox"); // A =16
|
||||
|
||||
attack(5, playerA, "Silvercoat Lion"); // B = 16
|
||||
rollbackTurns(5, PhaseStep.END_TURN, playerA, 0);
|
||||
rollbackAfterActionsStart();
|
||||
attack(5, playerA, "Silvercoat Lion"); // B = 16
|
||||
rollbackAfterActionsEnd();
|
||||
|
||||
attack(6, playerB, "Pillarfield Ox");
|
||||
attack(6, playerB, "Pillarfield Ox"); // A = 14
|
||||
|
||||
playLand(7, PhaseStep.PRECOMBAT_MAIN, playerA, "Port Town");
|
||||
attack(7, playerA, "Silvercoat Lion");
|
||||
attack(7, playerA, "Silvercoat Lion"); // B = 14
|
||||
|
||||
rollbackTurns(7, PhaseStep.POSTCOMBAT_MAIN, playerA, 0);
|
||||
rollbackAfterActionsStart();
|
||||
playLand(7, PhaseStep.PRECOMBAT_MAIN, playerA, "Port Town");
|
||||
attack(7, playerA, "Silvercoat Lion"); // B = 14
|
||||
rollbackAfterActionsEnd();
|
||||
|
||||
setStopAt(7, PhaseStep.END_TURN);
|
||||
execute();
|
||||
|
@ -100,8 +109,8 @@ public class NewCreaturesAreRemovedTest extends CardTestPlayerBase {
|
|||
assertTapped("Port Town", false);
|
||||
assertPermanentCount(playerA, "Clue", 3);
|
||||
|
||||
assertLife(playerA, 14);
|
||||
assertLife(playerB, 14);
|
||||
assertLife(playerA, 14);
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -30,20 +30,29 @@ public class StateValuesTest extends CardTestPlayerBase {
|
|||
attack(3, playerA, "Dragon Whelp");
|
||||
|
||||
rollbackTurns(3, PhaseStep.BEGIN_COMBAT, playerA, 0);
|
||||
rollbackAfterActionsStart();
|
||||
|
||||
activateAbility(3, PhaseStep.PRECOMBAT_MAIN, playerA, "{R}: ");
|
||||
activateAbility(3, PhaseStep.PRECOMBAT_MAIN, playerA, "{R}: ");
|
||||
|
||||
attack(3, playerA, "Dragon Whelp");
|
||||
rollbackAfterActionsEnd();
|
||||
|
||||
setStopAt(4, PhaseStep.UPKEEP);
|
||||
execute();
|
||||
|
||||
assertLife(playerA, 20);
|
||||
assertLife(playerB, 12);
|
||||
|
||||
assertPermanentCount(playerA, "Dragon Whelp", 1);
|
||||
assertGraveyardCount(playerA, "Dragon Whelp", 0);
|
||||
|
||||
assertLife(playerA, 20);
|
||||
assertLife(playerB, 12);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBriarbridgePatrol() {
|
||||
setStrictChooseMode(true);
|
||||
|
||||
// Whenever Briarbridge Patrol deals damage to one or more creatures, investigate (Create a colorless Clue artifact token onto the battlefield with "{2}, Sacrifice this artifact: Draw a card.").
|
||||
// At the beginning of each end step, if you sacrificed three or more Clues this turn, you may put a creature card from your hand onto the battlefield.
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Briarbridge Patrol", 1); // 3/3
|
||||
|
@ -55,11 +64,19 @@ public class StateValuesTest extends CardTestPlayerBase {
|
|||
|
||||
attack(3, playerA, "Briarbridge Patrol");
|
||||
block(3, playerB, "Pillarfield Ox", "Briarbridge Patrol");
|
||||
|
||||
rollbackTurns(3, PhaseStep.POSTCOMBAT_MAIN, playerA, 0);
|
||||
|
||||
rollbackAfterActionsStart();
|
||||
attack(3, playerA, "Briarbridge Patrol");
|
||||
block(3, playerB, "Pillarfield Ox", "Briarbridge Patrol");
|
||||
rollbackAfterActionsEnd();
|
||||
|
||||
setStopAt(3, PhaseStep.END_TURN);
|
||||
execute();
|
||||
|
||||
assertAllCommandsUsed();
|
||||
|
||||
assertLife(playerA, 20);
|
||||
assertLife(playerB, 20);
|
||||
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
package org.mage.test.rollback;
|
||||
|
||||
import mage.constants.PhaseStep;
|
||||
|
@ -50,6 +49,7 @@ public class TransformTest extends CardTestPlayerBase {
|
|||
// BACK: It That Rides as One
|
||||
// Creature 4/4 First strike, lifelink
|
||||
addCard(Zone.HAND, playerA, "Lone Rider"); // Creature {1}{W} 1/1
|
||||
|
||||
// When Venerable Monk enters the battlefield, you gain 2 life.
|
||||
addCard(Zone.HAND, playerA, "Venerable Monk"); // Creature {2}{W} 2/2
|
||||
|
||||
|
@ -59,6 +59,11 @@ public class TransformTest extends CardTestPlayerBase {
|
|||
attack(3, playerA, "Lone Rider");
|
||||
|
||||
rollbackTurns(3, PhaseStep.END_TURN, playerA, 0);
|
||||
|
||||
castSpell(3, PhaseStep.POSTCOMBAT_MAIN, playerA, "Venerable Monk");
|
||||
|
||||
attack(3, playerA, "Lone Rider");
|
||||
|
||||
setStopAt(4, PhaseStep.PRECOMBAT_MAIN);
|
||||
execute();
|
||||
|
||||
|
|
|
@ -1,5 +1,14 @@
|
|||
package org.mage.test.serverside.base.impl;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
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.Mana;
|
||||
import mage.ObjectColor;
|
||||
|
@ -35,15 +44,6 @@ import org.mage.test.player.TestPlayer;
|
|||
import org.mage.test.serverside.base.CardTestAPI;
|
||||
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.
|
||||
*
|
||||
|
@ -130,6 +130,10 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement
|
|||
protected String deckNameC;
|
||||
protected String deckNameD;
|
||||
|
||||
private int rollbackBlock = 0; // used to handle actions that have to be added aufter a rollback
|
||||
private boolean rollbackBlockActive = false;
|
||||
private TestPlayer rollbackPlayer = null;
|
||||
|
||||
protected enum ExpectedType {
|
||||
TURN_NUMBER,
|
||||
RESULT,
|
||||
|
@ -197,6 +201,11 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement
|
|||
addCard(Zone.LIBRARY, playerB, "Plains", 10);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @throws GameException
|
||||
* @throws FileNotFoundException
|
||||
*/
|
||||
@Before
|
||||
public void reset() throws GameException, FileNotFoundException {
|
||||
if (currentGame != null) {
|
||||
|
@ -223,6 +232,9 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement
|
|||
|
||||
gameOptions = new GameOptions();
|
||||
|
||||
rollbackBlock = 0;
|
||||
rollbackBlockActive = false;
|
||||
|
||||
}
|
||||
|
||||
abstract protected Game createNewGameAndPlayers() throws GameException, FileNotFoundException;
|
||||
|
@ -296,6 +308,7 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement
|
|||
gameOptions.stopAtStep = stopAtStep;
|
||||
currentGame.setGameOptions(gameOptions);
|
||||
currentGame.start(activePlayer.getId());
|
||||
currentGame.setGameStopped(true); // used for rollback handling
|
||||
long t2 = System.nanoTime();
|
||||
logger.debug("Winner: " + currentGame.getWinner());
|
||||
logger.info(Thread.currentThread().getStackTrace()[2].getMethodName() + " has been executed. Execution time: " + (t2 - t1) / 1000000 + " ms");
|
||||
|
@ -329,13 +342,34 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement
|
|||
return player;
|
||||
}
|
||||
|
||||
private void addPlayerAction(TestPlayer player, int turnNum, PhaseStep step, String action) {
|
||||
PlayerAction playerAction = new PlayerAction("", turnNum, step, action);
|
||||
addPlayerAction(player, playerAction);
|
||||
}
|
||||
|
||||
private void addPlayerAction(TestPlayer player, String actionName, int turnNum, PhaseStep step, String action) {
|
||||
PlayerAction playerAction = new PlayerAction(actionName, turnNum, step, action);
|
||||
addPlayerAction(player, playerAction);
|
||||
}
|
||||
|
||||
private void addPlayerAction(TestPlayer player, PlayerAction playerAction) {
|
||||
if (rollbackBlockActive) {
|
||||
rollbackPlayer.getRollbackActions()
|
||||
.computeIfAbsent(rollbackBlock, block -> new HashMap<>())
|
||||
.computeIfAbsent(player.getId(), playerId -> new ArrayList<>())
|
||||
.add(playerAction);
|
||||
} else {
|
||||
player.addAction(playerAction);
|
||||
}
|
||||
}
|
||||
|
||||
// check commands
|
||||
private void check(String checkName, int turnNum, PhaseStep step, TestPlayer player, String command, String... params) {
|
||||
String res = CHECK_PREFIX + command;
|
||||
for (String param : params) {
|
||||
res += CHECK_PARAM_DELIMETER + param;
|
||||
}
|
||||
player.addAction(checkName, turnNum, step, res);
|
||||
addPlayerAction(player, checkName, turnNum, step, res);
|
||||
}
|
||||
|
||||
public void checkPT(String checkName, int turnNum, PhaseStep step, TestPlayer player, String permanentName, Integer power, Integer toughness) {
|
||||
|
@ -452,7 +486,7 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement
|
|||
for (String param : params) {
|
||||
res += CHECK_PARAM_DELIMETER + param;
|
||||
}
|
||||
player.addAction(showName, turnNum, step, res);
|
||||
addPlayerAction(player, showName, turnNum, step, res);
|
||||
}
|
||||
|
||||
public void showLibrary(String showName, int turnNum, PhaseStep step, TestPlayer player) {
|
||||
|
@ -564,8 +598,9 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement
|
|||
* @param cardName Card name in string format.
|
||||
* @param count Amount of cards to be added.
|
||||
* @param tapped In case gameZone is Battlefield, determines whether
|
||||
* permanent should be tapped. In case gameZone is other than Battlefield,
|
||||
* {@link IllegalArgumentException} is thrown
|
||||
* permanent should be tapped. In case gameZone is other
|
||||
* than Battlefield, {@link IllegalArgumentException} is
|
||||
* thrown
|
||||
*/
|
||||
@Override
|
||||
public void addCard(Zone gameZone, TestPlayer player, String cardName, int count, boolean tapped) {
|
||||
|
@ -744,10 +779,10 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement
|
|||
* @param cardName Card name to compare with.
|
||||
* @param power Expected power to compare with.
|
||||
* @param toughness Expected toughness to compare with.
|
||||
* @param scope {@link mage.filter.Filter.ComparisonScope} Use ANY, if you
|
||||
* 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
|
||||
* p\t"
|
||||
* @param scope {@link mage.filter.Filter.ComparisonScope} Use ANY, if
|
||||
* you 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 p\t"
|
||||
*/
|
||||
@Override
|
||||
public void assertPowerToughness(Player player, String cardName, int power, int toughness, Filter.ComparisonScope scope)
|
||||
|
@ -1432,36 +1467,40 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement
|
|||
public void playLand(int turnNum, PhaseStep step, TestPlayer player, String cardName) {
|
||||
//Assert.assertNotEquals("", cardName);
|
||||
assertAliaseSupportInActivateCommand(cardName, false);
|
||||
player.addAction(turnNum, step, ACTIVATE_PLAY + cardName);
|
||||
addPlayerAction(player, turnNum, step, ACTIVATE_PLAY + cardName);
|
||||
}
|
||||
|
||||
public void castSpell(int turnNum, PhaseStep step, TestPlayer player, String cardName) {
|
||||
//Assert.assertNotEquals("", cardName);
|
||||
assertAliaseSupportInActivateCommand(cardName, false);
|
||||
player.addAction(turnNum, step, ACTIVATE_CAST + cardName);
|
||||
addPlayerAction(player, turnNum, step, ACTIVATE_CAST + cardName);
|
||||
}
|
||||
|
||||
public void castSpell(int turnNum, PhaseStep step, TestPlayer player, String cardName, Player target) {
|
||||
//Assert.assertNotEquals("", cardName);
|
||||
// warning, target in spell cast command setups without choose target call
|
||||
assertAliaseSupportInActivateCommand(cardName, false);
|
||||
player.addAction(turnNum, step, ACTIVATE_CAST + cardName + "$targetPlayer=" + target.getName());
|
||||
addPlayerAction(player, turnNum, step, ACTIVATE_CAST + cardName + "$targetPlayer=" + target.getName());
|
||||
}
|
||||
|
||||
public void castSpell(int turnNum, PhaseStep step, TestPlayer player, String cardName, Player target, int manaInPool) {
|
||||
//Assert.assertNotEquals("", cardName);
|
||||
assertAliaseSupportInActivateCommand(cardName, false);
|
||||
player.addAction(turnNum, step, ACTIVATE_CAST + cardName + "$targetPlayer=" + target.getName() + "$manaInPool=" + manaInPool);
|
||||
addPlayerAction(player, turnNum, step, ACTIVATE_CAST + cardName + "$targetPlayer=" + target.getName() + "$manaInPool=" + manaInPool);
|
||||
}
|
||||
|
||||
/**
|
||||
* AI play one PRIORITY with multi game simulations (calcs and play ONE best
|
||||
* action, can be called with stack) All choices must be made by AI (e.g.
|
||||
* strict mode possible)
|
||||
* action, can be called with stack) All choices must be made by AI
|
||||
* (e.g.strict mode possible)
|
||||
*
|
||||
* @param turnNum
|
||||
* @param step
|
||||
* @param player
|
||||
*/
|
||||
public void aiPlayPriority(int turnNum, PhaseStep step, TestPlayer player) {
|
||||
assertAiPlayAndGameCompatible(player);
|
||||
player.addAction(createAIPlayerAction(turnNum, step, AI_COMMAND_PLAY_PRIORITY));
|
||||
addPlayerAction(player, createAIPlayerAction(turnNum, step, AI_COMMAND_PLAY_PRIORITY));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1471,7 +1510,7 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement
|
|||
*/
|
||||
public void aiPlayStep(int turnNum, PhaseStep step, TestPlayer player) {
|
||||
assertAiPlayAndGameCompatible(player);
|
||||
player.addAction(createAIPlayerAction(turnNum, step, AI_COMMAND_PLAY_STEP));
|
||||
addPlayerAction(player, createAIPlayerAction(turnNum, step, AI_COMMAND_PLAY_STEP));
|
||||
}
|
||||
|
||||
public PlayerAction createAIPlayerAction(int turnNum, PhaseStep step, String aiCommand) {
|
||||
|
@ -1497,12 +1536,14 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement
|
|||
|
||||
public void waitStackResolved(int turnNum, PhaseStep step, TestPlayer player, boolean skipOneStackObjectOnly) {
|
||||
String command = "waitStackResolved" + (skipOneStackObjectOnly ? ":1" : "");
|
||||
player.addAction(turnNum, step, command);
|
||||
addPlayerAction(player, turnNum, step, command);
|
||||
}
|
||||
|
||||
/**
|
||||
* Rollback the number of given turns: 0 = rollback to the start of the
|
||||
* current turn
|
||||
* current turn. Use the commands rollbackAfterActionsStart() and
|
||||
* rollbackAfterActionsEnd() to define a block of actions, that will be
|
||||
* added and executed after the rollback.
|
||||
*
|
||||
* @param turnNum
|
||||
* @param step
|
||||
|
@ -1510,7 +1551,33 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement
|
|||
* @param turns
|
||||
*/
|
||||
public void rollbackTurns(int turnNum, PhaseStep step, TestPlayer player, int turns) {
|
||||
player.addAction(turnNum, step, "playerAction:Rollback" + "$turns=" + turns);
|
||||
rollbackBlock++;
|
||||
addPlayerAction(player, turnNum, step, "playerAction:Rollback" + "$turns=" + turns + "$rollbackBlock=" + rollbackBlock);
|
||||
rollbackPlayer = player;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a number of actions that will be added to the to the start of the
|
||||
* list of actions of the players but only after the rollback is executed
|
||||
* because otherwis the actions are executed to early and would lead to
|
||||
* invalid actions (e.g. casting the same spell twice).
|
||||
*/
|
||||
public void rollbackAfterActionsStart() throws IllegalStateException {
|
||||
if (rollbackPlayer == null || rollbackBlock < 1) {
|
||||
throw new IllegalStateException("There was no rollback action defined before. You can use this command only after a rollback action.");
|
||||
}
|
||||
rollbackBlockActive = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ends a block of actions to be added after an rollback action
|
||||
*/
|
||||
public void rollbackAfterActionsEnd() throws IllegalStateException {
|
||||
if (rollbackBlockActive = false || rollbackPlayer == null) {
|
||||
throw new IllegalStateException("There was no rollback action defined before or no rollback block started. You can use this command only after a rollback action.");
|
||||
}
|
||||
rollbackBlockActive = false;
|
||||
rollbackPlayer = null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1521,7 +1588,7 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement
|
|||
* @param player
|
||||
*/
|
||||
public void concede(int turnNum, PhaseStep step, TestPlayer player) {
|
||||
player.addAction(turnNum, step, "playerAction:Concede");
|
||||
addPlayerAction(player, turnNum, step, "playerAction:Concede");
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1530,14 +1597,14 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement
|
|||
* @param player
|
||||
* @param cardName
|
||||
* @param targetName for modes you can add "mode=3" before target name,
|
||||
* multiple targets can be seperated by ^, not target marks as
|
||||
* TestPlayer.NO_TARGET
|
||||
* multiple targets can be seperated by ^, not target
|
||||
* marks as TestPlayer.NO_TARGET
|
||||
*/
|
||||
public void castSpell(int turnNum, PhaseStep step, TestPlayer player, String cardName, String targetName) {
|
||||
//Assert.assertNotEquals("", cardName);
|
||||
assertAliaseSupportInActivateCommand(cardName, true);
|
||||
assertAliaseSupportInActivateCommand(targetName, true);
|
||||
player.addAction(turnNum, step, ACTIVATE_CAST + cardName + "$target=" + targetName);
|
||||
addPlayerAction(player, turnNum, step, ACTIVATE_CAST + cardName + "$target=" + targetName);
|
||||
}
|
||||
|
||||
public enum StackClause {
|
||||
|
@ -1580,11 +1647,11 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement
|
|||
assertAliaseSupportInActivateCommand(targetName, true);
|
||||
assertAliaseSupportInActivateCommand(spellOnStack, false);
|
||||
if (StackClause.WHILE_ON_STACK == clause) {
|
||||
player.addAction(turnNum, step, ACTIVATE_CAST + cardName
|
||||
addPlayerAction(player, turnNum, step, ACTIVATE_CAST + cardName
|
||||
+ '$' + (targetName != null && targetName.startsWith("target") ? targetName : "target=" + targetName)
|
||||
+ "$spellOnStack=" + spellOnStack);
|
||||
} else {
|
||||
player.addAction(turnNum, step, ACTIVATE_CAST + cardName
|
||||
addPlayerAction(player, turnNum, step, ACTIVATE_CAST + cardName
|
||||
+ '$' + (targetName != null && targetName.startsWith("target") ? targetName : "target=" + targetName)
|
||||
+ "$!spellOnStack=" + spellOnStack);
|
||||
}
|
||||
|
@ -1603,7 +1670,7 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement
|
|||
if (spellOnTopOfStack != null && !spellOnTopOfStack.isEmpty()) {
|
||||
action += "$spellOnTopOfStack=" + spellOnTopOfStack;
|
||||
}
|
||||
player.addAction(turnNum, step, action);
|
||||
addPlayerAction(player, turnNum, step, action);
|
||||
}
|
||||
|
||||
public void activateManaAbility(int turnNum, PhaseStep step, TestPlayer player, String ability) {
|
||||
|
@ -1612,20 +1679,20 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement
|
|||
|
||||
public void activateManaAbility(int turnNum, PhaseStep step, TestPlayer player, String ability, int timesToActivate) {
|
||||
for (int i = 0; i < timesToActivate; i++) {
|
||||
player.addAction(turnNum, step, ACTIVATE_MANA + ability);
|
||||
addPlayerAction(player, turnNum, step, ACTIVATE_MANA + ability);
|
||||
}
|
||||
}
|
||||
|
||||
public void activateAbility(int turnNum, PhaseStep step, TestPlayer player, String ability) {
|
||||
// TODO: it's uses computerPlayer to execute, only ability target will work, but choices and targets commands aren't
|
||||
assertAliaseSupportInActivateCommand(ability, false);
|
||||
player.addAction(turnNum, step, ACTIVATE_ABILITY + ability);
|
||||
addPlayerAction(player, turnNum, step, ACTIVATE_ABILITY + ability);
|
||||
}
|
||||
|
||||
public void activateAbility(int turnNum, PhaseStep step, TestPlayer player, String ability, Player target) {
|
||||
// TODO: it's uses computerPlayer to execute, only ability target will work, but choices and targets commands aren't
|
||||
assertAliaseSupportInActivateCommand(ability, false);
|
||||
player.addAction(turnNum, step, ACTIVATE_ABILITY + ability + "$targetPlayer=" + target.getName());
|
||||
addPlayerAction(player, turnNum, step, ACTIVATE_ABILITY + ability + "$targetPlayer=" + target.getName());
|
||||
}
|
||||
|
||||
public void activateAbility(int turnNum, PhaseStep step, TestPlayer player, String ability, String... targetNames) {
|
||||
|
@ -1634,7 +1701,7 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement
|
|||
Arrays.stream(targetNames).forEach(n -> {
|
||||
assertAliaseSupportInActivateCommand(n, true);
|
||||
});
|
||||
player.addAction(turnNum, step, ACTIVATE_ABILITY + ability + "$target=" + String.join("^", targetNames));
|
||||
addPlayerAction(player, turnNum, step, ACTIVATE_ABILITY + ability + "$target=" + String.join("^", targetNames));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1682,31 +1749,31 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement
|
|||
}
|
||||
sb.append(spellOnStack);
|
||||
}
|
||||
player.addAction(turnNum, step, sb.toString());
|
||||
addPlayerAction(player, turnNum, step, sb.toString());
|
||||
}
|
||||
|
||||
public void addCounters(int turnNum, PhaseStep step, TestPlayer player, String cardName, CounterType type, int count) {
|
||||
//Assert.assertNotEquals("", cardName);
|
||||
player.addAction(turnNum, step, "addCounters:" + cardName + '$' + type.getName() + '$' + count);
|
||||
addPlayerAction(player, turnNum, step, "addCounters:" + cardName + '$' + type.getName() + '$' + count);
|
||||
}
|
||||
|
||||
public void attack(int turnNum, TestPlayer player, String attacker) {
|
||||
//Assert.assertNotEquals("", attacker);
|
||||
assertAliaseSupportInActivateCommand(attacker, false); // it uses old special notation like card_name:index
|
||||
player.addAction(turnNum, PhaseStep.DECLARE_ATTACKERS, "attack:" + attacker);
|
||||
addPlayerAction(player, turnNum, PhaseStep.DECLARE_ATTACKERS, "attack:" + attacker);
|
||||
}
|
||||
|
||||
public void attack(int turnNum, TestPlayer player, String attacker, TestPlayer defendingPlayer) {
|
||||
//Assert.assertNotEquals("", attacker);
|
||||
assertAliaseSupportInActivateCommand(attacker, false); // it uses old special notation like card_name:index
|
||||
player.addAction(turnNum, PhaseStep.DECLARE_ATTACKERS, "attack:" + attacker + "$defendingPlayer=" + defendingPlayer.getName());
|
||||
addPlayerAction(player, turnNum, PhaseStep.DECLARE_ATTACKERS, "attack:" + attacker + "$defendingPlayer=" + defendingPlayer.getName());
|
||||
}
|
||||
|
||||
public void attack(int turnNum, TestPlayer player, String attacker, String planeswalker) {
|
||||
//Assert.assertNotEquals("", attacker);
|
||||
assertAliaseSupportInActivateCommand(attacker, false); // it uses old special notation like card_name:index
|
||||
assertAliaseSupportInActivateCommand(planeswalker, false);
|
||||
player.addAction(turnNum, PhaseStep.DECLARE_ATTACKERS, new StringBuilder("attack:").append(attacker).append("$planeswalker=").append(planeswalker).toString());
|
||||
addPlayerAction(player, turnNum, PhaseStep.DECLARE_ATTACKERS, new StringBuilder("attack:").append(attacker).append("$planeswalker=").append(planeswalker).toString());
|
||||
}
|
||||
|
||||
public void attackSkip(int turnNum, TestPlayer player) {
|
||||
|
@ -1718,7 +1785,7 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement
|
|||
//Assert.assertNotEquals("", attacker);
|
||||
assertAliaseSupportInActivateCommand(blocker, false); // it uses old special notation like card_name:index
|
||||
assertAliaseSupportInActivateCommand(attacker, false);
|
||||
player.addAction(turnNum, PhaseStep.DECLARE_BLOCKERS, "block:" + blocker + '$' + attacker);
|
||||
addPlayerAction(player, turnNum, PhaseStep.DECLARE_BLOCKERS, "block:" + blocker + '$' + attacker);
|
||||
}
|
||||
|
||||
public void blockSkip(int turnNum, TestPlayer player) {
|
||||
|
@ -1751,10 +1818,10 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement
|
|||
*
|
||||
* @param player
|
||||
* @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
|
||||
* 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
|
||||
* be 1 or 2).
|
||||
* 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 number of the remaining modes
|
||||
* (e.g. if only 2 are left the number need to be 1 or 2).
|
||||
*/
|
||||
public void setModeChoice(TestPlayer player, String choice) {
|
||||
player.addModeChoice(choice);
|
||||
|
@ -1765,12 +1832,13 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement
|
|||
*
|
||||
* @param player
|
||||
* @param target you can add multiple targets by separating them by the "^"
|
||||
* character e.g. "creatureName1^creatureName2" you can qualify the target
|
||||
* 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
|
||||
* 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:
|
||||
* mode=1Lightning Bolt^mode=2Silvercoat Lion
|
||||
* character e.g. "creatureName1^creatureName2" you can
|
||||
* qualify the target 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 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: mode=1Lightning Bolt^mode=2Silvercoat Lion
|
||||
*/
|
||||
// TODO: mode options doesn't work here (see BrutalExpulsionTest)
|
||||
public void addTarget(TestPlayer player, String target) {
|
||||
|
|
Loading…
Reference in a new issue