diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/continuous/CommandersCastTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/continuous/CommandersCastTest.java index fa09404e36..77e3a7e53d 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/continuous/CommandersCastTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/continuous/CommandersCastTest.java @@ -126,4 +126,61 @@ public class CommandersCastTest extends CardTestCommander4Players { assertTappedCount("Forest", true, 2); assertTappedCount("Mountain", true, 3); } + + @Test + public void test_ModesNormal() { + // Player order: A -> D -> C -> B + + // Choose four. You may choose the same mode more than once. + // • Create a 2/2 Citizen creature token that’s all colors. + // • Return target permanent card from your graveyard to your hand. + // • Proliferate. + // • You gain 4 life. + addCard(Zone.HAND, playerA, "Planewide Celebration", 1); // {5}{G}{G} + addCard(Zone.BATTLEFIELD, playerA, "Forest", 7); + + // cast (3 tokens + 4 life) + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Planewide Celebration"); + setModeChoice(playerA, "1"); + setModeChoice(playerA, "1"); + setModeChoice(playerA, "1"); + setModeChoice(playerA, "4"); + + checkPermanentCount("after", 1, PhaseStep.BEGIN_COMBAT, playerA, "Citizen", 3); + checkLife("after", 1, PhaseStep.BEGIN_COMBAT, playerA, 20 + 4); + + setStopAt(1, PhaseStep.END_TURN); + setStrictChooseMode(true); + execute(); + assertAllCommandsUsed(); + } + + @Test + public void test_ModesCommander() { + // Player order: A -> D -> C -> B + + // Choose four. You may choose the same mode more than once. + // • Create a 2/2 Citizen creature token that’s all colors. + // • Return target permanent card from your graveyard to your hand. + // • Proliferate. + // • You gain 4 life. + addCard(Zone.COMMAND, playerA, "Planewide Celebration", 1); // {5}{G}{G} + addCard(Zone.BATTLEFIELD, playerA, "Forest", 7); + + // cast (3 tokens + 4 life) + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Planewide Celebration"); + setModeChoice(playerA, "1"); + setModeChoice(playerA, "1"); + setModeChoice(playerA, "1"); + setModeChoice(playerA, "4"); + setChoice(playerA, "Yes"); // return commander + + checkPermanentCount("after", 1, PhaseStep.BEGIN_COMBAT, playerA, "Citizen", 3); + checkLife("after", 1, PhaseStep.BEGIN_COMBAT, playerA, 20 + 4); + + setStopAt(1, PhaseStep.END_TURN); + setStrictChooseMode(true); + execute(); + assertAllCommandsUsed(); + } } diff --git a/Mage/src/main/java/mage/abilities/Modes.java b/Mage/src/main/java/mage/abilities/Modes.java index c7eb75fbb0..1fd429d2cd 100644 --- a/Mage/src/main/java/mage/abilities/Modes.java +++ b/Mage/src/main/java/mage/abilities/Modes.java @@ -1,4 +1,3 @@ - package mage.abilities; import mage.abilities.costs.OptionalAdditionalModeSourceCosts; @@ -45,22 +44,23 @@ public class Modes extends LinkedHashMap { this.put(entry.getKey(), entry.getValue().copy()); } for (Map.Entry entry : modes.duplicateModes.entrySet()) { - this.put(entry.getKey(), entry.getValue().copy()); + duplicateModes.put(entry.getKey(), entry.getValue().copy()); } this.minModes = modes.minModes; this.maxModes = modes.maxModes; this.selectedModes.addAll(modes.getSelectedModes()); + this.modeChooser = modes.modeChooser; + this.eachModeOnlyOnce = modes.eachModeOnlyOnce; + this.eachModeMoreThanOnce = modes.eachModeMoreThanOnce; + this.optionalAdditionalModeSourceCosts = modes.optionalAdditionalModeSourceCosts; + this.maxModesFilter = modes.maxModesFilter; // can't change so no copy needed + if (modes.getSelectedModes().isEmpty()) { this.currentMode = values().iterator().next(); } else { this.currentMode = get(modes.getMode().getId()); } - this.modeChooser = modes.modeChooser; - this.eachModeOnlyOnce = modes.eachModeOnlyOnce; - this.eachModeMoreThanOnce = modes.eachModeMoreThanOnce; - this.optionalAdditionalModeSourceCosts = modes.optionalAdditionalModeSourceCosts; - this.maxModesFilter = modes.maxModesFilter; // can't change so no copy needed } public Modes copy() { diff --git a/Mage/src/main/java/mage/abilities/SpellAbility.java b/Mage/src/main/java/mage/abilities/SpellAbility.java index 21b8a96333..10b501dc75 100644 --- a/Mage/src/main/java/mage/abilities/SpellAbility.java +++ b/Mage/src/main/java/mage/abilities/SpellAbility.java @@ -186,6 +186,11 @@ public class SpellAbility extends ActivatedAbilityImpl { return amount * xMultiplier; } + public void setCardName(String cardName) { + this.cardName = cardName; + setSpellName(); + } + private void setSpellName() { switch (spellAbilityType) { case SPLIT_FUSED: diff --git a/Mage/src/main/java/mage/abilities/common/CastCommanderAbility.java b/Mage/src/main/java/mage/abilities/common/CastCommanderAbility.java index bf16207f27..7daff914a3 100644 --- a/Mage/src/main/java/mage/abilities/common/CastCommanderAbility.java +++ b/Mage/src/main/java/mage/abilities/common/CastCommanderAbility.java @@ -11,20 +11,10 @@ import mage.constants.Zone; public class CastCommanderAbility extends SpellAbility { public CastCommanderAbility(Card card) { - super(card.getManaCost(), card.getName(), Zone.COMMAND, SpellAbilityType.BASE); - if (card.getSpellAbility() != null) { - this.getCosts().addAll(card.getSpellAbility().getCosts().copy()); - this.getEffects().addAll(card.getSpellAbility().getEffects().copy()); - this.getTargets().addAll(card.getSpellAbility().getTargets().copy()); - this.setTargetAdjuster(card.getSpellAbility().getTargetAdjuster()); - this.setCostAdjuster(card.getSpellAbility().getCostAdjuster()); - this.timing = card.getSpellAbility().getTiming(); - } else { - throw new IllegalStateException("Cast commander ability must be used with spell ability only: " + card.getName()); - } - this.usesStack = true; - this.controllerId = card.getOwnerId(); - this.sourceId = card.getId(); + super(card.getSpellAbility()); + this.setCardName(cardName = card.getName()); + zone = Zone.COMMAND; + spellAbilityType = SpellAbilityType.BASE; } public CastCommanderAbility(final CastCommanderAbility ability) { diff --git a/Mage/src/main/java/mage/abilities/keyword/AwakenAbility.java b/Mage/src/main/java/mage/abilities/keyword/AwakenAbility.java index 26ec12ee78..e315f41775 100644 --- a/Mage/src/main/java/mage/abilities/keyword/AwakenAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/AwakenAbility.java @@ -1,7 +1,5 @@ - package mage.abilities.keyword; -import java.util.UUID; import mage.MageInt; import mage.MageObject; import mage.abilities.Ability; @@ -13,25 +11,20 @@ import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.continuous.BecomesCreatureTargetEffect; import mage.abilities.effects.common.counter.AddCountersTargetEffect; import mage.cards.Card; -import mage.constants.CardType; -import mage.constants.Duration; -import mage.constants.Outcome; -import mage.constants.SpellAbilityType; -import mage.constants.SubType; -import mage.constants.Zone; +import mage.constants.*; import mage.counters.CounterType; import mage.filter.common.FilterControlledLandPermanent; import mage.game.Game; import mage.game.permanent.token.TokenImpl; -import mage.game.permanent.token.Token; import mage.target.Target; import mage.target.common.TargetControlledPermanent; import mage.target.targetpointer.FixedTarget; import mage.util.CardUtil; import org.apache.log4j.Logger; +import java.util.UUID; + /** - * * @author LevelX2 */ public class AwakenAbility extends SpellAbility { @@ -44,12 +37,15 @@ public class AwakenAbility extends SpellAbility { private int awakenValue; public AwakenAbility(Card card, int awakenValue, String awakenCosts) { - super(new ManaCostsImpl<>(awakenCosts), card.getName() + " with awaken", Zone.HAND, SpellAbilityType.BASE_ALTERNATE); - this.getCosts().addAll(card.getSpellAbility().getCosts().copy()); - this.getEffects().addAll(card.getSpellAbility().getEffects().copy()); - this.getTargets().addAll(card.getSpellAbility().getTargets().copy()); - this.spellAbilityType = SpellAbilityType.BASE_ALTERNATE; - this.timing = card.getSpellAbility().getTiming(); + super(card.getSpellAbility()); + this.setCardName(card.getName() + " with awaken"); + zone = Zone.HAND; + spellAbilityType = SpellAbilityType.BASE_ALTERNATE; + + this.getManaCosts().clear(); + this.getManaCostsToPay().clear(); + this.addManaCost(new ManaCostsImpl<>(awakenCosts)); + this.addTarget(new TargetControlledPermanent(new FilterControlledLandPermanent(filterMessage))); this.addEffect(new AwakenEffect()); this.awakenValue = awakenValue; @@ -57,7 +53,6 @@ public class AwakenAbility extends SpellAbility { + " (If you cast this spell for " + awakenCosts + ", also put " + CardUtil.numberToText(awakenValue, "a") + " +1/+1 counters on target land you control and it becomes a 0/0 Elemental creature with haste. It's still a land.)"; - } public AwakenAbility(final AwakenAbility ability) { @@ -116,17 +111,17 @@ public class AwakenAbility extends SpellAbility { return effect.apply(game, source); } } else // source should never be null, but we are seeing a lot of NPEs from this section - if (source == null) { - logger.fatal("Source was null in AwakenAbility: Create a bug report or fix the source code"); - } else if (source.getTargets() == null) { - MageObject sourceObj = source.getSourceObject(game); - if (sourceObj != null) { - Class sourceClass = sourceObj.getClass(); - if (sourceClass != null) { - logger.fatal("getTargets was null in AwakenAbility for " + sourceClass.toString() + " : Create a bug report or fix the source code"); + if (source == null) { + logger.fatal("Source was null in AwakenAbility: Create a bug report or fix the source code"); + } else if (source.getTargets() == null) { + MageObject sourceObj = source.getSourceObject(game); + if (sourceObj != null) { + Class sourceClass = sourceObj.getClass(); + if (sourceClass != null) { + logger.fatal("getTargets was null in AwakenAbility for " + sourceClass.toString() + " : Create a bug report or fix the source code"); + } } } - } return true; } } diff --git a/Mage/src/main/java/mage/abilities/keyword/EmergeAbility.java b/Mage/src/main/java/mage/abilities/keyword/EmergeAbility.java index a4c4b679c7..f8cfa44aba 100644 --- a/Mage/src/main/java/mage/abilities/keyword/EmergeAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/EmergeAbility.java @@ -1,7 +1,5 @@ - package mage.abilities.keyword; -import java.util.UUID; import mage.Mana; import mage.abilities.SpellAbility; import mage.abilities.costs.common.SacrificeTargetCost; @@ -21,8 +19,9 @@ import mage.target.TargetPermanent; import mage.target.common.TargetControlledCreaturePermanent; import mage.util.CardUtil; +import java.util.UUID; + /** - * * @author emerald000 */ public class EmergeAbility extends SpellAbility { @@ -30,11 +29,15 @@ public class EmergeAbility extends SpellAbility { private final ManaCosts emergeCost; public EmergeAbility(Card card, ManaCosts emergeCost) { - super(emergeCost, card.getName() + " with emerge", Zone.HAND, SpellAbilityType.BASE_ALTERNATE); - this.getCosts().addAll(card.getSpellAbility().getCosts().copy()); - this.getEffects().addAll(card.getSpellAbility().getEffects().copy()); - this.getTargets().addAll(card.getSpellAbility().getTargets().copy()); - this.timing = card.getSpellAbility().getTiming(); + super(card.getSpellAbility()); + this.setCardName(card.getName() + " with emerge"); + zone = Zone.HAND; + spellAbilityType = SpellAbilityType.BASE_ALTERNATE; + + this.getManaCosts().clear(); + this.getManaCostsToPay().clear(); + this.addManaCost(emergeCost.copy()); + this.setRuleAtTheTop(true); this.emergeCost = emergeCost.copy(); } diff --git a/Mage/src/main/java/mage/abilities/keyword/JumpStartAbility.java b/Mage/src/main/java/mage/abilities/keyword/JumpStartAbility.java index 815f625c75..8293c1af73 100644 --- a/Mage/src/main/java/mage/abilities/keyword/JumpStartAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/JumpStartAbility.java @@ -36,16 +36,14 @@ public class JumpStartAbility extends SpellAbility { private boolean replacementEffectAdded = false; public JumpStartAbility(Card card) { - super(card.getManaCost(), card.getName() + " with jump-start", Zone.GRAVEYARD, SpellAbilityType.BASE_ALTERNATE); - this.getCosts().addAll(card.getSpellAbility().getCosts().copy()); + super(card.getSpellAbility()); + this.setCardName(card.getName() + " with jump-start"); + zone = Zone.GRAVEYARD; + spellAbilityType = SpellAbilityType.BASE_ALTERNATE; + Cost cost = new DiscardTargetCost(new TargetCardInHand()); cost.setText(""); this.addCost(cost); - this.getEffects().addAll(card.getSpellAbility().getEffects().copy()); - this.getTargets().addAll(card.getSpellAbility().getTargets().copy()); - this.spellAbilityType = SpellAbilityType.BASE_ALTERNATE; - this.timing = card.getSpellAbility().getTiming(); - } public JumpStartAbility(final JumpStartAbility ability) { @@ -132,9 +130,7 @@ class JumpStartReplacementEffect extends ReplacementEffectImpl { if (event.getTargetId().equals(source.getSourceId()) && ((ZoneChangeEvent) event).getFromZone() == Zone.STACK && ((ZoneChangeEvent) event).getToZone() != Zone.EXILED) { - if (game.getState().getZoneChangeCounter(source.getSourceId()) == source.getSourceObjectZoneChangeCounter() + 1) { - return true; - } + return game.getState().getZoneChangeCounter(source.getSourceId()) == source.getSourceObjectZoneChangeCounter() + 1; } return false; diff --git a/Mage/src/main/java/mage/abilities/keyword/RetraceAbility.java b/Mage/src/main/java/mage/abilities/keyword/RetraceAbility.java index ed41e0e817..deee4a1805 100644 --- a/Mage/src/main/java/mage/abilities/keyword/RetraceAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/RetraceAbility.java @@ -1,4 +1,3 @@ - package mage.abilities.keyword; import mage.abilities.SpellAbility; @@ -11,22 +10,21 @@ import mage.filter.common.FilterLandCard; import mage.target.common.TargetCardInHand; /** - * * @author LevelX2 */ public class RetraceAbility extends SpellAbility { public RetraceAbility(Card card) { - super(card.getManaCost(), card.getName() + " with retrace", Zone.GRAVEYARD, SpellAbilityType.BASE_ALTERNATE); - this.getCosts().addAll(card.getSpellAbility().getCosts().copy()); + super(card.getSpellAbility()); + this.setCardName(card.getName() + " with retrace"); + zone = Zone.GRAVEYARD; + spellAbilityType = SpellAbilityType.BASE_ALTERNATE; + Cost cost = new DiscardTargetCost(new TargetCardInHand(new FilterLandCard())); cost.setText(""); this.addCost(cost); - this.getEffects().addAll(card.getSpellAbility().getEffects().copy()); - this.getTargets().addAll(card.getSpellAbility().getTargets().copy()); - this.spellAbilityType = SpellAbilityType.BASE_ALTERNATE; - this.timing = card.getSpellAbility().getTiming(); + this.setRuleAtTheTop(true); } public RetraceAbility(final RetraceAbility ability) { diff --git a/Mage/src/main/java/mage/abilities/keyword/SpectacleAbility.java b/Mage/src/main/java/mage/abilities/keyword/SpectacleAbility.java index 5845fdeeb6..10600c3f68 100644 --- a/Mage/src/main/java/mage/abilities/keyword/SpectacleAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/SpectacleAbility.java @@ -22,12 +22,15 @@ public class SpectacleAbility extends SpellAbility { private String rule; public SpectacleAbility(Card card, ManaCost spectacleCosts) { - super(spectacleCosts, card.getName() + " with spectacle", Zone.HAND, SpellAbilityType.BASE_ALTERNATE); - this.getCosts().addAll(card.getSpellAbility().getCosts().copy()); - this.getEffects().addAll(card.getSpellAbility().getEffects().copy()); - this.getTargets().addAll(card.getSpellAbility().getTargets().copy()); - this.spellAbilityType = SpellAbilityType.BASE_ALTERNATE; - this.timing = card.getSpellAbility().getTiming(); + super(card.getSpellAbility()); + this.setCardName(card.getName() + " with spectacle"); + zone = Zone.HAND; + spellAbilityType = SpellAbilityType.BASE_ALTERNATE; + + this.getManaCosts().clear(); + this.getManaCostsToPay().clear(); + this.addManaCost(spectacleCosts.copy()); + this.setRuleAtTheTop(true); this.rule = "Spectacle " + spectacleCosts.getText() + " (You may cast this spell for its spectacle cost rather than its mana cost if an opponent lost life this turn.)"; diff --git a/Mage/src/main/java/mage/abilities/keyword/SurgeAbility.java b/Mage/src/main/java/mage/abilities/keyword/SurgeAbility.java index 0e4c28b540..63030341c8 100644 --- a/Mage/src/main/java/mage/abilities/keyword/SurgeAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/SurgeAbility.java @@ -1,8 +1,5 @@ - package mage.abilities.keyword; -import java.util.ArrayList; -import java.util.UUID; import mage.abilities.SpellAbility; import mage.abilities.costs.mana.ManaCostsImpl; import mage.cards.Card; @@ -12,8 +9,10 @@ import mage.game.Game; import mage.players.Player; import mage.watchers.common.CastSpellLastTurnWatcher; +import java.util.ArrayList; +import java.util.UUID; + /** - * * @author LevelX2 */ public class SurgeAbility extends SpellAbility { @@ -23,12 +22,15 @@ public class SurgeAbility extends SpellAbility { private String rule; public SurgeAbility(Card card, String surgeCosts) { - super(new ManaCostsImpl<>(surgeCosts), card.getName() + " with surge", Zone.HAND, SpellAbilityType.BASE_ALTERNATE); - this.getCosts().addAll(card.getSpellAbility().getCosts().copy()); - this.getEffects().addAll(card.getSpellAbility().getEffects().copy()); - this.getTargets().addAll(card.getSpellAbility().getTargets().copy()); - this.spellAbilityType = SpellAbilityType.BASE_ALTERNATE; - this.timing = card.getSpellAbility().getTiming(); + super(card.getSpellAbility()); + this.setCardName(card.getName() + " with surge"); + zone = Zone.HAND; + spellAbilityType = SpellAbilityType.BASE_ALTERNATE; + + this.getManaCosts().clear(); + this.getManaCostsToPay().clear(); + this.addManaCost(new ManaCostsImpl<>(surgeCosts)); + this.setRuleAtTheTop(true); this.rule = "Surge " + surgeCosts + " (You may cast this spell for its surge cost if you or a teammate has cast another spell this turn.)";