updated copy implementation to work with stack objects

This commit is contained in:
Evan Kranzler 2021-04-26 18:55:48 -04:00
parent 1352beee9f
commit 92007f0132
14 changed files with 206 additions and 218 deletions

View file

@ -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<MageObjectReferencePredicate> 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<MageObjectReferencePredicate> getPossibleTargets(Spell spell, Player player, Ability source, Game game);
protected abstract List<MageObjectReferencePredicate> 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<MageObjectReferencePredicate> predicates = getPossibleTargets(spell, actingPlayer, source, game);
spell.createCopyOnStack(
List<MageObjectReferencePredicate> predicates = getPossibleTargets(stackObject, actingPlayer, source, game);
stackObject.createCopyOnStack(
game, source, actingPlayer.getId(), false,
predicates.size(), new CopyApplier(predicates)
);

View file

@ -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<MageObjectReferencePredicate> {
private final SpellCopyApplier applier;
private final Player player;
private final int amount;
private final Game game;
private Map<String, MageObjectReferencePredicate> predicateMap = null;
private int anyCount = 0;
private int setCount = 0;
private Iterator<MageObjectReferencePredicate> 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<MageObjectReferencePredicate> 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

View file

@ -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<CardType> emptyCardType = new ArrayList<>();
private static final List<String> 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

View file

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

View file

@ -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<MageObjectReferencePredicate> {
private final StackObjectCopyApplier applier;
private final Player player;
private final int amount;
private final Game game;
private Map<String, MageObjectReferencePredicate> predicateMap = null;
private int anyCount = 0;
private int setCount = 0;
private Iterator<MageObjectReferencePredicate> 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<MageObjectReferencePredicate> 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
*

View file

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