From 92007f0132a164822a9ee5d1d94de977dd49accd Mon Sep 17 00:00:00 2001 From: Evan Kranzler Date: Mon, 26 Apr 2021 18:55:48 -0400 Subject: [PATCH] updated copy implementation to work with stack objects --- .../src/mage/cards/b/BeamsplitterMage.java | 7 +- Mage.Sets/src/mage/cards/d/DoubleMajor.java | 9 +- Mage.Sets/src/mage/cards/f/Fork.java | 9 +- .../src/mage/cards/i/InkTreaderNephilim.java | 7 +- .../src/mage/cards/m/MirrorwingDragon.java | 9 +- .../src/mage/cards/p/PrecursorGolem.java | 9 +- Mage.Sets/src/mage/cards/r/Radiate.java | 11 +- .../src/mage/cards/z/ZadaHedronGrinder.java | 7 +- .../CopySpellForEachItCouldTargetEffect.java | 20 +-- Mage/src/main/java/mage/game/stack/Spell.java | 141 ++---------------- .../java/mage/game/stack/StackAbility.java | 53 ++----- .../java/mage/game/stack/StackObject.java | 7 +- ...StackObjImpl.java => StackObjectImpl.java} | 129 +++++++++++++++- ...plier.java => StackObjectCopyApplier.java} | 6 +- 14 files changed, 206 insertions(+), 218 deletions(-) rename Mage/src/main/java/mage/game/stack/{StackObjImpl.java => StackObjectImpl.java} (75%) rename Mage/src/main/java/mage/util/functions/{SpellCopyApplier.java => StackObjectCopyApplier.java} (60%) diff --git a/Mage.Sets/src/mage/cards/b/BeamsplitterMage.java b/Mage.Sets/src/mage/cards/b/BeamsplitterMage.java index 8fe0826044..c7ce30bbfc 100644 --- a/Mage.Sets/src/mage/cards/b/BeamsplitterMage.java +++ b/Mage.Sets/src/mage/cards/b/BeamsplitterMage.java @@ -24,10 +24,11 @@ import mage.game.Game; import mage.game.events.GameEvent; import mage.game.permanent.Permanent; import mage.game.stack.Spell; +import mage.game.stack.StackObject; import mage.players.Player; import mage.target.Target; import mage.target.TargetPermanent; -import mage.util.functions.SpellCopyApplier; +import mage.util.functions.StackObjectCopyApplier; import java.util.*; @@ -192,7 +193,7 @@ class BeamsplitterMagePredicate implements Predicate { } } -class BeamsplitterMageApplier implements SpellCopyApplier { +class BeamsplitterMageApplier implements StackObjectCopyApplier { private final Iterator predicate; @@ -203,7 +204,7 @@ class BeamsplitterMageApplier implements SpellCopyApplier { } @Override - public void modifySpell(Spell spell, Game game) { + public void modifySpell(StackObject stackObject, Game game) { } @Override diff --git a/Mage.Sets/src/mage/cards/d/DoubleMajor.java b/Mage.Sets/src/mage/cards/d/DoubleMajor.java index 2552df482e..000fc43584 100644 --- a/Mage.Sets/src/mage/cards/d/DoubleMajor.java +++ b/Mage.Sets/src/mage/cards/d/DoubleMajor.java @@ -11,8 +11,9 @@ import mage.filter.StaticFilters; import mage.filter.predicate.mageobject.MageObjectReferencePredicate; import mage.game.Game; import mage.game.stack.Spell; +import mage.game.stack.StackObject; import mage.target.TargetSpell; -import mage.util.functions.SpellCopyApplier; +import mage.util.functions.StackObjectCopyApplier; import java.util.UUID; @@ -69,12 +70,12 @@ class DoubleMajorEffect extends OneShotEffect { } } -enum DoubleMajorApplier implements SpellCopyApplier { +enum DoubleMajorApplier implements StackObjectCopyApplier { instance; @Override - public void modifySpell(Spell spell, Game game) { - spell.getSuperType().remove(SuperType.LEGENDARY); + public void modifySpell(StackObject stackObject, Game game) { + stackObject.getSuperType().remove(SuperType.LEGENDARY); } @Override diff --git a/Mage.Sets/src/mage/cards/f/Fork.java b/Mage.Sets/src/mage/cards/f/Fork.java index 94a9008f70..d7e8825524 100644 --- a/Mage.Sets/src/mage/cards/f/Fork.java +++ b/Mage.Sets/src/mage/cards/f/Fork.java @@ -11,8 +11,9 @@ import mage.filter.StaticFilters; import mage.filter.predicate.mageobject.MageObjectReferencePredicate; import mage.game.Game; import mage.game.stack.Spell; +import mage.game.stack.StackObject; import mage.target.TargetSpell; -import mage.util.functions.SpellCopyApplier; +import mage.util.functions.StackObjectCopyApplier; import java.util.UUID; @@ -70,12 +71,12 @@ class ForkEffect extends OneShotEffect { } } -enum ForkApplier implements SpellCopyApplier { +enum ForkApplier implements StackObjectCopyApplier { instance; @Override - public void modifySpell(Spell spell, Game game) { - spell.getColor(game).setColor(ObjectColor.RED); + public void modifySpell(StackObject stackObject, Game game) { + stackObject.getColor(game).setColor(ObjectColor.RED); } @Override diff --git a/Mage.Sets/src/mage/cards/i/InkTreaderNephilim.java b/Mage.Sets/src/mage/cards/i/InkTreaderNephilim.java index db5fe8c009..a0703ce003 100644 --- a/Mage.Sets/src/mage/cards/i/InkTreaderNephilim.java +++ b/Mage.Sets/src/mage/cards/i/InkTreaderNephilim.java @@ -16,6 +16,7 @@ import mage.game.Game; import mage.game.events.GameEvent; import mage.game.permanent.Permanent; import mage.game.stack.Spell; +import mage.game.stack.StackObject; import mage.players.Player; import mage.target.Target; import mage.util.TargetAddress; @@ -132,7 +133,7 @@ class InkTreaderNephilimEffect extends CopySpellForEachItCouldTargetEffect { } @Override - protected List getPossibleTargets(Spell spell, Player player, Ability source, Game game) { + protected List getPossibleTargets(StackObject stackObject, Player player, Ability source, Game game) { Permanent permanent = source.getSourcePermanentIfItStillExists(game); return game.getBattlefield() .getActivePermanents( @@ -141,14 +142,14 @@ class InkTreaderNephilimEffect extends CopySpellForEachItCouldTargetEffect { ).stream() .filter(Objects::nonNull) .filter(p -> !p.equals(permanent)) - .filter(p -> spell.canTarget(game, p.getId())) + .filter(p -> stackObject.canTarget(game, p.getId())) .map(p -> new MageObjectReference(p, game)) .map(MageObjectReferencePredicate::new) .collect(Collectors.toList()); } @Override - protected Spell getSpell(Game game, Ability source) { + protected Spell getStackObject(Game game, Ability source) { return (Spell) getValue("triggeringSpell"); } diff --git a/Mage.Sets/src/mage/cards/m/MirrorwingDragon.java b/Mage.Sets/src/mage/cards/m/MirrorwingDragon.java index 1b85517bb4..18673de1ea 100644 --- a/Mage.Sets/src/mage/cards/m/MirrorwingDragon.java +++ b/Mage.Sets/src/mage/cards/m/MirrorwingDragon.java @@ -17,6 +17,7 @@ import mage.game.Game; import mage.game.events.GameEvent; import mage.game.permanent.Permanent; import mage.game.stack.Spell; +import mage.game.stack.StackObject; import mage.players.Player; import mage.target.Target; import mage.util.TargetAddress; @@ -133,7 +134,7 @@ class MirrorwingDragonCopySpellEffect extends CopySpellForEachItCouldTargetEffec @Override protected Player getPlayer(Game game, Ability source) { - Spell spell = getSpell(game, source); + Spell spell = getStackObject(game, source); if (spell == null) { return null; } @@ -141,7 +142,7 @@ class MirrorwingDragonCopySpellEffect extends CopySpellForEachItCouldTargetEffec } @Override - protected List getPossibleTargets(Spell spell, Player player, Ability source, Game game) { + protected List getPossibleTargets(StackObject stackObject, Player player, Ability source, Game game) { Permanent permanent = source.getSourcePermanentIfItStillExists(game); return game.getBattlefield() .getActivePermanents( @@ -150,14 +151,14 @@ class MirrorwingDragonCopySpellEffect extends CopySpellForEachItCouldTargetEffec ).stream() .filter(Objects::nonNull) .filter(p -> !p.equals(permanent)) - .filter(p -> spell.canTarget(game, p.getId())) + .filter(p -> stackObject.canTarget(game, p.getId())) .map(p -> new MageObjectReference(p, game)) .map(MageObjectReferencePredicate::new) .collect(Collectors.toList()); } @Override - protected Spell getSpell(Game game, Ability source) { + protected Spell getStackObject(Game game, Ability source) { return (Spell) getValue("triggeringSpell"); } diff --git a/Mage.Sets/src/mage/cards/p/PrecursorGolem.java b/Mage.Sets/src/mage/cards/p/PrecursorGolem.java index 6171396269..6cc52fe281 100644 --- a/Mage.Sets/src/mage/cards/p/PrecursorGolem.java +++ b/Mage.Sets/src/mage/cards/p/PrecursorGolem.java @@ -19,6 +19,7 @@ import mage.game.events.GameEvent; import mage.game.permanent.Permanent; import mage.game.permanent.token.GolemToken; import mage.game.stack.Spell; +import mage.game.stack.StackObject; import mage.players.Player; import mage.target.Target; import mage.util.TargetAddress; @@ -131,7 +132,7 @@ class PrecursorGolemCopySpellEffect extends CopySpellForEachItCouldTargetEffect @Override protected Player getPlayer(Game game, Ability source) { - Spell spell = getSpell(game, source); + Spell spell = getStackObject(game, source); if (spell == null) { return null; } @@ -139,7 +140,7 @@ class PrecursorGolemCopySpellEffect extends CopySpellForEachItCouldTargetEffect } @Override - protected List getPossibleTargets(Spell spell, Player player, Ability source, Game game) { + protected List getPossibleTargets(StackObject stackObject, Player player, Ability source, Game game) { Permanent permanent = (Permanent) getValue("targetedGolem"); return game.getBattlefield() .getActivePermanents( @@ -147,14 +148,14 @@ class PrecursorGolemCopySpellEffect extends CopySpellForEachItCouldTargetEffect ).stream() .filter(Objects::nonNull) .filter(p -> !p.equals(permanent)) - .filter(p -> spell.canTarget(game, p.getId())) + .filter(p -> stackObject.canTarget(game, p.getId())) .map(p -> new MageObjectReference(p, game)) .map(MageObjectReferencePredicate::new) .collect(Collectors.toList()); } @Override - protected Spell getSpell(Game game, Ability source) { + protected Spell getStackObject(Game game, Ability source) { return (Spell) getValue("triggeringSpell"); } diff --git a/Mage.Sets/src/mage/cards/r/Radiate.java b/Mage.Sets/src/mage/cards/r/Radiate.java index a952819fdf..f12b71b592 100644 --- a/Mage.Sets/src/mage/cards/r/Radiate.java +++ b/Mage.Sets/src/mage/cards/r/Radiate.java @@ -15,6 +15,7 @@ import mage.filter.predicate.ObjectPlayerPredicate; import mage.filter.predicate.mageobject.MageObjectReferencePredicate; import mage.game.Game; import mage.game.stack.Spell; +import mage.game.stack.StackObject; import mage.players.Player; import mage.target.Target; import mage.target.TargetSpell; @@ -119,9 +120,9 @@ class RadiateEffect extends CopySpellForEachItCouldTargetEffect { } @Override - protected List getPossibleTargets(Spell spell, Player player, Ability source, Game game) { + protected List getPossibleTargets(StackObject stackObject, Player player, Ability source, Game game) { List predicates = new ArrayList<>(); - UUID targeted = spell + UUID targeted = ((Spell) stackObject) .getSpellAbilities() .stream() .map(AbilityImpl::getTargets) @@ -137,7 +138,7 @@ class RadiateEffect extends CopySpellForEachItCouldTargetEffect { ).stream() .filter(Objects::nonNull) .filter(p -> !p.equals(game.getPermanent(targeted))) - .filter(p -> spell.canTarget(game, p.getId())) + .filter(p -> stackObject.canTarget(game, p.getId())) .map(p -> new MageObjectReference(p, game)) .map(MageObjectReferencePredicate::new) .forEach(predicates::add); @@ -145,7 +146,7 @@ class RadiateEffect extends CopySpellForEachItCouldTargetEffect { .getPlayersInRange(source.getControllerId(), game) .stream() .filter(uuid -> !uuid.equals(targeted)) - .filter(uuid -> spell.canTarget(game, uuid)) + .filter(uuid -> stackObject.canTarget(game, uuid)) .map(MageObjectReference::new) .map(MageObjectReferencePredicate::new) .forEach(predicates::add); @@ -153,7 +154,7 @@ class RadiateEffect extends CopySpellForEachItCouldTargetEffect { } @Override - protected Spell getSpell(Game game, Ability source) { + protected Spell getStackObject(Game game, Ability source) { return game.getSpell(source.getFirstTarget()); } diff --git a/Mage.Sets/src/mage/cards/z/ZadaHedronGrinder.java b/Mage.Sets/src/mage/cards/z/ZadaHedronGrinder.java index 1acad70795..85bd7a3a4b 100644 --- a/Mage.Sets/src/mage/cards/z/ZadaHedronGrinder.java +++ b/Mage.Sets/src/mage/cards/z/ZadaHedronGrinder.java @@ -17,6 +17,7 @@ import mage.game.Game; import mage.game.events.GameEvent; import mage.game.permanent.Permanent; import mage.game.stack.Spell; +import mage.game.stack.StackObject; import mage.players.Player; import mage.target.Target; import mage.util.TargetAddress; @@ -134,7 +135,7 @@ class ZadaHedronGrinderCopySpellEffect extends CopySpellForEachItCouldTargetEffe } @Override - protected List getPossibleTargets(Spell spell, Player player, Ability source, Game game) { + protected List getPossibleTargets(StackObject stackObject, Player player, Ability source, Game game) { Permanent permanent = source.getSourcePermanentIfItStillExists(game); return game.getBattlefield() .getActivePermanents( @@ -143,14 +144,14 @@ class ZadaHedronGrinderCopySpellEffect extends CopySpellForEachItCouldTargetEffe ).stream() .filter(Objects::nonNull) .filter(p -> !p.equals(permanent)) - .filter(p -> spell.canTarget(game, p.getId())) + .filter(p -> stackObject.canTarget(game, p.getId())) .map(p -> new MageObjectReference(p, game)) .map(MageObjectReferencePredicate::new) .collect(Collectors.toList()); } @Override - protected Spell getSpell(Game game, Ability source) { + protected Spell getStackObject(Game game, Ability source) { return (Spell) getValue("triggeringSpell"); } diff --git a/Mage/src/main/java/mage/abilities/effects/common/CopySpellForEachItCouldTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/CopySpellForEachItCouldTargetEffect.java index e9b7c1efef..c420de95be 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/CopySpellForEachItCouldTargetEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/CopySpellForEachItCouldTargetEffect.java @@ -5,9 +5,9 @@ import mage.abilities.effects.OneShotEffect; import mage.constants.Outcome; import mage.filter.predicate.mageobject.MageObjectReferencePredicate; import mage.game.Game; -import mage.game.stack.Spell; +import mage.game.stack.StackObject; import mage.players.Player; -import mage.util.functions.SpellCopyApplier; +import mage.util.functions.StackObjectCopyApplier; import java.util.Iterator; import java.util.List; @@ -17,7 +17,7 @@ import java.util.List; */ public abstract class CopySpellForEachItCouldTargetEffect extends OneShotEffect { - private static final class CopyApplier implements SpellCopyApplier { + private static final class CopyApplier implements StackObjectCopyApplier { private final Iterator iterator; @@ -26,7 +26,7 @@ public abstract class CopySpellForEachItCouldTargetEffect extends OneShotEffect } @Override - public void modifySpell(Spell spell, Game game) { + public void modifySpell(StackObject stackObject, Game game) { } @Override @@ -46,21 +46,21 @@ public abstract class CopySpellForEachItCouldTargetEffect extends OneShotEffect super(effect); } - protected abstract Spell getSpell(Game game, Ability source); + protected abstract StackObject getStackObject(Game game, Ability source); protected abstract Player getPlayer(Game game, Ability source); - protected abstract List getPossibleTargets(Spell spell, Player player, Ability source, Game game); + protected abstract List getPossibleTargets(StackObject stackObject, Player player, Ability source, Game game); @Override public boolean apply(Game game, Ability source) { Player actingPlayer = getPlayer(game, source); - Spell spell = getSpell(game, source); - if (actingPlayer == null || spell == null) { + StackObject stackObject = getStackObject(game, source); + if (actingPlayer == null || stackObject == null) { return false; } - List predicates = getPossibleTargets(spell, actingPlayer, source, game); - spell.createCopyOnStack( + List predicates = getPossibleTargets(stackObject, actingPlayer, source, game); + stackObject.createCopyOnStack( game, source, actingPlayer.getId(), false, predicates.size(), new CopyApplier(predicates) ); diff --git a/Mage/src/main/java/mage/game/stack/Spell.java b/Mage/src/main/java/mage/game/stack/Spell.java index 2dbdf8b175..3d9287d60b 100644 --- a/Mage/src/main/java/mage/game/stack/Spell.java +++ b/Mage/src/main/java/mage/game/stack/Spell.java @@ -13,20 +13,15 @@ import mage.abilities.keyword.BestowAbility; import mage.abilities.keyword.MorphAbility; import mage.abilities.text.TextPart; import mage.cards.*; -import mage.choices.Choice; -import mage.choices.ChoiceImpl; import mage.constants.*; import mage.counters.Counter; import mage.counters.Counters; import mage.filter.FilterMana; -import mage.filter.predicate.Predicate; import mage.filter.predicate.mageobject.MageObjectReferencePredicate; import mage.game.Game; import mage.game.GameState; import mage.game.MageObjectAttribute; import mage.game.events.CopiedStackObjectEvent; -import mage.game.events.CopyStackObjectEvent; -import mage.game.events.GameEvent; import mage.game.events.ZoneChangeEvent; import mage.game.permanent.Permanent; import mage.game.permanent.PermanentCard; @@ -36,16 +31,15 @@ import mage.util.CardUtil; import mage.util.GameLog; import mage.util.ManaUtil; import mage.util.SubTypes; -import mage.util.functions.SpellCopyApplier; +import mage.util.functions.StackObjectCopyApplier; import org.apache.log4j.Logger; import java.util.*; -import java.util.stream.Collectors; /** * @author BetaSteward_at_googlemail.com */ -public class Spell extends StackObjImpl implements Card { +public class Spell extends StackObjectImpl implements Card { private static final Logger logger = Logger.getLogger(Spell.class); @@ -1063,128 +1057,19 @@ public class Spell extends StackObjImpl implements Card { } @Override - public void createCopyOnStack(Game game, Ability source, UUID newControllerId, boolean chooseNewTargets) { - createCopyOnStack(game, source, newControllerId, chooseNewTargets, 1); - } - - @Override - public void createCopyOnStack(Game game, Ability source, UUID newControllerId, boolean chooseNewTargets, int amount) { - createCopyOnStack(game, source, newControllerId, chooseNewTargets, amount, null); - } - - private static final class PredicateIterator implements Iterator { - private final SpellCopyApplier applier; - private final Player player; - private final int amount; - private final Game game; - private Map predicateMap = null; - private int anyCount = 0; - private int setCount = 0; - private Iterator iterator = null; - private Choice choice = null; - - private PredicateIterator(Game game, UUID newControllerId, int amount, SpellCopyApplier applier) { - this.applier = applier; - this.player = game.getPlayer(newControllerId); - this.amount = amount; - this.game = game; + public void createSingleCopy(UUID newControllerId, StackObjectCopyApplier applier, MageObjectReferencePredicate predicate, Game game, Ability source, boolean chooseNewTargets) { + Spell spellCopy = this.copySpell(game, source, newControllerId); + if (applier != null) { + applier.modifySpell(spellCopy, game); } - - @Override - public boolean hasNext() { - return true; - } - - private void makeMap() { - if (predicateMap != null) { - return; - } - predicateMap = new HashMap<>(); - - for (int i = 0; i < amount; i++) { - MageObjectReferencePredicate predicate = applier.getNextPredicate(); - if (predicate == null) { - anyCount++; - String message = "Any target"; - if (anyCount > 1) { - message += " (" + anyCount + ")"; - } - predicateMap.put(message, predicate); - continue; - } - setCount++; - predicateMap.put(predicate.getName(game), predicate); - } - if ((setCount == 1 && anyCount == 0) || setCount == 0) { - iterator = predicateMap.values().stream().collect(Collectors.toList()).iterator(); - } - } - - private void makeChoice() { - if (choice != null) { - return; - } - choice = new ChoiceImpl(false); - choice.setMessage("Choose the order of copies to go on the stack"); - choice.setSubMessage("Press cancel to put the rest in any order"); - choice.setChoices(new HashSet<>(predicateMap.keySet())); - } - - @Override - public MageObjectReferencePredicate next() { - if (player == null || applier == null) { - return null; - } - makeMap(); - if (iterator != null) { - return iterator.hasNext() ? iterator.next() : null; - } - makeChoice(); - if (choice.getChoices().size() < 2) { - iterator = choice.getChoices().stream().map(predicateMap::get).iterator(); - return next(); - } - choice.clearChoice(); - player.choose(Outcome.AIDontUseIt, choice, game); - String chosen = choice.getChoice(); - if (chosen == null) { - iterator = choice.getChoices().stream().map(predicateMap::get).iterator(); - return next(); - } - choice.getChoices().remove(chosen); - return predicateMap.get(chosen); - } - } - - @Override - public void createCopyOnStack(Game game, Ability source, UUID newControllerId, boolean chooseNewTargets, int amount, SpellCopyApplier applier) { - GameEvent gameEvent = new CopyStackObjectEvent(source, this, newControllerId, amount); - if (game.replaceEvent(gameEvent)) { - return; - } - Iterator predicates = new PredicateIterator(game, newControllerId, gameEvent.getAmount(), applier); - for (int i = 0; i < gameEvent.getAmount(); i++) { - Spell spellCopy = this.copySpell(game, source, newControllerId); - if (applier != null) { - applier.modifySpell(spellCopy, game); - } - spellCopy.setZone(Zone.STACK, game); // required for targeting ex: Nivmagus Elemental - game.getStack().push(spellCopy); - Predicate predicate = predicates.next(); - if (predicate != null) { - spellCopy.chooseNewTargets(game, newControllerId, true, false, predicate); - } else if (chooseNewTargets || applier != null) { // if applier is non-null but predicate is null then it's extra - spellCopy.chooseNewTargets(game, newControllerId); - } - game.fireEvent(new CopiedStackObjectEvent(this, spellCopy, newControllerId)); - } - Player player = game.getPlayer(newControllerId); - if (player != null) { - game.informPlayers( - player.getName() + " created " + CardUtil.numberToText(gameEvent.getAmount(), "a") - + " cop" + (gameEvent.getAmount() == 1 ? "y" : "ies") + " of " + getIdName() - ); + spellCopy.setZone(Zone.STACK, game); // required for targeting ex: Nivmagus Elemental + game.getStack().push(spellCopy); + if (predicate != null) { + spellCopy.chooseNewTargets(game, newControllerId, true, false, predicate); + } else if (chooseNewTargets || applier != null) { // if applier is non-null but predicate is null then it's extra + spellCopy.chooseNewTargets(game, newControllerId); } + game.fireEvent(new CopiedStackObjectEvent(this, spellCopy, newControllerId)); } @Override diff --git a/Mage/src/main/java/mage/game/stack/StackAbility.java b/Mage/src/main/java/mage/game/stack/StackAbility.java index bde9067719..c954f9f167 100644 --- a/Mage/src/main/java/mage/game/stack/StackAbility.java +++ b/Mage/src/main/java/mage/game/stack/StackAbility.java @@ -20,9 +20,9 @@ import mage.abilities.text.TextPart; import mage.cards.Card; import mage.cards.FrameStyle; import mage.constants.*; +import mage.filter.predicate.mageobject.MageObjectReferencePredicate; import mage.game.Game; import mage.game.events.CopiedStackObjectEvent; -import mage.game.events.CopyStackObjectEvent; import mage.game.events.GameEvent; import mage.game.events.ZoneChangeEvent; import mage.game.permanent.Permanent; @@ -30,10 +30,9 @@ import mage.players.Player; import mage.target.Target; import mage.target.Targets; import mage.target.targetadjustment.TargetAdjuster; -import mage.util.CardUtil; import mage.util.GameLog; import mage.util.SubTypes; -import mage.util.functions.SpellCopyApplier; +import mage.util.functions.StackObjectCopyApplier; import mage.watchers.Watcher; import java.util.ArrayList; @@ -44,7 +43,7 @@ import java.util.UUID; /** * @author BetaSteward_at_googlemail.com */ -public class StackAbility extends StackObjImpl implements Ability { +public class StackAbility extends StackObjectImpl implements Ability { private static final ArrayList emptyCardType = new ArrayList<>(); private static final List emptyString = new ArrayList<>(); @@ -598,43 +597,17 @@ public class StackAbility extends StackObjImpl implements Ability { } @Override - public void createCopyOnStack(Game game, Ability source, UUID newControllerId, boolean chooseNewTargets) { - createCopyOnStack(game, source, newControllerId, chooseNewTargets, 1); - } - - @Override - public void createCopyOnStack(Game game, Ability source, UUID newControllerId, boolean chooseNewTargets, int amount) { - createCopyOnStack(game, source, newControllerId, chooseNewTargets, amount, null); - } - - public void createCopyOnStack(Game game, Ability source, UUID newControllerId, boolean chooseNewTargets, int amount, SpellCopyApplier applier) { - StackAbility newStackAbility = null; - GameEvent gameEvent = new CopyStackObjectEvent(source, this, newControllerId, amount); - if (game.replaceEvent(gameEvent)) { - return; - } - 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 CopiedStackObjectEvent(this, newStackAbility, newControllerId)); - } - Player player = game.getPlayer(newControllerId); - if (player != null) { - game.informPlayers( - player.getName() + " created " + CardUtil.numberToText(gameEvent.getAmount(), "a") - + " cop" + (gameEvent.getAmount() == 1 ? "y" : "ies") + " of " + getIdName() - ); + public void createSingleCopy(UUID newControllerId, StackObjectCopyApplier applier, MageObjectReferencePredicate predicate, Game game, Ability source, boolean chooseNewTargets) { + Ability newAbility = this.copy(); + newAbility.newId(); + StackAbility newStackAbility = new StackAbility(newAbility, newControllerId); + game.getStack().push(newStackAbility); + if (predicate != null) { + newStackAbility.chooseNewTargets(game, newControllerId, true, false, predicate); + } else if (chooseNewTargets || applier != null) { // if applier is non-null but predicate is null then it's extra + newStackAbility.chooseNewTargets(game, newControllerId); } + game.fireEvent(new CopiedStackObjectEvent(this, newStackAbility, newControllerId)); } @Override diff --git a/Mage/src/main/java/mage/game/stack/StackObject.java b/Mage/src/main/java/mage/game/stack/StackObject.java index 23f4537864..7e078e61ef 100644 --- a/Mage/src/main/java/mage/game/stack/StackObject.java +++ b/Mage/src/main/java/mage/game/stack/StackObject.java @@ -5,9 +5,10 @@ import mage.abilities.Ability; import mage.constants.Zone; import mage.constants.ZoneDetail; import mage.filter.predicate.Predicate; +import mage.filter.predicate.mageobject.MageObjectReferencePredicate; import mage.game.Controllable; import mage.game.Game; -import mage.util.functions.SpellCopyApplier; +import mage.util.functions.StackObjectCopyApplier; import java.util.UUID; @@ -35,7 +36,9 @@ public interface StackObject extends MageObject, Controllable { void createCopyOnStack(Game game, Ability source, UUID newControllerId, boolean chooseNewTargets, int amount); - void createCopyOnStack(Game game, Ability source, UUID newControllerId, boolean chooseNewTargets, int amount, SpellCopyApplier applier); + void createCopyOnStack(Game game, Ability source, UUID newControllerId, boolean chooseNewTargets, int amount, StackObjectCopyApplier applier); + + void createSingleCopy(UUID newControllerId, StackObjectCopyApplier applier, MageObjectReferencePredicate predicate, Game game, Ability source, boolean chooseNewTargets); boolean isTargetChanged(); diff --git a/Mage/src/main/java/mage/game/stack/StackObjImpl.java b/Mage/src/main/java/mage/game/stack/StackObjectImpl.java similarity index 75% rename from Mage/src/main/java/mage/game/stack/StackObjImpl.java rename to Mage/src/main/java/mage/game/stack/StackObjectImpl.java index 1c1c118924..d67130d9e1 100644 --- a/Mage/src/main/java/mage/game/stack/StackObjImpl.java +++ b/Mage/src/main/java/mage/game/stack/StackObjectImpl.java @@ -6,25 +6,144 @@ import mage.abilities.AbilitiesImpl; import mage.abilities.Ability; import mage.abilities.Mode; import mage.abilities.dynamicvalue.common.StaticValue; +import mage.choices.Choice; +import mage.choices.ChoiceImpl; import mage.constants.Outcome; import mage.filter.predicate.Predicate; +import mage.filter.predicate.mageobject.MageObjectReferencePredicate; import mage.game.Game; +import mage.game.events.CopyStackObjectEvent; +import mage.game.events.GameEvent; import mage.players.Player; import mage.target.Target; import mage.target.TargetAmount; +import mage.util.CardUtil; +import mage.util.functions.StackObjectCopyApplier; -import java.util.Collection; -import java.util.Objects; -import java.util.Set; -import java.util.UUID; +import java.util.*; +import java.util.stream.Collectors; /** * @author LevelX2 */ -public abstract class StackObjImpl implements StackObject { +public abstract class StackObjectImpl implements StackObject { protected boolean targetChanged; // for Psychic Battle + @Override + public void createCopyOnStack(Game game, Ability source, UUID newControllerId, boolean chooseNewTargets) { + createCopyOnStack(game, source, newControllerId, chooseNewTargets, 1); + } + + @Override + public void createCopyOnStack(Game game, Ability source, UUID newControllerId, boolean chooseNewTargets, int amount) { + createCopyOnStack(game, source, newControllerId, chooseNewTargets, amount, null); + } + + private static final class PredicateIterator implements Iterator { + private final StackObjectCopyApplier applier; + private final Player player; + private final int amount; + private final Game game; + private Map predicateMap = null; + private int anyCount = 0; + private int setCount = 0; + private Iterator iterator = null; + private Choice choice = null; + + private PredicateIterator(Game game, UUID newControllerId, int amount, StackObjectCopyApplier applier) { + this.applier = applier; + this.player = game.getPlayer(newControllerId); + this.amount = amount; + this.game = game; + } + + @Override + public boolean hasNext() { + return true; + } + + private void makeMap() { + if (predicateMap != null) { + return; + } + predicateMap = new HashMap<>(); + + for (int i = 0; i < amount; i++) { + MageObjectReferencePredicate predicate = applier.getNextPredicate(); + if (predicate == null) { + anyCount++; + String message = "Any target"; + if (anyCount > 1) { + message += " (" + anyCount + ")"; + } + predicateMap.put(message, predicate); + continue; + } + setCount++; + predicateMap.put(predicate.getName(game), predicate); + } + if ((setCount == 1 && anyCount == 0) || setCount == 0) { + iterator = predicateMap.values().stream().collect(Collectors.toList()).iterator(); + } + } + + private void makeChoice() { + if (choice != null) { + return; + } + choice = new ChoiceImpl(false); + choice.setMessage("Choose the order of copies to go on the stack"); + choice.setSubMessage("Press cancel to put the rest in any order"); + choice.setChoices(new HashSet<>(predicateMap.keySet())); + } + + @Override + public MageObjectReferencePredicate next() { + if (player == null || applier == null) { + return null; + } + makeMap(); + if (iterator != null) { + return iterator.hasNext() ? iterator.next() : null; + } + makeChoice(); + if (choice.getChoices().size() < 2) { + iterator = choice.getChoices().stream().map(predicateMap::get).iterator(); + return next(); + } + choice.clearChoice(); + player.choose(Outcome.AIDontUseIt, choice, game); + String chosen = choice.getChoice(); + if (chosen == null) { + iterator = choice.getChoices().stream().map(predicateMap::get).iterator(); + return next(); + } + choice.getChoices().remove(chosen); + return predicateMap.get(chosen); + } + } + + @Override + public void createCopyOnStack(Game game, Ability source, UUID newControllerId, boolean chooseNewTargets, int amount, StackObjectCopyApplier applier) { + GameEvent gameEvent = new CopyStackObjectEvent(source, this, newControllerId, amount); + if (game.replaceEvent(gameEvent)) { + return; + } + Iterator predicates = new PredicateIterator(game, newControllerId, gameEvent.getAmount(), applier); + for (int i = 0; i < gameEvent.getAmount(); i++) { + createSingleCopy(newControllerId, applier, predicates.next(), game, source, chooseNewTargets); + } + Player player = game.getPlayer(newControllerId); + if (player == null) { + return; + } + game.informPlayers( + player.getName() + " created " + CardUtil.numberToText(gameEvent.getAmount(), "a") + + " cop" + (gameEvent.getAmount() == 1 ? "y" : "ies") + " of " + getIdName() + ); + } + /** * Choose new targets for a stack Object * diff --git a/Mage/src/main/java/mage/util/functions/SpellCopyApplier.java b/Mage/src/main/java/mage/util/functions/StackObjectCopyApplier.java similarity index 60% rename from Mage/src/main/java/mage/util/functions/SpellCopyApplier.java rename to Mage/src/main/java/mage/util/functions/StackObjectCopyApplier.java index c9ef163970..1bd74d6212 100644 --- a/Mage/src/main/java/mage/util/functions/SpellCopyApplier.java +++ b/Mage/src/main/java/mage/util/functions/StackObjectCopyApplier.java @@ -2,16 +2,16 @@ package mage.util.functions; import mage.filter.predicate.mageobject.MageObjectReferencePredicate; import mage.game.Game; -import mage.game.stack.Spell; +import mage.game.stack.StackObject; import java.io.Serializable; /** * @author TheElk801 */ -public interface SpellCopyApplier extends Serializable { +public interface StackObjectCopyApplier extends Serializable { - void modifySpell(Spell spell, Game game); + void modifySpell(StackObject stackObject, Game game); MageObjectReferencePredicate getNextPredicate(); }