mirror of
https://github.com/correl/mage.git
synced 2024-11-15 03:00:16 +00:00
Alternative spell abilities: added support of modes and other extra things in commander, awaken, jump-start, spectacle, retrace and surge abilities;
This commit is contained in:
parent
a8c047a2be
commit
d25ae47104
10 changed files with 138 additions and 89 deletions
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
package mage.abilities;
|
||||
|
||||
import mage.abilities.costs.OptionalAdditionalModeSourceCosts;
|
||||
|
@ -45,22 +44,23 @@ public class Modes extends LinkedHashMap<UUID, Mode> {
|
|||
this.put(entry.getKey(), entry.getValue().copy());
|
||||
}
|
||||
for (Map.Entry<UUID, Mode> 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() {
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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 {
|
|||
+ " <i>(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.)</i>";
|
||||
|
||||
}
|
||||
|
||||
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<? extends MageObject> 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<? extends MageObject> 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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<ManaCost> emergeCost;
|
||||
|
||||
public EmergeAbility(Card card, ManaCosts<ManaCost> 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();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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()
|
||||
+ " <i>(You may cast this spell for its spectacle cost rather than its mana cost if an opponent lost life this turn.)</i>";
|
||||
|
|
|
@ -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
|
||||
+ " <i>(You may cast this spell for its surge cost if you or a teammate has cast another spell this turn.)</i>";
|
||||
|
|
Loading…
Reference in a new issue