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:
Evan Kranzler 2020-09-27 10:54:44 -04:00 committed by GitHub
parent f32597b164
commit 7647a3d8f0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 531 additions and 29 deletions

View file

@ -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 {

View 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";
}
}

View 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);
}
}

View file

@ -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));

View file

@ -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);
}
}

View file

@ -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();
}
}

View file

@ -247,7 +247,7 @@ public class KickerAbility extends StaticAbility implements OptionalAdditionalSo
sb.append(' ').append(remarkText);
}
return sb.toString().replace(" .",".");
return sb.toString().replace(" .", ".");
}
@Override

View file

@ -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;
}

View file

@ -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);

View file

@ -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'");
}
}

View file

@ -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 isnt 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 isnt 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;
}
/**