diff --git a/Mage.Sets/src/mage/cards/e/EchoMage.java b/Mage.Sets/src/mage/cards/e/EchoMage.java index 28acd87fd5b..e5a635d78c0 100644 --- a/Mage.Sets/src/mage/cards/e/EchoMage.java +++ b/Mage.Sets/src/mage/cards/e/EchoMage.java @@ -1,7 +1,6 @@ package mage.cards.e; -import java.util.UUID; import mage.MageInt; import mage.abilities.Abilities; import mage.abilities.AbilitiesImpl; @@ -16,22 +15,23 @@ import mage.abilities.keyword.LevelerCardBuilder; import mage.cards.CardSetInfo; import mage.cards.LevelerCard; import mage.constants.CardType; -import mage.constants.SubType; import mage.constants.Outcome; +import mage.constants.SubType; import mage.constants.Zone; import mage.filter.StaticFilters; import mage.game.Game; import mage.game.stack.Spell; import mage.target.TargetSpell; +import java.util.UUID; + /** - * * @author North */ public final class EchoMage extends LevelerCard { public EchoMage(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{1}{U}{U}"); + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{U}{U}"); this.subtype.add(SubType.HUMAN); this.subtype.add(SubType.WIZARD); @@ -94,8 +94,7 @@ class EchoMageEffect extends OneShotEffect { public boolean apply(Game game, Ability source) { Spell spell = game.getStack().getSpell(targetPointer.getFirst(game, source)); if (spell != null) { - spell.createCopyOnStack(game, source, source.getControllerId(), true); - spell.createCopyOnStack(game, source, source.getControllerId(), true); + spell.createCopyOnStack(game, source, source.getControllerId(), true, 2); return true; } return false; diff --git a/Mage.Sets/src/mage/cards/f/FinaleOfPromise.java b/Mage.Sets/src/mage/cards/f/FinaleOfPromise.java index 9ebf263dde1..444affb50c6 100644 --- a/Mage.Sets/src/mage/cards/f/FinaleOfPromise.java +++ b/Mage.Sets/src/mage/cards/f/FinaleOfPromise.java @@ -125,8 +125,8 @@ class FinaleOfPromiseEffect extends OneShotEffect { .filter(Objects::nonNull) .map(Card::getName) .collect(Collectors.joining(" -> ")); - if (!controller.chooseUse(Outcome.Detriment, "Cast cards by choose order: " - + cardsOrder + "?", "Finale of Promise", + if (!controller.chooseUse(Outcome.Detriment, "Cast cards by choose order: " + + cardsOrder + "?", "Finale of Promise", "Use that order", "Reverse", source, game)) { Collections.reverse(cardsToCast); } @@ -154,8 +154,7 @@ class FinaleOfPromiseEffect extends OneShotEffect { if (card != null) { Spell spell = game.getStack().getSpell(card.getId()); if (spell != null) { - spell.createCopyOnStack(game, source, controller.getId(), true); - spell.createCopyOnStack(game, source, controller.getId(), true); + spell.createCopyOnStack(game, source, controller.getId(), true, 2); game.informPlayers(controller.getLogName() + " copies " + spell.getName() + " twice."); } } diff --git a/Mage.Sets/src/mage/cards/i/IncreasingVengeance.java b/Mage.Sets/src/mage/cards/i/IncreasingVengeance.java index 9c740c9f27e..3ad6985b68e 100644 --- a/Mage.Sets/src/mage/cards/i/IncreasingVengeance.java +++ b/Mage.Sets/src/mage/cards/i/IncreasingVengeance.java @@ -1,7 +1,6 @@ package mage.cards.i; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.effects.OneShotEffect; @@ -18,8 +17,9 @@ import mage.players.Player; import mage.target.Target; import mage.target.TargetSpell; +import java.util.UUID; + /** - * * @author BetaSteward */ public final class IncreasingVengeance extends CardImpl { @@ -34,7 +34,7 @@ public final class IncreasingVengeance extends CardImpl { } public IncreasingVengeance(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.INSTANT},"{R}{R}"); + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{R}{R}"); // Copy target instant or sorcery spell you control. If this spell was cast from a graveyard, copy that spell twice instead. You may choose new targets for the copies. this.getSpellAbility().addEffect(new IncreasingVengeanceEffect()); @@ -69,26 +69,23 @@ class IncreasingVengeanceEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); - if (controller != null) { - Spell spell = game.getStack().getSpell(targetPointer.getFirst(game, source)); - if (spell != null) { - StackObject stackObjectCopy = spell.createCopyOnStack(game, source, source.getControllerId(), true); - if (stackObjectCopy instanceof Spell) { - game.informPlayers(controller.getLogName() + ((Spell) stackObjectCopy).getActivatedMessage(game)); - } - Spell sourceSpell = (Spell) game.getStack().getStackObject(source.getSourceId()); - if (sourceSpell != null) { - if (sourceSpell.getFromZone() == Zone.GRAVEYARD) { - stackObjectCopy = spell.createCopyOnStack(game, source, source.getControllerId(), true); - if (stackObjectCopy instanceof Spell) { - game.informPlayers(new StringBuilder(controller.getLogName()).append(((Spell) stackObjectCopy).getActivatedMessage(game)).toString()); - } - } - } - return true; - } + if (controller == null) { + return false; } - return false; + Spell spell = game.getStack().getSpell(targetPointer.getFirst(game, source)); + if (spell == null) { + return false; + } + Spell sourceSpell = (Spell) game.getSpell(source.getSourceId()); + int copies = 1; + if (sourceSpell != null && sourceSpell.getFromZone() == Zone.GRAVEYARD) { + copies++; + } + StackObject stackObjectCopy = spell.createCopyOnStack(game, source, source.getControllerId(), true, copies); + if (stackObjectCopy instanceof Spell) { + game.informPlayers(controller.getLogName() + ((Spell) stackObjectCopy).getActivatedMessage(game)); + } + return true; } @Override diff --git a/Mage.Sets/src/mage/cards/r/RepeatedReverberation.java b/Mage.Sets/src/mage/cards/r/RepeatedReverberation.java index e017510a8ca..b6e30492ec3 100644 --- a/Mage.Sets/src/mage/cards/r/RepeatedReverberation.java +++ b/Mage.Sets/src/mage/cards/r/RepeatedReverberation.java @@ -122,8 +122,7 @@ class RepeatedReverberationEffect extends OneShotEffect { if (controller == null || sourcePermanent == null) { return false; } - stackAbility.createCopyOnStack(game, source, source.getControllerId(), true); - stackAbility.createCopyOnStack(game, source, source.getControllerId(), true); + stackAbility.createCopyOnStack(game, source, source.getControllerId(), true, 2); game.informPlayers(sourcePermanent.getIdName() + ": " + controller.getLogName() + " copied loyalty ability twice"); return true; } diff --git a/Mage.Sets/src/mage/cards/t/ThousandYearStorm.java b/Mage.Sets/src/mage/cards/t/ThousandYearStorm.java index f520069d780..1fd0f08a6ea 100644 --- a/Mage.Sets/src/mage/cards/t/ThousandYearStorm.java +++ b/Mage.Sets/src/mage/cards/t/ThousandYearStorm.java @@ -22,7 +22,6 @@ import mage.watchers.Watcher; import java.util.HashMap; import java.util.Map; -import java.util.Map.Entry; import java.util.UUID; /** @@ -114,9 +113,7 @@ class ThousandYearStormEffect extends OneShotEffect { numberOfCopies = (int) game.getState().getValue(stateSearchId); } if (numberOfCopies > 0) { - for (int i = 0; i < numberOfCopies; i++) { - spell.createCopyOnStack(game, source, source.getControllerId(), true); - } + spell.createCopyOnStack(game, source, source.getControllerId(), true, numberOfCopies); } return true; } diff --git a/Mage.Sets/src/mage/cards/t/TwinningStaff.java b/Mage.Sets/src/mage/cards/t/TwinningStaff.java new file mode 100644 index 00000000000..75ccd4506d6 --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TwinningStaff.java @@ -0,0 +1,92 @@ +package mage.cards.t; + +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.ReplacementEffectImpl; +import mage.abilities.effects.common.CopyTargetSpellEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.TargetController; +import mage.filter.FilterSpell; +import mage.filter.common.FilterInstantOrSorcerySpell; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.target.TargetSpell; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class TwinningStaff extends CardImpl { + + private static final FilterSpell filter = new FilterInstantOrSorcerySpell("instant or sorcery spell you control"); + + static { + filter.add(TargetController.YOU.getControllerPredicate()); + } + + public TwinningStaff(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{3}"); + + // If you would copy a spell one or more times, instead copy it that many times plus an additional time. You may choose new targets for the additional copy. + this.addAbility(new SimpleStaticAbility(new TwinningStaffEffect())); + + // {7}, {T}: Copy target instant or sorcery spell you control. You may choose new targets for the copy. + Ability ability = new SimpleActivatedAbility(new CopyTargetSpellEffect(), new GenericManaCost(7)); + ability.addCost(new TapSourceCost()); + ability.addTarget(new TargetSpell(filter)); + this.addAbility(ability); + } + + private TwinningStaff(final TwinningStaff card) { + super(card); + } + + @Override + public TwinningStaff copy() { + return new TwinningStaff(this); + } +} + +class TwinningStaffEffect extends ReplacementEffectImpl { + + TwinningStaffEffect() { + super(Duration.WhileOnBattlefield, Outcome.Benefit); + staticText = "If you would copy a spell one or more times, " + + "instead copy it that many times plus an additional time. " + + "You may choose new targets for the additional copy."; + } + + private TwinningStaffEffect(final TwinningStaffEffect effect) { + super(effect); + } + + @Override + public boolean replaceEvent(GameEvent event, Ability source, Game game) { + event.setAmount(event.getAmount() + 1); + return false; + } + + @Override + public boolean checksEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.COPY_STACKOBJECT; + } + + @Override + public boolean applies(GameEvent event, Ability source, Game game) { + return event.getPlayerId().equals(source.getControllerId()) + && game.getSpellOrLKIStack(event.getTargetId()) != null; + } + + @Override + public TwinningStaffEffect copy() { + return new TwinningStaffEffect(this); + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/sets/Commander2020Edition.java b/Mage.Sets/src/mage/sets/Commander2020Edition.java index a1d13084864..b47a88c41b7 100644 --- a/Mage.Sets/src/mage/sets/Commander2020Edition.java +++ b/Mage.Sets/src/mage/sets/Commander2020Edition.java @@ -322,6 +322,7 @@ public final class Commander2020Edition extends ExpansionSet { cards.add(new SetCardInfo("Tribute to the Wild", 193, Rarity.UNCOMMON, mage.cards.t.TributeToTheWild.class)); cards.add(new SetCardInfo("Trygon Predator", 232, Rarity.UNCOMMON, mage.cards.t.TrygonPredator.class)); cards.add(new SetCardInfo("Trynn, Champion of Freedom", 1, Rarity.MYTHIC, mage.cards.t.TrynnChampionOfFreedom.class)); + cards.add(new SetCardInfo("Twinning Staff", 70, Rarity.RARE, mage.cards.t.TwinningStaff.class)); cards.add(new SetCardInfo("Ukkima, Stalking Shadow", 17, Rarity.MYTHIC, mage.cards.u.UkkimaStalkingShadow.class)); cards.add(new SetCardInfo("Unburial Rites", 139, Rarity.UNCOMMON, mage.cards.u.UnburialRites.class)); cards.add(new SetCardInfo("Unclaimed Territory", 320, Rarity.UNCOMMON, mage.cards.u.UnclaimedTerritory.class)); diff --git a/Mage/src/main/java/mage/abilities/keyword/CommanderStormAbility.java b/Mage/src/main/java/mage/abilities/keyword/CommanderStormAbility.java index 31a707aefa8..0ab7c537b6a 100644 --- a/Mage/src/main/java/mage/abilities/keyword/CommanderStormAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/CommanderStormAbility.java @@ -101,9 +101,7 @@ class CommanderStormEffect extends OneShotEffect { return false; } game.informPlayers(spell.getLogName() + " will be copied " + stormCount + " time" + (stormCount > 1 ? "s" : "")); - for (int i = 0; i < stormCount; i++) { - spell.createCopyOnStack(game, source, source.getControllerId(), true); - } + spell.createCopyOnStack(game, source, source.getControllerId(), true, stormCount); return true; } diff --git a/Mage/src/main/java/mage/abilities/keyword/GravestormAbility.java b/Mage/src/main/java/mage/abilities/keyword/GravestormAbility.java index ab4823303ef..16f3ef71e48 100644 --- a/Mage/src/main/java/mage/abilities/keyword/GravestormAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/GravestormAbility.java @@ -16,7 +16,6 @@ import mage.game.stack.StackObject; import mage.watchers.common.GravestormWatcher; /** - * * @author emerald000 */ public class GravestormAbility extends TriggeredAbilityImpl { @@ -75,7 +74,7 @@ class GravestormEffect extends OneShotEffect { MageObjectReference spellRef = (MageObjectReference) this.getValue("GravestormSpellRef"); if (spellRef != null) { GravestormWatcher watcher = game.getState().getWatcher(GravestormWatcher.class); - if(watcher != null) { + if (watcher != null) { int gravestormCount = watcher.getGravestormCount(); if (gravestormCount > 0) { Spell spell = (Spell) this.getValue("GravestormSpell"); @@ -83,9 +82,7 @@ class GravestormEffect extends OneShotEffect { if (!game.isSimulation()) { game.informPlayers("Gravestorm: " + spell.getName() + " will be copied " + gravestormCount + " time" + (gravestormCount > 1 ? "s" : "")); } - for (int i = 0; i < gravestormCount; i++) { - spell.createCopyOnStack(game, source, source.getControllerId(), true); - } + spell.createCopyOnStack(game, source, source.getControllerId(), true, gravestormCount); } } return true; diff --git a/Mage/src/main/java/mage/abilities/keyword/ReplicateAbility.java b/Mage/src/main/java/mage/abilities/keyword/ReplicateAbility.java index e4dd52dc791..8f7b1224ecb 100644 --- a/Mage/src/main/java/mage/abilities/keyword/ReplicateAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/ReplicateAbility.java @@ -218,12 +218,7 @@ class ReplicateCopyEffect extends OneShotEffect { } } // create the copies - for (int i = 0; i < replicateCount; i++) { - StackObject newStackObject = spell.createCopyOnStack(game, source, source.getControllerId(), true); - if (newStackObject instanceof Spell && !game.isSimulation()) { - game.informPlayers(controller.getLogName() + ((Spell) newStackObject).getActivatedMessage(game)); - } - } + StackObject newStackObject = spell.createCopyOnStack(game, source, source.getControllerId(), true, replicateCount); return true; } diff --git a/Mage/src/main/java/mage/abilities/keyword/StormAbility.java b/Mage/src/main/java/mage/abilities/keyword/StormAbility.java index 001c1add9b3..d7ea81e60c7 100644 --- a/Mage/src/main/java/mage/abilities/keyword/StormAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/StormAbility.java @@ -16,7 +16,6 @@ import mage.watchers.common.CastSpellLastTurnWatcher; import org.apache.log4j.Logger; /** - * * @author Plopman */ public class StormAbility extends TriggeredAbilityImpl { @@ -83,13 +82,11 @@ class StormEffect extends OneShotEffect { if (!game.isSimulation()) { game.informPlayers("Storm: " + spell.getLogName() + " will be copied " + stormCount + " time" + (stormCount > 1 ? "s" : "")); } - for (int i = 0; i < stormCount; i++) { - spell.createCopyOnStack(game, source, source.getControllerId(), true); - } + spell.createCopyOnStack(game, source, source.getControllerId(), true, stormCount); } } } else { - Logger.getLogger(StormEffect.class).fatal("CastSpellLastTurnWatcher not found. game = " +game.getGameType().toString()); + Logger.getLogger(StormEffect.class).fatal("CastSpellLastTurnWatcher not found. game = " + game.getGameType().toString()); } return true; } diff --git a/Mage/src/main/java/mage/game/events/GameEvent.java b/Mage/src/main/java/mage/game/events/GameEvent.java index a6e39ceeaa1..cad89d63a98 100644 --- a/Mage/src/main/java/mage/game/events/GameEvent.java +++ b/Mage/src/main/java/mage/game/events/GameEvent.java @@ -150,7 +150,7 @@ public class GameEvent implements Serializable { SPELL_CAST, ACTIVATE_ABILITY, ACTIVATED_ABILITY, TRIGGERED_ABILITY, - COPIED_STACKOBJECT, + COPY_STACKOBJECT,COPIED_STACKOBJECT, /* ADD_MANA targetId id of the ability that added the mana sourceId sourceId of the ability that added the mana diff --git a/Mage/src/main/java/mage/game/stack/Spell.java b/Mage/src/main/java/mage/game/stack/Spell.java index 3c29624c2e6..e9e262711a8 100644 --- a/Mage/src/main/java/mage/game/stack/Spell.java +++ b/Mage/src/main/java/mage/game/stack/Spell.java @@ -1,6 +1,5 @@ package mage.game.stack; -import java.util.*; import mage.MageInt; import mage.MageObject; import mage.Mana; @@ -32,6 +31,8 @@ import mage.players.Player; import mage.util.GameLog; import mage.util.SubTypeList; +import java.util.*; + /** * @author BetaSteward_at_googlemail.com */ @@ -992,13 +993,25 @@ public class Spell extends StackObjImpl implements Card { @Override public StackObject createCopyOnStack(Game game, Ability source, UUID newControllerId, boolean chooseNewTargets) { - Spell copy = this.copySpell(newControllerId); - game.getState().setZone(copy.getId(), Zone.STACK); // required for targeting ex: Nivmagus Elemental - game.getStack().push(copy); - if (chooseNewTargets) { - copy.chooseNewTargets(game, newControllerId); + return createCopyOnStack(game, source, newControllerId, chooseNewTargets, 1); + } + + @Override + public StackObject createCopyOnStack(Game game, Ability source, UUID newControllerId, boolean chooseNewTargets, int amount) { + Spell copy = null; + GameEvent gameEvent = GameEvent.getEvent(EventType.COPY_STACKOBJECT, this.getId(), source.getSourceId(), newControllerId, amount); + if (game.replaceEvent(gameEvent)) { + return null; + } + for (int i = 0; i < gameEvent.getAmount(); i++) { + copy = this.copySpell(newControllerId); + game.getState().setZone(copy.getId(), Zone.STACK); // required for targeting ex: Nivmagus Elemental + game.getStack().push(copy); + if (chooseNewTargets) { + copy.chooseNewTargets(game, newControllerId); + } + game.fireEvent(new GameEvent(EventType.COPIED_STACKOBJECT, copy.getId(), this.getId(), newControllerId)); } - game.fireEvent(new GameEvent(EventType.COPIED_STACKOBJECT, copy.getId(), this.getId(), newControllerId)); return copy; } diff --git a/Mage/src/main/java/mage/game/stack/StackAbility.java b/Mage/src/main/java/mage/game/stack/StackAbility.java index bd37c6436fa..cb4149bcfef 100644 --- a/Mage/src/main/java/mage/game/stack/StackAbility.java +++ b/Mage/src/main/java/mage/game/stack/StackAbility.java @@ -573,19 +573,30 @@ public class StackAbility extends StackObjImpl implements Ability { @Override public StackObject createCopyOnStack(Game game, Ability source, UUID newControllerId, boolean chooseNewTargets) { - Ability newAbility = this.copy(); - newAbility.newId(); - StackAbility newStackAbility = new StackAbility(newAbility, newControllerId); - game.getStack().push(newStackAbility); - if (chooseNewTargets && !newAbility.getTargets().isEmpty()) { - Player controller = game.getPlayer(newControllerId); - Outcome outcome = newAbility.getEffects().getOutcome(newAbility); - if (controller.chooseUse(outcome, "Choose new targets?", source, game)) { - newAbility.getTargets().clearChosen(); - newAbility.getTargets().chooseTargets(outcome, newControllerId, newAbility, false, game, false); - } + return createCopyOnStack(game, source, newControllerId, chooseNewTargets, 1); + } + + public StackObject createCopyOnStack(Game game, Ability source, UUID newControllerId, boolean chooseNewTargets, int amount) { + StackAbility newStackAbility = null; + GameEvent gameEvent = GameEvent.getEvent(GameEvent.EventType.COPY_STACKOBJECT, this.getId(), source.getSourceId(), newControllerId, amount); + if (game.replaceEvent(gameEvent)) { + return null; + } + for (int i = 0; i < gameEvent.getAmount(); i++) { + Ability newAbility = this.copy(); + newAbility.newId(); + newStackAbility = new StackAbility(newAbility, newControllerId); + game.getStack().push(newStackAbility); + if (chooseNewTargets && !newAbility.getTargets().isEmpty()) { + Player controller = game.getPlayer(newControllerId); + Outcome outcome = newAbility.getEffects().getOutcome(newAbility); + if (controller.chooseUse(outcome, "Choose new targets?", source, game)) { + newAbility.getTargets().clearChosen(); + newAbility.getTargets().chooseTargets(outcome, newControllerId, newAbility, false, game, false); + } + } + game.fireEvent(new GameEvent(GameEvent.EventType.COPIED_STACKOBJECT, newStackAbility.getId(), this.getId(), newControllerId)); } - game.fireEvent(new GameEvent(GameEvent.EventType.COPIED_STACKOBJECT, newStackAbility.getId(), this.getId(), newControllerId)); return newStackAbility; } diff --git a/Mage/src/main/java/mage/game/stack/StackObject.java b/Mage/src/main/java/mage/game/stack/StackObject.java index 9a51ed2d4d6..4536ee44f1c 100644 --- a/Mage/src/main/java/mage/game/stack/StackObject.java +++ b/Mage/src/main/java/mage/game/stack/StackObject.java @@ -1,7 +1,5 @@ - package mage.game.stack; -import java.util.UUID; import mage.MageObject; import mage.abilities.Ability; import mage.constants.Zone; @@ -10,6 +8,8 @@ import mage.filter.FilterPermanent; import mage.game.Controllable; import mage.game.Game; +import java.util.UUID; + public interface StackObject extends MageObject, Controllable { boolean resolve(Game game); @@ -22,13 +22,15 @@ public interface StackObject extends MageObject, Controllable { Ability getStackAbility(); -// int getConvertedManaCost(); + // int getConvertedManaCost(); boolean chooseNewTargets(Game game, UUID playerId, boolean forceChange, boolean onlyOneTarget, FilterPermanent filterNewTarget); StackObject createCopyOnStack(Game game, Ability source, UUID newControllerId, boolean chooseNewTargets); - + + StackObject createCopyOnStack(Game game, Ability source, UUID newControllerId, boolean chooseNewTargets, int amount); + boolean isTargetChanged(); - + void setTargetChanged(boolean targetChanged); @Override