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

* Commander: added support of lands as commander ();

This commit is contained in:
Oleg Agafonov 2019-05-23 12:40:45 +04:00
parent b3e7c4f136
commit 42ed14df52
18 changed files with 441 additions and 201 deletions

View file

@ -1,17 +1,12 @@
package mage.cards.m;
import java.util.UUID;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.common.BeginningOfUpkeepTriggeredAbility;
import mage.abilities.common.SimpleActivatedAbility;
import mage.abilities.costs.Cost;
import mage.abilities.costs.CostImpl;
import mage.abilities.costs.common.DynamicValueGenericManaCost;
import mage.abilities.costs.common.TapSourceCost;
import mage.abilities.costs.mana.GenericManaCost;
import mage.constants.SubType;
import mage.abilities.costs.mana.ManaCostsImpl;
import mage.abilities.dynamicvalue.DynamicValue;
import mage.abilities.dynamicvalue.common.CountersSourceCount;
import mage.abilities.effects.Effect;
import mage.abilities.effects.common.DestroySourceEffect;
@ -21,17 +16,13 @@ import mage.abilities.effects.common.counter.AddCountersTargetEffect;
import mage.abilities.keyword.CumulativeUpkeepAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.Duration;
import mage.constants.TargetController;
import mage.constants.Zone;
import mage.constants.*;
import mage.counters.CounterType;
import mage.game.Game;
import mage.players.Player;
import mage.target.common.TargetCreaturePermanent;
import java.util.UUID;
/**
*
* @author jeffwadsworth
*/
public final class Musician extends CardImpl {
@ -51,8 +42,8 @@ public final class Musician extends CardImpl {
Effect effect = new DoUnlessControllerPaysEffect(
new DestroySourceEffect(),
new DynamicValueGenericManaCost(
new CountersSourceCount(
CounterType.MUSIC)));
new CountersSourceCount(CounterType.MUSIC),
"{1} for each music counter on {this}"));
effect.setText("destroy this creature unless you pay {1} for each music counter on it");
Ability ability = new BeginningOfUpkeepTriggeredAbility(
Zone.BATTLEFIELD,
@ -87,48 +78,3 @@ public final class Musician extends CardImpl {
}
}
class DynamicValueGenericManaCost extends CostImpl {
DynamicValue amount;
public DynamicValueGenericManaCost(DynamicValue amount) {
this.amount = amount;
setText();
}
public DynamicValueGenericManaCost(DynamicValueGenericManaCost cost) {
super(cost);
this.amount = cost.amount;
}
@Override
public boolean canPay(Ability ability, UUID sourceId, UUID controllerId, Game game) {
Player controller = game.getPlayer(controllerId);
if (controller == null) {
return false;
}
int convertedCost = amount.calculate(game, ability, null);
Cost cost = new GenericManaCost(convertedCost);
return cost.canPay(ability, sourceId, controllerId, game);
}
@Override
public boolean pay(Ability ability, Game game, UUID sourceId, UUID controllerId, boolean noMana, Cost costToPay) {
Player controller = game.getPlayer(controllerId);
int convertedCost = amount.calculate(game, ability, null);
Cost cost = new GenericManaCost(convertedCost);
if (controller != null) {
paid = cost.pay(ability, game, sourceId, controllerId, noMana);
}
return paid;
}
@Override
public DynamicValueGenericManaCost copy() {
return new DynamicValueGenericManaCost(this);
}
private void setText() {
text = ("{1} for each music counter on {this}");
}
}

View file

@ -1,7 +1,7 @@
package mage.cards.m;
import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.PlayLandAbility;
import mage.abilities.SpellAbility;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.common.ZoneChangeAllTriggeredAbility;
@ -9,22 +9,18 @@ import mage.abilities.effects.common.DrawCardSourceControllerEffect;
import mage.abilities.effects.common.cost.CostModificationEffectImpl;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.CostModificationType;
import mage.constants.Duration;
import mage.constants.Outcome;
import mage.constants.TargetController;
import mage.constants.Zone;
import mage.constants.*;
import mage.filter.FilterPermanent;
import mage.filter.predicate.other.OwnerPredicate;
import mage.filter.predicate.permanent.CommanderPredicate;
import mage.game.Game;
import mage.game.stack.Spell;
import mage.players.Player;
import mage.util.CardUtil;
import mage.watchers.common.CommanderPlaysCountWatcher;
import java.util.UUID;
/**
*
* @author TheElk801
*/
public final class MythUnbound extends CardImpl {
@ -80,9 +76,10 @@ class MythUnboundCostReductionEffect extends CostModificationEffectImpl {
public boolean apply(Game game, Ability source, Ability abilityToModify) {
Ability spellAbility = abilityToModify;
if (spellAbility != null) {
Integer amount = (Integer) game.getState().getValue(abilityToModify.getSourceId() + "_castCount");
if (amount != null && amount > 0) {
CardUtil.reduceCost(spellAbility, amount);
CommanderPlaysCountWatcher watcher = game.getState().getWatcher(CommanderPlaysCountWatcher.class);
int castCount = watcher.getPlaysCount(abilityToModify.getSourceId());
if (castCount > 0) {
CardUtil.reduceCost(spellAbility, castCount);
return true;
}
}
@ -95,12 +92,10 @@ class MythUnboundCostReductionEffect extends CostModificationEffectImpl {
if (player == null) {
return false;
}
if (abilityToModify instanceof SpellAbility) {
if (abilityToModify instanceof SpellAbility || abilityToModify instanceof PlayLandAbility) {
if (abilityToModify.isControlledBy(source.getControllerId())) {
Spell spell = (Spell) game.getStack().getStackObject(abilityToModify.getId());
if (spell != null) {
return player.getCommandersIds().contains(spell.getSourceId());
}
return player.getCommandersIds().contains(abilityToModify.getSourceId());
}
}
return false;

View file

@ -1,9 +1,5 @@
package mage.cards.o;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.costs.common.TapSourceCost;
@ -14,11 +10,7 @@ import mage.abilities.mana.CommanderColorIdentityManaAbility;
import mage.cards.Card;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.Duration;
import mage.constants.Outcome;
import mage.constants.WatcherScope;
import mage.constants.Zone;
import mage.constants.*;
import mage.counters.CounterType;
import mage.game.Game;
import mage.game.events.EntersTheBattlefieldEvent;
@ -28,9 +20,13 @@ import mage.game.permanent.Permanent;
import mage.game.stack.Spell;
import mage.players.Player;
import mage.watchers.Watcher;
import mage.watchers.common.CommanderPlaysCountWatcher;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
/**
*
* @author LevelX2
*/
public final class OpalPalace extends CardImpl {
@ -135,16 +131,16 @@ class OpalPalaceEntersBattlefieldEffect extends ReplacementEffectImpl {
@Override
public boolean applies(GameEvent event, Ability source, Game game) {
OpalPalaceWatcher watcher = game.getState().getWatcher(OpalPalaceWatcher.class, source.getSourceId());
return watcher != null
&& watcher.manaUsedToCastCommander(event.getTargetId());
return watcher != null && watcher.manaUsedToCastCommander(event.getTargetId());
}
@Override
public boolean replaceEvent(GameEvent event, Ability source, Game game) {
Permanent permanent = ((EntersTheBattlefieldEvent) event).getTarget();
if (permanent != null) {
Integer castCount = (Integer) game.getState().getValue(permanent.getId() + "_castCount");
if (castCount != null && castCount > 0) {
CommanderPlaysCountWatcher watcher = game.getState().getWatcher(CommanderPlaysCountWatcher.class);
int castCount = watcher.getPlaysCount(permanent.getId());
if (castCount > 0) {
permanent.addCounters(CounterType.P1P1.createInstance(castCount), source, game);
}
}

View file

@ -27,6 +27,7 @@ public class CommandersCastTest extends CardTestCommander4Players {
assertCommandZoneCount(playerA, "Balduvian Bears", 0);
assertPermanentCount(playerA, "Balduvian Bears", 1);
assertTappedCount("Forest", true, 2);
}
@Test
@ -62,5 +63,67 @@ public class CommandersCastTest extends CardTestCommander4Players {
assertCommandZoneCount(playerA, "Balduvian Bears", 0);
assertPermanentCount(playerA, "Balduvian Bears", 1);
assertGraveyardCount(playerB, "Lightning Bolt", 1);
assertTappedCount("Forest", true, 2 + 4);
}
@Test
public void test_PlayAsLandOneTime() {
addCard(Zone.COMMAND, playerA, "Academy Ruins", 1);
showAvaileableAbilities("before", 1, PhaseStep.PRECOMBAT_MAIN, playerA);
playLand(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Academy Ruins");
//castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Academy Ruins");
setStopAt(1, PhaseStep.END_TURN);
setStrictChooseMode(true);
execute();
assertAllCommandsUsed();
assertCommandZoneCount(playerA, "Academy Ruins", 0);
assertPermanentCount(playerA, "Academy Ruins", 1);
}
@Test
public void test_PlayAsLandTwoTimes() {
// Player order: A -> D -> C -> B
addCard(Zone.COMMAND, playerA, "Academy Ruins", 1);
addCard(Zone.BATTLEFIELD, playerA, "Forest", 2); // 0 + 2
//
addCard(Zone.HAND, playerA, "Pillage", 1);
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3);
// cast 1
playLand(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Academy Ruins");
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
checkPermanentCount("after play 1", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Academy Ruins", 1);
// destroy commander land
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Pillage", "Academy Ruins");
setChoice(playerA, "Yes"); // put to command zone again
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
checkPermanentCount("after destroy", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Academy Ruins", 0);
// remove unnecessary mana, only 2 forest need (workaround to remove random mana payments)
activateManaAbility(5, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {R}");
activateManaAbility(5, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {R}");
activateManaAbility(5, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {R}");
// cast 2
playLand(5, PhaseStep.POSTCOMBAT_MAIN, playerA, "Academy Ruins");
waitStackResolved(5, PhaseStep.POSTCOMBAT_MAIN);
checkPermanentCount("after cast 2", 5, PhaseStep.POSTCOMBAT_MAIN, playerA, "Academy Ruins", 1);
showBattlefield("end battlefield", 5, PhaseStep.END_TURN, playerA);
setStopAt(5, PhaseStep.END_TURN);
setStrictChooseMode(true);
execute();
assertAllCommandsUsed();
assertCommandZoneCount(playerA, "Academy Ruins", 0);
assertPermanentCount(playerA, "Academy Ruins", 1);
assertGraveyardCount(playerA, "Pillage", 1);
assertTappedCount("Forest", true, 2);
assertTappedCount("Mountain", true, 3);
}
}

View file

@ -1,4 +1,3 @@
package org.mage.test.commander.duel;
import mage.constants.PhaseStep;
@ -8,7 +7,6 @@ import org.junit.Test;
import org.mage.test.serverside.base.CardTestCommanderDuelBase;
/**
*
* @author LevelX2
*/
public class OpalPalaceTest extends CardTestCommanderDuelBase {
@ -29,10 +27,19 @@ public class OpalPalaceTest extends CardTestCommanderDuelBase {
// equal to the number of times it's been cast from the command zone this game.
addCard(Zone.BATTLEFIELD, playerA, "Opal Palace", 1);
showHand("hand", 1, PhaseStep.PRECOMBAT_MAIN, playerA);
showCommand("command", 1, PhaseStep.PRECOMBAT_MAIN, playerA);
showAvaileableAbilities("abi", 1, PhaseStep.PRECOMBAT_MAIN, playerA);
activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {G}");
activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{1}, {T}");
setChoice(playerA, "Opal Palace"); // activate mana replace effect first (+3 counters)
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Ob Nixilis of the Black Oath"); // {3}{B}{B}
setStrictChooseMode(true);
setStopAt(1, PhaseStep.BEGIN_COMBAT);
execute();
assertAllCommandsUsed();
assertLife(playerA, 40);
assertLife(playerB, 40);

View file

@ -1,15 +1,13 @@
package mage.abilities.common;
import mage.abilities.SpellAbility;
import mage.abilities.costs.CostsImpl;
import mage.abilities.costs.common.CommanderAdditionalCost;
import mage.cards.Card;
import mage.constants.SpellAbilityType;
import mage.constants.TimingRule;
import mage.constants.Zone;
import mage.game.Game;
/**
* @author Plopman
* @author Plopman, JayDi85
*/
public class CastCommanderAbility extends SpellAbility {
@ -20,9 +18,11 @@ public class CastCommanderAbility extends SpellAbility {
this.getEffects().addAll(card.getSpellAbility().getEffects().copy());
this.getTargets().addAll(card.getSpellAbility().getTargets().copy());
this.timing = card.getSpellAbility().getTiming();
// extra cost
this.addCost(new CommanderAdditionalCost());
} else {
this.costs = new CostsImpl<>();
this.timing = TimingRule.SORCERY;
throw new IllegalStateException("Cast commander ability must be used with spell ability only: " + card.getName());
}
this.usesStack = true;
this.controllerId = card.getOwnerId();
@ -33,22 +33,6 @@ public class CastCommanderAbility extends SpellAbility {
super(ability);
}
@Override
public boolean activate(Game game, boolean noMana) {
if (super.activate(game, noMana)) {
// save amount of times commander was cast
Integer castCount = (Integer) game.getState().getValue(sourceId + "_castCount");
if (castCount == null) {
castCount = 1;
} else {
castCount++;
}
game.getState().setValue(sourceId + "_castCount", castCount);
return true;
}
return false;
}
@Override
public CastCommanderAbility copy() {
return new CastCommanderAbility(this);

View file

@ -0,0 +1,28 @@
package mage.abilities.common;
import mage.abilities.PlayLandAbility;
import mage.abilities.costs.common.CommanderAdditionalCost;
import mage.constants.Zone;
/**
* @author JayDi85
*/
public class PlayLandAsCommanderAbility extends PlayLandAbility {
public PlayLandAsCommanderAbility(PlayLandAbility originalAbility) {
super(originalAbility);
zone = Zone.COMMAND;
// extra cost
this.addCost(new CommanderAdditionalCost());
}
private PlayLandAsCommanderAbility(PlayLandAsCommanderAbility ability) {
super(ability);
}
@Override
public PlayLandAsCommanderAbility copy() {
return new PlayLandAsCommanderAbility(this);
}
}

View file

@ -0,0 +1,34 @@
package mage.abilities.costs.common;
import mage.abilities.Ability;
import mage.abilities.dynamicvalue.common.CommanderPlaysCount;
import mage.game.Game;
/**
* @author JayDi85
*/
public class CommanderAdditionalCost extends DynamicValueGenericManaCost {
/*
903.8. A player may cast a commander they own from the command zone. A commander cast from the
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.
*/
public CommanderAdditionalCost() {
super(new CommanderPlaysCount(2), "{2} for each previous time the player casting it has cast it from the command zone");
}
public CommanderAdditionalCost(final CommanderAdditionalCost cost) {
super(cost);
}
@Override
public CommanderAdditionalCost copy() {
return new CommanderAdditionalCost(this);
}
public boolean isEmptyPay(Ability ability, Game game) {
return amount.calculate(game, ability, null) == 0;
}
}

View file

@ -0,0 +1,46 @@
package mage.abilities.costs.common;
import mage.abilities.Ability;
import mage.abilities.costs.Cost;
import mage.abilities.costs.CostImpl;
import mage.abilities.costs.mana.GenericManaCost;
import mage.abilities.dynamicvalue.DynamicValue;
import mage.game.Game;
import java.util.UUID;
public class DynamicValueGenericManaCost extends CostImpl {
DynamicValue amount;
public DynamicValueGenericManaCost(DynamicValue amount, String text) {
this.amount = amount;
setText(text);
}
public DynamicValueGenericManaCost(DynamicValueGenericManaCost cost) {
super(cost);
this.amount = cost.amount;
}
@Override
public boolean canPay(Ability ability, UUID sourceId, UUID controllerId, Game game) {
int convertedCost = amount.calculate(game, ability, null);
Cost cost = new GenericManaCost(convertedCost);
return cost.canPay(ability, sourceId, controllerId, game);
}
@Override
public boolean pay(Ability ability, Game game, UUID sourceId, UUID controllerId, boolean noMana, Cost costToPay) {
int convertedCost = amount.calculate(game, ability, null);
Cost cost = new GenericManaCost(convertedCost);
paid = cost.pay(ability, game, sourceId, controllerId, noMana);
return paid;
}
@Override
public DynamicValueGenericManaCost copy() {
return new DynamicValueGenericManaCost(this);
}
}

View file

@ -0,0 +1,52 @@
package mage.abilities.dynamicvalue.common;
import mage.abilities.Ability;
import mage.abilities.dynamicvalue.DynamicValue;
import mage.abilities.effects.Effect;
import mage.game.Game;
import mage.watchers.common.CommanderPlaysCountWatcher;
/**
* @author JayDi85
*/
public class CommanderPlaysCount implements DynamicValue {
private Integer multiplier;
public CommanderPlaysCount() {
this(1);
}
public CommanderPlaysCount(Integer multiplier) {
this.multiplier = multiplier;
}
public CommanderPlaysCount(final CommanderPlaysCount dynamicValue) {
this.multiplier = dynamicValue.multiplier;
}
@Override
public int calculate(Game game, Ability sourceAbility, Effect effect) {
CommanderPlaysCountWatcher watcher = game.getState().getWatcher(CommanderPlaysCountWatcher.class);
int value = 0;
if (watcher != null) {
value = watcher.getPlaysCount(sourceAbility.getSourceId());
}
return value * multiplier;
}
@Override
public CommanderPlaysCount copy() {
return new CommanderPlaysCount(this);
}
@Override
public String toString() {
return "X";
}
@Override
public String getMessage() {
return "";
}
}

View file

@ -1,54 +0,0 @@
package mage.abilities.effects.common.cost;
import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.common.CastCommanderAbility;
import mage.abilities.costs.mana.GenericManaCost;
import mage.constants.CostModificationType;
import mage.constants.Duration;
import mage.constants.Outcome;
import mage.game.Game;
/**
*
* @author Plopman
*/
//20130711
/*903.10. A player may cast a commander he or she owns from the command zone.
* Doing so costs that player an additional {2} for each previous time he or she cast that commander from the command zone that game.
* */
public class CommanderCostModification extends CostModificationEffectImpl {
private final UUID commanderId;
public CommanderCostModification(UUID commanderId) {
super(Duration.Custom, Outcome.Neutral, CostModificationType.INCREASE_COST);
this.commanderId = commanderId;
}
public CommanderCostModification(final CommanderCostModification effect) {
super(effect);
this.commanderId = effect.commanderId;
}
@Override
public boolean apply(Game game, Ability source, Ability abilityToModify) {
Integer castCount = (Integer) game.getState().getValue(commanderId + "_castCount");
if (castCount > 0) {
abilityToModify.getManaCostsToPay().add(new GenericManaCost(2 * castCount));
}
return true;
}
@Override
public boolean applies(Ability abilityToModify, Ability source, Game game) {
return abilityToModify instanceof CastCommanderAbility && abilityToModify.getSourceId().equals(commanderId);
}
@Override
public CommanderCostModification copy() {
return new CommanderCostModification(this);
}
}

View file

@ -13,9 +13,9 @@ import mage.game.events.GameEvent.EventType;
import mage.game.stack.Spell;
import mage.game.stack.StackObject;
import mage.players.Player;
import mage.watchers.common.CommanderPlaysCountWatcher;
/**
*
* @author Plopman
*/
public class CommanderStormAbility extends TriggeredAbilityImpl {
@ -83,9 +83,9 @@ class CommanderStormEffect extends OneShotEffect {
if (player == null) {
return false;
}
stormCount = player.getCommandersIds().stream().map(
(commanderId) -> (Integer) game.getState().getValue(commanderId + "_castCount")
).map((castCount) -> castCount).reduce(stormCount, Integer::sum);
stormCount = player.getCommandersIds().stream()
.map((commanderId) -> game.getState().getWatcher(CommanderPlaysCountWatcher.class).getPlaysCount(commanderId))
.reduce(stormCount, Integer::sum);
if (stormCount == 0) {
return true;
}

View file

@ -4,7 +4,6 @@ import mage.abilities.Ability;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.effects.common.InfoEffect;
import mage.abilities.effects.common.continuous.CommanderReplacementEffect;
import mage.abilities.effects.common.cost.CommanderCostModification;
import mage.cards.Card;
import mage.constants.MultiplayerAttackOption;
import mage.constants.PhaseStep;
@ -14,6 +13,7 @@ import mage.game.mulligan.Mulligan;
import mage.game.turn.TurnMod;
import mage.players.Player;
import mage.watchers.common.CommanderInfoWatcher;
import mage.watchers.common.CommanderPlaysCountWatcher;
import java.util.Map;
import java.util.UUID;
@ -40,7 +40,11 @@ public abstract class GameCommanderImpl extends GameImpl {
@Override
protected void init(UUID choosingPlayerId) {
//Move commander to command zone
// plays watcher
state.addWatcher(new CommanderPlaysCountWatcher());
// move commanders to command zone
for (UUID playerId : state.getPlayerList(startingPlayerId)) {
Player player = getPlayer(playerId);
if (player != null) {
@ -62,6 +66,7 @@ public abstract class GameCommanderImpl extends GameImpl {
}
}
}
super.init(choosingPlayerId);
if (startingPlayerSkipsDraw) {
state.getTurnMods().add(new TurnMod(startingPlayerId, PhaseStep.DRAW));
@ -73,8 +78,6 @@ public abstract class GameCommanderImpl extends GameImpl {
commander.moveToZone(Zone.COMMAND, null, this, true);
commander.getAbilities().setControllerId(player.getId());
ability.addEffect(new CommanderReplacementEffect(commander.getId(), alsoHand, alsoLibrary));
ability.addEffect(new CommanderCostModification(commander.getId()));
getState().setValue(commander.getId() + "_castCount", 0);
CommanderInfoWatcher watcher = new CommanderInfoWatcher(commander.getId(), checkCommanderDamage);
getState().addWatcher(watcher);
watcher.addCardInfoToCommander(this);

View file

@ -5,7 +5,6 @@ import mage.abilities.Ability;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.effects.common.InfoEffect;
import mage.abilities.effects.common.continuous.CommanderReplacementEffect;
import mage.abilities.effects.common.cost.CommanderCostModification;
import mage.cards.Card;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
@ -16,6 +15,7 @@ import mage.game.mulligan.Mulligan;
import mage.game.turn.TurnMod;
import mage.players.Player;
import mage.watchers.common.CommanderInfoWatcher;
import mage.watchers.common.CommanderPlaysCountWatcher;
import java.util.HashSet;
import java.util.Set;
@ -42,7 +42,10 @@ public abstract class GameTinyLeadersImpl extends GameImpl {
@Override
protected void init(UUID choosingPlayerId) {
//Move tiny leader to command zone
// plays watcher
state.addWatcher(new CommanderPlaysCountWatcher());
// move tiny leader to command zone
for (UUID playerId : state.getPlayerList(startingPlayerId)) {
Player player = getPlayer(playerId);
if (player != null) {
@ -55,10 +58,8 @@ public abstract class GameTinyLeadersImpl extends GameImpl {
commander.moveToZone(Zone.COMMAND, null, this, true);
Ability ability = new SimpleStaticAbility(Zone.COMMAND, new InfoEffect("Commander effects"));
ability.addEffect(new CommanderReplacementEffect(commander.getId(), alsoHand, alsoLibrary));
ability.addEffect(new CommanderCostModification(commander.getId()));
// Commander rule #4 was removed Jan. 18, 2016
// ability.addEffect(new CommanderManaReplacementEffect(player.getId(), CardUtil.getColorIdentity(commander)));
getState().setValue(commander.getId() + "_castCount", 0);
CommanderInfoWatcher watcher = new CommanderInfoWatcher(commander.getId(), false);
getState().addWatcher(watcher);
watcher.addCardInfoToCommander(this);

View file

@ -3,11 +3,9 @@ package mage.game.command;
import mage.MageInt;
import mage.MageObject;
import mage.ObjectColor;
import mage.abilities.Abilities;
import mage.abilities.AbilitiesImpl;
import mage.abilities.Ability;
import mage.abilities.SpellAbility;
import mage.abilities.*;
import mage.abilities.common.CastCommanderAbility;
import mage.abilities.common.PlayLandAsCommanderAbility;
import mage.abilities.costs.mana.ManaCost;
import mage.abilities.costs.mana.ManaCosts;
import mage.abilities.text.TextPart;
@ -34,9 +32,23 @@ public class Commander implements CommandObject {
public Commander(Card card) {
this.sourceObject = card;
// replace spell ability by commander cast spell (to cast from command zone)
if (card.getSpellAbility() != null) {
abilities.add(new CastCommanderAbility(card));
}
// replace play land with commander play land (to play from command zone)
for (Ability ability : card.getAbilities()) {
if (!(ability instanceof SpellAbility)) {
if (ability instanceof PlayLandAbility) {
Ability newAbility = new PlayLandAsCommanderAbility((PlayLandAbility) ability);
abilities.add(newAbility);
}
}
// other abilities
for (Ability ability : card.getAbilities()) {
if (!(ability instanceof SpellAbility) && !(ability instanceof PlayLandAbility)) {
Ability newAbility = ability.copy();
abilities.add(newAbility);
}

View file

@ -8,9 +8,11 @@ import mage.Mana;
import mage.abilities.*;
import mage.abilities.ActivatedAbility.ActivationStatus;
import mage.abilities.common.PassAbility;
import mage.abilities.common.PlayLandAsCommanderAbility;
import mage.abilities.common.WhileSearchingPlayFromLibraryAbility;
import mage.abilities.common.delayed.AtTheEndOfTurnStepPostDelayedTriggeredAbility;
import mage.abilities.costs.*;
import mage.abilities.costs.common.CommanderAdditionalCost;
import mage.abilities.costs.mana.ManaCost;
import mage.abilities.costs.mana.ManaCosts;
import mage.abilities.costs.mana.ManaCostsImpl;
@ -1145,10 +1147,13 @@ public abstract class PlayerImpl implements Player, Serializable {
}
}
// warning, if you change code here then fix it in activateAbility too (play commander as land)
//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()));
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()));
@ -1252,10 +1257,60 @@ public abstract class PlayerImpl implements Player, Serializable {
pass(game);
return true;
}
if (ability instanceof PlayLandAbility) {
Card card = game.getCard(ability.getSourceId());
if (ability instanceof PlayLandAsCommanderAbility) {
// LAND as commander:
// * first time - play without cost as land
// * second+ times -- cast as spell with cost and stack
// code from playLand
ActivationStatus activationStatus = ability.canActivate(this.playerId, game);
if (!activationStatus.canActivate() || !this.canPlayLand()) {
return false;
}
if (card == null) {
return false;
}
// workaround to find out empty pay in commander land
boolean isEmptyPay = true;
Costs<Cost> costs = ability.getCosts().copy();
for (Cost cost : costs) {
if (!(cost instanceof CommanderAdditionalCost) || !((CommanderAdditionalCost) cost).isEmptyPay(ability, game)) {
isEmptyPay = false;
}
}
if (isEmptyPay) {
// play as land
result = playLand(card, game, false);
} else {
// cast as spell with cost, but with all land's restrictions and events like Damping Engine
// look at code in playLand
if (!game.replaceEvent(GameEvent.getEvent(GameEvent.EventType.PLAY_LAND, card.getId(), card.getId(), playerId, activationStatus.getPermittingObject()))) {
game.fireEvent(GameEvent.getEvent(GameEvent.EventType.PLAY_LAND, card.getId(), card.getId(), playerId, activationStatus.getPermittingObject()));
SpellAbility spellAbility = new SpellAbility(null, card.getName(), game.getState().getZone(card.getId()));
spellAbility.addCost(costs);
spellAbility.setControllerId(this.getId());
spellAbility.setSourceId(card.getId());
result = cast(spellAbility, game, false, activationStatus.getPermittingObject());
if (result) {
landsPlayed++;
game.fireEvent(GameEvent.getEvent(GameEvent.EventType.LAND_PLAYED, card.getId(), card.getId(), playerId, activationStatus.getPermittingObject()));
}
} else {
result = false;
}
}
} else if (ability instanceof PlayLandAbility) {
// LAND as normal card: without cost and stack
result = playLand(card, game, false);
} else {
// ABILITY
ActivationStatus activationStatus = ability.canActivate(this.playerId, game);
if (!activationStatus.canActivate()) {
return false;
@ -3030,6 +3085,7 @@ public abstract class PlayerImpl implements Player, Serializable {
for (ConditionalMana conditionalMana : manaPool.getConditionalMana()) {
availableMana.addMana(conditionalMana);
}
if (hidden) {
for (Card card : hand.getUniqueCards(game)) {
for (Ability ability : card.getAbilities(game)) { // gets this activated ability from hand? (Morph?)
@ -3058,6 +3114,7 @@ public abstract class PlayerImpl implements Player, Serializable {
}
}
}
for (Card card : graveyard.getUniqueCards(game)) {
// Handle split cards in graveyard to support Aftermath
if (card instanceof SplitCard) {
@ -3074,6 +3131,7 @@ public abstract class PlayerImpl implements Player, Serializable {
getOtherUseableActivatedAbilities(card, Zone.GRAVEYARD, game, useable);
playable.addAll(useable.values());
}
for (ExileZone exile : game.getExile().getExileZones()) {
for (Card card : exile.getCards(game)) {
if (null != game.getContinuousEffects().asThough(card.getId(), AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, null, this.getId(), game)) {
@ -3091,7 +3149,8 @@ public abstract class PlayerImpl implements Player, Serializable {
}
}
}
// Check to play revealed cards
// check to play revealed cards
for (Cards cards : game.getState().getRevealed().values()) {
for (Card card : cards.getCards(game)) {
if (null != game.getContinuousEffects().asThough(card.getId(), AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, null, this.getId(), game)) {
@ -3103,6 +3162,7 @@ public abstract class PlayerImpl implements Player, Serializable {
}
}
}
// check if it's possible to play the top card of a library
for (UUID playerInRangeId : game.getState().getPlayersInRange(getId(), game)) {
Player player = game.getPlayer(playerInRangeId);
@ -3119,6 +3179,7 @@ public abstract class PlayerImpl implements Player, Serializable {
}
}
}
// eliminate duplicate activated abilities
Map<String, Ability> playableActivated = new HashMap<>();
for (Permanent permanent : game.getBattlefield().getAllActivePermanents(playerId)) {
@ -3127,6 +3188,7 @@ public abstract class PlayerImpl implements Player, Serializable {
playableActivated.putIfAbsent(ability.toString(), ability);
}
}
// activated abilities from stack objects
for (StackObject stackObject : game.getState().getStack()) {
for (ActivatedAbility ability : stackObject.getAbilities().getActivatedAbilities(Zone.STACK)) {
@ -3136,15 +3198,16 @@ public abstract class PlayerImpl implements Player, Serializable {
}
}
// activated abilities from objects in the command zone (emblems or commanders)
for (CommandObject commandObject : game.getState().getCommand()) {
for (ActivatedAbility ability : commandObject.getAbilities().getActivatedAbilities(Zone.COMMAND)) {
if (ability.isControlledBy(getId()) && canPlay(ability, availableMana, game.getObject(ability.getSourceId()), game)) {
playableActivated.put(ability.toString(), ability);
}
}
}
}
}
playable.addAll(playableActivated.values());
}

View file

@ -1,9 +1,5 @@
package mage.watchers.common;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import mage.MageObject;
import mage.cards.Card;
import mage.constants.WatcherScope;
@ -15,6 +11,10 @@ import mage.game.permanent.Permanent;
import mage.players.Player;
import mage.watchers.Watcher;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
/* 20130711
*903.14a A player that's been dealt 21 or more combat damage by the same commander
* over the course of the game loses the game. (This is a state-based action. See rule 704.)
@ -79,11 +79,13 @@ public class CommanderInfoWatcher extends Watcher {
if (object != null) {
StringBuilder sb = new StringBuilder();
sb.append("<b>Commander</b>");
Integer castCount = (Integer) game.getState().getValue(sourceId + "_castCount");
if (castCount != null) {
sb.append(' ').append(castCount).append(castCount == 1 ? " time" : " times").append(" casted from the command zone.");
CommanderPlaysCountWatcher watcher = game.getState().getWatcher(CommanderPlaysCountWatcher.class);
int playsCount = watcher.getPlaysCount(sourceId);
if (playsCount > 0) {
sb.append(' ').append(playsCount).append(playsCount == 1 ? " time" : " times").append(" played from the command zone.");
}
this.addInfo(object, "Commander", sb.toString(), game);
if (checkCommanderDamage) {
for (Map.Entry<UUID, Integer> entry : damageToPlayer.entrySet()) {
Player damagedPlayer = game.getPlayer(entry.getKey());

View file

@ -0,0 +1,62 @@
package mage.watchers.common;
import mage.constants.WatcherScope;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.events.GameEvent.EventType;
import mage.players.Player;
import mage.watchers.Watcher;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
/**
* Calcs commanders play count (spell or land)
*
* @author JayDi85
*/
public class CommanderPlaysCountWatcher extends Watcher {
private final Map<UUID, Integer> playsCount = new HashMap<>();
public CommanderPlaysCountWatcher() {
super(WatcherScope.GAME);
}
public CommanderPlaysCountWatcher(final CommanderPlaysCountWatcher watcher) {
super(watcher);
this.playsCount.putAll(watcher.playsCount);
}
@Override
public CommanderPlaysCountWatcher copy() {
return new CommanderPlaysCountWatcher(this);
}
@Override
public void watch(GameEvent event, Game game) {
if (event.getType() != EventType.LAND_PLAYED && event.getType() != EventType.SPELL_CAST) {
return;
}
UUID possibleCommanderId = event.getSourceId();
boolean isCommanderObject = false;
for (Player player : game.getPlayers().values()) {
if (player.getCommandersIds().contains(possibleCommanderId)) {
isCommanderObject = true;
break;
}
}
if (isCommanderObject) {
int count = playsCount.getOrDefault(possibleCommanderId, 0);
count++;
playsCount.put(possibleCommanderId, count);
}
}
public int getPlaysCount(UUID commanderId) {
return this.playsCount.getOrDefault(commanderId, 0);
}
}