This commit is contained in:
Ingmar Goudt 2019-07-14 10:16:35 +02:00
commit 3fa2deaa64
60 changed files with 890 additions and 353 deletions

View file

@ -22,7 +22,7 @@ import mage.game.command.emblems.AjaniAdversaryOfTyrantsEmblem;
import mage.game.command.planes.AkoumPlane; import mage.game.command.planes.AkoumPlane;
import mage.game.match.MatchType; import mage.game.match.MatchType;
import mage.game.mulligan.Mulligan; import mage.game.mulligan.Mulligan;
import mage.game.mulligan.VancouverMulligan; import mage.game.mulligan.MulliganType;
import mage.game.permanent.PermanentCard; import mage.game.permanent.PermanentCard;
import mage.players.Player; import mage.players.Player;
import mage.players.StubPlayer; import mage.players.StubPlayer;
@ -113,7 +113,7 @@ public class TestCardRenderDialog extends MageDialog {
cardsPanel.cleanUp(); cardsPanel.cleanUp();
cardsPanel.setCustomRenderMode(comboRenderMode.getSelectedIndex()); cardsPanel.setCustomRenderMode(comboRenderMode.getSelectedIndex());
Game game = new TestGame(MultiplayerAttackOption.MULTIPLE, RangeOfInfluence.ALL, new VancouverMulligan(0), 20); Game game = new TestGame(MultiplayerAttackOption.MULTIPLE, RangeOfInfluence.ALL, MulliganType.GAME_DEFAULT.getMulligan(0), 20);
Player player = new StubPlayer("player1", RangeOfInfluence.ALL); Player player = new StubPlayer("player1", RangeOfInfluence.ALL);
Deck deck = new Deck(); Deck deck = new Deck();
game.addPlayer(player, deck); game.addPlayer(player, deck);

View file

@ -13,9 +13,9 @@ public class MageVersion implements Serializable, Comparable<MageVersion> {
public static final int MAGE_VERSION_MINOR = 4; public static final int MAGE_VERSION_MINOR = 4;
public static final int MAGE_VERSION_PATCH = 37; public static final int MAGE_VERSION_PATCH = 37;
public static final String MAGE_EDITION_INFO = ""; // set "-beta" for 1.4.32-betaV0 public static final String MAGE_EDITION_INFO = ""; // set "-beta" for 1.4.32-betaV0
public static final String MAGE_VERSION_MINOR_PATCH = "V3"; // default public static final String MAGE_VERSION_MINOR_PATCH = "V4"; // default
// strict mode // strict mode
private static final boolean MAGE_VERSION_MINOR_PATCH_MUST_BE_SAME = false; // set true on uncompatible github changes, set false after new major release (after MAGE_VERSION_PATCH changes) private static final boolean MAGE_VERSION_MINOR_PATCH_MUST_BE_SAME = true; // set true on uncompatible github changes, set false after new major release (after MAGE_VERSION_PATCH changes)
public static final boolean MAGE_VERSION_SHOW_BUILD_TIME = true; public static final boolean MAGE_VERSION_SHOW_BUILD_TIME = true;
private final int major; private final int major;

View file

@ -1,4 +1,3 @@
package mage.game; package mage.game;
import mage.game.match.MatchImpl; import mage.game.match.MatchImpl;
@ -6,7 +5,6 @@ import mage.game.match.MatchOptions;
import mage.game.mulligan.Mulligan; import mage.game.mulligan.Mulligan;
/** /**
*
* @author spjspj * @author spjspj
*/ */
public class BrawlDuelMatch extends MatchImpl { public class BrawlDuelMatch extends MatchImpl {
@ -22,8 +20,6 @@ public class BrawlDuelMatch extends MatchImpl {
BrawlDuel game = new BrawlDuel(options.getAttackOption(), options.getRange(), mulligan, startLife); BrawlDuel game = new BrawlDuel(options.getAttackOption(), options.getRange(), mulligan, startLife);
game.setCheckCommanderDamage(false); game.setCheckCommanderDamage(false);
game.setStartMessage(this.createGameStartMessage()); game.setStartMessage(this.createGameStartMessage());
game.setAlsoHand(true);
game.setAlsoLibrary(true);
initGame(game); initGame(game);
games.add(game); games.add(game);
} }

View file

@ -1,4 +1,3 @@
package mage.game; package mage.game;
import mage.game.match.MatchImpl; import mage.game.match.MatchImpl;
@ -6,7 +5,6 @@ import mage.game.match.MatchOptions;
import mage.game.mulligan.Mulligan; import mage.game.mulligan.Mulligan;
/** /**
*
* @author spjspj * @author spjspj
*/ */
public class BrawlFreeForAllMatch extends MatchImpl { public class BrawlFreeForAllMatch extends MatchImpl {
@ -21,9 +19,7 @@ public class BrawlFreeForAllMatch extends MatchImpl {
Mulligan mulligan = options.getMulliganType().getMulligan(options.getFreeMulligans()); Mulligan mulligan = options.getMulliganType().getMulligan(options.getFreeMulligans());
BrawlFreeForAll game = new BrawlFreeForAll(options.getAttackOption(), options.getRange(), mulligan, startLife); BrawlFreeForAll game = new BrawlFreeForAll(options.getAttackOption(), options.getRange(), mulligan, startLife);
game.setStartMessage(this.createGameStartMessage()); game.setStartMessage(this.createGameStartMessage());
game.setAlsoHand(true);
game.setCheckCommanderDamage(false); game.setCheckCommanderDamage(false);
game.setAlsoLibrary(true);
initGame(game); initGame(game);
games.add(game); games.add(game);
} }

View file

@ -1,4 +1,3 @@
package mage.game; package mage.game;
import mage.game.match.MatchImpl; import mage.game.match.MatchImpl;
@ -6,7 +5,6 @@ import mage.game.match.MatchOptions;
import mage.game.mulligan.Mulligan; import mage.game.mulligan.Mulligan;
/** /**
*
* @author BetaSteward_at_googlemail.com * @author BetaSteward_at_googlemail.com
*/ */
public class CommanderDuelMatch extends MatchImpl { public class CommanderDuelMatch extends MatchImpl {
@ -18,24 +16,19 @@ public class CommanderDuelMatch extends MatchImpl {
@Override @Override
public void startGame() throws GameException { public void startGame() throws GameException {
int startLife = 40; int startLife = 40;
boolean alsoHand = true;
// Don't like it to compare but seems like it's complicated to do it in another way // Don't like it to compare but seems like it's complicated to do it in another way
boolean checkCommanderDamage = true; boolean checkCommanderDamage = true;
if (options.getDeckType().equals("Variant Magic - Duel Commander")) { if (options.getDeckType().equals("Variant Magic - Duel Commander")) {
startLife = 20; // Starting with the Commander 2016 update (on November 11th, 2016), Duel Commander will be played with 20 life points instead of 30. startLife = 20; // Starting with the Commander 2016 update (on November 11th, 2016), Duel Commander will be played with 20 life points instead of 30.
alsoHand = true; // commander going to hand allowed to go to command zone effective July 17, 2015
checkCommanderDamage = false; // since nov 16 duel commander uses no longer commander damage rule checkCommanderDamage = false; // since nov 16 duel commander uses no longer commander damage rule
} }
if (options.getDeckType().equals("Variant Magic - MTGO 1v1 Commander")) { if (options.getDeckType().equals("Variant Magic - MTGO 1v1 Commander")) {
startLife = 30; startLife = 30;
alsoHand = true; // commander going to hand allowed to go to command zone effective July 17, 2015
} }
Mulligan mulligan = options.getMulliganType().getMulligan(options.getFreeMulligans()); Mulligan mulligan = options.getMulliganType().getMulligan(options.getFreeMulligans());
CommanderDuel game = new CommanderDuel(options.getAttackOption(), options.getRange(), mulligan, startLife); CommanderDuel game = new CommanderDuel(options.getAttackOption(), options.getRange(), mulligan, startLife);
game.setCheckCommanderDamage(checkCommanderDamage); game.setCheckCommanderDamage(checkCommanderDamage);
game.setStartMessage(this.createGameStartMessage()); game.setStartMessage(this.createGameStartMessage());
game.setAlsoHand(alsoHand);
game.setAlsoLibrary(true);
initGame(game); initGame(game);
games.add(game); games.add(game);
} }

View file

@ -1,5 +1,3 @@
package mage.game; package mage.game;
import mage.game.match.MatchImpl; import mage.game.match.MatchImpl;
@ -7,7 +5,6 @@ import mage.game.match.MatchOptions;
import mage.game.mulligan.Mulligan; import mage.game.mulligan.Mulligan;
/** /**
*
* @author LevelX2 * @author LevelX2
*/ */
public class CommanderFreeForAllMatch extends MatchImpl { public class CommanderFreeForAllMatch extends MatchImpl {
@ -19,16 +16,12 @@ public class CommanderFreeForAllMatch extends MatchImpl {
@Override @Override
public void startGame() throws GameException { public void startGame() throws GameException {
int startLife = 40; int startLife = 40;
boolean alsoHand = true;
if (options.getDeckType().equals("Variant Magic - Duel Commander")) { if (options.getDeckType().equals("Variant Magic - Duel Commander")) {
startLife = 30; startLife = 30;
alsoHand = true; // commander going to hand allowed to go to command zone effective July 17, 2015
} }
Mulligan mulligan = options.getMulliganType().getMulligan(options.getFreeMulligans()); Mulligan mulligan = options.getMulliganType().getMulligan(options.getFreeMulligans());
CommanderFreeForAll game = new CommanderFreeForAll(options.getAttackOption(), options.getRange(), mulligan, startLife); CommanderFreeForAll game = new CommanderFreeForAll(options.getAttackOption(), options.getRange(), mulligan, startLife);
game.setStartMessage(this.createGameStartMessage()); game.setStartMessage(this.createGameStartMessage());
game.setAlsoHand(alsoHand);
game.setAlsoLibrary(true);
initGame(game); initGame(game);
games.add(game); games.add(game);
} }

View file

@ -16,15 +16,11 @@ public class FreeformCommanderDuelMatch extends MatchImpl {
@Override @Override
public void startGame() throws GameException { public void startGame() throws GameException {
int startLife = 20; int startLife = 20;
boolean alsoHand = true;
boolean checkCommanderDamage = true;
Mulligan mulligan = options.getMulliganType().getMulligan(options.getFreeMulligans()); Mulligan mulligan = options.getMulliganType().getMulligan(options.getFreeMulligans());
FreeformCommanderDuel game = new FreeformCommanderDuel(options.getAttackOption(), options.getRange(), mulligan, startLife); FreeformCommanderDuel game = new FreeformCommanderDuel(options.getAttackOption(), options.getRange(), mulligan, startLife);
game.setCheckCommanderDamage(checkCommanderDamage); game.setCheckCommanderDamage(true);
game.setStartMessage(this.createGameStartMessage()); game.setStartMessage(this.createGameStartMessage());
game.setAlsoHand(alsoHand);
game.setAlsoLibrary(true);
initGame(game); initGame(game);
games.add(game); games.add(game);
} }

View file

@ -1,4 +1,3 @@
package mage.game; package mage.game;
import mage.game.match.MatchImpl; import mage.game.match.MatchImpl;
@ -6,7 +5,6 @@ import mage.game.match.MatchOptions;
import mage.game.mulligan.Mulligan; import mage.game.mulligan.Mulligan;
/** /**
*
* @author spjspj * @author spjspj
*/ */
public class FreeformCommanderFreeForAllMatch extends MatchImpl { public class FreeformCommanderFreeForAllMatch extends MatchImpl {
@ -18,12 +16,9 @@ public class FreeformCommanderFreeForAllMatch extends MatchImpl {
@Override @Override
public void startGame() throws GameException { public void startGame() throws GameException {
int startLife = 40; int startLife = 40;
boolean alsoHand = true;
Mulligan mulligan = options.getMulliganType().getMulligan(options.getFreeMulligans()); Mulligan mulligan = options.getMulliganType().getMulligan(options.getFreeMulligans());
FreeformCommanderFreeForAll game = new FreeformCommanderFreeForAll(options.getAttackOption(), options.getRange(), mulligan, startLife); FreeformCommanderFreeForAll game = new FreeformCommanderFreeForAll(options.getAttackOption(), options.getRange(), mulligan, startLife);
game.setStartMessage(this.createGameStartMessage()); game.setStartMessage(this.createGameStartMessage());
game.setAlsoHand(alsoHand);
game.setAlsoLibrary(true);
initGame(game); initGame(game);
games.add(game); games.add(game);
} }

View file

@ -5,6 +5,8 @@ import mage.constants.RangeOfInfluence;
import mage.game.match.MatchType; import mage.game.match.MatchType;
import mage.game.mulligan.Mulligan; import mage.game.mulligan.Mulligan;
import java.util.UUID;
/** /**
* @author JayDi85 * @author JayDi85
*/ */
@ -12,6 +14,7 @@ public class OathbreakerDuel extends OathbreakerFreeForAll {
public OathbreakerDuel(MultiplayerAttackOption attackOption, RangeOfInfluence range, Mulligan mulligan, int startLife) { public OathbreakerDuel(MultiplayerAttackOption attackOption, RangeOfInfluence range, Mulligan mulligan, int startLife) {
super(attackOption, range, mulligan, startLife); super(attackOption, range, mulligan, startLife);
this.startingPlayerSkipsDraw = true;
} }
public OathbreakerDuel(final OathbreakerDuel game) { public OathbreakerDuel(final OathbreakerDuel game) {
@ -33,4 +36,11 @@ public class OathbreakerDuel extends OathbreakerFreeForAll {
return new OathbreakerDuel(this); return new OathbreakerDuel(this);
} }
@Override
protected void init(UUID choosingPlayerId) {
super.init(choosingPlayerId);
startingPlayerSkipsDraw = false;
}
} }

View file

@ -16,13 +16,10 @@ public class OathbreakerDuelMatch extends MatchImpl {
@Override @Override
public void startGame() throws GameException { public void startGame() throws GameException {
int startLife = 20; int startLife = 20;
boolean alsoHand = true;
Mulligan mulligan = options.getMulliganType().getMulligan(options.getFreeMulligans()); Mulligan mulligan = options.getMulliganType().getMulligan(options.getFreeMulligans());
OathbreakerDuel game = new OathbreakerDuel(options.getAttackOption(), options.getRange(), mulligan, startLife); OathbreakerDuel game = new OathbreakerDuel(options.getAttackOption(), options.getRange(), mulligan, startLife);
game.setCheckCommanderDamage(false); game.setCheckCommanderDamage(false);
game.setStartMessage(this.createGameStartMessage()); game.setStartMessage(this.createGameStartMessage());
game.setAlsoHand(alsoHand);
game.setAlsoLibrary(true);
initGame(game); initGame(game);
games.add(game); games.add(game);
} }

View file

@ -33,6 +33,7 @@ public class OathbreakerFreeForAll extends GameCommanderImpl {
public OathbreakerFreeForAll(MultiplayerAttackOption attackOption, RangeOfInfluence range, Mulligan mulligan, int startLife) { public OathbreakerFreeForAll(MultiplayerAttackOption attackOption, RangeOfInfluence range, Mulligan mulligan, int startLife) {
super(attackOption, range, mulligan, startLife); super(attackOption, range, mulligan, startLife);
this.startingPlayerSkipsDraw = false;
} }
public OathbreakerFreeForAll(final OathbreakerFreeForAll game) { public OathbreakerFreeForAll(final OathbreakerFreeForAll game) {
@ -42,13 +43,6 @@ public class OathbreakerFreeForAll extends GameCommanderImpl {
game.playerOathbreakers.forEach((key, value) -> this.playerOathbreakers.put(key, new HashSet<>(value))); game.playerOathbreakers.forEach((key, value) -> this.playerOathbreakers.put(key, new HashSet<>(value)));
} }
@Override
protected void init(UUID choosingPlayerId) {
// init base commander game
startingPlayerSkipsDraw = false;
super.init(choosingPlayerId);
}
private String getCommanderTypeName(Card commander) { private String getCommanderTypeName(Card commander) {
return commander.isInstantOrSorcery() ? COMMANDER_NAME_SIGNATURE_SPELL : COMMANDER_NAME_OATHBREAKER; return commander.isInstantOrSorcery() ? COMMANDER_NAME_SIGNATURE_SPELL : COMMANDER_NAME_OATHBREAKER;
} }

View file

@ -16,13 +16,10 @@ public class OathbreakerFreeForAllMatch extends MatchImpl {
@Override @Override
public void startGame() throws GameException { public void startGame() throws GameException {
int startLife = 20; int startLife = 20;
boolean alsoHand = true;
Mulligan mulligan = options.getMulliganType().getMulligan(options.getFreeMulligans()); Mulligan mulligan = options.getMulliganType().getMulligan(options.getFreeMulligans());
OathbreakerFreeForAll game = new OathbreakerFreeForAll(options.getAttackOption(), options.getRange(), mulligan, startLife); OathbreakerFreeForAll game = new OathbreakerFreeForAll(options.getAttackOption(), options.getRange(), mulligan, startLife);
game.setCheckCommanderDamage(false); game.setCheckCommanderDamage(false);
game.setStartMessage(this.createGameStartMessage()); game.setStartMessage(this.createGameStartMessage());
game.setAlsoHand(alsoHand);
game.setAlsoLibrary(true);
initGame(game); initGame(game);
games.add(game); games.add(game);
} }

View file

@ -1,5 +1,3 @@
package mage.game; package mage.game;
import mage.game.match.MatchImpl; import mage.game.match.MatchImpl;
@ -7,7 +5,6 @@ import mage.game.match.MatchOptions;
import mage.game.mulligan.Mulligan; import mage.game.mulligan.Mulligan;
/** /**
*
* @author spjspj * @author spjspj
*/ */
public class PennyDreadfulCommanderFreeForAllMatch extends MatchImpl { public class PennyDreadfulCommanderFreeForAllMatch extends MatchImpl {
@ -19,16 +16,12 @@ public class PennyDreadfulCommanderFreeForAllMatch extends MatchImpl {
@Override @Override
public void startGame() throws GameException { public void startGame() throws GameException {
int startLife = 40; int startLife = 40;
boolean alsoHand = true;
if (options.getDeckType().equals("Variant Magic - Duel Penny Dreadful Commander")) { if (options.getDeckType().equals("Variant Magic - Duel Penny Dreadful Commander")) {
startLife = 30; startLife = 30;
alsoHand = true; // commander going to hand allowed to go to command zone effective July 17, 2015
} }
Mulligan mulligan = options.getMulliganType().getMulligan(options.getFreeMulligans()); Mulligan mulligan = options.getMulliganType().getMulligan(options.getFreeMulligans());
PennyDreadfulCommanderFreeForAll game = new PennyDreadfulCommanderFreeForAll(options.getAttackOption(), options.getRange(), mulligan, startLife); PennyDreadfulCommanderFreeForAll game = new PennyDreadfulCommanderFreeForAll(options.getAttackOption(), options.getRange(), mulligan, startLife);
game.setStartMessage(this.createGameStartMessage()); game.setStartMessage(this.createGameStartMessage());
game.setAlsoHand(alsoHand);
game.setAlsoLibrary(true);
initGame(game); initGame(game);
games.add(game); games.add(game);
} }

View file

@ -3,6 +3,7 @@ package mage.player.ai;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.ActivatedAbility; import mage.abilities.ActivatedAbility;
import mage.abilities.SpellAbility; import mage.abilities.SpellAbility;
import mage.abilities.StaticAbility;
import mage.abilities.common.PassAbility; import mage.abilities.common.PassAbility;
import mage.abilities.effects.Effect; import mage.abilities.effects.Effect;
import mage.abilities.effects.SearchEffect; import mage.abilities.effects.SearchEffect;
@ -36,7 +37,6 @@ import org.apache.log4j.Logger;
import java.io.File; import java.io.File;
import java.util.*; import java.util.*;
import java.util.concurrent.*; import java.util.concurrent.*;
import mage.abilities.StaticAbility;
/** /**
* @author nantuko * @author nantuko
@ -794,72 +794,100 @@ public class ComputerPlayer6 extends ComputerPlayer /*implements Player*/ {
private void declareAttackers(Game game, UUID activePlayerId) { private void declareAttackers(Game game, UUID activePlayerId) {
game.fireEvent(new GameEvent(GameEvent.EventType.DECLARE_ATTACKERS_STEP_PRE, null, null, activePlayerId)); game.fireEvent(new GameEvent(GameEvent.EventType.DECLARE_ATTACKERS_STEP_PRE, null, null, activePlayerId));
if (!game.replaceEvent(GameEvent.getEvent(GameEvent.EventType.DECLARING_ATTACKERS, activePlayerId, activePlayerId))) { if (!game.replaceEvent(GameEvent.getEvent(GameEvent.EventType.DECLARING_ATTACKERS, activePlayerId, activePlayerId))) {
Player attackingPlayer = game.getPlayer(activePlayerId); Player attackingPlayer = game.getPlayer(activePlayerId);
// TODO: this works only in two player game, also no attack of Planeswalker
UUID defenderId = game.getOpponents(playerId).iterator().next();
Player defender = game.getPlayer(defenderId);
List<Permanent> attackersList = super.getAvailableAttackers(defenderId, game); // TODO: add attack of Planeswalker
if (attackersList.isEmpty()) {
return;
}
List<Permanent> possibleBlockers = defender.getAvailableBlockers(game); // 1. check alpha strike first (all in attack to kill)
for (UUID defenderId : game.getOpponents(playerId)) {
List<Permanent> killers = CombatUtil.canKillOpponent(game, attackersList, possibleBlockers, defender); Player defender = game.getPlayer(defenderId);
if (!killers.isEmpty()) { if (!defender.isInGame()) {
for (Permanent attacker : killers) { continue;
attackingPlayer.declareAttacker(attacker.getId(), defenderId, game, false); }
List<Permanent> attackersList = super.getAvailableAttackers(defenderId, game);
if (attackersList.isEmpty()) {
continue;
}
List<Permanent> possibleBlockers = defender.getAvailableBlockers(game);
List<Permanent> killers = CombatUtil.canKillOpponent(game, attackersList, possibleBlockers, defender);
if (!killers.isEmpty()) {
for (Permanent attacker : killers) {
attackingPlayer.declareAttacker(attacker.getId(), defenderId, game, false);
}
return;
} }
return;
} }
// The AI will now attack more sanely. Simple, but good enough for now. // 2. check all other actions
// The sim minmax does not work at the moment. for (UUID defenderId : game.getOpponents(playerId)) {
boolean safeToAttack; Player defender = game.getPlayer(defenderId);
CombatEvaluator eval = new CombatEvaluator(); if (!defender.isInGame()) {
continue;
}
List<Permanent> attackersList = super.getAvailableAttackers(defenderId, game);
if (attackersList.isEmpty()) {
continue;
}
List<Permanent> possibleBlockers = defender.getAvailableBlockers(game);
for (Permanent attacker : attackersList) { // The AI will now attack more sanely. Simple, but good enough for now.
safeToAttack = true; // The sim minmax does not work at the moment.
int attackerValue = eval.evaluate(attacker, game); boolean safeToAttack;
for (Permanent blocker : possibleBlockers) { CombatEvaluator eval = new CombatEvaluator();
int blockerValue = eval.evaluate(blocker, game);
if (attacker.getPower().getValue() <= blocker.getToughness().getValue() for (Permanent attacker : attackersList) {
&& attacker.getToughness().getValue() <= blocker.getPower().getValue()) { safeToAttack = true;
safeToAttack = false; int attackerValue = eval.evaluate(attacker, game);
} for (Permanent blocker : possibleBlockers) {
if (attacker.getToughness().getValue() == blocker.getPower().getValue() int blockerValue = eval.evaluate(blocker, game);
&& attacker.getPower().getValue() == blocker.getToughness().getValue()) {
if (attackerValue > blockerValue // blocker can kill attacker
|| blocker.getAbilities().containsKey(FirstStrikeAbility.getInstance().getId()) if (attacker.getPower().getValue() <= blocker.getToughness().getValue()
|| blocker.getAbilities().containsKey(DoubleStrikeAbility.getInstance().getId()) && attacker.getToughness().getValue() <= blocker.getPower().getValue()) {
|| blocker.getAbilities().contains(new ExaltedAbility())
|| blocker.getAbilities().containsKey(DeathtouchAbility.getInstance().getId())
|| blocker.getAbilities().containsKey(IndestructibleAbility.getInstance().getId())
|| !attacker.getAbilities().containsKey(FirstStrikeAbility.getInstance().getId())
|| !attacker.getAbilities().containsKey(DoubleStrikeAbility.getInstance().getId())
|| !attacker.getAbilities().contains(new ExaltedAbility())) {
safeToAttack = false; safeToAttack = false;
} }
// kill each other
if (attacker.getToughness().getValue() == blocker.getPower().getValue()
&& attacker.getPower().getValue() == blocker.getToughness().getValue()) {
if (attackerValue > blockerValue
|| blocker.getAbilities().containsKey(FirstStrikeAbility.getInstance().getId())
|| blocker.getAbilities().containsKey(DoubleStrikeAbility.getInstance().getId())
|| blocker.getAbilities().contains(new ExaltedAbility())
|| blocker.getAbilities().containsKey(DeathtouchAbility.getInstance().getId())
|| blocker.getAbilities().containsKey(IndestructibleAbility.getInstance().getId())
|| !attacker.getAbilities().containsKey(FirstStrikeAbility.getInstance().getId())
|| !attacker.getAbilities().containsKey(DoubleStrikeAbility.getInstance().getId())
|| !attacker.getAbilities().contains(new ExaltedAbility())) {
safeToAttack = false;
}
}
// attacker can kill by deathtouch
if (attacker.getAbilities().containsKey(DeathtouchAbility.getInstance().getId())
|| attacker.getAbilities().containsKey(IndestructibleAbility.getInstance().getId())) {
safeToAttack = true;
}
// attacker can ignore blocker
if (attacker.getAbilities().containsKey(FlyingAbility.getInstance().getId())
&& !blocker.getAbilities().containsKey(FlyingAbility.getInstance().getId())
&& !blocker.getAbilities().containsKey(ReachAbility.getInstance().getId())) {
safeToAttack = true;
}
} }
if (attacker.getAbilities().containsKey(DeathtouchAbility.getInstance().getId())
|| attacker.getAbilities().containsKey(IndestructibleAbility.getInstance().getId())) { // 0 damage
safeToAttack = true; if (attacker.getPower().getValue() == 0) {
safeToAttack = false;
} }
if (attacker.getAbilities().containsKey(FlyingAbility.getInstance().getId())
&& !blocker.getAbilities().containsKey(FlyingAbility.getInstance().getId()) if (safeToAttack) {
&& !blocker.getAbilities().containsKey(ReachAbility.getInstance().getId())) { // undo has to be possible e.g. if not able to pay a attack fee (e.g. Ghostly Prison)
safeToAttack = true; attackingPlayer.declareAttacker(attacker.getId(), defenderId, game, true);
} }
} }
if (attacker.getPower().getValue() == 0) {
safeToAttack = false;
}
if (safeToAttack) {
// undo has to be possible e.g. if not able to pay a attack fee (e.g. Ghostly Prison)
attackingPlayer.declareAttacker(attacker.getId(), defenderId, game, true);
}
} }
} }
} }

View file

@ -32,7 +32,7 @@ public final class CombatUtil {
public static List<Permanent> canKillOpponent(Game game, List<Permanent> attackersList, List<Permanent> blockersList, public static List<Permanent> canKillOpponent(Game game, List<Permanent> attackersList, List<Permanent> blockersList,
Player defender) { Player defender) {
List<Permanent> blockableAttackers = new ArrayList<>(blockersList); List<Permanent> blockableAttackers = new ArrayList<>(attackersList);
List<Permanent> unblockableAttackers = new ArrayList<>(); List<Permanent> unblockableAttackers = new ArrayList<>();
for (Permanent attacker : attackersList) { for (Permanent attacker : attackersList) {
if (!canBeBlocked(game, attacker, blockersList)) { if (!canBeBlocked(game, attacker, blockersList)) {
@ -292,7 +292,7 @@ public final class CombatUtil {
} }
return canBlock; return canBlock;
} }
public static CombatInfo blockWithGoodTrade2(Game game, List<Permanent> attackers, List<Permanent> blockers) { public static CombatInfo blockWithGoodTrade2(Game game, List<Permanent> attackers, List<Permanent> blockers) {
UUID attackerId = game.getCombat().getAttackingPlayerId(); UUID attackerId = game.getCombat().getAttackingPlayerId();
@ -319,7 +319,7 @@ public final class CombatUtil {
return combatInfo; return combatInfo;
} }
private static List<Permanent> getBlockersThatWillSurvive2(Game game, UUID attackerId, UUID defenderId, Permanent attacker, List<Permanent> possibleBlockers) { private static List<Permanent> getBlockersThatWillSurvive2(Game game, UUID attackerId, UUID defenderId, Permanent attacker, List<Permanent> possibleBlockers) {
List<Permanent> blockers = new ArrayList<>(); List<Permanent> blockers = new ArrayList<>();
for (Permanent blocker : possibleBlockers) { for (Permanent blocker : possibleBlockers) {
@ -335,9 +335,9 @@ public final class CombatUtil {
} }
return blockers; return blockers;
} }
public static SurviveInfo willItSurvive2(Game game, UUID attackingPlayerId, UUID defendingPlayerId, Permanent attacker, Permanent blocker) { public static SurviveInfo willItSurvive2(Game game, UUID attackingPlayerId, UUID defendingPlayerId, Permanent attacker, Permanent blocker) {
Game sim = game.copy(); Game sim = game.copy();
Combat combat = sim.getCombat(); Combat combat = sim.getCombat();
@ -347,7 +347,7 @@ public final class CombatUtil {
if (blocker == null || attacker == null || sim.getPlayer(defendingPlayerId) == null) { if (blocker == null || attacker == null || sim.getPlayer(defendingPlayerId) == null) {
return null; return null;
} }
if (attacker.getPower().getValue() >= blocker.getToughness().getValue()) { if (attacker.getPower().getValue() >= blocker.getToughness().getValue()) {
sim.getBattlefield().removePermanent(blocker.getId()); sim.getBattlefield().removePermanent(blocker.getId());
} }

View file

@ -1993,12 +1993,14 @@ public class HumanPlayer extends PlayerImpl {
} }
break; break;
case PASS_PRIORITY_UNTIL_STACK_RESOLVED: case PASS_PRIORITY_UNTIL_STACK_RESOLVED:
// stop recording only, real stack processing in PlayerImpl
if (recordingMacro) { if (recordingMacro) {
logger.debug("Adding a resolveStack"); logger.debug("Adding a resolveStack");
PlayerResponse tResponse = new PlayerResponse(); PlayerResponse tResponse = new PlayerResponse();
tResponse.setString("resolveStack"); tResponse.setString("resolveStack");
actionQueueSaved.add(tResponse); actionQueueSaved.add(tResponse);
} }
super.sendPlayerAction(playerAction, game, data);
break; break;
default: default:
super.sendPlayerAction(playerAction, game, data); super.sendPlayerAction(playerAction, game, data);

View file

@ -1,7 +1,5 @@
package mage.cards.k; package mage.cards.k;
import java.util.UUID;
import mage.MageObject; import mage.MageObject;
import mage.MageObjectReference; import mage.MageObjectReference;
import mage.abilities.Ability; import mage.abilities.Ability;
@ -16,6 +14,8 @@ import mage.constants.CardType;
import mage.constants.Outcome; import mage.constants.Outcome;
import mage.constants.Zone; import mage.constants.Zone;
import mage.filter.common.FilterNonlandCard; import mage.filter.common.FilterNonlandCard;
import mage.filter.predicate.Predicates;
import mage.filter.predicate.mageobject.CardIdPredicate;
import mage.game.Game; import mage.game.Game;
import mage.game.events.GameEvent; import mage.game.events.GameEvent;
import mage.game.events.GameEvent.EventType; import mage.game.events.GameEvent.EventType;
@ -26,8 +26,9 @@ import mage.target.common.TargetCardInExile;
import mage.target.targetpointer.FixedTarget; import mage.target.targetpointer.FixedTarget;
import mage.util.CardUtil; import mage.util.CardUtil;
import java.util.UUID;
/** /**
*
* @author BetaSteward_at_googlemail.com * @author BetaSteward_at_googlemail.com
*/ */
public final class KnowledgePool extends CardImpl { public final class KnowledgePool extends CardImpl {
@ -148,13 +149,15 @@ class KnowledgePoolEffect2 extends OneShotEffect {
if (controller.moveCardsToExile(spell, source, game, true, exileZoneId, sourceObject.getIdName())) { if (controller.moveCardsToExile(spell, source, game, true, exileZoneId, sourceObject.getIdName())) {
Player player = game.getPlayer(spell.getControllerId()); Player player = game.getPlayer(spell.getControllerId());
if (player != null && player.chooseUse(Outcome.PlayForFree, "Cast another nonland card exiled with " + sourceObject.getLogName() + " without paying that card's mana cost?", source, game)) { if (player != null && player.chooseUse(Outcome.PlayForFree, "Cast another nonland card exiled with " + sourceObject.getLogName() + " without paying that card's mana cost?", source, game)) {
TargetCardInExile target = new TargetCardInExile(filter, source.getSourceId()); FilterNonlandCard realFilter = filter.copy();
while (player.choose(Outcome.PlayForFree, game.getExile().getExileZone(exileZoneId), target, game)) { realFilter.add(Predicates.not(new CardIdPredicate(spell.getSourceId())));
TargetCardInExile target = new TargetCardInExile(0, 1, realFilter, source.getSourceId());
target.setNotTarget(true);
if (player.choose(Outcome.PlayForFree, game.getExile().getExileZone(exileZoneId), target, game)) {
Card card = game.getCard(target.getFirstTarget()); Card card = game.getCard(target.getFirstTarget());
if (card != null && !card.getId().equals(spell.getSourceId())) { if (card != null && !card.getId().equals(spell.getSourceId())) {
return player.cast(card.getSpellAbility(), game, true, new MageObjectReference(source.getSourceObject(game), game)); player.cast(card.getSpellAbility(), game, true, new MageObjectReference(source.getSourceObject(game), game));
} }
target.clearChosen();
} }
} }
return true; return true;

View file

@ -1,7 +1,6 @@
package mage.cards.p; package mage.cards.p;
import java.util.UUID;
import mage.MageObject; import mage.MageObject;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.effects.AsThoughEffectImpl; import mage.abilities.effects.AsThoughEffectImpl;
@ -9,11 +8,7 @@ import mage.abilities.effects.OneShotEffect;
import mage.cards.Card; import mage.cards.Card;
import mage.cards.CardImpl; import mage.cards.CardImpl;
import mage.cards.CardSetInfo; import mage.cards.CardSetInfo;
import mage.constants.AsThoughEffectType; import mage.constants.*;
import mage.constants.CardType;
import mage.constants.Duration;
import mage.constants.Outcome;
import mage.constants.Zone;
import mage.game.ExileZone; import mage.game.ExileZone;
import mage.game.Game; import mage.game.Game;
import mage.players.Player; import mage.players.Player;
@ -21,14 +16,15 @@ import mage.target.common.TargetCardInLibrary;
import mage.target.common.TargetOpponent; import mage.target.common.TargetOpponent;
import mage.util.CardUtil; import mage.util.CardUtil;
import java.util.UUID;
/** /**
*
* @author BetaSteward * @author BetaSteward
*/ */
public final class PraetorsGrasp extends CardImpl { public final class PraetorsGrasp extends CardImpl {
public PraetorsGrasp(UUID ownerId, CardSetInfo setInfo) { public PraetorsGrasp(UUID ownerId, CardSetInfo setInfo) {
super(ownerId,setInfo,new CardType[]{CardType.SORCERY},"{1}{B}{B}"); super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{1}{B}{B}");
// Search target opponent's library for a card and exile it face down. Then that player shuffles their library. You may look at and play that card for as long as it remains exiled. // Search target opponent's library for a card and exile it face down. Then that player shuffles their library. You may look at and play that card for as long as it remains exiled.
this.getSpellAbility().addEffect(new PraetorsGraspEffect()); this.getSpellAbility().addEffect(new PraetorsGraspEffect());
@ -120,11 +116,7 @@ class PraetorsGraspPlayEffect extends AsThoughEffectImpl {
if (exileId != null && controller != null) { if (exileId != null && controller != null) {
ExileZone exileZone = game.getExile().getExileZone(exileId); ExileZone exileZone = game.getExile().getExileZone(exileId);
if (exileZone != null && exileZone.contains(cardId)) { if (exileZone != null && exileZone.contains(cardId)) {
if (controller.chooseUse(outcome, "Play the exiled card?", source, game)) { return true;
return true;
}
} else {
discard();
} }
} }
} }

View file

@ -1,17 +1,23 @@
package mage.cards.t; package mage.cards.t;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.Mode;
import mage.abilities.common.SpellCastControllerTriggeredAbility; import mage.abilities.common.SpellCastControllerTriggeredAbility;
import mage.abilities.dynamicvalue.DynamicValue;
import mage.abilities.effects.Effect;
import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.OneShotEffect;
import mage.abilities.hint.ValueHint;
import mage.cards.CardImpl; import mage.cards.CardImpl;
import mage.cards.CardSetInfo; import mage.cards.CardSetInfo;
import mage.constants.CardType; import mage.constants.CardType;
import mage.constants.Outcome; import mage.constants.Outcome;
import mage.constants.WatcherScope; import mage.constants.WatcherScope;
import mage.constants.Zone;
import mage.filter.common.FilterInstantOrSorcerySpell; import mage.filter.common.FilterInstantOrSorcerySpell;
import mage.game.Game; import mage.game.Game;
import mage.game.events.GameEvent; import mage.game.events.GameEvent;
import mage.game.stack.Spell; import mage.game.stack.Spell;
import mage.players.Player;
import mage.watchers.Watcher; import mage.watchers.Watcher;
import java.util.HashMap; import java.util.HashMap;
@ -28,9 +34,7 @@ public final class ThousandYearStorm extends CardImpl {
super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{4}{U}{R}"); super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{4}{U}{R}");
// Whenever you cast an instant or sorcery spell, copy it for each other instant and sorcery spell you've cast before it this turn. You may choose new targets for the copies. // Whenever you cast an instant or sorcery spell, copy it for each other instant and sorcery spell you've cast before it this turn. You may choose new targets for the copies.
this.addAbility(new SpellCastControllerTriggeredAbility( this.addAbility(new ThousandYearStormAbility());
new ThousandYearStormEffect(), new FilterInstantOrSorcerySpell(), false, true
), new ThousandYearWatcher());
} }
public ThousandYearStorm(final ThousandYearStorm card) { public ThousandYearStorm(final ThousandYearStorm card) {
@ -43,11 +47,48 @@ public final class ThousandYearStorm extends CardImpl {
} }
} }
class ThousandYearStormAbility extends SpellCastControllerTriggeredAbility {
String stormCountInfo = null;
public ThousandYearStormAbility() {
super(Zone.BATTLEFIELD, new ThousandYearStormEffect(), new FilterInstantOrSorcerySpell(), false, true);
this.addHint(new ValueHint("You've cast instant and sorcery this turn", ThousandYearSpellsCastThatTurnValue.instance));
this.addWatcher(new ThousandYearWatcher());
}
public ThousandYearStormAbility(final ThousandYearStormAbility ability) {
super(ability);
this.stormCountInfo = ability.stormCountInfo;
}
@Override
public boolean checkTrigger(GameEvent event, Game game) {
// save storm count info, real count will be calculated to stack ability in resolve effect only
if (super.checkTrigger(event, game)) {
int stormCount = ThousandYearSpellsCastThatTurnValue.instance.calculate(game, this, null);
stormCountInfo = " (<b>storm count: " + Math.max(0, stormCount - 1) + "</b>) ";
return true;
}
return false;
}
@Override
public String getRule() {
return "Whenever you cast an instant or sorcery spell, copy it for each other instant and sorcery spell you've cast before it this turn"
+ (stormCountInfo != null ? stormCountInfo : "") + ". You may choose new targets for the copies.";
}
@Override
public ThousandYearStormAbility copy() {
return new ThousandYearStormAbility(this);
}
}
class ThousandYearStormEffect extends OneShotEffect { class ThousandYearStormEffect extends OneShotEffect {
public ThousandYearStormEffect() { public ThousandYearStormEffect() {
super(Outcome.Benefit); super(Outcome.Benefit);
this.staticText = "copy it for each other instant and sorcery spell you've cast before it this turn. You may choose new targets for the copies";
} }
public ThousandYearStormEffect(final ThousandYearStormEffect effect) { public ThousandYearStormEffect(final ThousandYearStormEffect effect) {
@ -62,7 +103,8 @@ class ThousandYearStormEffect extends OneShotEffect {
@Override @Override
public boolean apply(Game game, Ability source) { public boolean apply(Game game, Ability source) {
Spell spell = game.getSpellOrLKIStack(getTargetPointer().getFirst(game, source)); Spell spell = game.getSpellOrLKIStack(getTargetPointer().getFirst(game, source));
if (spell != null) { Player controller = spell != null ? game.getPlayer(spell.getControllerId()) : null;
if (spell != null && controller != null) {
ThousandYearWatcher watcher = game.getState().getWatcher(ThousandYearWatcher.class); ThousandYearWatcher watcher = game.getState().getWatcher(ThousandYearWatcher.class);
if (watcher != null) { if (watcher != null) {
String stateSearchId = spell.getId().toString() + source.getSourceId().toString(); String stateSearchId = spell.getId().toString() + source.getSourceId().toString();
@ -81,6 +123,11 @@ class ThousandYearStormEffect extends OneShotEffect {
} }
return false; return false;
} }
@Override
public String getText(Mode mode) {
return "copy it for each other instant and sorcery spell you've cast before it this turn. You may choose new targets for the copies";
}
} }
class ThousandYearWatcher extends Watcher { class ThousandYearWatcher extends Watcher {
@ -131,3 +178,32 @@ class ThousandYearWatcher extends Watcher {
} }
} }
enum ThousandYearSpellsCastThatTurnValue implements DynamicValue {
instance;
@Override
public int calculate(Game game, Ability sourceAbility, Effect effect) {
ThousandYearWatcher watcher = game.getState().getWatcher(ThousandYearWatcher.class);
if (watcher == null) {
return 0;
}
return watcher.getAmountOfSpellsPlayerCastOnCurrentTurn(sourceAbility.getControllerId());
}
@Override
public ThousandYearSpellsCastThatTurnValue copy() {
return instance;
}
@Override
public String toString() {
return "X";
}
@Override
public String getMessage() {
return "You cast an instant or sorcery spell in this turn";
}
}

View file

@ -0,0 +1,222 @@
package org.mage.test.AI.basic;
import mage.constants.PhaseStep;
import mage.constants.Zone;
import org.junit.Ignore;
import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBaseAI;
/**
* @author JayDi85
*/
public class AttackAndBlockByAITest extends CardTestPlayerBaseAI {
// only PlayerA is AI controlled
// Trove of Temptation
// Each opponent must attack you or a planeswalker you control with at least one creature each combat if able.
@Test
public void test_Attack_2_big_vs_0() {
// 2 x 2/2 vs 0 - can't lose any attackers
addCard(Zone.BATTLEFIELD, playerA, "Balduvian Bears", 2); // 2/2
setStopAt(1, PhaseStep.END_TURN);
setStrictChooseMode(true);
execute();
assertAllCommandsUsed();
assertLife(playerA, 20);
assertLife(playerB, 20 - 2 - 2);
}
@Test
public void test_Attack_2_big_vs_1_small() {
// 2 x 2/2 vs 1 x 1/1 - can't lose any attackers
addCard(Zone.BATTLEFIELD, playerA, "Balduvian Bears", 2); // 2/2
addCard(Zone.BATTLEFIELD, playerB, "Arbor Elf", 1); // 1/1
setStopAt(1, PhaseStep.END_TURN);
setStrictChooseMode(true);
execute();
assertAllCommandsUsed();
assertLife(playerA, 20);
assertLife(playerB, 20 - 2 - 2);
}
@Test
public void test_Attack_1_big_vs_2_small() {
// 1 x 2/2 vs 2 x 1/1 - can lose 1 attacker, but will kill more opponents
addCard(Zone.BATTLEFIELD, playerA, "Balduvian Bears", 1); // 2/2
addCard(Zone.BATTLEFIELD, playerB, "Arbor Elf", 2); // 1/1
setStopAt(1, PhaseStep.END_TURN);
setStrictChooseMode(true);
execute();
assertAllCommandsUsed();
assertLife(playerA, 20);
assertLife(playerB, 20 - 2);
}
@Test
public void test_Attack_2_big_vs_2_small() {
// 2 x 2/2 vs 2 x 1/1 - can lose 1 attacker, but will kill more opponents
addCard(Zone.BATTLEFIELD, playerA, "Balduvian Bears", 2); // 2/2
addCard(Zone.BATTLEFIELD, playerB, "Arbor Elf", 2); // 1/1
setStopAt(1, PhaseStep.END_TURN);
setStrictChooseMode(true);
execute();
assertAllCommandsUsed();
assertLife(playerA, 20);
assertLife(playerB, 20 - 2 - 2);
}
@Test
public void test_Attack_1_small_vs_0() {
addCard(Zone.BATTLEFIELD, playerA, "Arbor Elf", 1); // 1/1
setStopAt(1, PhaseStep.END_TURN);
setStrictChooseMode(true);
execute();
assertAllCommandsUsed();
assertLife(playerA, 20);
assertLife(playerB, 20 - 1);
}
@Test
public void test_Attack_1_small_vs_1_big() {
addCard(Zone.BATTLEFIELD, playerA, "Arbor Elf", 1); // 1/1
addCard(Zone.BATTLEFIELD, playerB, "Balduvian Bears", 1); // 2/2
setStopAt(1, PhaseStep.END_TURN);
setStrictChooseMode(true);
execute();
assertAllCommandsUsed();
assertLife(playerA, 20);
assertLife(playerB, 20); // no attacks
}
@Test
public void test_Attack_2_small_vs_1_big() {
addCard(Zone.BATTLEFIELD, playerA, "Arbor Elf", 2); // 1/1
addCard(Zone.BATTLEFIELD, playerB, "Balduvian Bears", 1); // 2/2
setStopAt(1, PhaseStep.END_TURN);
setStrictChooseMode(true);
execute();
assertAllCommandsUsed();
assertLife(playerA, 20);
assertLife(playerB, 20); // no attacks
}
@Test
public void test_Attack_15_small_vs_1_big_kill_stike() {
addCard(Zone.BATTLEFIELD, playerA, "Balduvian Bears", 15); // 2/2
addCard(Zone.BATTLEFIELD, playerB, "Ancient Brontodon", 1); // 9/9
block(1, playerB, "Ancient Brontodon", "Balduvian Bears");
setStopAt(1, PhaseStep.END_TURN);
setStrictChooseMode(true);
execute();
assertAllCommandsUsed();
assertLife(playerA, 20);
assertLife(playerB, 20 - ((15 - 1) * 2)); // one will be blocked
}
@Test
@Ignore // TODO: add massive attack vs small amount of blockers
public void test_Attack_10_small_vs_1_big_massive_strike() {
addCard(Zone.BATTLEFIELD, playerA, "Balduvian Bears", 10); // 2/2
addCard(Zone.BATTLEFIELD, playerB, "Ancient Brontodon", 1); // 9/9
block(1, playerB, "Ancient Brontodon", "Balduvian Bears");
setStopAt(1, PhaseStep.END_TURN);
setStrictChooseMode(true);
execute();
assertAllCommandsUsed();
assertLife(playerA, 20);
assertLife(playerB, 20 - ((10 - 1) * 2)); // one will be blocked
}
@Test
public void test_ForceAttack_1_small_vs_1_big_a() {
addCard(Zone.BATTLEFIELD, playerA, "Goblin Brigand", 1); // 1/1, force to attack
addCard(Zone.BATTLEFIELD, playerB, "Ancient Brontodon", 1); // 9/9
block(1, playerB, "Ancient Brontodon", "Goblin Brigand");
setStopAt(1, PhaseStep.END_TURN);
setStrictChooseMode(true);
execute();
assertAllCommandsUsed();
assertGraveyardCount(playerA, "Goblin Brigand", 1);
assertLife(playerA, 20);
assertLife(playerB, 20);
}
@Test
public void test_ForceAttack_2_small_vs_2_big() {
addCard(Zone.BATTLEFIELD, playerA, "Goblin Brigand", 2); // 1/1, force to attack
addCard(Zone.BATTLEFIELD, playerB, "Ancient Brontodon", 2); // 9/9
block(1, playerB, "Ancient Brontodon:0", "Goblin Brigand:0");
block(1, playerB, "Ancient Brontodon:1", "Goblin Brigand:1");
setStopAt(1, PhaseStep.END_TURN);
setStrictChooseMode(true);
execute();
assertAllCommandsUsed();
assertGraveyardCount(playerA, "Goblin Brigand", 2);
assertLife(playerA, 20);
assertLife(playerB, 20);
}
@Test
@Ignore // TODO: need to fix Trove of Temptation effect (player must attack by one creature)
public void test_ForceAttack_1_small_vs_1_big_b() {
addCard(Zone.BATTLEFIELD, playerA, "Arbor Elf", 1); // 1/1
addCard(Zone.BATTLEFIELD, playerB, "Ancient Brontodon", 1); // 9/9
// Trove of Temptation
// Each opponent must attack you or a planeswalker you control with at least one creature each combat if able.
addCard(Zone.BATTLEFIELD, playerB, "Trove of Temptation", 1); // 9/9
block(1, playerB, "Ancient Brontodon", "Arbor Elf");
setStopAt(1, PhaseStep.END_TURN);
setStrictChooseMode(true);
execute();
assertAllCommandsUsed();
assertGraveyardCount(playerA, "Arbor Elf", 1);
assertLife(playerA, 20);
assertLife(playerB, 20);
}
@Test
public void test_Attack_1_with_counters_vs_1() {
// chainbreaker real stats is 1/1, it's can be saftly attacked
addCard(Zone.BATTLEFIELD, playerA, "Balduvian Bears", 1); // 2/2
addCard(Zone.BATTLEFIELD, playerB, "Chainbreaker", 1); // 3/3, but with 2x -1/-1 counters
setStopAt(1, PhaseStep.END_TURN);
setStrictChooseMode(true);
execute();
assertAllCommandsUsed();
assertLife(playerA, 20);
assertLife(playerB, 20 - 2);
}
}

View file

@ -1,4 +1,3 @@
package org.mage.test.AI.basic; package org.mage.test.AI.basic;
import mage.constants.PhaseStep; import mage.constants.PhaseStep;
@ -10,14 +9,12 @@ import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBaseAI; import org.mage.test.serverside.base.CardTestPlayerBaseAI;
/** /**
*
* @author LevelX2 * @author LevelX2
*/ */
public class PreventRepeatedActionsTest extends CardTestPlayerBaseAI { public class PreventRepeatedActionsTest extends CardTestPlayerBaseAI {
/** /**
* Check that an equipment is not switched again an again between creatures * Check that an equipment is not switched again an again between creatures
*
*/ */
@Test @Test
public void testEquipOnlyOnce() { public void testEquipOnlyOnce() {
@ -77,10 +74,13 @@ public class PreventRepeatedActionsTest extends CardTestPlayerBaseAI {
attack(2, playerB, "Silvercoat Lion"); attack(2, playerB, "Silvercoat Lion");
attack(2, playerB, "Silvercoat Lion"); attack(2, playerB, "Silvercoat Lion");
blockSkip(2, playerA);
setStopAt(2, PhaseStep.END_TURN); setStopAt(2, PhaseStep.END_TURN);
execute(); execute();
assertPermanentCount(playerA, "Kiora's Follower", 2);
assertPermanentCount(playerB, "Silvercoat Lion", 2);
assertLife(playerA, 16); assertLife(playerA, 16);
assertTapped("Kiora's Follower", false); assertTapped("Kiora's Follower", false);
} }

View file

@ -9,6 +9,7 @@ import mage.abilities.effects.common.DamageMultiEffect;
import mage.constants.CardType; import mage.constants.CardType;
import mage.constants.PhaseStep; import mage.constants.PhaseStep;
import mage.constants.Zone; import mage.constants.Zone;
import mage.counters.CounterType;
import mage.target.common.TargetCreaturePermanentAmount; import mage.target.common.TargetCreaturePermanentAmount;
import org.junit.Ignore; import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
@ -96,7 +97,70 @@ public class TargetPriorityTest extends CardTestPlayerBaseAI {
assertPermanentCount(playerB, "Battering Sliver", 3); assertPermanentCount(playerB, "Battering Sliver", 3);
} }
// @Test
@Ignore // TODO: enable it after chooseTarget will be rewrites like choseTargetAmount
public void test_target_KillCreatureNotDamage() {
// https://github.com/magefree/mage/issues/4497
// choose target for damage selects wrong target
addCard(Zone.HAND, playerA, "Burning Sun's Avatar"); // 3 damage to target on enter
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 5);
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 1, true); // avatar can be cast on 3 turn
//
addCard(Zone.BATTLEFIELD, playerB, "Ancient Brontodon", 1); // 9/9
addCard(Zone.BATTLEFIELD, playerB, "Balduvian Bears", 1); // 2/2, will be +2 counters
addCard(Zone.BATTLEFIELD, playerB, "Arbor Elf", 1); // 1/1
// prepare, A can't cast avatar until mana is tapped
addCounters(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Balduvian Bears", CounterType.P1P1, 2);
checkPermanentCounters("counters", 1, PhaseStep.BEGIN_COMBAT, playerB, "Balduvian Bears", CounterType.P1P1, 2);
// AI cast avatar on turn 3 and target 1 creature to kil by 3 damage
//setStrictChooseMode(true); // AI
setStopAt(3, PhaseStep.BEGIN_COMBAT);
execute();
assertAllCommandsUsed();
assertPermanentCount(playerA, "Burning Sun's Avatar", 1);
assertPermanentCount(playerB, "Ancient Brontodon", 1); // can't be killed
assertPermanentCount(playerB, "Balduvian Bears", 1); // 2/2, but with counters is 4/4, can't be killed
assertPermanentCount(playerB, "Arbor Elf", 0); // must be killed
assertGraveyardCount(playerB, "Arbor Elf", 1);
}
@Test
@Ignore // do not enable it in production, only for devs
public void test_target_Performance() {
int cardsMultiplier = 10;
addCard(Zone.HAND, playerA, "Lightning Bolt", 1 * cardsMultiplier);
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 1 * cardsMultiplier);
//
addCard(Zone.BATTLEFIELD, playerB, "Memnite", 1 * cardsMultiplier); // 1/1
addCard(Zone.BATTLEFIELD, playerB, "Balduvian Bears", 1 * cardsMultiplier); // 2/2
addCard(Zone.BATTLEFIELD, playerB, "Ashcoat Bear", 1 * cardsMultiplier); // 2/2 with ability
addCard(Zone.BATTLEFIELD, playerB, "Golden Bear", 1 * cardsMultiplier); // 4/3
addCard(Zone.BATTLEFIELD, playerB, "Battering Sliver", 1 * cardsMultiplier); // 4/4 with ability
//castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt");
showHand("after", 1, PhaseStep.BEGIN_COMBAT, playerA);
setStopAt(1, PhaseStep.BEGIN_COMBAT);
execute();
assertAllCommandsUsed();
/* disabled checks cause target is not yet fixed, see comments at the start file
assertPermanentCount(playerB, "Memnite", 1 * cardsMultiplier);
assertPermanentCount(playerB, "Balduvian Bears", 1 * cardsMultiplier);
assertPermanentCount(playerB, "Ashcoat Bear", 1 * cardsMultiplier);
assertPermanentCount(playerB, "Golden Bear", 1 * cardsMultiplier - 1);
assertPermanentCount(playerB, "Battering Sliver", 1 * cardsMultiplier);
*/
}
// TARGET AMOUNT
@Test @Test
public void test_targetAmount_PriorityKillByBigPT() { public void test_targetAmount_PriorityKillByBigPT() {
@ -228,4 +292,33 @@ public class TargetPriorityTest extends CardTestPlayerBaseAI {
assertPermanentCount(playerA, "Golden Bear", 3); assertPermanentCount(playerA, "Golden Bear", 3);
assertPermanentCount(playerA, "Battering Sliver", 3); assertPermanentCount(playerA, "Battering Sliver", 3);
} }
@Test
@Ignore // do not enable it in production, only for devs
public void test_targetAmount_Performance() {
int cardsMultiplier = 3;
Ability ability = new SimpleActivatedAbility(Zone.ALL, new DamageMultiEffect(3), new ManaCostsImpl("R"));
ability.addTarget(new TargetCreaturePermanentAmount(3));
addCustomCardWithAbility("damage 3", playerA, ability);
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 1);
//
addCard(Zone.BATTLEFIELD, playerB, "Memnite", 1 * cardsMultiplier); // 1/1
addCard(Zone.BATTLEFIELD, playerB, "Balduvian Bears", 1 * cardsMultiplier); // 2/2
addCard(Zone.BATTLEFIELD, playerB, "Ashcoat Bear", 1 * cardsMultiplier); // 2/2 with ability
addCard(Zone.BATTLEFIELD, playerB, "Golden Bear", 1 * cardsMultiplier); // 4/3
addCard(Zone.BATTLEFIELD, playerB, "Battering Sliver", 1 * cardsMultiplier); // 4/4 with ability
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{R}: ");
setStopAt(1, PhaseStep.BEGIN_COMBAT);
execute();
assertAllCommandsUsed();
assertPermanentCount(playerB, "Memnite", 1 * cardsMultiplier);
assertPermanentCount(playerB, "Balduvian Bears", 1 * cardsMultiplier);
assertPermanentCount(playerB, "Ashcoat Bear", 1 * cardsMultiplier);
assertPermanentCount(playerB, "Golden Bear", 1 * cardsMultiplier - 1);
assertPermanentCount(playerB, "Battering Sliver", 1 * cardsMultiplier);
}
} }

View file

@ -1,4 +1,3 @@
package org.mage.test.AI.basic; package org.mage.test.AI.basic;
import mage.constants.PhaseStep; import mage.constants.PhaseStep;
@ -9,7 +8,6 @@ import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBaseAI; import org.mage.test.serverside.base.CardTestPlayerBaseAI;
/** /**
*
* @author LevelX2 * @author LevelX2
*/ */
public class TargetsAreChosenTest extends CardTestPlayerBaseAI { public class TargetsAreChosenTest extends CardTestPlayerBaseAI {
@ -158,6 +156,7 @@ public class TargetsAreChosenTest extends CardTestPlayerBaseAI {
// Whenever a creature an opponent controls dies, put a +1/+1 counter on Malakir Cullblade. // Whenever a creature an opponent controls dies, put a +1/+1 counter on Malakir Cullblade.
addCard(Zone.BATTLEFIELD, playerA, "Malakir Cullblade", 5); addCard(Zone.BATTLEFIELD, playerA, "Malakir Cullblade", 5);
attackSkip(1, playerA);
attack(3, playerA, "Nefashu"); attack(3, playerA, "Nefashu");
setStopAt(3, PhaseStep.POSTCOMBAT_MAIN); setStopAt(3, PhaseStep.POSTCOMBAT_MAIN);
execute(); execute();

View file

@ -7,7 +7,7 @@ import mage.constants.Zone;
import mage.game.FreeForAll; import mage.game.FreeForAll;
import mage.game.Game; import mage.game.Game;
import mage.game.GameException; import mage.game.GameException;
import mage.game.mulligan.VancouverMulligan; import mage.game.mulligan.MulliganType;
import org.junit.Test; import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase; import org.mage.test.serverside.base.CardTestPlayerBase;
@ -16,7 +16,7 @@ import java.io.FileNotFoundException;
public class NaturesWillTest extends CardTestPlayerBase { public class NaturesWillTest extends CardTestPlayerBase {
@Override @Override
protected Game createNewGameAndPlayers() throws GameException, FileNotFoundException { protected Game createNewGameAndPlayers() throws GameException, FileNotFoundException {
Game game = new FreeForAll(MultiplayerAttackOption.MULTIPLE, RangeOfInfluence.ALL, new VancouverMulligan(0), 20); Game game = new FreeForAll(MultiplayerAttackOption.MULTIPLE, RangeOfInfluence.ALL, MulliganType.GAME_DEFAULT.getMulligan(0), 20);
playerA = createPlayer(game, playerA, "PlayerA"); playerA = createPlayer(game, playerA, "PlayerA");
playerB = createPlayer(game, playerB, "PlayerB"); playerB = createPlayer(game, playerB, "PlayerB");
playerC = createPlayer(game, playerC, "PlayerC"); playerC = createPlayer(game, playerC, "PlayerC");

View file

@ -7,7 +7,7 @@ import mage.constants.Zone;
import mage.game.FreeForAll; import mage.game.FreeForAll;
import mage.game.Game; import mage.game.Game;
import mage.game.GameException; import mage.game.GameException;
import mage.game.mulligan.VancouverMulligan; import mage.game.mulligan.MulliganType;
import org.junit.Test; import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase; import org.mage.test.serverside.base.CardTestPlayerBase;
@ -16,7 +16,7 @@ import java.io.FileNotFoundException;
public class StormTheVaultTest extends CardTestPlayerBase { public class StormTheVaultTest extends CardTestPlayerBase {
@Override @Override
protected Game createNewGameAndPlayers() throws GameException, FileNotFoundException { protected Game createNewGameAndPlayers() throws GameException, FileNotFoundException {
Game game = new FreeForAll(MultiplayerAttackOption.MULTIPLE, RangeOfInfluence.ALL, new VancouverMulligan(0), 20); Game game = new FreeForAll(MultiplayerAttackOption.MULTIPLE, RangeOfInfluence.ALL, MulliganType.GAME_DEFAULT.getMulligan(0), 20);
playerA = createPlayer(game, playerA, "PlayerA"); playerA = createPlayer(game, playerA, "PlayerA");
playerB = createPlayer(game, playerB, "PlayerB"); playerB = createPlayer(game, playerB, "PlayerB");
playerC = createPlayer(game, playerC, "PlayerC"); playerC = createPlayer(game, playerC, "PlayerC");

View file

@ -1,5 +1,6 @@
package org.mage.test.cards.continuous; package org.mage.test.cards.continuous;
import mage.abilities.keyword.FirstStrikeAbility;
import mage.constants.PhaseStep; import mage.constants.PhaseStep;
import mage.constants.Zone; import mage.constants.Zone;
import org.junit.Test; import org.junit.Test;
@ -183,4 +184,91 @@ public class CommandersCastTest extends CardTestCommander4Players {
execute(); execute();
assertAllCommandsUsed(); assertAllCommandsUsed();
} }
@Test
public void test_CastFromHandWithoutTaxIncrease() {
// Player order: A -> D -> C -> B
addCard(Zone.COMMAND, playerA, "Balduvian Bears", 1); // {1}{G}, 2/2, commander
addCard(Zone.BATTLEFIELD, playerA, "Forest", 4); // cast from command for {1}{G}, from hand for {1}{G}, from command for {1}{G}{2}
//
// Counter target spell. If that spell is countered this way, put it into its owners hand instead of into that players graveyard.
// Draw a card.
addCard(Zone.HAND, playerB, "Remand", 2);
addCard(Zone.BATTLEFIELD, playerB, "Island", 2); // counter 2 times
// cast 1 and counter (increase commander tax)
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Balduvian Bears");
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Remand", "Balduvian Bears", "Balduvian Bears");
setChoice(playerA, "No"); // move to hand
checkCommandCardCount("cast 1", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Balduvian Bears", 0);
checkHandCardCount("cast 1", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Balduvian Bears", 1);
checkPermanentCount("cast 1", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Balduvian Bears", 0);
///*
// cast 2 from hand without tax
castSpell(5, PhaseStep.PRECOMBAT_MAIN, playerA, "Balduvian Bears");
castSpell(5, PhaseStep.PRECOMBAT_MAIN, playerB, "Remand", "Balduvian Bears", "Balduvian Bears");
setChoice(playerA, "Yes"); // move to command zone
checkCommandCardCount("cast 2", 5, PhaseStep.POSTCOMBAT_MAIN, playerA, "Balduvian Bears", 1);
checkHandCardCount("cast 2", 5, PhaseStep.POSTCOMBAT_MAIN, playerA, "Balduvian Bears", 0);
checkPermanentCount("cast 2", 5, PhaseStep.POSTCOMBAT_MAIN, playerA, "Balduvian Bears", 0);
// cast 3 from command with tax for 1 play
castSpell(9, PhaseStep.PRECOMBAT_MAIN, playerA, "Balduvian Bears");
checkCommandCardCount("cast 3", 9, PhaseStep.BEGIN_COMBAT, playerA, "Balduvian Bears", 0);
checkHandCardCount("cast 3", 9, PhaseStep.BEGIN_COMBAT, playerA, "Balduvian Bears", 0);
checkPermanentCount("cast 3", 9, PhaseStep.BEGIN_COMBAT, playerA, "Balduvian Bears", 1);
setStrictChooseMode(true);
setStopAt(9, PhaseStep.END_TURN);
execute();
assertAllCommandsUsed();
}
@Test
public void test_AlternativeSpellNormal() {
// Player order: A -> D -> C -> B
// Weapon Surge
// Target creature you control gets +1/+0 and gains first strike until end of turn.
// Overload {1}{R} (You may cast this spell for its overload cost. If you do, change its text by replacing all instances of target with each.)
addCard(Zone.HAND, playerA, "Weapon Surge", 1); // {R} or {1}{R}
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 2);
//
addCard(Zone.BATTLEFIELD, playerA, "Balduvian Bears", 2);
// cast overload
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Weapon Surge with overload");
checkAbility("after", 1, PhaseStep.BEGIN_COMBAT, playerA, "Balduvian Bears", FirstStrikeAbility.class, true);
setStopAt(1, PhaseStep.END_TURN);
setStrictChooseMode(true);
execute();
assertAllCommandsUsed();
}
@Test
public void test_AlternativeSpellCommander() {
// Player order: A -> D -> C -> B
// Weapon Surge
// Target creature you control gets +1/+0 and gains first strike until end of turn.
// Overload {1}{R} (You may cast this spell for its overload cost. If you do, change its text by replacing all instances of target with each.)
addCard(Zone.COMMAND, playerA, "Weapon Surge", 1); // {R} or {1}{R}
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 2);
//
addCard(Zone.BATTLEFIELD, playerA, "Balduvian Bears", 2);
// cast overload
showAvaileableAbilities("before", 1, PhaseStep.PRECOMBAT_MAIN, playerA);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Weapon Surge with overload");
setChoice(playerA, "Yes"); // move to command zone
checkAbility("after", 1, PhaseStep.BEGIN_COMBAT, playerA, "Balduvian Bears", FirstStrikeAbility.class, true);
setStopAt(1, PhaseStep.END_TURN);
setStrictChooseMode(true);
execute();
assertAllCommandsUsed();
}
} }

View file

@ -0,0 +1,34 @@
package org.mage.test.cards.continuous;
import mage.constants.PhaseStep;
import mage.constants.Zone;
import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase;
/**
* @author JayDi85
*/
public class PraetorsGraspTest extends CardTestPlayerBase {
@Test
public void test_SimpleCast() {
// Search target opponents library for a card and exile it face down. Then that player shuffles their library.
// You may look at and play that card for as long as it remains exiled.
addCard(Zone.HAND, playerA, "Praetor's Grasp", 1); // {1}{B}{B}
addCard(Zone.BATTLEFIELD, playerA, "Swamp", 3);
addCard(Zone.LIBRARY, playerB, "Mountain", 1);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Praetor's Grasp");
addTarget(playerA, playerB);
addTarget(playerA, "Mountain");
showAvaileableAbilities("after", 1, PhaseStep.POSTCOMBAT_MAIN, playerA);
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertAllCommandsUsed();
assertGraveyardCount(playerA, "Praetor's Grasp", 1);
}
}

View file

@ -1,27 +0,0 @@
package org.mage.test.cards.continuous;
import mage.constants.PhaseStep;
import mage.constants.Zone;
import org.junit.Ignore;
import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase;
/**
* @author JayDi85
*/
public class TroveOfTemptationTest extends CardTestPlayerBase {
@Test
@Ignore // TODO: 2019-04-28 - improve and uncomment test after computer player can process playerMustBeAttackedIfAble restriction
public void test_SingleOpponentMustAttack() {
// Each opponent must attack you or a planeswalker you control with at least one creature each combat if able.
addCard(Zone.BATTLEFIELD, playerA, "Trove of Temptation");
addCard(Zone.BATTLEFIELD, playerB, "Balduvian Bears", 1); // 2/2
addCard(Zone.BATTLEFIELD, playerB, "Ashcoat Bear", 1); // 2/2
setStopAt(2, PhaseStep.END_TURN);
setStrictChooseMode(true);
execute();
assertAllCommandsUsed();
}
}

View file

@ -7,7 +7,7 @@ import mage.constants.Zone;
import mage.game.FreeForAll; import mage.game.FreeForAll;
import mage.game.Game; import mage.game.Game;
import mage.game.GameException; import mage.game.GameException;
import mage.game.mulligan.VancouverMulligan; import mage.game.mulligan.MulliganType;
import org.junit.Test; import org.junit.Test;
import org.mage.test.serverside.base.CardTestMultiPlayerBase; import org.mage.test.serverside.base.CardTestMultiPlayerBase;
@ -19,7 +19,7 @@ import java.io.FileNotFoundException;
public class RagsRichesTest extends CardTestMultiPlayerBase { public class RagsRichesTest extends CardTestMultiPlayerBase {
@Override @Override
protected Game createNewGameAndPlayers() throws GameException, FileNotFoundException { protected Game createNewGameAndPlayers() throws GameException, FileNotFoundException {
Game game = new FreeForAll(MultiplayerAttackOption.LEFT, RangeOfInfluence.ALL, new VancouverMulligan(0), 20); Game game = new FreeForAll(MultiplayerAttackOption.LEFT, RangeOfInfluence.ALL, MulliganType.GAME_DEFAULT.getMulligan(0), 20);
// Player order: A -> D -> C -> B // Player order: A -> D -> C -> B
playerA = createPlayer(game, playerA, "PlayerA"); playerA = createPlayer(game, playerA, "PlayerA");
playerB = createPlayer(game, playerB, "PlayerB"); playerB = createPlayer(game, playerB, "PlayerB");

View file

@ -5,7 +5,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;
@ -13,19 +12,20 @@ import mage.constants.Zone;
import mage.game.FreeForAll; import mage.game.FreeForAll;
import mage.game.Game; import mage.game.Game;
import mage.game.GameException; import mage.game.GameException;
import mage.game.mulligan.VancouverMulligan; import mage.game.mulligan.MulliganType;
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
*/ */
public class BlatantThieveryTest extends CardTestMultiPlayerBase { public class BlatantThieveryTest extends CardTestMultiPlayerBase {
@Override @Override
protected Game createNewGameAndPlayers() throws GameException, FileNotFoundException { protected Game createNewGameAndPlayers() throws GameException, FileNotFoundException {
Game game = new FreeForAll(MultiplayerAttackOption.MULTIPLE, RangeOfInfluence.ALL, new VancouverMulligan(0), 20); Game game = new FreeForAll(MultiplayerAttackOption.MULTIPLE, RangeOfInfluence.ALL, MulliganType.GAME_DEFAULT.getMulligan(0), 20);
// Player order: A -> D -> C -> B // Player order: A -> D -> C -> B
playerA = createPlayer(game, playerA, "PlayerA"); playerA = createPlayer(game, playerA, "PlayerA");
playerB = createPlayer(game, playerB, "PlayerB"); playerB = createPlayer(game, playerB, "PlayerB");

View file

@ -1,6 +1,5 @@
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;
@ -8,23 +7,25 @@ import mage.constants.Zone;
import mage.game.FreeForAll; import mage.game.FreeForAll;
import mage.game.Game; import mage.game.Game;
import mage.game.GameException; import mage.game.GameException;
import mage.game.mulligan.VancouverMulligan; import mage.game.mulligan.MulliganType;
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;
/** /**
* Enchantment {3}{B} * Enchantment {3}{B}
* At the beginning of your upkeep, each player discards a card. * At the beginning of your upkeep, each player discards a card.
* Each opponent who discarded a card that shares a card type with the card you discarded loses 3 life. * Each opponent who discarded a card that shares a card type with the card you discarded loses 3 life.
* (Players reveal the discarded cards simultaneously.) * (Players reveal the discarded cards simultaneously.)
* *
* @author escplan9 (Derek Monturo - dmontur1 at gmail dot com * @author escplan9 (Derek Monturo - dmontur1 at gmail dot com
*/ */
public class CreepingDreadTest extends CardTestMultiPlayerBase { public class CreepingDreadTest extends CardTestMultiPlayerBase {
@Override @Override
protected Game createNewGameAndPlayers() throws GameException, FileNotFoundException { protected Game createNewGameAndPlayers() throws GameException, FileNotFoundException {
Game game = new FreeForAll(MultiplayerAttackOption.MULTIPLE, RangeOfInfluence.ALL, new VancouverMulligan(0), 40); Game game = new FreeForAll(MultiplayerAttackOption.MULTIPLE, RangeOfInfluence.ALL, MulliganType.GAME_DEFAULT.getMulligan(0), 40);
// Player order: A -> D -> C -> B // Player order: A -> D -> C -> B
playerA = createPlayer(game, playerA, "PlayerA"); playerA = createPlayer(game, playerA, "PlayerA");
playerB = createPlayer(game, playerB, "PlayerB"); playerB = createPlayer(game, playerB, "PlayerB");
@ -32,27 +33,27 @@ public class CreepingDreadTest extends CardTestMultiPlayerBase {
playerD = createPlayer(game, playerD, "PlayerD"); playerD = createPlayer(game, playerD, "PlayerD");
return game; return game;
} }
/** /**
* Discard creature and all opponents who discard creature lose 3 life * Discard creature and all opponents who discard creature lose 3 life
*/ */
@Test @Test
public void basicTest() { public void basicTest() {
addCard(Zone.BATTLEFIELD, playerA, "Creeping Dread"); addCard(Zone.BATTLEFIELD, playerA, "Creeping Dread");
addCard(Zone.HAND, playerA, "Merfolk Looter"); addCard(Zone.HAND, playerA, "Merfolk Looter");
addCard(Zone.HAND, playerB, "Hill Giant"); addCard(Zone.HAND, playerB, "Hill Giant");
addCard(Zone.HAND, playerC, "Elite Vanguard"); addCard(Zone.HAND, playerC, "Elite Vanguard");
addCard(Zone.HAND, playerD, "Bone Saw"); addCard(Zone.HAND, playerD, "Bone Saw");
setChoice(playerA, "Merfolk Looter"); setChoice(playerA, "Merfolk Looter");
setChoice(playerB, "Hill Giant"); setChoice(playerB, "Hill Giant");
setChoice(playerC, "Elite Vanguard"); setChoice(playerC, "Elite Vanguard");
setChoice(playerD, "Bone Saw"); setChoice(playerD, "Bone Saw");
setStopAt(1, PhaseStep.DRAW); setStopAt(1, PhaseStep.DRAW);
execute(); execute();
assertGraveyardCount(playerA, "Merfolk Looter", 1); assertGraveyardCount(playerA, "Merfolk Looter", 1);
assertGraveyardCount(playerB, "Hill Giant", 1); assertGraveyardCount(playerB, "Hill Giant", 1);
assertGraveyardCount(playerC, "Elite Vanguard", 1); assertGraveyardCount(playerC, "Elite Vanguard", 1);
@ -63,27 +64,27 @@ public class CreepingDreadTest extends CardTestMultiPlayerBase {
assertLife(playerC, 37); assertLife(playerC, 37);
assertLife(playerD, 40); // no match assertLife(playerD, 40); // no match
} }
/** /**
* Discard Artifact Creature and all opponents who discard either an Artifact or Creature lose 3 life * Discard Artifact Creature and all opponents who discard either an Artifact or Creature lose 3 life
*/ */
@Test @Test
public void twoTypesTest() { public void twoTypesTest() {
addCard(Zone.BATTLEFIELD, playerA, "Creeping Dread"); addCard(Zone.BATTLEFIELD, playerA, "Creeping Dread");
addCard(Zone.HAND, playerA, "Bronze Sable"); addCard(Zone.HAND, playerA, "Bronze Sable");
addCard(Zone.HAND, playerB, "Hill Giant"); addCard(Zone.HAND, playerB, "Hill Giant");
addCard(Zone.HAND, playerC, "Swamp"); addCard(Zone.HAND, playerC, "Swamp");
addCard(Zone.HAND, playerD, "Bone Saw"); addCard(Zone.HAND, playerD, "Bone Saw");
setChoice(playerA, "Bronze Sable"); setChoice(playerA, "Bronze Sable");
setChoice(playerB, "Hill Giant"); setChoice(playerB, "Hill Giant");
setChoice(playerC, "Swamp"); setChoice(playerC, "Swamp");
setChoice(playerD, "Bone Saw"); setChoice(playerD, "Bone Saw");
setStopAt(1, PhaseStep.DRAW); setStopAt(1, PhaseStep.DRAW);
execute(); execute();
assertGraveyardCount(playerA, "Bronze Sable", 1); assertGraveyardCount(playerA, "Bronze Sable", 1);
assertGraveyardCount(playerB, "Hill Giant", 1); assertGraveyardCount(playerB, "Hill Giant", 1);
assertGraveyardCount(playerC, "Swamp", 1); assertGraveyardCount(playerC, "Swamp", 1);
@ -94,63 +95,63 @@ public class CreepingDreadTest extends CardTestMultiPlayerBase {
assertLife(playerC, 40); // neither assertLife(playerC, 40); // neither
assertLife(playerD, 37); // artifact assertLife(playerD, 37); // artifact
} }
/** /**
* Discard enchantment and no opponents discard an enchantment, so no one loses life * Discard enchantment and no opponents discard an enchantment, so no one loses life
*/ */
@Test @Test
public void noMatchesTest() { public void noMatchesTest() {
addCard(Zone.BATTLEFIELD, playerA, "Creeping Dread"); addCard(Zone.BATTLEFIELD, playerA, "Creeping Dread");
addCard(Zone.HAND, playerA, "Moat"); // enchantment addCard(Zone.HAND, playerA, "Moat"); // enchantment
addCard(Zone.HAND, playerB, "Hill Giant"); addCard(Zone.HAND, playerB, "Hill Giant");
addCard(Zone.HAND, playerC, "Swamp"); addCard(Zone.HAND, playerC, "Swamp");
addCard(Zone.HAND, playerD, "Bone Saw"); addCard(Zone.HAND, playerD, "Bone Saw");
setChoice(playerA, "Moat"); setChoice(playerA, "Moat");
setChoice(playerB, "Hill Giant"); setChoice(playerB, "Hill Giant");
setChoice(playerC, "Swamp"); setChoice(playerC, "Swamp");
setChoice(playerD, "Bone Saw"); setChoice(playerD, "Bone Saw");
setStopAt(1, PhaseStep.DRAW); setStopAt(1, PhaseStep.DRAW);
execute(); execute();
assertGraveyardCount(playerA, "Moat", 1); assertGraveyardCount(playerA, "Moat", 1);
assertGraveyardCount(playerB, "Hill Giant", 1); assertGraveyardCount(playerB, "Hill Giant", 1);
assertGraveyardCount(playerC, "Swamp", 1); assertGraveyardCount(playerC, "Swamp", 1);
assertGraveyardCount(playerD, "Bone Saw", 1); assertGraveyardCount(playerD, "Bone Saw", 1);
assertLife(playerA, 40); // no matches assertLife(playerA, 40); // no matches
assertLife(playerB, 40); assertLife(playerB, 40);
assertLife(playerC, 40); assertLife(playerC, 40);
assertLife(playerD, 40); assertLife(playerD, 40);
} }
/** /**
* Upkeep player has no cards to discard, so no matches * Upkeep player has no cards to discard, so no matches
*/ */
@Test @Test
public void noDiscardNoMatches() { public void noDiscardNoMatches() {
addCard(Zone.BATTLEFIELD, playerA, "Creeping Dread"); addCard(Zone.BATTLEFIELD, playerA, "Creeping Dread");
addCard(Zone.HAND, playerB, "Hill Giant"); addCard(Zone.HAND, playerB, "Hill Giant");
addCard(Zone.HAND, playerC, "Swamp"); addCard(Zone.HAND, playerC, "Swamp");
addCard(Zone.HAND, playerD, "Bone Saw"); addCard(Zone.HAND, playerD, "Bone Saw");
setChoice(playerB, "Hill Giant"); setChoice(playerB, "Hill Giant");
setChoice(playerC, "Swamp"); setChoice(playerC, "Swamp");
setChoice(playerD, "Bone Saw"); setChoice(playerD, "Bone Saw");
setStopAt(1, PhaseStep.DRAW); setStopAt(1, PhaseStep.DRAW);
execute(); execute();
assertGraveyardCount(playerB, "Hill Giant", 1); assertGraveyardCount(playerB, "Hill Giant", 1);
assertGraveyardCount(playerC, "Swamp", 1); assertGraveyardCount(playerC, "Swamp", 1);
assertGraveyardCount(playerD, "Bone Saw", 1); assertGraveyardCount(playerD, "Bone Saw", 1);
assertLife(playerA, 40); // no matches assertLife(playerA, 40); // no matches
assertLife(playerB, 40); assertLife(playerB, 40);
assertLife(playerC, 40); assertLife(playerC, 40);
assertLife(playerD, 40); assertLife(playerD, 40);
} }
} }

View file

@ -1,6 +1,5 @@
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;
@ -8,15 +7,17 @@ import mage.constants.Zone;
import mage.game.FreeForAll; import mage.game.FreeForAll;
import mage.game.Game; import mage.game.Game;
import mage.game.GameException; import mage.game.GameException;
import mage.game.mulligan.VancouverMulligan; import mage.game.mulligan.MulliganType;
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;
public class MultiplayerTriggerTest extends CardTestMultiPlayerBase { public class MultiplayerTriggerTest extends CardTestMultiPlayerBase {
@Override @Override
protected Game createNewGameAndPlayers() throws GameException, FileNotFoundException { protected Game createNewGameAndPlayers() throws GameException, FileNotFoundException {
Game game = new FreeForAll(MultiplayerAttackOption.MULTIPLE, RangeOfInfluence.ALL, new VancouverMulligan(0), 40); Game game = new FreeForAll(MultiplayerAttackOption.MULTIPLE, RangeOfInfluence.ALL, MulliganType.GAME_DEFAULT.getMulligan(0), 40);
// Player order: A -> D -> C -> B // Player order: A -> D -> C -> B
playerA = createPlayer(game, playerA, "PlayerA"); playerA = createPlayer(game, playerA, "PlayerA");
playerB = createPlayer(game, playerB, "PlayerB"); playerB = createPlayer(game, playerB, "PlayerB");

View file

@ -7,7 +7,7 @@ import mage.constants.Zone;
import mage.game.FreeForAll; import mage.game.FreeForAll;
import mage.game.Game; import mage.game.Game;
import mage.game.GameException; import mage.game.GameException;
import mage.game.mulligan.VancouverMulligan; import mage.game.mulligan.MulliganType;
import org.junit.Test; import org.junit.Test;
import org.mage.test.serverside.base.CardTestMultiPlayerBase; import org.mage.test.serverside.base.CardTestMultiPlayerBase;
@ -20,7 +20,7 @@ public class MyriadTest extends CardTestMultiPlayerBase {
@Override @Override
protected Game createNewGameAndPlayers() throws GameException, FileNotFoundException { protected Game createNewGameAndPlayers() throws GameException, FileNotFoundException {
Game game = new FreeForAll(MultiplayerAttackOption.MULTIPLE, RangeOfInfluence.ALL, new VancouverMulligan(0), 40); Game game = new FreeForAll(MultiplayerAttackOption.MULTIPLE, RangeOfInfluence.ALL, MulliganType.GAME_DEFAULT.getMulligan(0), 40);
// Player order: A -> D -> C -> B // Player order: A -> D -> C -> B
playerA = createPlayer(game, playerA, "PlayerA"); playerA = createPlayer(game, playerA, "PlayerA");
playerB = createPlayer(game, playerB, "PlayerB"); playerB = createPlayer(game, playerB, "PlayerB");

View file

@ -5,7 +5,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;
@ -13,13 +12,14 @@ import mage.constants.Zone;
import mage.game.FreeForAll; import mage.game.FreeForAll;
import mage.game.Game; import mage.game.Game;
import mage.game.GameException; import mage.game.GameException;
import mage.game.mulligan.VancouverMulligan; import mage.game.mulligan.MulliganType;
import org.junit.Assert; 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
*/ */
public class PlayerDiedStackTargetHandlingTest extends CardTestMultiPlayerBase { public class PlayerDiedStackTargetHandlingTest extends CardTestMultiPlayerBase {
@ -27,7 +27,7 @@ public class PlayerDiedStackTargetHandlingTest extends CardTestMultiPlayerBase {
@Override @Override
protected Game createNewGameAndPlayers() throws GameException, FileNotFoundException { protected Game createNewGameAndPlayers() throws GameException, FileNotFoundException {
// Start Life = 2 // Start Life = 2
Game game = new FreeForAll(MultiplayerAttackOption.MULTIPLE, RangeOfInfluence.ONE, new VancouverMulligan(0), 3); Game game = new FreeForAll(MultiplayerAttackOption.MULTIPLE, RangeOfInfluence.ONE, MulliganType.GAME_DEFAULT.getMulligan(0), 3);
// Player order: A -> D -> C -> B // Player order: A -> D -> C -> B
playerA = createPlayer(game, playerA, "PlayerA"); playerA = createPlayer(game, playerA, "PlayerA");
playerB = createPlayer(game, playerB, "PlayerB"); playerB = createPlayer(game, playerB, "PlayerB");

View file

@ -1,7 +1,5 @@
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;
@ -10,14 +8,15 @@ import mage.counters.CounterType;
import mage.game.FreeForAll; import mage.game.FreeForAll;
import mage.game.Game; import mage.game.Game;
import mage.game.GameException; import mage.game.GameException;
import mage.game.mulligan.VancouverMulligan; import mage.game.mulligan.MulliganType;
import mage.game.permanent.Permanent; import mage.game.permanent.Permanent;
import org.junit.Assert; 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
*/ */
public class PlayerLeftGameRange1Test extends CardTestMultiPlayerBase { public class PlayerLeftGameRange1Test extends CardTestMultiPlayerBase {
@ -25,7 +24,7 @@ public class PlayerLeftGameRange1Test extends CardTestMultiPlayerBase {
@Override @Override
protected Game createNewGameAndPlayers() throws GameException, FileNotFoundException { protected Game createNewGameAndPlayers() throws GameException, FileNotFoundException {
// Start Life = 2 // Start Life = 2
Game game = new FreeForAll(MultiplayerAttackOption.MULTIPLE, RangeOfInfluence.ONE, new VancouverMulligan(0), 2); Game game = new FreeForAll(MultiplayerAttackOption.MULTIPLE, RangeOfInfluence.ONE, MulliganType.GAME_DEFAULT.getMulligan(0), 2);
// Player order: A -> D -> C -> B // Player order: A -> D -> C -> B
playerA = createPlayer(game, playerA, "PlayerA"); playerA = createPlayer(game, playerA, "PlayerA");
playerB = createPlayer(game, playerB, "PlayerB"); playerB = createPlayer(game, playerB, "PlayerB");
@ -131,7 +130,7 @@ public class PlayerLeftGameRange1Test extends CardTestMultiPlayerBase {
* player concedes the game and removes the Planeswalker. Once it becomes an * player concedes the game and removes the Planeswalker. Once it becomes an
* opponent of the original player's turn and that opponent plays a spell, * opponent of the original player's turn and that opponent plays a spell,
* Xmage throws an error and rollsback the turn. * Xmage throws an error and rollsback the turn.
* * <p>
* I don't have the actual error report on my due to negligence, but what I * I don't have the actual error report on my due to negligence, but what I
* can recollect is that the error message was along the lines of "The * can recollect is that the error message was along the lines of "The
* emblem cannot find the original source. This turn will be rolled back". * emblem cannot find the original source. This turn will be rolled back".
@ -278,7 +277,7 @@ public class PlayerLeftGameRange1Test extends CardTestMultiPlayerBase {
/** /**
* Pithing Needle keeps the named card's abilities disabled even after the * Pithing Needle keeps the named card's abilities disabled even after the
* player controlling the Needle loses the game. * player controlling the Needle loses the game.
* * <p>
* I saw it happen during a Commander game. A player cast Pithing Needle * I saw it happen during a Commander game. A player cast Pithing Needle
* targeting my Proteus Staff. After I killed him, I still couldn't activate * targeting my Proteus Staff. After I killed him, I still couldn't activate
* the Staff. * the Staff.

View file

@ -1,6 +1,5 @@
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;
@ -9,13 +8,14 @@ import mage.counters.CounterType;
import mage.game.FreeForAll; import mage.game.FreeForAll;
import mage.game.Game; import mage.game.Game;
import mage.game.GameException; import mage.game.GameException;
import mage.game.mulligan.VancouverMulligan; import mage.game.mulligan.MulliganType;
import org.junit.Assert; 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
*/ */
public class PlayerLeftGameRangeAllTest extends CardTestMultiPlayerBase { public class PlayerLeftGameRangeAllTest extends CardTestMultiPlayerBase {
@ -23,7 +23,7 @@ public class PlayerLeftGameRangeAllTest extends CardTestMultiPlayerBase {
@Override @Override
protected Game createNewGameAndPlayers() throws GameException, FileNotFoundException { protected Game createNewGameAndPlayers() throws GameException, FileNotFoundException {
// Start Life = 2 // Start Life = 2
Game game = new FreeForAll(MultiplayerAttackOption.MULTIPLE, RangeOfInfluence.ALL, new VancouverMulligan(0), 2); Game game = new FreeForAll(MultiplayerAttackOption.MULTIPLE, RangeOfInfluence.ALL, MulliganType.GAME_DEFAULT.getMulligan(0), 2);
// Player order: A -> D -> C -> B // Player order: A -> D -> C -> B
playerA = createPlayer(game, playerA, "PlayerA"); playerA = createPlayer(game, playerA, "PlayerA");
playerB = createPlayer(game, playerB, "PlayerB"); playerB = createPlayer(game, playerB, "PlayerB");
@ -129,7 +129,7 @@ public class PlayerLeftGameRangeAllTest extends CardTestMultiPlayerBase {
* player concedes the game and removes the Planeswalker. Once it becomes an * player concedes the game and removes the Planeswalker. Once it becomes an
* opponent of the original player's turn and that opponent plays a spell, * opponent of the original player's turn and that opponent plays a spell,
* Xmage throws an error and rollsback the turn. * Xmage throws an error and rollsback the turn.
* * <p>
* I don't have the actual error report on my due to negligence, but what I * I don't have the actual error report on my due to negligence, but what I
* can recollect is that the error message was along the lines of "The * can recollect is that the error message was along the lines of "The
* emblem cannot find the original source. This turn will be rolled back". * emblem cannot find the original source. This turn will be rolled back".
@ -283,7 +283,7 @@ public class PlayerLeftGameRangeAllTest extends CardTestMultiPlayerBase {
} }
/** /**
* * 11/4/2015: In a multiplayer game, if Grasp of Fate's owner leaves the * * 11/4/2015: In a multiplayer game, if Grasp of Fate's owner leaves the
* game, the exiled cards will return to the battlefield. Because the * game, the exiled cards will return to the battlefield. Because the
* one-shot effect that returns the cards isn't an ability that goes on the * one-shot effect that returns the cards isn't an ability that goes on the
* stack, it won't cease to exist along with the leaving player's spells and * stack, it won't cease to exist along with the leaving player's spells and

View file

@ -9,7 +9,7 @@ import mage.counters.CounterType;
import mage.game.FreeForAll; import mage.game.FreeForAll;
import mage.game.Game; import mage.game.Game;
import mage.game.GameException; import mage.game.GameException;
import mage.game.mulligan.VancouverMulligan; import mage.game.mulligan.MulliganType;
import org.junit.Test; import org.junit.Test;
import org.mage.test.serverside.base.CardTestMultiPlayerBase; import org.mage.test.serverside.base.CardTestMultiPlayerBase;
@ -22,7 +22,7 @@ public class PrivilegedPositionTest extends CardTestMultiPlayerBase {
@Override @Override
protected Game createNewGameAndPlayers() throws GameException, FileNotFoundException { protected Game createNewGameAndPlayers() throws GameException, FileNotFoundException {
Game game = new FreeForAll(MultiplayerAttackOption.MULTIPLE, RangeOfInfluence.ALL, new VancouverMulligan(0), 40); Game game = new FreeForAll(MultiplayerAttackOption.MULTIPLE, RangeOfInfluence.ALL, MulliganType.GAME_DEFAULT.getMulligan(0), 40);
// Player order: A -> D -> C -> B // Player order: A -> D -> C -> B
playerA = createPlayer(game, playerA, "PlayerA"); playerA = createPlayer(game, playerA, "PlayerA");
playerB = createPlayer(game, playerB, "PlayerB"); playerB = createPlayer(game, playerB, "PlayerB");
@ -32,9 +32,9 @@ public class PrivilegedPositionTest extends CardTestMultiPlayerBase {
} }
/* /*
* Reported bug: see issue #3328 * Reported bug: see issue #3328
* Players unable to attack Planeswalker with Privileged Position on battlefield. * Players unable to attack Planeswalker with Privileged Position on battlefield.
*/ */
@Test @Test
public void testAttackPlaneswalkerWithHexproofPrivilegedPosition() { public void testAttackPlaneswalkerWithHexproofPrivilegedPosition() {

View file

@ -1,7 +1,5 @@
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;
@ -9,19 +7,20 @@ import mage.constants.Zone;
import mage.game.FreeForAll; import mage.game.FreeForAll;
import mage.game.Game; import mage.game.Game;
import mage.game.GameException; import mage.game.GameException;
import mage.game.mulligan.VancouverMulligan; import mage.game.mulligan.MulliganType;
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
*/ */
public class VindictiveLichTest extends CardTestMultiPlayerBase { public class VindictiveLichTest extends CardTestMultiPlayerBase {
@Override @Override
protected Game createNewGameAndPlayers() throws GameException, FileNotFoundException { protected Game createNewGameAndPlayers() throws GameException, FileNotFoundException {
Game game = new FreeForAll(MultiplayerAttackOption.MULTIPLE, RangeOfInfluence.ALL, new VancouverMulligan(0), 40); Game game = new FreeForAll(MultiplayerAttackOption.MULTIPLE, RangeOfInfluence.ALL, MulliganType.GAME_DEFAULT.getMulligan(0), 40);
// Player order: A -> D -> C -> B // Player order: A -> D -> C -> B
playerA = createPlayer(game, playerA, "PlayerA"); playerA = createPlayer(game, playerA, "PlayerA");
playerB = createPlayer(game, playerB, "PlayerB"); playerB = createPlayer(game, playerB, "PlayerB");

View file

@ -72,7 +72,9 @@ public class TestPlayer implements Player {
private static final Logger logger = Logger.getLogger(TestPlayer.class); private static final Logger logger = Logger.getLogger(TestPlayer.class);
public static final String TARGET_SKIP = "[skip]"; public static final String TARGET_SKIP = "[target_skip]";
public static final String BLOCK_SKIP = "[block_skip]";
public static final String ATTACK_SKIP = "[attack_skip]";
private int maxCallsWithoutAction = 100; private int maxCallsWithoutAction = 100;
private int foundNoAction = 0; private int foundNoAction = 0;
@ -574,7 +576,8 @@ public class TestPlayer implements Player {
if (permanent.getName().equals(groups[0])) { if (permanent.getName().equals(groups[0])) {
Counter counter = new Counter(groups[1], Integer.parseInt(groups[2])); Counter counter = new Counter(groups[1], Integer.parseInt(groups[2]));
permanent.addCounters(counter, null, game); permanent.addCounters(counter, null, game);
break; actions.remove(action);
return true;
} }
} }
} else if (action.getAction().startsWith("waitStackResolved")) { } else if (action.getAction().startsWith("waitStackResolved")) {
@ -901,8 +904,8 @@ public class TestPlayer implements Player {
.map(a -> ( .map(a -> (
a.getZone() + " -> " a.getZone() + " -> "
+ a.getSourceObject(game).getIdName() + " -> " + a.getSourceObject(game).getIdName() + " -> "
+ (a.getRule().length() > 0 + (a.toString().length() > 0
? a.getRule().substring(0, Math.min(20, a.getRule().length()) - 1) ? a.toString().substring(0, Math.min(20, a.toString().length()) - 1)
: a.getClass().getSimpleName()) : a.getClass().getSimpleName())
+ "..." + "..."
)) ))
@ -1242,6 +1245,14 @@ public class TestPlayer implements Player {
mustAttackByAction = true; mustAttackByAction = true;
String command = action.getAction(); String command = action.getAction();
command = command.substring(command.indexOf("attack:") + 7); command = command.substring(command.indexOf("attack:") + 7);
// skip attack
if (command.startsWith(ATTACK_SKIP)) {
it.remove();
madeAttackByAction = true;
break;
}
String[] groups = command.split("\\$"); String[] groups = command.split("\\$");
for (int i = 1; i < groups.length; i++) { for (int i = 1; i < groups.length; i++) {
String group = groups[i]; String group = groups[i];
@ -1291,6 +1302,11 @@ public class TestPlayer implements Player {
if (mustAttackByAction && !madeAttackByAction) { if (mustAttackByAction && !madeAttackByAction) {
this.chooseStrictModeFailed(game, "select attackers must use attack command but don't"); this.chooseStrictModeFailed(game, "select attackers must use attack command but don't");
} }
// AI play if no actions available
if (!mustAttackByAction && this.AIPlayer) {
this.computerPlayer.selectAttackers(game, attackingPlayerId);
}
} }
@Override @Override
@ -1306,10 +1322,19 @@ public class TestPlayer implements Player {
UUID opponentId = game.getOpponents(computerPlayer.getId()).iterator().next(); UUID opponentId = game.getOpponents(computerPlayer.getId()).iterator().next();
// Map of Blocker reference -> list of creatures blocked // Map of Blocker reference -> list of creatures blocked
Map<MageObjectReference, List<MageObjectReference>> blockedCreaturesByCreature = new HashMap<>(); Map<MageObjectReference, List<MageObjectReference>> blockedCreaturesByCreature = new HashMap<>();
boolean mustBlockByAction = false;
for (PlayerAction action : tempActions) { for (PlayerAction action : tempActions) {
if (action.getTurnNum() == game.getTurnNum() && action.getAction().startsWith("block:")) { if (action.getTurnNum() == game.getTurnNum() && action.getAction().startsWith("block:")) {
mustBlockByAction = true;
String command = action.getAction(); String command = action.getAction();
command = command.substring(command.indexOf("block:") + 6); command = command.substring(command.indexOf("block:") + 6);
// skip block
if (command.startsWith(BLOCK_SKIP)) {
actions.remove(action);
break;
}
String[] groups = command.split("\\$"); String[] groups = command.split("\\$");
String blockerName = groups[0]; String blockerName = groups[0];
String attackerName = groups[1]; String attackerName = groups[1];
@ -1324,6 +1349,11 @@ public class TestPlayer implements Player {
} }
} }
checkMultipleBlockers(game, blockedCreaturesByCreature); checkMultipleBlockers(game, blockedCreaturesByCreature);
// AI play if no actions available
if (!mustBlockByAction && this.AIPlayer) {
this.computerPlayer.selectBlockers(game, defendingPlayerId);
}
} }
// Checks if a creature can block at least one more creature // Checks if a creature can block at least one more creature

View file

@ -10,7 +10,7 @@ import mage.game.Game;
import mage.game.GameException; import mage.game.GameException;
import mage.game.GameOptions; import mage.game.GameOptions;
import mage.game.TwoPlayerDuel; import mage.game.TwoPlayerDuel;
import mage.game.mulligan.VancouverMulligan; import mage.game.mulligan.MulliganType;
import mage.player.ai.ComputerPlayer; import mage.player.ai.ComputerPlayer;
import mage.players.Player; import mage.players.Player;
import mage.players.PlayerType; import mage.players.PlayerType;
@ -36,7 +36,7 @@ public class PlayGameTest extends MageTestBase {
@Ignore @Ignore
@Test @Test
public void playOneGame() throws GameException, FileNotFoundException, IllegalArgumentException { public void playOneGame() throws GameException, FileNotFoundException, IllegalArgumentException {
Game game = new TwoPlayerDuel(MultiplayerAttackOption.LEFT, RangeOfInfluence.ALL, new VancouverMulligan(0), 20); Game game = new TwoPlayerDuel(MultiplayerAttackOption.LEFT, RangeOfInfluence.ALL, MulliganType.GAME_DEFAULT.getMulligan(0), 20);
Player computerA = createPlayer("ComputerA", PlayerType.COMPUTER_MINIMAX_HYBRID); Player computerA = createPlayer("ComputerA", PlayerType.COMPUTER_MINIMAX_HYBRID);
// Player playerA = createPlayer("ComputerA", "Computer - mad"); // Player playerA = createPlayer("ComputerA", "Computer - mad");

View file

@ -10,7 +10,7 @@ import mage.game.Game;
import mage.game.GameException; import mage.game.GameException;
import mage.game.GameOptions; import mage.game.GameOptions;
import mage.game.TwoPlayerDuel; import mage.game.TwoPlayerDuel;
import mage.game.mulligan.VancouverMulligan; import mage.game.mulligan.MulliganType;
import mage.player.ai.ComputerPlayer; import mage.player.ai.ComputerPlayer;
import mage.players.Player; import mage.players.Player;
import mage.util.RandomUtil; import mage.util.RandomUtil;
@ -42,7 +42,7 @@ public class TestPlayRandomGame extends MageTestBase {
} }
private void playOneGame() throws GameException, FileNotFoundException, IllegalArgumentException { private void playOneGame() throws GameException, FileNotFoundException, IllegalArgumentException {
Game game = new TwoPlayerDuel(MultiplayerAttackOption.LEFT, RangeOfInfluence.ALL, new VancouverMulligan(0), 20); Game game = new TwoPlayerDuel(MultiplayerAttackOption.LEFT, RangeOfInfluence.ALL, MulliganType.GAME_DEFAULT.getMulligan(0), 20);
Player computerA = createRandomPlayer("ComputerA"); Player computerA = createRandomPlayer("ComputerA");
Deck deck = generateRandomDeck(); Deck deck = generateRandomDeck();

View file

@ -1,16 +1,16 @@
package org.mage.test.serverside.base; package org.mage.test.serverside.base;
import java.io.FileNotFoundException;
import mage.constants.MultiplayerAttackOption; import mage.constants.MultiplayerAttackOption;
import mage.constants.RangeOfInfluence; import mage.constants.RangeOfInfluence;
import mage.game.CommanderFreeForAll; import mage.game.CommanderFreeForAll;
import mage.game.Game; import mage.game.Game;
import mage.game.GameException; import mage.game.GameException;
import mage.game.mulligan.VancouverMulligan; import mage.game.mulligan.MulliganType;
import org.mage.test.serverside.base.impl.CardTestPlayerAPIImpl; import org.mage.test.serverside.base.impl.CardTestPlayerAPIImpl;
import java.io.FileNotFoundException;
/** /**
*
* @author LevelX2 * @author LevelX2
*/ */
public abstract class CardTestCommander3PlayersFFA extends CardTestPlayerAPIImpl { public abstract class CardTestCommander3PlayersFFA extends CardTestPlayerAPIImpl {
@ -24,7 +24,7 @@ public abstract class CardTestCommander3PlayersFFA extends CardTestPlayerAPIImpl
@Override @Override
protected Game createNewGameAndPlayers() throws GameException, FileNotFoundException { protected Game createNewGameAndPlayers() throws GameException, FileNotFoundException {
Game game = new CommanderFreeForAll(MultiplayerAttackOption.MULTIPLE, RangeOfInfluence.ONE, new VancouverMulligan(0), 40); Game game = new CommanderFreeForAll(MultiplayerAttackOption.MULTIPLE, RangeOfInfluence.ONE, MulliganType.GAME_DEFAULT.getMulligan(0), 40);
playerA = createPlayer(game, playerA, "PlayerA", deckNameA); playerA = createPlayer(game, playerA, "PlayerA", deckNameA);
playerB = createPlayer(game, playerB, "PlayerB", deckNameB); playerB = createPlayer(game, playerB, "PlayerB", deckNameB);
playerC = createPlayer(game, playerC, "PlayerC", deckNameC); playerC = createPlayer(game, playerC, "PlayerC", deckNameC);

View file

@ -5,7 +5,7 @@ import mage.constants.RangeOfInfluence;
import mage.game.CommanderFreeForAll; import mage.game.CommanderFreeForAll;
import mage.game.Game; import mage.game.Game;
import mage.game.GameException; import mage.game.GameException;
import mage.game.mulligan.VancouverMulligan; import mage.game.mulligan.MulliganType;
import org.mage.test.serverside.base.impl.CardTestPlayerAPIImpl; import org.mage.test.serverside.base.impl.CardTestPlayerAPIImpl;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
@ -17,7 +17,7 @@ public abstract class CardTestCommander4Players extends CardTestPlayerAPIImpl {
@Override @Override
protected Game createNewGameAndPlayers() throws GameException, FileNotFoundException { protected Game createNewGameAndPlayers() throws GameException, FileNotFoundException {
Game game = new CommanderFreeForAll(MultiplayerAttackOption.MULTIPLE, RangeOfInfluence.ALL, new VancouverMulligan(0), 20); Game game = new CommanderFreeForAll(MultiplayerAttackOption.MULTIPLE, RangeOfInfluence.ALL, MulliganType.GAME_DEFAULT.getMulligan(0), 20);
// Player order: A -> D -> C -> B // Player order: A -> D -> C -> B
playerA = createPlayer(game, playerA, "PlayerA"); playerA = createPlayer(game, playerA, "PlayerA");
playerB = createPlayer(game, playerB, "PlayerB"); playerB = createPlayer(game, playerB, "PlayerB");

View file

@ -1,17 +1,16 @@
package org.mage.test.serverside.base; package org.mage.test.serverside.base;
import java.io.FileNotFoundException;
import mage.constants.MultiplayerAttackOption; import mage.constants.MultiplayerAttackOption;
import mage.constants.RangeOfInfluence; import mage.constants.RangeOfInfluence;
import mage.game.CommanderDuel; import mage.game.CommanderDuel;
import mage.game.Game; import mage.game.Game;
import mage.game.GameException; import mage.game.GameException;
import mage.game.mulligan.VancouverMulligan; import mage.game.mulligan.MulliganType;
import org.mage.test.serverside.base.impl.CardTestPlayerAPIImpl; import org.mage.test.serverside.base.impl.CardTestPlayerAPIImpl;
import java.io.FileNotFoundException;
/** /**
*
* @author LevelX2 * @author LevelX2
*/ */
public abstract class CardTestCommanderDuelBase extends CardTestPlayerAPIImpl { public abstract class CardTestCommanderDuelBase extends CardTestPlayerAPIImpl {
@ -24,7 +23,7 @@ public abstract class CardTestCommanderDuelBase extends CardTestPlayerAPIImpl {
@Override @Override
protected Game createNewGameAndPlayers() throws GameException, FileNotFoundException { protected Game createNewGameAndPlayers() throws GameException, FileNotFoundException {
Game game = new CommanderDuel(MultiplayerAttackOption.LEFT, RangeOfInfluence.ONE, new VancouverMulligan(0), 40); Game game = new CommanderDuel(MultiplayerAttackOption.LEFT, RangeOfInfluence.ONE, MulliganType.GAME_DEFAULT.getMulligan(0), 40);
playerA = createPlayer(game, playerA, "PlayerA", deckNameA); playerA = createPlayer(game, playerA, "PlayerA", deckNameA);
playerB = createPlayer(game, playerB, "PlayerB", deckNameB); playerB = createPlayer(game, playerB, "PlayerB", deckNameB);

View file

@ -5,7 +5,7 @@ import mage.constants.RangeOfInfluence;
import mage.game.FreeForAll; import mage.game.FreeForAll;
import mage.game.Game; import mage.game.Game;
import mage.game.GameException; import mage.game.GameException;
import mage.game.mulligan.VancouverMulligan; import mage.game.mulligan.MulliganType;
import org.mage.test.serverside.base.impl.CardTestPlayerAPIImpl; import org.mage.test.serverside.base.impl.CardTestPlayerAPIImpl;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
@ -21,7 +21,7 @@ public abstract class CardTestMultiPlayerBase extends CardTestPlayerAPIImpl {
@Override @Override
protected Game createNewGameAndPlayers() throws GameException, FileNotFoundException { protected Game createNewGameAndPlayers() throws GameException, FileNotFoundException {
Game game = new FreeForAll(MultiplayerAttackOption.LEFT, RangeOfInfluence.ONE, new VancouverMulligan(0), 20); Game game = new FreeForAll(MultiplayerAttackOption.LEFT, RangeOfInfluence.ONE, MulliganType.GAME_DEFAULT.getMulligan(0), 20);
// Player order: A -> D -> C -> B // Player order: A -> D -> C -> B
playerA = createPlayer(game, playerA, "PlayerA"); playerA = createPlayer(game, playerA, "PlayerA");
playerB = createPlayer(game, playerB, "PlayerB"); playerB = createPlayer(game, playerB, "PlayerB");

View file

@ -5,7 +5,7 @@ import mage.constants.RangeOfInfluence;
import mage.game.FreeForAll; import mage.game.FreeForAll;
import mage.game.Game; import mage.game.Game;
import mage.game.GameException; import mage.game.GameException;
import mage.game.mulligan.VancouverMulligan; import mage.game.mulligan.MulliganType;
import org.mage.test.serverside.base.impl.CardTestPlayerAPIImpl; import org.mage.test.serverside.base.impl.CardTestPlayerAPIImpl;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
@ -17,7 +17,7 @@ public abstract class CardTestMultiPlayerBaseWithRangeAll extends CardTestPlayer
@Override @Override
protected Game createNewGameAndPlayers() throws GameException, FileNotFoundException { protected Game createNewGameAndPlayers() throws GameException, FileNotFoundException {
Game game = new FreeForAll(MultiplayerAttackOption.LEFT, RangeOfInfluence.ALL, new VancouverMulligan(0), 20); Game game = new FreeForAll(MultiplayerAttackOption.LEFT, RangeOfInfluence.ALL, MulliganType.GAME_DEFAULT.getMulligan(0), 20);
// Player order: A -> D -> C -> B // Player order: A -> D -> C -> B
playerA = createPlayer(game, playerA, "PlayerA"); playerA = createPlayer(game, playerA, "PlayerA");
playerB = createPlayer(game, playerB, "PlayerB"); playerB = createPlayer(game, playerB, "PlayerB");

View file

@ -1,33 +1,34 @@
package org.mage.test.serverside.base; package org.mage.test.serverside.base;
import java.io.FileNotFoundException;
import mage.constants.MultiplayerAttackOption; import mage.constants.MultiplayerAttackOption;
import mage.constants.RangeOfInfluence; import mage.constants.RangeOfInfluence;
import mage.game.Game; import mage.game.Game;
import mage.game.GameException; import mage.game.GameException;
import mage.game.TwoPlayerDuel; import mage.game.TwoPlayerDuel;
import mage.game.mulligan.VancouverMulligan; import mage.game.mulligan.MulliganType;
import org.mage.test.serverside.base.impl.CardTestPlayerAPIImpl; import org.mage.test.serverside.base.impl.CardTestPlayerAPIImpl;
import java.io.FileNotFoundException;
/** /**
* Base class for testing single cards and effects. * Base class for testing single cards and effects.
* *
* @author ayratn * @author ayratn
*/ */
public abstract class CardTestPlayerBase extends CardTestPlayerAPIImpl { public abstract class CardTestPlayerBase extends CardTestPlayerAPIImpl {
public CardTestPlayerBase() { public CardTestPlayerBase() {
deckNameA = "RB Aggro.dck"; deckNameA = "RB Aggro.dck";
deckNameB = "RB Aggro.dck"; deckNameB = "RB Aggro.dck";
} }
@Override @Override
protected Game createNewGameAndPlayers() throws GameException, FileNotFoundException { protected Game createNewGameAndPlayers() throws GameException, FileNotFoundException {
Game game = new TwoPlayerDuel(MultiplayerAttackOption.LEFT, RangeOfInfluence.ONE, new VancouverMulligan(0), 20); Game game = new TwoPlayerDuel(MultiplayerAttackOption.LEFT, RangeOfInfluence.ONE, MulliganType.GAME_DEFAULT.getMulligan(0), 20);
playerA = createPlayer(game, playerA, "PlayerA", deckNameA); playerA = createPlayer(game, playerA, "PlayerA", deckNameA);
playerB = createPlayer(game, playerB, "PlayerB", deckNameB); playerB = createPlayer(game, playerB, "PlayerB", deckNameB);
return game; return game;
} }
} }

View file

@ -5,7 +5,7 @@ import mage.constants.RangeOfInfluence;
import mage.game.Game; import mage.game.Game;
import mage.game.GameException; import mage.game.GameException;
import mage.game.TwoPlayerDuel; import mage.game.TwoPlayerDuel;
import mage.game.mulligan.VancouverMulligan; import mage.game.mulligan.MulliganType;
import org.mage.test.player.TestComputerPlayer7; import org.mage.test.player.TestComputerPlayer7;
import org.mage.test.player.TestPlayer; import org.mage.test.player.TestPlayer;
import org.mage.test.serverside.base.impl.CardTestPlayerAPIImpl; import org.mage.test.serverside.base.impl.CardTestPlayerAPIImpl;
@ -21,7 +21,7 @@ public abstract class CardTestPlayerBaseAI extends CardTestPlayerAPIImpl {
@Override @Override
protected Game createNewGameAndPlayers() throws GameException, FileNotFoundException { protected Game createNewGameAndPlayers() throws GameException, FileNotFoundException {
Game game = new TwoPlayerDuel(MultiplayerAttackOption.LEFT, RangeOfInfluence.ONE, new VancouverMulligan(0), 20); Game game = new TwoPlayerDuel(MultiplayerAttackOption.LEFT, RangeOfInfluence.ONE, MulliganType.GAME_DEFAULT.getMulligan(0), 20);
playerA = createPlayer(game, playerA, "PlayerA"); playerA = createPlayer(game, playerA, "PlayerA");
playerB = createPlayer(game, playerB, "PlayerB"); playerB = createPlayer(game, playerB, "PlayerB");
@ -37,8 +37,4 @@ public abstract class CardTestPlayerBaseAI extends CardTestPlayerAPIImpl {
} }
return super.createPlayer(name, rangeOfInfluence); return super.createPlayer(name, rangeOfInfluence);
} }
public void setAISkill(int skill) {
this.skill = skill;
}
} }

View file

@ -1527,12 +1527,20 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement
player.addAction(turnNum, PhaseStep.DECLARE_ATTACKERS, new StringBuilder("attack:").append(attacker).append("$planeswalker=").append(planeswalker).toString()); player.addAction(turnNum, PhaseStep.DECLARE_ATTACKERS, new StringBuilder("attack:").append(attacker).append("$planeswalker=").append(planeswalker).toString());
} }
public void attackSkip(int turnNum, TestPlayer player) {
attack(turnNum, player, TestPlayer.ATTACK_SKIP);
}
public void block(int turnNum, TestPlayer player, String blocker, String attacker) { public void block(int turnNum, TestPlayer player, String blocker, String attacker) {
//Assert.assertNotEquals("", blocker); //Assert.assertNotEquals("", blocker);
//Assert.assertNotEquals("", attacker); //Assert.assertNotEquals("", attacker);
player.addAction(turnNum, PhaseStep.DECLARE_BLOCKERS, "block:" + blocker + '$' + attacker); player.addAction(turnNum, PhaseStep.DECLARE_BLOCKERS, "block:" + blocker + '$' + attacker);
} }
public void blockSkip(int turnNum, TestPlayer player) {
block(turnNum, player, TestPlayer.BLOCK_SKIP, "");
}
/** /**
* 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. For X values set
* "X=[xValue]" example: for X=3 set choice string to "X=3". * "X=[xValue]" example: for X=3 set choice string to "X=3".

View file

@ -7,7 +7,7 @@ import mage.constants.PlanarDieRoll;
import mage.constants.RangeOfInfluence; import mage.constants.RangeOfInfluence;
import mage.game.Game; import mage.game.Game;
import mage.game.TwoPlayerDuel; import mage.game.TwoPlayerDuel;
import mage.game.mulligan.VancouverMulligan; import mage.game.mulligan.MulliganType;
import mage.player.human.HumanPlayer; import mage.player.human.HumanPlayer;
import mage.players.Player; import mage.players.Player;
import mage.util.RandomUtil; import mage.util.RandomUtil;
@ -93,7 +93,7 @@ public class RandomTest {
String dest = "f:/test/xmage/"; String dest = "f:/test/xmage/";
//RandomUtil.setSeed(123); //RandomUtil.setSeed(123);
Player player = new HumanPlayer("random", RangeOfInfluence.ALL, 1); Player player = new HumanPlayer("random", RangeOfInfluence.ALL, 1);
Game game = new TwoPlayerDuel(MultiplayerAttackOption.MULTIPLE, RangeOfInfluence.ALL, new VancouverMulligan(0), 50); Game game = new TwoPlayerDuel(MultiplayerAttackOption.MULTIPLE, RangeOfInfluence.ALL, MulliganType.GAME_DEFAULT.getMulligan(0), 50);
int height = 512; int height = 512;
int weight = 512; int weight = 512;
@ -116,7 +116,7 @@ public class RandomTest {
String dest = "f:/test/xmage/"; String dest = "f:/test/xmage/";
//RandomUtil.setSeed(123); //RandomUtil.setSeed(123);
Player player = new HumanPlayer("random", RangeOfInfluence.ALL, 1); Player player = new HumanPlayer("random", RangeOfInfluence.ALL, 1);
Game game = new TwoPlayerDuel(MultiplayerAttackOption.MULTIPLE, RangeOfInfluence.ALL, new VancouverMulligan(0), 50); Game game = new TwoPlayerDuel(MultiplayerAttackOption.MULTIPLE, RangeOfInfluence.ALL, MulliganType.GAME_DEFAULT.getMulligan(0), 50);
int height = 512; int height = 512;
int weight = 512; int weight = 512;
@ -141,7 +141,7 @@ public class RandomTest {
String dest = "f:/test/xmage/"; String dest = "f:/test/xmage/";
//RandomUtil.setSeed(123); //RandomUtil.setSeed(123);
Player player = new HumanPlayer("random", RangeOfInfluence.ALL, 1); Player player = new HumanPlayer("random", RangeOfInfluence.ALL, 1);
Game game = new TwoPlayerDuel(MultiplayerAttackOption.MULTIPLE, RangeOfInfluence.ALL, new VancouverMulligan(0), 50); Game game = new TwoPlayerDuel(MultiplayerAttackOption.MULTIPLE, RangeOfInfluence.ALL, MulliganType.GAME_DEFAULT.getMulligan(0), 50);
Deck deck = DeckTestUtils.buildRandomDeck("WGUBR", false, "GRN"); Deck deck = DeckTestUtils.buildRandomDeck("WGUBR", false, "GRN");
player.getLibrary().addAll(deck.getCards(), game); player.getLibrary().addAll(deck.getCards(), game);

View file

@ -2,7 +2,6 @@ package mage.abilities.common;
import mage.abilities.SpellAbility; import mage.abilities.SpellAbility;
import mage.cards.Card; import mage.cards.Card;
import mage.constants.SpellAbilityType;
import mage.constants.Zone; import mage.constants.Zone;
/** /**
@ -10,16 +9,20 @@ import mage.constants.Zone;
*/ */
public class CastCommanderAbility extends SpellAbility { public class CastCommanderAbility extends SpellAbility {
public CastCommanderAbility(Card card) { private String ruleText;
super(card.getSpellAbility());
public CastCommanderAbility(Card card, SpellAbility spellTemplate) {
super(spellTemplate);
this.newId(); this.newId();
this.setCardName(card.getName()); this.setCardName(spellTemplate.getCardName());
zone = Zone.COMMAND; this.zone = Zone.COMMAND;
spellAbilityType = SpellAbilityType.BASE; this.spellAbilityType = spellTemplate.getSpellAbilityType();
this.ruleText = spellTemplate.getRule(); // need to support custom rule texts like OverloadAbility
} }
public CastCommanderAbility(final CastCommanderAbility ability) { public CastCommanderAbility(final CastCommanderAbility ability) {
super(ability); super(ability);
this.ruleText = ability.ruleText;
} }
@Override @Override
@ -27,4 +30,9 @@ public class CastCommanderAbility extends SpellAbility {
return new CastCommanderAbility(this); return new CastCommanderAbility(this);
} }
@Override
public String getRule() {
return ruleText;
}
} }

View file

@ -13,18 +13,20 @@ import mage.game.permanent.Permanent;
import mage.game.stack.Spell; import mage.game.stack.Spell;
import mage.players.Player; import mage.players.Player;
import java.util.Locale;
import java.util.UUID; import java.util.UUID;
/** /**
* @author Plopman * @author Plopman, JayDi85
*/ */
//20130711 // 2019-07-12
/* /*
* 903.11. If a commander would be put into its owner's graveyard from anywhere, that player may put it into the command zone instead. 903.9. If a commander would be exiled from anywhere or put into its owners hand, graveyard, or library from anywhere,
* 903.12. If a commander would be put into the exile zone from anywhere, its owner may put it into the command zone instead. its owner may put it into the command zone instead. This replacement effect may apply more than once to the same event.
* 903.9. If a commander would be exiled from anywhere or put into its owner's hand, graveyard, or This is an exception to rule 614.5.
library from anywhere, its owner may put it into the command zone instead. This replacement effect 903.9a If a commander is a melded permanent and its owner chooses to put it into the command zone this way,
may apply more than once to the same event. This is an exception to rule 614.5. that permanent and the card representing it that isnt a commander are put into the appropriate zone, and the card
that represents it and is a commander is put into the command zone.
*/ */
// Oathbreaker mode: If your Oathbreaker changes zones, you may return it to the Command Zone. The Signature Spell must return to the Command Zone. // Oathbreaker mode: If your Oathbreaker changes zones, you may return it to the Command Zone. The Signature Spell must return to the Command Zone.
@ -32,8 +34,8 @@ may apply more than once to the same event. This is an exception to rule 614.5.
public class CommanderReplacementEffect extends ReplacementEffectImpl { public class CommanderReplacementEffect extends ReplacementEffectImpl {
private final UUID commanderId; private final UUID commanderId;
private final boolean alsoHand; private final boolean alsoHand; // return from hand to command zone
private final boolean alsoLibrary; private final boolean alsoLibrary; // return from library to command zone
private final boolean forceToMove; private final boolean forceToMove;
private final String commanderTypeName; private final String commanderTypeName;
@ -87,56 +89,56 @@ public class CommanderReplacementEffect extends ReplacementEffectImpl {
@Override @Override
public boolean applies(GameEvent event, Ability source, Game game) { public boolean applies(GameEvent event, Ability source, Game game) {
switch (((ZoneChangeEvent) event).getToZone()) { ZoneChangeEvent zEvent = (ZoneChangeEvent) event;
case HAND:
if (!alsoHand && ((ZoneChangeEvent) event).getToZone() == Zone.HAND) { if (!game.isSimulation() && commanderId.equals(zEvent.getTargetId())) {
return false; //System.out.println("applies " + game.getTurnNum() + ": " + game.getObject(event.getTargetId()).getName() + ": " + zEvent.getFromZone() + " -> " + zEvent.getToZone() + "; " + game.getObject(zEvent.getSourceId()));
} }
if (zEvent.getToZone().equals(Zone.HAND) && !alsoHand) {
return false;
}
if (zEvent.getToZone().equals(Zone.LIBRARY) && !alsoLibrary) {
return false;
}
// return to command zone
switch (zEvent.getToZone()) {
case LIBRARY: case LIBRARY:
if (!alsoLibrary && ((ZoneChangeEvent) event).getToZone() == Zone.LIBRARY) { case HAND:
return false;
}
case GRAVEYARD: case GRAVEYARD:
case EXILED: case EXILED:
if (((ZoneChangeEvent) event).getFromZone() == Zone.STACK) { if (commanderId.equals(zEvent.getTargetId())) {
Spell spell = game.getStack().getSpell(event.getTargetId());
if (spell != null && commanderId.equals(spell.getSourceId())) {
return true;
}
}
if (commanderId.equals(event.getTargetId())) {
return true; return true;
} }
break; break;
case STACK:
Spell spell = game.getStack().getSpell(event.getTargetId());
if (spell != null) {
if (commanderId.equals(spell.getSourceId())) {
return true;
}
}
break;
} }
return false; return false;
} }
@Override @Override
public boolean replaceEvent(GameEvent event, Ability source, Game game) { public boolean replaceEvent(GameEvent event, Ability source, Game game) {
if (((ZoneChangeEvent) event).getFromZone() == Zone.BATTLEFIELD) { ZoneChangeEvent zEvent = (ZoneChangeEvent) event;
Permanent permanent = ((ZoneChangeEvent) event).getTarget(); String originToZone = zEvent.getToZone().toString().toLowerCase(Locale.ENGLISH);
if (!game.isSimulation()) {
//System.out.println("replace " + game.getTurnNum() + ": " + game.getObject(event.getTargetId()).getName() + ": " + zEvent.getFromZone() + " -> " + zEvent.getToZone() + "; " + game.getObject(zEvent.getSourceId()));
}
if (zEvent.getFromZone() == Zone.BATTLEFIELD) {
Permanent permanent = zEvent.getTarget();
if (permanent != null) { if (permanent != null) {
Player player = game.getPlayer(permanent.getOwnerId()); Player player = game.getPlayer(permanent.getOwnerId());
if (player != null && (forceToMove || player.chooseUse(Outcome.Benefit, "Move " + commanderTypeName + " to command zone?", source, game))) { if (player != null && (forceToMove || player.chooseUse(Outcome.Benefit, "Move " + commanderTypeName + " to command zone instead " + originToZone + "?", source, game))) {
((ZoneChangeEvent) event).setToZone(Zone.COMMAND); zEvent.setToZone(Zone.COMMAND);
if (!game.isSimulation()) { if (!game.isSimulation()) {
game.informPlayers(player.getLogName() + " has moved their " + commanderTypeName + " to the command zone"); game.informPlayers(player.getLogName() + " has moved their " + commanderTypeName + " to the command zone instead " + originToZone);
} }
} }
} }
} else { } else {
Card card = null; Card card = null;
if (((ZoneChangeEvent) event).getFromZone() == Zone.STACK) { if (zEvent.getFromZone() == Zone.STACK) {
Spell spell = game.getStack().getSpell(event.getTargetId()); Spell spell = game.getStack().getSpell(event.getTargetId());
if (spell != null) { if (spell != null) {
card = game.getCard(spell.getSourceId()); card = game.getCard(spell.getSourceId());
@ -147,10 +149,10 @@ public class CommanderReplacementEffect extends ReplacementEffectImpl {
} }
if (card != null) { if (card != null) {
Player player = game.getPlayer(card.getOwnerId()); Player player = game.getPlayer(card.getOwnerId());
if (player != null && (forceToMove || player.chooseUse(Outcome.Benefit, "Move " + commanderTypeName + " to command zone?", source, game))) { if (player != null && (forceToMove || player.chooseUse(Outcome.Benefit, "Move " + commanderTypeName + " to command zone instead " + originToZone + "?", source, game))) {
((ZoneChangeEvent) event).setToZone(Zone.COMMAND); ((ZoneChangeEvent) event).setToZone(Zone.COMMAND);
if (!game.isSimulation()) { if (!game.isSimulation()) {
game.informPlayers(player.getLogName() + " has moved their " + commanderTypeName + " to the command zone"); game.informPlayers(player.getLogName() + " has moved their " + commanderTypeName + " to the command zone instead " + originToZone);
} }
} }
} }

View file

@ -20,6 +20,9 @@ import java.util.UUID;
command zone costs an additional {2} for each previous time the player casting it has cast it from command zone costs an additional {2} for each previous time the player casting it has cast it from
the command zone that game. This additional cost is informally known as the commander tax. the command zone that game. This additional cost is informally known as the commander tax.
*/ */
// cast from hand like Remand do not increase commander tax
public class CommanderCostModification extends CostModificationEffectImpl { public class CommanderCostModification extends CostModificationEffectImpl {
private final UUID commanderId; private final UUID commanderId;

View file

@ -5,7 +5,6 @@ package mage.constants;
* @author North * @author North
*/ */
public enum AsThoughEffectType { public enum AsThoughEffectType {
ATTACK, ATTACK,
ATTACK_AS_HASTE, ATTACK_AS_HASTE,
ACTIVATE_HASTE, ACTIVATE_HASTE,
@ -20,7 +19,7 @@ public enum AsThoughEffectType {
BLOCK_FORESTWALK, BLOCK_FORESTWALK,
DAMAGE_NOT_BLOCKED, DAMAGE_NOT_BLOCKED,
BE_BLOCKED, BE_BLOCKED,
PLAY_FROM_NOT_OWN_HAND_ZONE, PLAY_FROM_NOT_OWN_HAND_ZONE, // do not use dialogs in "applies" method for that type of effect (it calls multiple times)
CAST_AS_INSTANT, CAST_AS_INSTANT,
ACTIVATE_AS_INSTANT, ACTIVATE_AS_INSTANT,
DAMAGE, DAMAGE,

View file

@ -23,8 +23,11 @@ public abstract class GameCommanderImpl extends GameImpl {
// private final Map<UUID, Cards> mulliganedCards = new HashMap<>(); // private final Map<UUID, Cards> mulliganedCards = new HashMap<>();
protected boolean checkCommanderDamage = true; protected boolean checkCommanderDamage = true;
protected boolean alsoHand; // replace commander going to hand
protected boolean alsoLibrary; // replace commander going to library // old commander's versions (before 2017) restrict return from hand or library to command zone
protected boolean alsoHand = true; // replace commander going to hand
protected boolean alsoLibrary = true; // replace commander going to library
protected boolean startingPlayerSkipsDraw = true; protected boolean startingPlayerSkipsDraw = true;
public GameCommanderImpl(MultiplayerAttackOption attackOption, RangeOfInfluence range, Mulligan mulligan, int startLife) { public GameCommanderImpl(MultiplayerAttackOption attackOption, RangeOfInfluence range, Mulligan mulligan, int startLife) {

View file

@ -12,6 +12,7 @@ import mage.abilities.text.TextPart;
import mage.cards.Card; import mage.cards.Card;
import mage.cards.FrameStyle; import mage.cards.FrameStyle;
import mage.constants.CardType; import mage.constants.CardType;
import mage.constants.SpellAbilityType;
import mage.constants.SubType; import mage.constants.SubType;
import mage.constants.SuperType; import mage.constants.SuperType;
import mage.game.Game; import mage.game.Game;
@ -19,7 +20,6 @@ import mage.game.events.ZoneChangeEvent;
import mage.util.GameLog; import mage.util.GameLog;
import mage.util.SubTypeList; import mage.util.SubTypeList;
import java.util.EnumSet;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.UUID; import java.util.UUID;
@ -36,7 +36,17 @@ public class Commander implements CommandObject {
// replace spell ability by commander cast spell (to cast from command zone) // replace spell ability by commander cast spell (to cast from command zone)
if (card.getSpellAbility() != null) { if (card.getSpellAbility() != null) {
abilities.add(new CastCommanderAbility(card)); abilities.add(new CastCommanderAbility(card, card.getSpellAbility()));
}
// replace alternative spell abilities by commander cast spell (to cast from command zone)
for (Ability ability : card.getAbilities()) {
if (ability instanceof SpellAbility) {
SpellAbility spellAbility = (SpellAbility) ability;
if (spellAbility.getSpellAbilityType() == SpellAbilityType.BASE_ALTERNATE) {
abilities.add(new CastCommanderAbility(card, spellAbility));
}
}
} }
// replace play land with commander play land (to play from command zone) // replace play land with commander play land (to play from command zone)

View file

@ -91,6 +91,7 @@ public class LondonMulligan extends Mulligan {
} }
} }
openingHandSizes.put(playerId, openingHandSizes.get(playerId) - deduction); openingHandSizes.put(playerId, openingHandSizes.get(playerId) - deduction);
int newHandSize = openingHandSizes.get(player.getId());
if (deduction == 0) { if (deduction == 0) {
game.fireInformEvent(new StringBuilder(player.getLogName()) game.fireInformEvent(new StringBuilder(player.getLogName())
.append(" mulligans for free.") .append(" mulligans for free.")
@ -99,14 +100,13 @@ public class LondonMulligan extends Mulligan {
game.fireInformEvent(new StringBuilder(player.getLogName()) game.fireInformEvent(new StringBuilder(player.getLogName())
.append(" mulligans") .append(" mulligans")
.append(" down to ") .append(" down to ")
.append((numCards - deduction)) .append(newHandSize)
.append(numCards - deduction == 1 ? " card" : " cards").toString()); .append(newHandSize == 1 ? " card" : " cards").toString());
} }
player.drawCards(numCards, game); player.drawCards(numCards, game);
int handSize = openingHandSizes.get(player.getId()); if (player.getHand().size() > newHandSize) {
if (player.getHand().size() > handSize) { int cardsToDiscard = player.getHand().size() - newHandSize;
int cardsToDiscard = player.getHand().size() - handSize;
Cards cards = new CardsImpl(); Cards cards = new CardsImpl();
cards.addAll(player.getHand()); cards.addAll(player.getHand());
TargetCard target = new TargetCard(cardsToDiscard, cardsToDiscard, Zone.HAND, TargetCard target = new TargetCard(cardsToDiscard, cardsToDiscard, Zone.HAND,
@ -118,7 +118,8 @@ public class LondonMulligan extends Mulligan {
} }
@Override @Override
public void endMulligan(Game game, UUID playerId) {} public void endMulligan(Game game, UUID playerId) {
}
@Override @Override
public LondonMulligan copy() { public LondonMulligan copy() {

View file

@ -22,11 +22,11 @@ public enum MulliganType {
return new ParisMulligan(freeMulligans); return new ParisMulligan(freeMulligans);
case CANADIAN_HIGHLANDER: case CANADIAN_HIGHLANDER:
return new CanadianHighlanderMulligan(freeMulligans); return new CanadianHighlanderMulligan(freeMulligans);
case LONDON:
return new LondonMulligan(freeMulligans);
default:
case VANCOUVER: case VANCOUVER:
return new VancouverMulligan(freeMulligans); return new VancouverMulligan(freeMulligans);
default:
case LONDON:
return new LondonMulligan(freeMulligans);
} }
} }
@ -48,7 +48,6 @@ public enum MulliganType {
return res; return res;
} }
public MulliganType orDefault(MulliganType defaultMulligan) { public MulliganType orDefault(MulliganType defaultMulligan) {
if (this == GAME_DEFAULT) { if (this == GAME_DEFAULT) {
return defaultMulligan; return defaultMulligan;

View file

@ -1189,11 +1189,17 @@ public abstract class PlayerImpl implements Player, Serializable {
//20091005 - 305.1 //20091005 - 305.1
if (!game.replaceEvent(GameEvent.getEvent(GameEvent.EventType.PLAY_LAND, card.getId(), card.getId(), playerId, activationStatus.getPermittingObject()))) { if (!game.replaceEvent(GameEvent.getEvent(GameEvent.EventType.PLAY_LAND, card.getId(), card.getId(), playerId, activationStatus.getPermittingObject()))) {
// int bookmark = game.bookmarkState(); // int bookmark = game.bookmarkState();
game.fireEvent(GameEvent.getEvent(GameEvent.EventType.PLAY_LAND, card.getId(), card.getId(), playerId, activationStatus.getPermittingObject())); // land events must return original zone (uses for commander watcher)
Zone cardZoneBefore = game.getState().getZone(card.getId());
GameEvent landEventBefore = GameEvent.getEvent(GameEvent.EventType.PLAY_LAND, card.getId(), card.getId(), playerId, activationStatus.getPermittingObject());
landEventBefore.setZone(cardZoneBefore);
game.fireEvent(landEventBefore);
if (moveCards(card, Zone.BATTLEFIELD, playLandAbility, game, false, false, false, null)) { if (moveCards(card, Zone.BATTLEFIELD, playLandAbility, game, false, false, false, null)) {
landsPlayed++; landsPlayed++;
game.fireEvent(GameEvent.getEvent(GameEvent.EventType.LAND_PLAYED, card.getId(), card.getId(), playerId, activationStatus.getPermittingObject())); GameEvent landEventAfter = GameEvent.getEvent(GameEvent.EventType.LAND_PLAYED, card.getId(), card.getId(), playerId, activationStatus.getPermittingObject());
landEventAfter.setZone(cardZoneBefore);
game.fireEvent(landEventAfter);
game.fireInformEvent(getLogName() + " plays " + card.getLogName()); game.fireInformEvent(getLogName() + " plays " + card.getLogName());
// game.removeBookmark(bookmark); // game.removeBookmark(bookmark);
resetStoredBookmark(game); // prevent undo after playing a land resetStoredBookmark(game); // prevent undo after playing a land

View file

@ -1,6 +1,7 @@
package mage.watchers.common; package mage.watchers.common;
import mage.constants.WatcherScope; import mage.constants.WatcherScope;
import mage.constants.Zone;
import mage.game.Game; import mage.game.Game;
import mage.game.events.GameEvent; import mage.game.events.GameEvent;
import mage.game.events.GameEvent.EventType; import mage.game.events.GameEvent.EventType;
@ -12,7 +13,8 @@ import java.util.Map;
import java.util.UUID; import java.util.UUID;
/** /**
* Calcs commanders play count (spell or land) * Calcs commanders play count only from command zone (spell or land)
* Cards like Remand can put command to hand and cast it without commander tax increase
* *
* @author JayDi85 * @author JayDi85
*/ */
@ -49,7 +51,7 @@ public class CommanderPlaysCountWatcher extends Watcher {
} }
} }
if (isCommanderObject) { if (isCommanderObject && event.getZone() == Zone.COMMAND) {
int count = playsCount.getOrDefault(possibleCommanderId, 0); int count = playsCount.getOrDefault(possibleCommanderId, 0);
count++; count++;
playsCount.put(possibleCommanderId, count); playsCount.put(possibleCommanderId, count);