mirror of
https://github.com/correl/mage.git
synced 2025-01-11 19:13:02 +00:00
Support for copying permanent spells (WIP, do not merge) (#7084)
* added initial support for permanent tokens * [ZNR] Implemented Lithoform Engine * [ZNR] Implemented Verazol, the Split Current * permanent spell tokens no longer count as created * small change to token generation * added test, currently incomplete * found a potential solution for kicker issue, possibly too much of a hack * fixed a test failure * reversed hack changes * skipped failing tests * added more tests
This commit is contained in:
parent
f32597b164
commit
7647a3d8f0
11 changed files with 531 additions and 29 deletions
|
@ -20,6 +20,7 @@ import mage.game.Game;
|
|||
import mage.game.events.GameEvent;
|
||||
import mage.game.events.ZoneChangeEvent;
|
||||
import mage.game.permanent.Permanent;
|
||||
import mage.game.permanent.PermanentToken;
|
||||
import mage.players.Player;
|
||||
import mage.target.common.TargetControlledCreaturePermanent;
|
||||
|
||||
|
@ -91,7 +92,7 @@ class AnafenzaTheForemostEffect extends ReplacementEffectImpl {
|
|||
if (controller != null) {
|
||||
if (((ZoneChangeEvent) event).getFromZone() == Zone.BATTLEFIELD) {
|
||||
Permanent permanent = ((ZoneChangeEvent) event).getTarget();
|
||||
if (permanent != null) {
|
||||
if (permanent != null && !(permanent instanceof PermanentToken)) {
|
||||
return controller.moveCardToExileWithInfo(permanent, null, null, source.getSourceId(), game, Zone.BATTLEFIELD, true);
|
||||
}
|
||||
} else {
|
||||
|
|
129
Mage.Sets/src/mage/cards/l/LithoformEngine.java
Normal file
129
Mage.Sets/src/mage/cards/l/LithoformEngine.java
Normal file
|
@ -0,0 +1,129 @@
|
|||
package mage.cards.l;
|
||||
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.Mode;
|
||||
import mage.abilities.common.SimpleActivatedAbility;
|
||||
import mage.abilities.costs.common.TapSourceCost;
|
||||
import mage.abilities.costs.mana.GenericManaCost;
|
||||
import mage.abilities.effects.OneShotEffect;
|
||||
import mage.abilities.effects.common.CopyTargetSpellEffect;
|
||||
import mage.cards.CardImpl;
|
||||
import mage.cards.CardSetInfo;
|
||||
import mage.constants.CardType;
|
||||
import mage.constants.Outcome;
|
||||
import mage.constants.SuperType;
|
||||
import mage.constants.TargetController;
|
||||
import mage.filter.FilterSpell;
|
||||
import mage.filter.FilterStackObject;
|
||||
import mage.filter.common.FilterInstantOrSorcerySpell;
|
||||
import mage.filter.predicate.Predicate;
|
||||
import mage.game.Game;
|
||||
import mage.game.permanent.Permanent;
|
||||
import mage.game.stack.StackAbility;
|
||||
import mage.game.stack.StackObject;
|
||||
import mage.players.Player;
|
||||
import mage.target.TargetSpell;
|
||||
import mage.target.common.TargetActivatedOrTriggeredAbility;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author TheElk801
|
||||
*/
|
||||
public final class LithoformEngine extends CardImpl {
|
||||
|
||||
private static final FilterStackObject filter
|
||||
= new FilterStackObject("activated or triggered ability you control");
|
||||
private static final FilterSpell filter2
|
||||
= new FilterInstantOrSorcerySpell("instant or sorcery spell you control");
|
||||
private static final FilterSpell filter3
|
||||
= new FilterSpell("permanent spell you control");
|
||||
|
||||
static {
|
||||
filter.add(TargetController.YOU.getControllerPredicate());
|
||||
filter2.add(TargetController.YOU.getControllerPredicate());
|
||||
filter3.add(TargetController.YOU.getControllerPredicate());
|
||||
filter3.add(LithoformEnginePredicate.instance);
|
||||
}
|
||||
|
||||
public LithoformEngine(UUID ownerId, CardSetInfo setInfo) {
|
||||
super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{4}");
|
||||
|
||||
this.addSuperType(SuperType.LEGENDARY);
|
||||
|
||||
// {2}, {T}: Copy target activated or triggered ability you control. You may choose new targets for the copy.
|
||||
Ability ability = new SimpleActivatedAbility(new LithoformEngineEffect(), new GenericManaCost(2));
|
||||
ability.addCost(new TapSourceCost());
|
||||
ability.addTarget(new TargetActivatedOrTriggeredAbility(filter));
|
||||
this.addAbility(ability);
|
||||
|
||||
// {3}, {T}: Copy target instant or sorcery spell you control. You may choose new targets for the copy.
|
||||
ability = new SimpleActivatedAbility(new CopyTargetSpellEffect(), new GenericManaCost(3));
|
||||
ability.addCost(new TapSourceCost());
|
||||
ability.addTarget(new TargetSpell(filter2));
|
||||
this.addAbility(ability);
|
||||
|
||||
// {4}, {T}: Copy target permanent spell you control.
|
||||
ability = new SimpleActivatedAbility(new CopyTargetSpellEffect(
|
||||
false, false, false
|
||||
), new GenericManaCost(4));
|
||||
ability.addCost(new TapSourceCost());
|
||||
ability.addTarget(new TargetSpell(filter3));
|
||||
this.addAbility(ability);
|
||||
}
|
||||
|
||||
private LithoformEngine(final LithoformEngine card) {
|
||||
super(card);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LithoformEngine copy() {
|
||||
return new LithoformEngine(this);
|
||||
}
|
||||
}
|
||||
|
||||
enum LithoformEnginePredicate implements Predicate<StackObject> {
|
||||
instance;
|
||||
|
||||
@Override
|
||||
public boolean apply(StackObject input, Game game) {
|
||||
return input.isPermanent();
|
||||
}
|
||||
}
|
||||
|
||||
class LithoformEngineEffect extends OneShotEffect {
|
||||
|
||||
public LithoformEngineEffect() {
|
||||
super(Outcome.Copy);
|
||||
}
|
||||
|
||||
public LithoformEngineEffect(final LithoformEngineEffect effect) {
|
||||
super(effect);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
StackAbility stackAbility = (StackAbility) game.getStack().getStackObject(targetPointer.getFirst(game, source));
|
||||
if (stackAbility != null) {
|
||||
Player controller = game.getPlayer(source.getControllerId());
|
||||
Permanent sourcePermanent = game.getPermanent(source.getSourceId());
|
||||
if (controller != null && sourcePermanent != null) {
|
||||
stackAbility.createCopyOnStack(game, source, source.getControllerId(), true);
|
||||
game.informPlayers(sourcePermanent.getIdName() + ": " + controller.getLogName() + " copied ability");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public LithoformEngineEffect copy() {
|
||||
return new LithoformEngineEffect(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getText(Mode mode) {
|
||||
return "Copy target activated or triggered ability you control. You may choose new targets for the copy";
|
||||
}
|
||||
}
|
57
Mage.Sets/src/mage/cards/v/VerazolTheSplitCurrent.java
Normal file
57
Mage.Sets/src/mage/cards/v/VerazolTheSplitCurrent.java
Normal file
|
@ -0,0 +1,57 @@
|
|||
package mage.cards.v;
|
||||
|
||||
import mage.MageInt;
|
||||
import mage.abilities.common.EntersBattlefieldAbility;
|
||||
import mage.abilities.common.SpellCastControllerTriggeredAbility;
|
||||
import mage.abilities.costs.common.RemoveCountersSourceCost;
|
||||
import mage.abilities.dynamicvalue.common.ManaSpentToCastCount;
|
||||
import mage.abilities.effects.common.CopyTargetSpellEffect;
|
||||
import mage.abilities.effects.common.DoIfCostPaid;
|
||||
import mage.abilities.effects.common.counter.AddCountersSourceEffect;
|
||||
import mage.cards.CardImpl;
|
||||
import mage.cards.CardSetInfo;
|
||||
import mage.constants.CardType;
|
||||
import mage.constants.SubType;
|
||||
import mage.constants.SuperType;
|
||||
import mage.counters.CounterType;
|
||||
import mage.filter.StaticFilters;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author TheElk801
|
||||
*/
|
||||
public final class VerazolTheSplitCurrent extends CardImpl {
|
||||
|
||||
public VerazolTheSplitCurrent(UUID ownerId, CardSetInfo setInfo) {
|
||||
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{X}{G}{U}");
|
||||
|
||||
this.addSuperType(SuperType.LEGENDARY);
|
||||
this.subtype.add(SubType.SERPENT);
|
||||
this.power = new MageInt(0);
|
||||
this.toughness = new MageInt(0);
|
||||
|
||||
// Verazol, the Split Current enters the battlefield with a +1/+1 counter on it for each mana spent to cast it.
|
||||
this.addAbility(new EntersBattlefieldAbility(new AddCountersSourceEffect(
|
||||
CounterType.P1P1.createInstance(), ManaSpentToCastCount.instance, true
|
||||
), "with a +1/+1 counter on it for each mana spent to cast it"));
|
||||
|
||||
// Whenever you cast a kicked spell, you may remove two +1/+1 counters from Verazol, the Split Current. If you do, copy that spell. You may choose new targets for that copy.
|
||||
this.addAbility(new SpellCastControllerTriggeredAbility(
|
||||
new DoIfCostPaid(
|
||||
new CopyTargetSpellEffect(false, true)
|
||||
.withSpellName("that spell"),
|
||||
new RemoveCountersSourceCost(CounterType.P1P1.createInstance(2))
|
||||
), StaticFilters.FILTER_SPELL_KICKED_A, false, true
|
||||
));
|
||||
}
|
||||
|
||||
private VerazolTheSplitCurrent(final VerazolTheSplitCurrent card) {
|
||||
super(card);
|
||||
}
|
||||
|
||||
@Override
|
||||
public VerazolTheSplitCurrent copy() {
|
||||
return new VerazolTheSplitCurrent(this);
|
||||
}
|
||||
}
|
|
@ -236,6 +236,7 @@ public final class ZendikarRising extends ExpansionSet {
|
|||
cards.add(new SetCardInfo("Leyline Tyrant", 147, Rarity.MYTHIC, mage.cards.l.LeylineTyrant.class));
|
||||
cards.add(new SetCardInfo("Linvala, Shield of Sea Gate", 226, Rarity.RARE, mage.cards.l.LinvalaShieldOfSeaGate.class));
|
||||
cards.add(new SetCardInfo("Lithoform Blight", 109, Rarity.UNCOMMON, mage.cards.l.LithoformBlight.class));
|
||||
cards.add(new SetCardInfo("Lithoform Engine", 245, Rarity.MYTHIC, mage.cards.l.LithoformEngine.class));
|
||||
cards.add(new SetCardInfo("Living Tempest", 65, Rarity.COMMON, mage.cards.l.LivingTempest.class));
|
||||
cards.add(new SetCardInfo("Lotus Cobra", 193, Rarity.RARE, mage.cards.l.LotusCobra.class));
|
||||
cards.add(new SetCardInfo("Lullmage's Domination", 66, Rarity.UNCOMMON, mage.cards.l.LullmagesDomination.class));
|
||||
|
@ -402,6 +403,7 @@ public final class ZendikarRising extends ExpansionSet {
|
|||
cards.add(new SetCardInfo("Vastwood Fortification", 216, Rarity.UNCOMMON, mage.cards.v.VastwoodFortification.class));
|
||||
cards.add(new SetCardInfo("Vastwood Surge", 217, Rarity.UNCOMMON, mage.cards.v.VastwoodSurge.class));
|
||||
cards.add(new SetCardInfo("Vastwood Thicket", 216, Rarity.UNCOMMON, mage.cards.v.VastwoodThicket.class));
|
||||
cards.add(new SetCardInfo("Verazol, the Split Current", 239, Rarity.RARE, mage.cards.v.VerazolTheSplitCurrent.class));
|
||||
cards.add(new SetCardInfo("Veteran Adventurer", 218, Rarity.UNCOMMON, mage.cards.v.VeteranAdventurer.class));
|
||||
cards.add(new SetCardInfo("Vine Gecko", 219, Rarity.UNCOMMON, mage.cards.v.VineGecko.class));
|
||||
cards.add(new SetCardInfo("Wayward Guide-Beast", 176, Rarity.RARE, mage.cards.w.WaywardGuideBeast.class));
|
||||
|
|
|
@ -0,0 +1,263 @@
|
|||
package org.mage.test.cards.copy;
|
||||
|
||||
import mage.abilities.common.SpellCastControllerTriggeredAbility;
|
||||
import mage.abilities.effects.common.CopyTargetSpellEffect;
|
||||
import mage.abilities.keyword.FlyingAbility;
|
||||
import mage.constants.PhaseStep;
|
||||
import mage.constants.Zone;
|
||||
import mage.filter.Filter;
|
||||
import mage.filter.StaticFilters;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.mage.test.serverside.base.CardTestPlayerBase;
|
||||
|
||||
/**
|
||||
* @author TheElk801
|
||||
*/
|
||||
public class CopyPermanentSpellTest extends CardTestPlayerBase {
|
||||
|
||||
private void makeTester() {
|
||||
addCustomCardWithAbility(
|
||||
"Forker", playerA,
|
||||
new SpellCastControllerTriggeredAbility(
|
||||
new CopyTargetSpellEffect(true),
|
||||
StaticFilters.FILTER_SPELL, false, true
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSimpleToken() {
|
||||
makeTester();
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Forest", 2);
|
||||
addCard(Zone.HAND, playerA, "Grizzly Bears");
|
||||
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Grizzly Bears");
|
||||
|
||||
setStopAt(1, PhaseStep.END_TURN);
|
||||
execute();
|
||||
assertAllCommandsUsed();
|
||||
|
||||
assertPermanentCount(playerA, "Grizzly Bears", 2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAuraToken() {
|
||||
makeTester();
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Plains", 1);
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Grizzly Bears");
|
||||
addCard(Zone.HAND, playerA, "Holy Strength");
|
||||
|
||||
setChoice(playerA, "No");
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Holy Strength", "Grizzly Bears");
|
||||
|
||||
setStopAt(1, PhaseStep.END_TURN);
|
||||
execute();
|
||||
assertAllCommandsUsed();
|
||||
|
||||
assertPermanentCount(playerA, "Grizzly Bears", 1);
|
||||
assertPermanentCount(playerA, "Holy Strength", 2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAuraTokenRedirect() {
|
||||
makeTester();
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Swamp", 1);
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Centaur Courser");
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Hill Giant");
|
||||
addCard(Zone.HAND, playerA, "Dead Weight");
|
||||
|
||||
setChoice(playerA, "Yes");
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Dead Weight", "Centaur Courser");
|
||||
|
||||
setStopAt(1, PhaseStep.END_TURN);
|
||||
execute();
|
||||
assertAllCommandsUsed();
|
||||
|
||||
assertPermanentCount(playerA, "Centaur Courser", 1);
|
||||
assertPowerToughness(playerA, "Centaur Courser", 1, 1);
|
||||
assertPermanentCount(playerA, "Hill Giant", 1);
|
||||
assertPowerToughness(playerA, "Hill Giant", 1, 1);
|
||||
assertPermanentCount(playerA, "Dead Weight", 2);
|
||||
}
|
||||
|
||||
@Ignore // currently fails
|
||||
@Test
|
||||
public void testKickerTrigger() {
|
||||
makeTester();
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 2);
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Grizzly Bears");
|
||||
addCard(Zone.HAND, playerA, "Goblin Bushwhacker");
|
||||
|
||||
setChoice(playerA, "Yes");
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Goblin Bushwhacker");
|
||||
|
||||
setStopAt(1, PhaseStep.END_TURN);
|
||||
execute();
|
||||
assertAllCommandsUsed();
|
||||
|
||||
assertPermanentCount(playerA, "Goblin Bushwhacker", 2);
|
||||
assertPowerToughness(playerA, "Grizzly Bears", 4, 2);
|
||||
}
|
||||
|
||||
@Ignore // currently fails
|
||||
@Test
|
||||
public void testKickerReplacement() {
|
||||
makeTester();
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Island", 5);
|
||||
addCard(Zone.HAND, playerA, "Aether Figment");
|
||||
|
||||
setChoice(playerA, "Yes");
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Aether Figment");
|
||||
|
||||
setStopAt(1, PhaseStep.END_TURN);
|
||||
execute();
|
||||
assertAllCommandsUsed();
|
||||
|
||||
assertPermanentCount(playerA, "Aether Figment", 2);
|
||||
assertPowerToughness(playerA, "Aether Figment", 3, 3, Filter.ComparisonScope.All);
|
||||
}
|
||||
|
||||
@Ignore // currently fails
|
||||
@Test
|
||||
public void testSurgeTrigger() {
|
||||
makeTester();
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3);
|
||||
addCard(Zone.HAND, playerA, "Memnite");
|
||||
addCard(Zone.HAND, playerA, "Reckless Bushwhacker");
|
||||
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Memnite");
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Reckless Bushwhacker with surge");
|
||||
|
||||
setStopAt(1, PhaseStep.END_TURN);
|
||||
execute();
|
||||
assertAllCommandsUsed();
|
||||
|
||||
assertPermanentCount(playerA, "Reckless Bushwhacker", 2);
|
||||
assertPowerToughness(playerA, "Memnite", 3, 1, Filter.ComparisonScope.All);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBestow() {
|
||||
makeTester();
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Island", 5);
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Grizzly Bears");
|
||||
addCard(Zone.HAND, playerA, "Nimbus Naiad");
|
||||
|
||||
setChoice(playerA, "No");
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Nimbus Naiad using bestow", "Grizzly Bears");
|
||||
|
||||
setStopAt(1, PhaseStep.END_TURN);
|
||||
execute();
|
||||
assertAllCommandsUsed();
|
||||
|
||||
assertPermanentCount(playerA, "Grizzly Bears", 1);
|
||||
assertPowerToughness(playerA, "Grizzly Bears", 6, 6);
|
||||
assertPermanentCount(playerA, "Nimbus Naiad", 2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBestowRedirect() {
|
||||
makeTester();
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Island", 5);
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Grizzly Bears");
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Silvercoat Lion");
|
||||
addCard(Zone.HAND, playerA, "Nimbus Naiad");
|
||||
|
||||
setChoice(playerA, "Yes");
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Nimbus Naiad using bestow", "Grizzly Bears");
|
||||
|
||||
setStopAt(1, PhaseStep.END_TURN);
|
||||
execute();
|
||||
assertAllCommandsUsed();
|
||||
|
||||
assertPermanentCount(playerA, "Grizzly Bears", 1);
|
||||
assertPowerToughness(playerA, "Grizzly Bears", 4, 4);
|
||||
assertAbility(playerA, "Grizzly Bears", FlyingAbility.getInstance(), true);
|
||||
|
||||
assertPermanentCount(playerA, "Silvercoat Lion", 1);
|
||||
assertPowerToughness(playerA, "Silvercoat Lion", 4, 4);
|
||||
assertAbility(playerA, "Silvercoat Lion", FlyingAbility.getInstance(), true);
|
||||
|
||||
assertPermanentCount(playerA, "Nimbus Naiad", 2);
|
||||
}
|
||||
|
||||
@Ignore // currently fails
|
||||
@Test
|
||||
public void testBestowFallOff() {
|
||||
makeTester();
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Underground Sea", 8);
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Grizzly Bears");
|
||||
addCard(Zone.HAND, playerA, "Murder", 1);
|
||||
addCard(Zone.HAND, playerA, "Nimbus Naiad");
|
||||
|
||||
setChoice(playerA, "Yes");
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Nimbus Naiad using bestow", "Grizzly Bears");
|
||||
|
||||
setChoice(playerA, "No");
|
||||
castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Murder", "Grizzly Bears");
|
||||
|
||||
setStopAt(1, PhaseStep.END_TURN);
|
||||
execute();
|
||||
assertAllCommandsUsed();
|
||||
|
||||
assertPermanentCount(playerA, "Grizzly Bears", 0);
|
||||
assertGraveyardCount(playerA, "Grizzly Bears", 1);
|
||||
|
||||
assertPermanentCount(playerA, "Nimbus Naiad", 2);
|
||||
}
|
||||
|
||||
@Ignore // currently fails
|
||||
@Test
|
||||
public void testBestowRedirectFallOff() {
|
||||
makeTester();
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Underground Sea", 8);
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Grizzly Bears");
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Silvercoat Lion");
|
||||
addCard(Zone.HAND, playerA, "Murder", 1);
|
||||
addCard(Zone.HAND, playerA, "Nimbus Naiad");
|
||||
|
||||
setChoice(playerA, "Yes");
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Nimbus Naiad using bestow", "Grizzly Bears");
|
||||
|
||||
setChoice(playerA, "Yes");
|
||||
castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Murder", "Silvercoat Lion");
|
||||
|
||||
setStopAt(1, PhaseStep.END_TURN);
|
||||
execute();
|
||||
assertAllCommandsUsed();
|
||||
|
||||
assertPermanentCount(playerA, "Grizzly Bears", 0);
|
||||
assertGraveyardCount(playerA, "Grizzly Bears", 1);
|
||||
assertPermanentCount(playerA, "Silvercoat Lion", 0);
|
||||
assertGraveyardCount(playerA, "Silvercoat Lion", 1);
|
||||
|
||||
assertPermanentCount(playerA, "Nimbus Naiad", 2);
|
||||
}
|
||||
|
||||
@Ignore // currently fails
|
||||
@Test
|
||||
public void testBestowIllegalTarget() {
|
||||
makeTester();
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Underground Sea", 8);
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Grizzly Bears");
|
||||
addCard(Zone.HAND, playerA, "Murder", 1);
|
||||
addCard(Zone.HAND, playerA, "Nimbus Naiad");
|
||||
|
||||
setChoice(playerA, "Yes");
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Nimbus Naiad using bestow", "Grizzly Bears");
|
||||
|
||||
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, true);
|
||||
setChoice(playerA, "No");
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Murder", "Grizzly Bears");
|
||||
|
||||
setStopAt(1, PhaseStep.END_TURN);
|
||||
execute();
|
||||
assertAllCommandsUsed();
|
||||
|
||||
assertPermanentCount(playerA, "Grizzly Bears", 0);
|
||||
assertGraveyardCount(playerA, "Grizzly Bears", 1);
|
||||
|
||||
assertPermanentCount(playerA, "Nimbus Naiad", 2);
|
||||
}
|
||||
}
|
|
@ -19,6 +19,7 @@ public class CopyTargetSpellEffect extends OneShotEffect {
|
|||
private final boolean useController;
|
||||
private final boolean useLKI;
|
||||
private String copyThatSpellName = "that spell";
|
||||
private final boolean chooseTargets;
|
||||
|
||||
public CopyTargetSpellEffect() {
|
||||
this(false);
|
||||
|
@ -29,9 +30,14 @@ public class CopyTargetSpellEffect extends OneShotEffect {
|
|||
}
|
||||
|
||||
public CopyTargetSpellEffect(boolean useController, boolean useLKI) {
|
||||
this(useController, useLKI, true);
|
||||
}
|
||||
|
||||
public CopyTargetSpellEffect(boolean useController, boolean useLKI, boolean chooseTargets) {
|
||||
super(Outcome.Copy);
|
||||
this.useController = useController;
|
||||
this.useLKI = useLKI;
|
||||
this.chooseTargets = chooseTargets;
|
||||
}
|
||||
|
||||
public CopyTargetSpellEffect(final CopyTargetSpellEffect effect) {
|
||||
|
@ -39,6 +45,7 @@ public class CopyTargetSpellEffect extends OneShotEffect {
|
|||
this.useLKI = effect.useLKI;
|
||||
this.useController = effect.useController;
|
||||
this.copyThatSpellName = effect.copyThatSpellName;
|
||||
this.chooseTargets = effect.chooseTargets;
|
||||
}
|
||||
|
||||
public Effect withSpellName(String copyThatSpellName) {
|
||||
|
@ -58,7 +65,7 @@ public class CopyTargetSpellEffect extends OneShotEffect {
|
|||
spell = (Spell) game.getLastKnownInformation(targetPointer.getFirst(game, source), Zone.STACK);
|
||||
}
|
||||
if (spell != null) {
|
||||
StackObject newStackObject = spell.createCopyOnStack(game, source, useController ? spell.getControllerId() : source.getControllerId(), true);
|
||||
StackObject newStackObject = spell.createCopyOnStack(game, source, useController ? spell.getControllerId() : source.getControllerId(), chooseTargets);
|
||||
Player player = game.getPlayer(source.getControllerId());
|
||||
if (player != null && newStackObject instanceof Spell) {
|
||||
String activateMessage = ((Spell) newStackObject).getActivatedMessage(game);
|
||||
|
@ -91,8 +98,9 @@ public class CopyTargetSpellEffect extends OneShotEffect {
|
|||
} else {
|
||||
sb.append(copyThatSpellName);
|
||||
}
|
||||
sb.append(". You may choose new targets for the copy");
|
||||
|
||||
if (chooseTargets) {
|
||||
sb.append(". You may choose new targets for the copy");
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -247,7 +247,7 @@ public class KickerAbility extends StaticAbility implements OptionalAdditionalSo
|
|||
sb.append(' ').append(remarkText);
|
||||
}
|
||||
|
||||
return sb.toString().replace(" .",".");
|
||||
return sb.toString().replace(" .", ".");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -532,7 +532,6 @@ public abstract class GameImpl implements Game, Serializable {
|
|||
if (card == null) {
|
||||
card = (Card) state.getValue(GameState.COPIED_FROM_CARD_KEY + cardId.toString());
|
||||
}
|
||||
|
||||
return card;
|
||||
}
|
||||
|
||||
|
|
|
@ -5,11 +5,11 @@ import mage.MageObject;
|
|||
import mage.abilities.Ability;
|
||||
import mage.cards.Card;
|
||||
import mage.game.Game;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author ArcadeMode
|
||||
*/
|
||||
public interface Token extends MageObject {
|
||||
|
@ -33,6 +33,8 @@ public interface Token extends MageObject {
|
|||
|
||||
boolean putOntoBattlefield(int amount, Game game, UUID sourceId, UUID controllerId, boolean tapped, boolean attacking, UUID attackedPlayer);
|
||||
|
||||
boolean putOntoBattlefield(int amount, Game game, UUID sourceId, UUID controllerId, boolean tapped, boolean attacking, UUID attackedPlayer, boolean created);
|
||||
|
||||
void setPower(int power);
|
||||
|
||||
void setToughness(int toughness);
|
||||
|
|
|
@ -1,9 +1,5 @@
|
|||
package mage.game.permanent.token;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.UUID;
|
||||
import mage.MageObject;
|
||||
import mage.MageObjectImpl;
|
||||
import mage.abilities.Ability;
|
||||
|
@ -18,6 +14,11 @@ import mage.game.permanent.PermanentToken;
|
|||
import mage.players.Player;
|
||||
import mage.util.RandomUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.UUID;
|
||||
|
||||
public abstract class TokenImpl extends MageObjectImpl implements Token {
|
||||
|
||||
protected String description;
|
||||
|
@ -158,6 +159,11 @@ public abstract class TokenImpl extends MageObjectImpl implements Token {
|
|||
|
||||
@Override
|
||||
public boolean putOntoBattlefield(int amount, Game game, UUID sourceId, UUID controllerId, boolean tapped, boolean attacking, UUID attackedPlayer) {
|
||||
return putOntoBattlefield(amount, game, sourceId, controllerId, tapped, attacking, attackedPlayer, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean putOntoBattlefield(int amount, Game game, UUID sourceId, UUID controllerId, boolean tapped, boolean attacking, UUID attackedPlayer, boolean created) {
|
||||
Player controller = game.getPlayer(controllerId);
|
||||
if (controller == null) {
|
||||
return false;
|
||||
|
@ -168,14 +174,14 @@ public abstract class TokenImpl extends MageObjectImpl implements Token {
|
|||
lastAddedTokenIds.clear();
|
||||
|
||||
CreateTokenEvent event = new CreateTokenEvent(sourceId, controllerId, amount, this);
|
||||
if (!game.replaceEvent(event)) {
|
||||
putOntoBattlefieldHelper(event, game, tapped, attacking, attackedPlayer);
|
||||
if (!created || !game.replaceEvent(event)) {
|
||||
putOntoBattlefieldHelper(event, game, tapped, attacking, attackedPlayer, created);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void putOntoBattlefieldHelper(CreateTokenEvent event, Game game, boolean tapped, boolean attacking, UUID attackedPlayer) {
|
||||
private static void putOntoBattlefieldHelper(CreateTokenEvent event, Game game, boolean tapped, boolean attacking, UUID attackedPlayer, boolean created) {
|
||||
Player controller = game.getPlayer(event.getPlayerId());
|
||||
Token token = event.getToken();
|
||||
int amount = event.getAmount();
|
||||
|
@ -212,14 +218,16 @@ public abstract class TokenImpl extends MageObjectImpl implements Token {
|
|||
((TokenImpl) token).lastAddedTokenId = permanent.getId();
|
||||
}
|
||||
game.addSimultaneousEvent(new ZoneChangeEvent(permanent, permanent.getControllerId(), Zone.OUTSIDE, Zone.BATTLEFIELD));
|
||||
if (permanent instanceof PermanentToken) {
|
||||
if (permanent instanceof PermanentToken && created) {
|
||||
game.addSimultaneousEvent(new CreatedTokenEvent(event.getSourceId(), (PermanentToken) permanent));
|
||||
}
|
||||
if (attacking && game.getCombat() != null && game.getActivePlayerId().equals(permanent.getControllerId())) {
|
||||
game.getCombat().addAttackingCreature(permanent.getId(), game, attackedPlayer);
|
||||
}
|
||||
if (!game.isSimulation()) {
|
||||
if (created) {
|
||||
game.informPlayers(controller.getLogName() + " creates a " + permanent.getLogName() + " token");
|
||||
} else {
|
||||
game.informPlayers(permanent.getLogName() + " enters the battlefield as a token under " + controller.getLogName() + "'s control'");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -9,10 +9,10 @@ import mage.abilities.Ability;
|
|||
import mage.abilities.Mode;
|
||||
import mage.abilities.SpellAbility;
|
||||
import mage.abilities.costs.AlternativeSourceCosts;
|
||||
import mage.abilities.costs.Cost;
|
||||
import mage.abilities.costs.mana.ActivationManaAbilityStep;
|
||||
import mage.abilities.costs.mana.ManaCost;
|
||||
import mage.abilities.costs.mana.ManaCosts;
|
||||
import mage.abilities.keyword.BestowAbility;
|
||||
import mage.abilities.keyword.MorphAbility;
|
||||
import mage.abilities.text.TextPart;
|
||||
import mage.cards.*;
|
||||
|
@ -27,7 +27,9 @@ import mage.game.events.GameEvent.EventType;
|
|||
import mage.game.events.ZoneChangeEvent;
|
||||
import mage.game.permanent.Permanent;
|
||||
import mage.game.permanent.PermanentCard;
|
||||
import mage.game.permanent.token.EmptyToken;
|
||||
import mage.players.Player;
|
||||
import mage.util.CardUtil;
|
||||
import mage.util.GameLog;
|
||||
import mage.util.SubTypeList;
|
||||
|
||||
|
@ -248,11 +250,25 @@ public class Spell extends StackObjImpl implements Card {
|
|||
card.getSubtype(game).add(SubType.AURA);
|
||||
}
|
||||
}
|
||||
if (controller.moveCards(card, Zone.BATTLEFIELD, ability, game, false, faceDown, false, null)) {
|
||||
UUID permId = null;
|
||||
boolean flag = false;
|
||||
if (!isCopy()) {
|
||||
permId = card.getId();
|
||||
flag = controller.moveCards(card, Zone.BATTLEFIELD, ability, game, false, faceDown, false, null);
|
||||
} else {
|
||||
EmptyToken token = new EmptyToken();
|
||||
CardUtil.copyTo(token).from(card);
|
||||
// The token that a resolving copy of a spell becomes isn’t said to have been “created.” (2020-09-25)
|
||||
if (token.putOntoBattlefield(1, game, ability.getSourceId(), getControllerId(), false, false, null, false)) {
|
||||
permId = token.getLastAddedToken();
|
||||
flag = true;
|
||||
}
|
||||
}
|
||||
if (flag) {
|
||||
if (bestow) {
|
||||
// card will be copied during putOntoBattlefield, so the card of CardPermanent has to be changed
|
||||
// TODO: Find a better way to prevent bestow creatures from being effected by creature affecting abilities
|
||||
Permanent permanent = game.getPermanent(card.getId());
|
||||
Permanent permanent = game.getPermanent(permId);
|
||||
if (permanent instanceof PermanentCard) {
|
||||
permanent.setSpellAbility(ability); // otherwise spell ability without bestow will be set
|
||||
if (!card.getCardType().contains(CardType.CREATURE)) {
|
||||
|
@ -261,6 +277,20 @@ public class Spell extends StackObjImpl implements Card {
|
|||
card.getSubtype(game).remove(SubType.AURA);
|
||||
}
|
||||
}
|
||||
if (isCopy()) {
|
||||
Permanent token = game.getPermanent(permId);
|
||||
if (token == null) {
|
||||
return false;
|
||||
}
|
||||
for (Ability ability2 : token.getAbilities()) {
|
||||
if (!bestow || ability2 instanceof BestowAbility) {
|
||||
ability2.getTargets().get(0).add(ability.getFirstTarget(), game);
|
||||
ability2.getEffects().get(0).apply(game, ability2);
|
||||
return ability2.resolve(game);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return ability.resolve(game);
|
||||
}
|
||||
if (bestow) {
|
||||
|
@ -287,24 +317,27 @@ public class Spell extends StackObjImpl implements Card {
|
|||
counter(null, game);
|
||||
return false;
|
||||
}
|
||||
} else if (isCopy()) {
|
||||
EmptyToken token = new EmptyToken();
|
||||
CardUtil.copyTo(token).from(card);
|
||||
// The token that a resolving copy of a spell becomes isn’t said to have been “created.” (2020-09-25)
|
||||
token.putOntoBattlefield(1, game, ability.getSourceId(), getControllerId(), false, false, null, false);
|
||||
return true;
|
||||
} else {
|
||||
return controller.moveCards(card, Zone.BATTLEFIELD, ability, game, false, faceDown, false, null);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean hasTargets(SpellAbility spellAbility, Game game) {
|
||||
if (spellAbility.getModes().getSelectedModes().size() > 1) {
|
||||
for (UUID modeId : spellAbility.getModes().getSelectedModes()) {
|
||||
Mode mode = spellAbility.getModes().get(modeId);
|
||||
if (!mode.getTargets().isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
if (spellAbility.getModes().getSelectedModes().size() < 2) {
|
||||
return !spellAbility.getTargets().isEmpty();
|
||||
}
|
||||
for (UUID modeId : spellAbility.getModes().getSelectedModes()) {
|
||||
if (!spellAbility.getModes().get(modeId).getTargets().isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
Loading…
Reference in a new issue