diff --git a/Mage.Sets/src/mage/cards/a/AnafenzaTheForemost.java b/Mage.Sets/src/mage/cards/a/AnafenzaTheForemost.java index 96e8487a47e..c0aed36a977 100644 --- a/Mage.Sets/src/mage/cards/a/AnafenzaTheForemost.java +++ b/Mage.Sets/src/mage/cards/a/AnafenzaTheForemost.java @@ -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 { diff --git a/Mage.Sets/src/mage/cards/l/LithoformEngine.java b/Mage.Sets/src/mage/cards/l/LithoformEngine.java new file mode 100644 index 00000000000..eefc40e7510 --- /dev/null +++ b/Mage.Sets/src/mage/cards/l/LithoformEngine.java @@ -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 { + 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"; + } +} diff --git a/Mage.Sets/src/mage/cards/v/VerazolTheSplitCurrent.java b/Mage.Sets/src/mage/cards/v/VerazolTheSplitCurrent.java new file mode 100644 index 00000000000..c4006af2957 --- /dev/null +++ b/Mage.Sets/src/mage/cards/v/VerazolTheSplitCurrent.java @@ -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); + } +} diff --git a/Mage.Sets/src/mage/sets/ZendikarRising.java b/Mage.Sets/src/mage/sets/ZendikarRising.java index 634a76efa8f..0a35c08d291 100644 --- a/Mage.Sets/src/mage/sets/ZendikarRising.java +++ b/Mage.Sets/src/mage/sets/ZendikarRising.java @@ -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)); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/copy/CopyPermanentSpellTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/copy/CopyPermanentSpellTest.java new file mode 100644 index 00000000000..b00a321bbda --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/copy/CopyPermanentSpellTest.java @@ -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); + } +} diff --git a/Mage/src/main/java/mage/abilities/effects/common/CopyTargetSpellEffect.java b/Mage/src/main/java/mage/abilities/effects/common/CopyTargetSpellEffect.java index c3a4879b4d5..4269829f4e7 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/CopyTargetSpellEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/CopyTargetSpellEffect.java @@ -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(); } } diff --git a/Mage/src/main/java/mage/abilities/keyword/KickerAbility.java b/Mage/src/main/java/mage/abilities/keyword/KickerAbility.java index 5750c33033c..1c004e70647 100644 --- a/Mage/src/main/java/mage/abilities/keyword/KickerAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/KickerAbility.java @@ -247,7 +247,7 @@ public class KickerAbility extends StaticAbility implements OptionalAdditionalSo sb.append(' ').append(remarkText); } - return sb.toString().replace(" .","."); + return sb.toString().replace(" .", "."); } @Override diff --git a/Mage/src/main/java/mage/game/GameImpl.java b/Mage/src/main/java/mage/game/GameImpl.java index a80f1ab61e7..2486fcfa803 100644 --- a/Mage/src/main/java/mage/game/GameImpl.java +++ b/Mage/src/main/java/mage/game/GameImpl.java @@ -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; } diff --git a/Mage/src/main/java/mage/game/permanent/token/Token.java b/Mage/src/main/java/mage/game/permanent/token/Token.java index 2663b290d76..7cc1fbaba62 100644 --- a/Mage/src/main/java/mage/game/permanent/token/Token.java +++ b/Mage/src/main/java/mage/game/permanent/token/Token.java @@ -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); diff --git a/Mage/src/main/java/mage/game/permanent/token/TokenImpl.java b/Mage/src/main/java/mage/game/permanent/token/TokenImpl.java index 0ba43809a5f..8732c5d6071 100644 --- a/Mage/src/main/java/mage/game/permanent/token/TokenImpl.java +++ b/Mage/src/main/java/mage/game/permanent/token/TokenImpl.java @@ -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'"); } } diff --git a/Mage/src/main/java/mage/game/stack/Spell.java b/Mage/src/main/java/mage/game/stack/Spell.java index 969c5cbe67f..d8342f952c0 100644 --- a/Mage/src/main/java/mage/game/stack/Spell.java +++ b/Mage/src/main/java/mage/game/stack/Spell.java @@ -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; } /**