Some changes/fixes to conspire ability.

This commit is contained in:
LevelX2 2015-10-03 13:33:22 +02:00
parent 8e50c18d8c
commit 7b68604471
14 changed files with 234 additions and 128 deletions

View file

@ -58,7 +58,7 @@ public class AEthertow extends CardImpl {
this.getSpellAbility().addTarget(new TargetCreaturePermanent(filter));
// Conspire
this.addAbility(new ConspireAbility(this));
this.addAbility(new ConspireAbility(getId(), ConspireAbility.ConspireTargets.ONE));
}
public AEthertow(final AEthertow card) {

View file

@ -51,7 +51,7 @@ public class BarkshellBlessing extends CardImpl {
this.getSpellAbility().addEffect(new BoostTargetEffect(2, 2, Duration.EndOfTurn));
// Conspire
this.addAbility(new ConspireAbility(this));
this.addAbility(new ConspireAbility(getId(), ConspireAbility.ConspireTargets.ONE));
}
public BarkshellBlessing(final BarkshellBlessing card) {

View file

@ -50,7 +50,7 @@ public class BurnTrail extends CardImpl {
this.getSpellAbility().addTarget(new TargetCreatureOrPlayer());
// Conspire
this.addAbility(new ConspireAbility(this));
this.addAbility(new ConspireAbility(getId(), ConspireAbility.ConspireTargets.ONE));
}
public BurnTrail(final BurnTrail card) {

View file

@ -51,7 +51,7 @@ public class DisturbingPlot extends CardImpl {
this.getSpellAbility().addTarget(new TargetCardInGraveyard(new FilterCreatureCard("creature card in a graveyard")));
// Conspire
this.addAbility(new ConspireAbility(this));
this.addAbility(new ConspireAbility(getId(), ConspireAbility.ConspireTargets.ONE));
}

View file

@ -52,7 +52,7 @@ public class GhastlyDiscovery extends CardImpl {
this.getSpellAbility().addEffect(new GhastlyDiscoveryEffect());
// Conspire
this.addAbility(new ConspireAbility(this));
this.addAbility(new ConspireAbility(getId(), ConspireAbility.ConspireTargets.NONE));
}
public GhastlyDiscovery(final GhastlyDiscovery card) {

View file

@ -58,7 +58,7 @@ public class Giantbaiting extends CardImpl {
this.getSpellAbility().addEffect(new GiantbaitingEffect());
// Conspire
this.addAbility(new ConspireAbility(this));
this.addAbility(new ConspireAbility(getId(), ConspireAbility.ConspireTargets.NONE));
}

View file

@ -51,7 +51,7 @@ public class GleefulSabotage extends CardImpl {
this.getSpellAbility().addTarget(new TargetPermanent(new FilterArtifactOrEnchantmentPermanent()));
// Conspire
this.addAbility(new ConspireAbility(this));
this.addAbility(new ConspireAbility(getId(), ConspireAbility.ConspireTargets.ONE));
}
public GleefulSabotage(final GleefulSabotage card) {

View file

@ -50,7 +50,7 @@ public class MemorySluice extends CardImpl {
this.getSpellAbility().addTarget(new TargetPlayer());
// Conspire
this.addAbility(new ConspireAbility(this));
this.addAbility(new ConspireAbility(getId(), ConspireAbility.ConspireTargets.ONE));
}

View file

@ -60,7 +60,7 @@ public class MineExcavation extends CardImpl {
this.getSpellAbility().addTarget(new TargetCardInGraveyard(filter));
// Conspire
this.addAbility(new ConspireAbility(this));
this.addAbility(new ConspireAbility(getId(), ConspireAbility.ConspireTargets.ONE));
}
public MineExcavation(final MineExcavation card) {

View file

@ -64,7 +64,7 @@ public class TraitorsRoar extends CardImpl {
this.getSpellAbility().addEffect(new TraitorsRoarEffect());
// Conspire
this.addAbility(new ConspireAbility(this));
this.addAbility(new ConspireAbility(getId(), ConspireAbility.ConspireTargets.ONE));
}

View file

@ -25,7 +25,6 @@
* authors and should not be interpreted as representing official policies, either expressed
* or implied, of BetaSteward_at_googlemail.com.
*/
package org.mage.test.cards.abilities.keywords;
import mage.constants.PhaseStep;
@ -40,31 +39,31 @@ import org.mage.test.serverside.base.CardTestPlayerBase;
public class ConspireTest extends CardTestPlayerBase {
/**
* 702.77. Conspire
* 702.77a Conspire is a keyword that represents two abilities. The first is a static ability that functions
* while the spell with conspire is on the stack. The second is a triggered ability that functions
* while the spell with conspire is on the stack. Conspire means As an additional cost to cast
* this spell, you may tap two untapped creatures you control that each share a color with it and
* When you cast this spell, if its conspire cost was paid, copy it. If the spell has any targets, you
* may choose new targets for the copy. Paying a spells conspire cost follows the rules for
* paying additional costs in rules 601.2b and 601.2eg.
* 702.77. Conspire 702.77a Conspire is a keyword that represents two
* abilities. The first is a static ability that functions while the spell
* with conspire is on the stack. The second is a triggered ability that
* functions while the spell with conspire is on the stack. Conspire means
* As an additional cost to cast this spell, you may tap two untapped
* creatures you control that each share a color with it and When you cast
* this spell, if its conspire cost was paid, copy it. If the spell has any
* targets, you may choose new targets for the copy. Paying a spells
* conspire cost follows the rules for paying additional costs in rules
* 601.2b and 601.2eg.
*
* 702.77b If a spell has multiple instances of conspire, each is paid separately and triggers based on
* its own payment, not any other instance of conspire
* 702.77b If a spell has multiple instances of conspire, each is paid
* separately and triggers based on its own payment, not any other instance
* of conspire
*
*/
/**
* Burn Trail
* Sorcery, 3R (4)
* Burn Trail deals 3 damage to target creature or player.
* Burn Trail Sorcery, 3R (4) Burn Trail deals 3 damage to target creature
* or player.
*
* Conspire (As you cast this spell, you may tap two untapped creatures you
* control that share a color with it. When you do, copy it and you may
* choose a new target for the copy.)
* Conspire (As you cast this spell, you may tap two untapped creatures you
* control that share a color with it. When you do, copy it and you may
* choose a new target for the copy.)
*
*/
@Test
public void testConspire() {
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 4);
@ -72,7 +71,6 @@ public class ConspireTest extends CardTestPlayerBase {
addCard(Zone.BATTLEFIELD, playerA, "Raging Goblin");
addCard(Zone.HAND, playerA, "Burn Trail");
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Burn Trail", playerB);
setChoice(playerA, "Yes");
@ -93,7 +91,6 @@ public class ConspireTest extends CardTestPlayerBase {
addCard(Zone.BATTLEFIELD, playerA, "Raging Goblin");
addCard(Zone.HAND, playerA, "Burn Trail");
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Burn Trail", playerB);
setChoice(playerA, "No");
@ -107,4 +104,50 @@ public class ConspireTest extends CardTestPlayerBase {
}
@Test
public void testWortTheRaidmother() {
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 7);
// When Wort, the Raidmother enters the battlefield, put two 1/1 red and green Goblin Warrior creature tokens onto the battlefield.
// Each red or green instant or sorcery spell you cast has conspire.
// (As you cast the spell, you may tap two untapped creatures you control that share a color with it. When you do, copy it and you may choose new targets for the copy.)
addCard(Zone.HAND, playerA, "Wort, the Raidmother");
addCard(Zone.HAND, playerA, "Lightning Bolt");
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Wort, the Raidmother");// {4}{R/G}{R/G}
castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Lightning Bolt", playerB);
setChoice(playerA, "Yes");
setStopAt(1, PhaseStep.END_TURN);
execute();
assertPermanentCount(playerA, "Wort, the Raidmother", 1);
assertGraveyardCount(playerA, "Lightning Bolt", 1);
assertLife(playerB, 14);
}
@Test
public void testWortTheRaidmotherWithConspireSpell() {
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 10);
addCard(Zone.BATTLEFIELD, playerA, "Raging Goblin", 2);
// When Wort, the Raidmother enters the battlefield, put two 1/1 red and green Goblin Warrior creature tokens onto the battlefield.
// Each red or green instant or sorcery spell you cast has conspire.
// (As you cast the spell, you may tap two untapped creatures you control that share a color with it. When you do, copy it and you may choose new targets for the copy.)
addCard(Zone.HAND, playerA, "Wort, the Raidmother");
addCard(Zone.HAND, playerA, "Burn Trail");
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Wort, the Raidmother"); // {4}{R/G}{R/G}
castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Burn Trail", playerB);
setChoice(playerA, "Yes"); // use Conspire from Burn Trail itself
setChoice(playerA, "Yes"); // use Conspire gained from Wort, the Raidmother
setStopAt(1, PhaseStep.END_TURN);
execute();
assertPermanentCount(playerA, "Wort, the Raidmother", 1);
assertLife(playerB, 11);
assertLife(playerA, 20);
assertGraveyardCount(playerA, "Burn Trail", 1);
}
}

View file

@ -445,20 +445,28 @@ public abstract class AbilityImpl implements Ability {
public boolean activateAlternateOrAdditionalCosts(MageObject sourceObject, boolean noMana, Player controller, Game game) {
boolean alternativeCostisUsed = false;
if (sourceObject != null && !(sourceObject instanceof Permanent) && !(this instanceof FlashbackAbility)) {
for (Ability ability : sourceObject.getAbilities()) {
// if cast for noMana no Alternative costs are allowed
if (!noMana && ability instanceof AlternativeSourceCosts) {
AlternativeSourceCosts alternativeSpellCosts = (AlternativeSourceCosts) ability;
if (alternativeSpellCosts.isAvailable(this, game)) {
if (alternativeSpellCosts.askToActivateAlternativeCosts(this, game)) {
// only one alternative costs may be activated
alternativeCostisUsed = true;
break;
Abilities<Ability> abilities = null;
if (sourceObject instanceof Card) {
abilities = ((Card) sourceObject).getAbilities(game);
} else {
sourceObject.getAbilities();
}
if (abilities != null) {
for (Ability ability : abilities) {
// if cast for noMana no Alternative costs are allowed
if (!noMana && ability instanceof AlternativeSourceCosts) {
AlternativeSourceCosts alternativeSpellCosts = (AlternativeSourceCosts) ability;
if (alternativeSpellCosts.isAvailable(this, game)) {
if (alternativeSpellCosts.askToActivateAlternativeCosts(this, game)) {
// only one alternative costs may be activated
alternativeCostisUsed = true;
break;
}
}
}
}
if (ability instanceof OptionalAdditionalSourceCosts) {
((OptionalAdditionalSourceCosts) ability).addOptionalAdditionalCosts(this, game);
if (ability instanceof OptionalAdditionalSourceCosts) {
((OptionalAdditionalSourceCosts) ability).addOptionalAdditionalCosts(this, game);
}
}
}
// controller specific alternate spell costs

View file

@ -27,14 +27,16 @@
*/
package mage.abilities.keyword;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.SpellAbility;
import mage.abilities.StaticAbility;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.costs.Cost;
import mage.abilities.costs.Costs;
import mage.abilities.costs.OptionalAdditionalCost;
import mage.abilities.costs.OptionalAdditionalCostImpl;
import mage.abilities.costs.OptionalAdditionalSourceCosts;
import mage.abilities.costs.common.TapTargetCost;
@ -50,48 +52,77 @@ import mage.filter.predicate.permanent.TappedPredicate;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.stack.Spell;
import mage.game.stack.StackObject;
import mage.players.Player;
import mage.target.common.TargetControlledPermanent;
/**
* 702.77. Conspire 702.77a Conspire is a keyword that represents two abilities.
* The first is a static ability that functions while the spell with conspire is
* on the stack. The second is a triggered ability that functions while the
* spell with conspire is on the stack. "Conspire" means "As an additional cost
* to cast this spell, you may tap two untapped creatures you control that each
* share a color with it" and "When you cast this spell, if its conspire cost
* was paid, copy it. If the spell has any targets, you may choose new targets
* for the copy." Paying a spells conspire cost follows the rules for paying
* additional costs in rules 601.2b and 601.2eg. 702.77b If a spell has
* multiple instances of conspire, each is paid separately and triggers based on
* its own payment, not any other instance of conspire. *
/*
* 702.77. Conspire
* 702.77a Conspire is a keyword that represents two abilities.
* The first is a static ability that functions while the spell with conspire is on the stack.
* The second is a triggered ability that functions while the spell with conspire is on the stack.
* "Conspire" means "As an additional cost to cast this spell,
* you may tap two untapped creatures you control that each share a color with it"
* and "When you cast this spell, if its conspire cost was paid, copy it.
* If the spell has any targets, you may choose new targets for the copy."
* Paying a spells conspire cost follows the rules for paying additional costs in rules 601.2b and 601.2eg.
* 702.77b If a spell has multiple instances of conspire, each is paid separately and triggers
* based on its own payment, not any other instance of conspire. *
*
* @author jeffwadsworth heavily based off the replicate keyword by LevelX
*/
public class ConspireAbility extends StaticAbility implements OptionalAdditionalSourceCosts {
private static final String keywordText = "Conspire";
private static final String reminderTextCost = "<i>As you cast this spell, you may tap two untapped creatures you control that share a color with it. When you do, copy it and you may choose a new target for the copy.)</i>";
private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent("two untapped creatures you control that share a color with it");
private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent("untapped creatures you control that share a color with it");
protected static final String CONSPIRE_ACTIVATION_KEY = "ConspireActivation";
static {
filter.add(Predicates.not(new TappedPredicate()));
filter.add(new SharesColorWithSourcePredicate());
}
Cost costConspire = new TapTargetCost(new TargetControlledPermanent(2, 2, filter, true));
OptionalAdditionalCost conspireCost = new OptionalAdditionalCostImpl(keywordText, "-", reminderTextCost, costConspire);
public enum ConspireTargets {
public ConspireAbility(Card card) {
NONE,
ONE,
MORE
}
private UUID conspireId;
private String reminderText;
private OptionalAdditionalCostImpl conspireCost;
/**
* Unique Id for a ConspireAbility but may not change while a continuous
* effect gives Conspire
*
* @param conspireId
* @param conspireTargets controls the content of the reminder text
*/
public ConspireAbility(UUID conspireId, ConspireTargets conspireTargets) {
super(Zone.STACK, null);
setRuleAtTheTop(false);
addSubAbility(new ConspireTriggeredAbility());
this.conspireId = conspireId;
switch (conspireTargets) {
case NONE:
reminderText = "As you cast this spell, you may tap two untapped creatures you control that share a color with it. When you do, copy it.)";
break;
case ONE:
reminderText = "As you cast this spell, you may tap two untapped creatures you control that share a color with it. When you do, copy it and you may choose a new target for the copy.)";
break;
case MORE:
reminderText = "As you cast this spell, you may tap two untapped creatures you control that share a color with it. When you do, copy it and you may choose a new targets for the copy.)";
break;
}
conspireCost = new OptionalAdditionalCostImpl(keywordText, "-", reminderText,
new TapTargetCost(new TargetControlledPermanent(2, 2, filter, true)));
addSubAbility(new ConspireTriggeredAbility(conspireId));
}
public ConspireAbility(final ConspireAbility ability) {
super(ability);
conspireCost = ability.conspireCost;
this.conspireId = ability.conspireId;
this.conspireCost = ability.conspireCost.copy();
this.reminderText = ability.reminderText;
}
@Override
@ -106,18 +137,21 @@ public class ConspireAbility extends StaticAbility implements OptionalAdditional
}
}
@Override
public boolean isActivated() {
if (conspireCost != null) {
return conspireCost.isActivated();
}
return false;
public UUID getConspireId() {
return conspireId;
}
public void resetConspire() {
if (conspireCost != null) {
conspireCost.reset();
@Override
public boolean isActivated() {
throw new UnsupportedOperationException("Use ConspireAbility.isActivated(Ability ability, Game game) method instead!");
}
public boolean isActivated(Ability ability, Game game) {
Set<UUID> activations = (Set<UUID>) game.getState().getValue(CONSPIRE_ACTIVATION_KEY + ability.getId());
if (activations != null) {
return activations.contains(getConspireId());
}
return false;
}
@Override
@ -125,9 +159,9 @@ public class ConspireAbility extends StaticAbility implements OptionalAdditional
if (ability instanceof SpellAbility) {
Player player = game.getPlayer(controllerId);
if (player != null) {
this.resetConspire();
if (player.chooseUse(Outcome.Benefit, new StringBuilder("Pay ").append(conspireCost.getText(false)).append(" ?").toString(), ability, game)) {
conspireCost.activate();
resetConspire(ability, game);
if (player.chooseUse(Outcome.Benefit, "Pay " + conspireCost.getText(false) + " ?", ability, game)) {
activateConspire(ability, game);
for (Iterator it = ((Costs) conspireCost).iterator(); it.hasNext();) {
Cost cost = (Cost) it.next();
ability.getCosts().add(cost.copy());
@ -137,6 +171,22 @@ public class ConspireAbility extends StaticAbility implements OptionalAdditional
}
}
private void activateConspire(Ability ability, Game game) {
Set<UUID> activations = (Set<UUID>) game.getState().getValue(CONSPIRE_ACTIVATION_KEY + ability.getId());
if (activations == null) {
activations = new HashSet<>();
game.getState().setValue(CONSPIRE_ACTIVATION_KEY + ability.getId(), activations);
}
activations.add(getConspireId());
}
private void resetConspire(Ability ability, Game game) {
Set<UUID> activations = (Set<UUID>) game.getState().getValue(CONSPIRE_ACTIVATION_KEY + ability.getId());
if (activations != null) {
activations.remove(getConspireId());
}
}
@Override
public String getRule() {
StringBuilder sb = new StringBuilder();
@ -167,13 +217,17 @@ public class ConspireAbility extends StaticAbility implements OptionalAdditional
class ConspireTriggeredAbility extends TriggeredAbilityImpl {
public ConspireTriggeredAbility() {
private UUID conspireId;
public ConspireTriggeredAbility(UUID conspireId) {
super(Zone.STACK, new ConspireEffect());
this.conspireId = conspireId;
this.setRuleVisible(false);
}
private ConspireTriggeredAbility(final ConspireTriggeredAbility ability) {
super(ability);
this.conspireId = ability.conspireId;
}
@Override
@ -188,20 +242,18 @@ class ConspireTriggeredAbility extends TriggeredAbilityImpl {
@Override
public boolean checkTrigger(GameEvent event, Game game) {
if (event.getSourceId().equals(this.sourceId)) {
StackObject spell = game.getStack().getStackObject(this.sourceId);
if (spell instanceof Spell) {
Card card = game.getCard(spell.getSourceId());
if (card != null) {
for (Ability ability : card.getAbilities()) {
if (ability instanceof ConspireAbility) {
if (((ConspireAbility) ability).isActivated()) {
for (Effect effect : this.getEffects()) {
effect.setValue("ConspireSpell", spell);
}
return true;
if (event.getSourceId().equals(getSourceId())) {
Spell spell = game.getStack().getSpell(event.getSourceId());
for (Ability ability : spell.getAbilities(game)) {
if (ability instanceof ConspireAbility
&& ((ConspireAbility) ability).getConspireId().equals(getConspireId())) {
if (((ConspireAbility) ability).isActivated(spell.getSpellAbility(), game)) {
for (Effect effect : this.getEffects()) {
if (effect instanceof ConspireEffect) {
((ConspireEffect) effect).setConspiredSpell(spell);
}
}
return true;
}
}
}
@ -209,52 +261,53 @@ class ConspireTriggeredAbility extends TriggeredAbilityImpl {
return false;
}
public UUID getConspireId() {
return conspireId;
}
@Override
public String getRule() {
return "Conspire: <i>As you cast this spell, you may tap two untapped creatures you control that share a color with it. When you do, copy it and you may choose a new target for the copy.)</i>";
return "When you pay the conspire costs, copy it and you may choose a new target for the copy.";
}
}
class ConspireEffect extends OneShotEffect {
private Spell conspiredSpell;
public ConspireEffect() {
super(Outcome.Copy);
}
public ConspireEffect(final ConspireEffect effect) {
super(effect);
this.conspiredSpell = effect.conspiredSpell;
}
@Override
public boolean apply(Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId());
if (controller != null) {
Spell spell = (Spell) this.getValue("ConspireSpell");
if (spell != null) {
Card card = game.getCard(spell.getSourceId());
if (card != null) {
for (Ability ability : card.getAbilities()) {
if (ability instanceof ConspireAbility) {
if (((ConspireAbility) ability).isActivated()) {
((ConspireAbility) ability).resetConspire();
}
}
}
Spell copy = spell.copySpell();
copy.setControllerId(source.getControllerId());
copy.setCopiedSpell(true);
game.getStack().push(copy);
copy.chooseNewTargets(game, source.getControllerId());
if (!game.isSimulation()) {
game.informPlayers(new StringBuilder(controller.getLogName()).append(copy.getActivatedMessage(game)).toString());
}
return true;
if (controller != null && conspiredSpell != null) {
Card card = game.getCard(conspiredSpell.getSourceId());
if (card != null) {
Spell copy = conspiredSpell.copySpell();
copy.setControllerId(source.getControllerId());
copy.setCopiedSpell(true);
game.getStack().push(copy);
copy.chooseNewTargets(game, source.getControllerId());
if (!game.isSimulation()) {
game.informPlayers(controller.getLogName() + copy.getActivatedMessage(game));
}
return true;
}
}
return false;
}
public void setConspiredSpell(Spell conspiredSpell) {
this.conspiredSpell = conspiredSpell;
}
@Override
public ConspireEffect copy() {
return new ConspireEffect(this);

View file

@ -106,6 +106,7 @@ public abstract class StackObjImpl implements StackObject {
public boolean chooseNewTargets(Game game, UUID targetControllerId, boolean forceChange, boolean onlyOneTarget, FilterPermanent filterNewTarget) {
Player targetController = game.getPlayer(targetControllerId);
if (targetController != null) {
StringBuilder oldTargetDescription = new StringBuilder();
StringBuilder newTargetDescription = new StringBuilder();
// Fused split spells or spells where "Splice on Arcane" was used can have more than one ability
Abilities<Ability> objectAbilities = new AbilitiesImpl<>();
@ -118,6 +119,7 @@ public abstract class StackObjImpl implements StackObject {
// Some spells can have more than one mode
for (UUID modeId : ability.getModes().getSelectedModes()) {
Mode mode = ability.getModes().get(modeId);
oldTargetDescription.append(ability.getTargetDescription(mode.getTargets(), game));
for (Target target : mode.getTargets()) {
Target newTarget = chooseNewTarget(targetController, ability, mode, target, forceChange, filterNewTarget, game);
// clear the old target and copy all targets from new target
@ -131,7 +133,7 @@ public abstract class StackObjImpl implements StackObject {
}
}
if (newTargetDescription.length() > 0 && !game.isSimulation()) {
if (!newTargetDescription.toString().equals(oldTargetDescription.toString()) && !game.isSimulation()) {
game.informPlayers(this.getLogName() + " is now " + newTargetDescription.toString());
}
return true;