1
0
Fork 0
mirror of https://github.com/correl/mage.git synced 2025-04-03 09:18:59 -09:00

* Commander abilities - fixed that it increases commander tax after cast/play from non-command zone (as example: after Remand effect);

This commit is contained in:
Oleg Agafonov 2019-07-13 10:47:02 +04:00
parent 2197d8ee4a
commit 8c40a1d1a7
15 changed files with 105 additions and 92 deletions
Mage.Server.Plugins
Mage.Game.BrawlDuel/src/mage/game
Mage.Game.BrawlFreeForAll/src/mage/game
Mage.Game.CommanderDuel/src/mage/game
Mage.Game.CommanderFreeForAll/src/mage/game
Mage.Game.FreeformCommanderDuel/src/mage/game
Mage.Game.FreeformCommanderFreeForAll/src/mage/game
Mage.Game.OathbreakerDuel/src/mage/game
Mage.Game.OathbreakerFreeForAll/src/mage/game
Mage.Game.PennyDreadfulCommanderFreeForAll/src/mage/game
Mage.Tests/src/test/java/org/mage/test/cards/continuous
Mage/src/main/java/mage

View file

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

View file

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

View file

@ -1,4 +1,3 @@
package mage.game;
import mage.game.match.MatchImpl;
@ -6,7 +5,6 @@ import mage.game.match.MatchOptions;
import mage.game.mulligan.Mulligan;
/**
*
* @author BetaSteward_at_googlemail.com
*/
public class CommanderDuelMatch extends MatchImpl {
@ -18,24 +16,19 @@ public class CommanderDuelMatch extends MatchImpl {
@Override
public void startGame() throws GameException {
int startLife = 40;
boolean alsoHand = true;
// Don't like it to compare but seems like it's complicated to do it in another way
boolean checkCommanderDamage = true;
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.
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
}
if (options.getDeckType().equals("Variant Magic - MTGO 1v1 Commander")) {
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());
CommanderDuel game = new CommanderDuel(options.getAttackOption(), options.getRange(), mulligan, startLife);
game.setCheckCommanderDamage(checkCommanderDamage);
game.setStartMessage(this.createGameStartMessage());
game.setAlsoHand(alsoHand);
game.setAlsoLibrary(true);
initGame(game);
games.add(game);
}

View file

@ -1,5 +1,3 @@
package mage.game;
import mage.game.match.MatchImpl;
@ -7,7 +5,6 @@ import mage.game.match.MatchOptions;
import mage.game.mulligan.Mulligan;
/**
*
* @author LevelX2
*/
public class CommanderFreeForAllMatch extends MatchImpl {
@ -19,16 +16,12 @@ public class CommanderFreeForAllMatch extends MatchImpl {
@Override
public void startGame() throws GameException {
int startLife = 40;
boolean alsoHand = true;
if (options.getDeckType().equals("Variant Magic - Duel Commander")) {
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());
CommanderFreeForAll game = new CommanderFreeForAll(options.getAttackOption(), options.getRange(), mulligan, startLife);
game.setStartMessage(this.createGameStartMessage());
game.setAlsoHand(alsoHand);
game.setAlsoLibrary(true);
initGame(game);
games.add(game);
}

View file

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

View file

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

View file

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

View file

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

View file

@ -1,5 +1,3 @@
package mage.game;
import mage.game.match.MatchImpl;
@ -7,7 +5,6 @@ import mage.game.match.MatchOptions;
import mage.game.mulligan.Mulligan;
/**
*
* @author spjspj
*/
public class PennyDreadfulCommanderFreeForAllMatch extends MatchImpl {
@ -19,16 +16,12 @@ public class PennyDreadfulCommanderFreeForAllMatch extends MatchImpl {
@Override
public void startGame() throws GameException {
int startLife = 40;
boolean alsoHand = true;
if (options.getDeckType().equals("Variant Magic - Duel Penny Dreadful Commander")) {
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());
PennyDreadfulCommanderFreeForAll game = new PennyDreadfulCommanderFreeForAll(options.getAttackOption(), options.getRange(), mulligan, startLife);
game.setStartMessage(this.createGameStartMessage());
game.setAlsoHand(alsoHand);
game.setAlsoLibrary(true);
initGame(game);
games.add(game);
}

View file

@ -183,4 +183,45 @@ public class CommandersCastTest extends CardTestCommander4Players {
execute();
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();
}
}

View file

@ -13,18 +13,20 @@ import mage.game.permanent.Permanent;
import mage.game.stack.Spell;
import mage.players.Player;
import java.util.Locale;
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.12. If a commander would be put into the exile zone from anywhere, its owner may put it into the command zone instead.
* 903.9. If a commander would be exiled from anywhere or put into its owner's hand, graveyard, or
library from anywhere, its owner may put it into the command zone instead. This replacement effect
may apply more than once to the same event. This is an exception to rule 614.5.
903.9. If a commander would be exiled from anywhere or put into its owners hand, graveyard, or library from anywhere,
its owner may put it into the command zone instead. This replacement effect may apply more than once to the same event.
This is an exception to rule 614.5.
903.9a If a commander is a melded permanent and its owner chooses to put it into the command zone this way,
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.
@ -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 {
private final UUID commanderId;
private final boolean alsoHand;
private final boolean alsoLibrary;
private final boolean alsoHand; // return from hand to command zone
private final boolean alsoLibrary; // return from library to command zone
private final boolean forceToMove;
private final String commanderTypeName;
@ -87,56 +89,56 @@ public class CommanderReplacementEffect extends ReplacementEffectImpl {
@Override
public boolean applies(GameEvent event, Ability source, Game game) {
switch (((ZoneChangeEvent) event).getToZone()) {
case HAND:
if (!alsoHand && ((ZoneChangeEvent) event).getToZone() == Zone.HAND) {
return false;
}
ZoneChangeEvent zEvent = (ZoneChangeEvent) event;
if (!game.isSimulation() && commanderId.equals(zEvent.getTargetId())) {
//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:
if (!alsoLibrary && ((ZoneChangeEvent) event).getToZone() == Zone.LIBRARY) {
return false;
}
case HAND:
case GRAVEYARD:
case EXILED:
if (((ZoneChangeEvent) event).getFromZone() == Zone.STACK) {
Spell spell = game.getStack().getSpell(event.getTargetId());
if (spell != null && commanderId.equals(spell.getSourceId())) {
return true;
}
}
if (commanderId.equals(event.getTargetId())) {
if (commanderId.equals(zEvent.getTargetId())) {
return true;
}
break;
case STACK:
Spell spell = game.getStack().getSpell(event.getTargetId());
if (spell != null) {
if (commanderId.equals(spell.getSourceId())) {
return true;
}
}
break;
}
return false;
}
@Override
public boolean replaceEvent(GameEvent event, Ability source, Game game) {
if (((ZoneChangeEvent) event).getFromZone() == Zone.BATTLEFIELD) {
Permanent permanent = ((ZoneChangeEvent) event).getTarget();
ZoneChangeEvent zEvent = (ZoneChangeEvent) event;
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) {
Player player = game.getPlayer(permanent.getOwnerId());
if (player != null && (forceToMove || player.chooseUse(Outcome.Benefit, "Move " + commanderTypeName + " to command zone?", source, game))) {
((ZoneChangeEvent) event).setToZone(Zone.COMMAND);
if (player != null && (forceToMove || player.chooseUse(Outcome.Benefit, "Move " + commanderTypeName + " to command zone instead " + originToZone + "?", source, game))) {
zEvent.setToZone(Zone.COMMAND);
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 {
Card card = null;
if (((ZoneChangeEvent) event).getFromZone() == Zone.STACK) {
if (zEvent.getFromZone() == Zone.STACK) {
Spell spell = game.getStack().getSpell(event.getTargetId());
if (spell != null) {
card = game.getCard(spell.getSourceId());
@ -147,10 +149,10 @@ public class CommanderReplacementEffect extends ReplacementEffectImpl {
}
if (card != null) {
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);
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
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 {
private final UUID commanderId;

View file

@ -23,8 +23,11 @@ public abstract class GameCommanderImpl extends GameImpl {
// private final Map<UUID, Cards> mulliganedCards = new HashMap<>();
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;
public GameCommanderImpl(MultiplayerAttackOption attackOption, RangeOfInfluence range, Mulligan mulligan, int startLife) {

View file

@ -1189,11 +1189,17 @@ public abstract class PlayerImpl implements Player, Serializable {
//20091005 - 305.1
if (!game.replaceEvent(GameEvent.getEvent(GameEvent.EventType.PLAY_LAND, card.getId(), card.getId(), playerId, activationStatus.getPermittingObject()))) {
// 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)) {
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.removeBookmark(bookmark);
resetStoredBookmark(game); // prevent undo after playing a land

View file

@ -1,6 +1,7 @@
package mage.watchers.common;
import mage.constants.WatcherScope;
import mage.constants.Zone;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.events.GameEvent.EventType;
@ -12,7 +13,8 @@ import java.util.Map;
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
*/
@ -49,7 +51,7 @@ public class CommanderPlaysCountWatcher extends Watcher {
}
}
if (isCommanderObject) {
if (isCommanderObject && event.getZone() == Zone.COMMAND) {
int count = playsCount.getOrDefault(possibleCommanderId, 0);
count++;
playsCount.put(possibleCommanderId, count);