mirror of
https://github.com/magefree/mage.git
synced 2025-12-26 21:42:07 -08:00
Refactor implementation of spell copies for cards like Twinning Staff as well as refactor handling of target changing (WIP) (#7662)
* refactored createCopyOnStack to be void * added new interface for modifying copied spellsspells * update implementation of Fork to use new applier * reworked epic effect * add applier to spell copy code * updated implementation of Beamsplitter Mage * updated cards which copy for each possible target * added support for additional copies having targets changed * fixed/ignored failing tests * updated target changing to prevent unnecessary choosing * added test for Twinning Staff * updated implementation of spell copy applier * added new method for choosing order of copies on stack * fixed test failures * [TSR] various text fixes * fixed a test failure * [SLD] fixed Rick, Steadfast Leader only counting Human creatures * updated test framework to handle skips without affecting starting player choice * fixed another test failure * updated copy messaging for consistency * added copy messaging to stack abilities
This commit is contained in:
parent
b51915f6e8
commit
9c56a98dc9
47 changed files with 972 additions and 1092 deletions
|
|
@ -1,58 +1,47 @@
|
|||
|
||||
package mage.abilities.effects.common;
|
||||
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.Mode;
|
||||
import mage.abilities.effects.OneShotEffect;
|
||||
import mage.constants.Outcome;
|
||||
import mage.filter.FilterPermanent;
|
||||
import mage.game.Game;
|
||||
import mage.game.stack.StackObject;
|
||||
|
||||
/**
|
||||
* @author BetaSteward_at_googlemail.com
|
||||
*
|
||||
*/
|
||||
public class ChooseNewTargetsTargetEffect extends OneShotEffect {
|
||||
|
||||
private boolean forceChange;
|
||||
private boolean onlyOneTarget;
|
||||
private FilterPermanent filterNewTarget;
|
||||
private final boolean forceChange;
|
||||
private final boolean onlyOneTarget;
|
||||
|
||||
public ChooseNewTargetsTargetEffect() {
|
||||
this(false, false);
|
||||
}
|
||||
|
||||
public ChooseNewTargetsTargetEffect(boolean forceChange, boolean onlyOneTarget) {
|
||||
this(forceChange, onlyOneTarget, null);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param forceChange forces the user to choose another target (only targets
|
||||
* with maxtargets = 1 supported)
|
||||
* @param onlyOneTarget only one target can be selected for the change
|
||||
* @param forceChange forces the user to choose another target (only targets
|
||||
* with maxtargets = 1 supported)
|
||||
* @param onlyOneTarget only one target can be selected for the change
|
||||
* @param filterNewTarget restriction to the new target
|
||||
*/
|
||||
public ChooseNewTargetsTargetEffect(boolean forceChange, boolean onlyOneTarget, FilterPermanent filterNewTarget) {
|
||||
public ChooseNewTargetsTargetEffect(boolean forceChange, boolean onlyOneTarget) {
|
||||
super(Outcome.Benefit);
|
||||
this.forceChange = forceChange;
|
||||
this.onlyOneTarget = onlyOneTarget;
|
||||
this.filterNewTarget = filterNewTarget;
|
||||
}
|
||||
|
||||
public ChooseNewTargetsTargetEffect(final ChooseNewTargetsTargetEffect effect) {
|
||||
super(effect);
|
||||
this.forceChange = effect.forceChange;
|
||||
this.onlyOneTarget = effect.onlyOneTarget;
|
||||
this.filterNewTarget = effect.filterNewTarget;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
StackObject stackObject = game.getStack().getStackObject(source.getFirstTarget());
|
||||
if (stackObject != null) {
|
||||
return stackObject.chooseNewTargets(game, source.getControllerId(), forceChange, onlyOneTarget, filterNewTarget);
|
||||
return stackObject.chooseNewTargets(game, source.getControllerId(), forceChange, onlyOneTarget, null);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,463 +1,69 @@
|
|||
package mage.abilities.effects.common;
|
||||
|
||||
import mage.MageItem;
|
||||
import mage.MageObject;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.effects.OneShotEffect;
|
||||
import mage.constants.Outcome;
|
||||
import mage.constants.Zone;
|
||||
import mage.filter.FilterImpl;
|
||||
import mage.filter.FilterInPlay;
|
||||
import mage.filter.predicate.other.FromSetPredicate;
|
||||
import mage.filter.predicate.mageobject.MageObjectReferencePredicate;
|
||||
import mage.game.Game;
|
||||
import mage.game.events.CopiedStackObjectEvent;
|
||||
import mage.game.stack.Spell;
|
||||
import mage.players.Player;
|
||||
import mage.target.Target;
|
||||
import mage.target.TargetImpl;
|
||||
import mage.util.TargetAddress;
|
||||
import mage.util.functions.SpellCopyApplier;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @param <T>
|
||||
* @author duncant
|
||||
*/
|
||||
public abstract class CopySpellForEachItCouldTargetEffect<T extends MageItem> extends OneShotEffect {
|
||||
public abstract class CopySpellForEachItCouldTargetEffect extends OneShotEffect {
|
||||
|
||||
protected final FilterInPlay<T> filter;
|
||||
private static final class CopyApplier implements SpellCopyApplier {
|
||||
|
||||
public CopySpellForEachItCouldTargetEffect(FilterInPlay<T> filter) {
|
||||
super(Outcome.Copy);
|
||||
this.staticText = "copy the spell for each other " + filter.getMessage()
|
||||
+ " that spell could target. Each copy targets a different one";
|
||||
this.filter = filter;
|
||||
private final Iterator<MageObjectReferencePredicate> iterator;
|
||||
|
||||
private CopyApplier(List<MageObjectReferencePredicate> predicates) {
|
||||
this.iterator = predicates.iterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void modifySpell(Spell spell, Game game) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public MageObjectReferencePredicate getNextPredicate() {
|
||||
if (iterator.hasNext()) {
|
||||
return iterator.next();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public CopySpellForEachItCouldTargetEffect(final CopySpellForEachItCouldTargetEffect effect) {
|
||||
protected CopySpellForEachItCouldTargetEffect() {
|
||||
super(Outcome.Copy);
|
||||
}
|
||||
|
||||
protected CopySpellForEachItCouldTargetEffect(final CopySpellForEachItCouldTargetEffect effect) {
|
||||
super(effect);
|
||||
this.filter = effect.filter;
|
||||
}
|
||||
|
||||
protected abstract Spell getSpell(Game game, Ability source);
|
||||
|
||||
protected abstract Player getPlayer(Game game, Ability source);
|
||||
|
||||
protected abstract boolean changeTarget(Target target, Game game, Ability source);
|
||||
|
||||
protected abstract void modifyCopy(Spell copy, Game game, Ability source);
|
||||
|
||||
protected void modifyCopy(Spell copy, T newTarget, Game game, Ability source) {
|
||||
modifyCopy(copy, game, source);
|
||||
}
|
||||
|
||||
protected boolean okUUIDToCopyFor(UUID potentialTarget, Game game, Ability source, Spell spell) {
|
||||
return true;
|
||||
}
|
||||
protected abstract List<MageObjectReferencePredicate> getPossibleTargets(Spell spell, Player player, Ability source, Game game);
|
||||
|
||||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
Player actingPlayer = getPlayer(game, source);
|
||||
if (actingPlayer == null) {
|
||||
return false;
|
||||
}
|
||||
Spell spell = getSpell(game, source);
|
||||
if (spell != null) {
|
||||
Set<TargetAddress> targetsToBeChanged = new HashSet<>();
|
||||
boolean madeACopy = false;
|
||||
for (TargetAddress addr : TargetAddress.walk(spell)) {
|
||||
Target targetInstance = addr.getTarget(spell);
|
||||
if (targetInstance.getNumberOfTargets() > 1) {
|
||||
throw new UnsupportedOperationException("Changing Target instances "
|
||||
+ "with multiple targets is unsupported");
|
||||
}
|
||||
if (changeTarget(targetInstance, game, source)) {
|
||||
targetsToBeChanged.add(addr);
|
||||
}
|
||||
}
|
||||
|
||||
if (targetsToBeChanged.size() < 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO: add support if multiple copies? See Twinning Staff
|
||||
// generate copies for each possible target, but do not put it to stack (must choose targets in custom order later)
|
||||
Spell copy = spell.copySpell(game, source, source.getControllerId());
|
||||
modifyCopy(copy, game, source);
|
||||
Target sampleTarget = targetsToBeChanged.iterator().next().getTarget(copy);
|
||||
sampleTarget.setNotTarget(true);
|
||||
|
||||
Map<UUID, Map<UUID, Spell>> playerTargetCopyMap = new HashMap<>();
|
||||
for (UUID objId : sampleTarget.possibleTargets(actingPlayer.getId(), game)) {
|
||||
MageItem obj = game.getObject(objId);
|
||||
if (obj == null) {
|
||||
obj = game.getPlayer(objId);
|
||||
}
|
||||
if (obj != null) {
|
||||
// TODO: add support if multiple copies? See Twinning Staff
|
||||
copy = spell.copySpell(game, source, source.getControllerId());
|
||||
try {
|
||||
modifyCopy(copy, (T) obj, game, source);
|
||||
if (!filter.match((T) obj, source.getSourceId(), actingPlayer.getId(), game)) {
|
||||
continue;
|
||||
}
|
||||
} catch (ClassCastException e) {
|
||||
continue;
|
||||
}
|
||||
|
||||
boolean legal = true;
|
||||
for (TargetAddress addr : targetsToBeChanged) {
|
||||
// potential target must be legal for all targets that we're about to change
|
||||
Target targetInstance = addr.getTarget(copy);
|
||||
legal &= targetInstance.canTarget(actingPlayer.getId(), objId, addr.getSpellAbility(copy), game);
|
||||
if (!legal) {
|
||||
break;
|
||||
}
|
||||
|
||||
// potential target must not be the thing that was targeted initially
|
||||
targetInstance = addr.getTarget(spell);
|
||||
legal &= !targetInstance.getTargets().contains(objId);
|
||||
if (!legal) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
legal &= okUUIDToCopyFor(objId, game, source, spell);
|
||||
if (legal) {
|
||||
for (TargetAddress addr : targetsToBeChanged) {
|
||||
Target targetInstance = addr.getTarget(copy);
|
||||
targetInstance.clearChosen();
|
||||
targetInstance.add(objId, game);
|
||||
}
|
||||
if (!playerTargetCopyMap.containsKey(copy.getControllerId())) {
|
||||
playerTargetCopyMap.put(copy.getControllerId(), new HashMap<>());
|
||||
}
|
||||
playerTargetCopyMap.get(copy.getControllerId()).put(objId, copy);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// allows controller of the copies to choose spells order on stack (by using targeting GUI)
|
||||
for (Player player : game.getPlayers().values()) {
|
||||
if (playerTargetCopyMap.containsKey(player.getId())) {
|
||||
Map<UUID, Spell> targetCopyMap = playerTargetCopyMap.get(player.getId());
|
||||
if (targetCopyMap != null) {
|
||||
while (!targetCopyMap.isEmpty()) {
|
||||
// all checks must be make for new copied spell, not original (controller can be changed)
|
||||
Spell spellSample = targetCopyMap.values().stream().findFirst().get();
|
||||
FilterInPlay<T> setFilter = filter.copy();
|
||||
setFilter.add(new FromSetPredicate(targetCopyMap.keySet())); // allows only unselected targets
|
||||
Target target = new TargetWithAdditionalFilter(sampleTarget, setFilter);
|
||||
target.setNotTarget(false); // it is targeted, not chosen
|
||||
target.setMinNumberOfTargets(0); // if not selected then it uses first target (see below), same for AI
|
||||
target.setMaxNumberOfTargets(1);
|
||||
target.setTargetName(filter.getMessage() + " that " + spellSample.getLogName()
|
||||
+ " could target (" + targetCopyMap.size() + " remaining)");
|
||||
|
||||
if (targetCopyMap.size() > 1
|
||||
&& target.canChoose(spellSample.getId(), player.getId(), game)) {
|
||||
// The original "source" is not applicable here due to the spell being a copy. ie: Zada, Hedron Grinder
|
||||
Outcome outcome = spellSample.getSpellAbility().getAllEffects().getOutcome(spellSample.getSpellAbility());
|
||||
player.chooseTarget(outcome, target, spellSample.getSpellAbility(), game); // not source, but the spell that is copied
|
||||
}
|
||||
|
||||
Collection<UUID> chosenIds = target.getTargets();
|
||||
if (chosenIds.isEmpty()) {
|
||||
// uses first target on cancel/non-selected
|
||||
chosenIds = targetCopyMap.keySet();
|
||||
}
|
||||
List<UUID> toDelete = new ArrayList<>();
|
||||
for (UUID chosenId : chosenIds) {
|
||||
Spell chosenCopy = targetCopyMap.get(chosenId);
|
||||
if (chosenCopy != null) {
|
||||
// COPY DONE, can put to stack
|
||||
chosenCopy.setZone(Zone.STACK, game);
|
||||
game.getStack().push(chosenCopy);
|
||||
game.fireEvent(new CopiedStackObjectEvent(spell, chosenCopy, source.getControllerId()));
|
||||
toDelete.add(chosenId);
|
||||
madeACopy = true;
|
||||
}
|
||||
}
|
||||
targetCopyMap.keySet().removeAll((toDelete));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return madeACopy;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
class CompoundFilter<T extends MageItem> extends FilterImpl<T> implements FilterInPlay<T> {
|
||||
|
||||
protected final FilterInPlay<T> filterA;
|
||||
protected final FilterInPlay<T> filterB;
|
||||
|
||||
public CompoundFilter(String name) {
|
||||
super(name);
|
||||
this.filterA = null;
|
||||
this.filterB = null;
|
||||
}
|
||||
|
||||
public CompoundFilter(FilterInPlay<T> filterA, FilterInPlay<T> filterB, String name) {
|
||||
super(name);
|
||||
this.filterA = filterA;
|
||||
this.filterB = filterB;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkObjectClass(Object object) {
|
||||
return true; // already checked in the filter classes itself
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean match(T obj, Game game) {
|
||||
return (filterA == null
|
||||
|| !filterA.match(obj, game))
|
||||
&& (filterB == null
|
||||
|| !filterB.match(obj, game));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean match(T obj, UUID sourceId, UUID playerId, Game game) {
|
||||
return (filterA == null
|
||||
|| !filterA.match(obj, sourceId, playerId, game))
|
||||
&& (filterB == null
|
||||
|| !filterB.match(obj, sourceId, playerId, game));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompoundFilter copy() {
|
||||
return new CompoundFilter(filterA == null ? null : filterA.copy(),
|
||||
filterB == null ? null : filterB.copy(),
|
||||
message);
|
||||
}
|
||||
}
|
||||
|
||||
class TargetWithAdditionalFilter<T extends MageItem> extends TargetImpl {
|
||||
|
||||
protected final FilterInPlay<T> additionalFilter;
|
||||
protected final Target originalTarget;
|
||||
|
||||
public TargetWithAdditionalFilter(final TargetWithAdditionalFilter target) {
|
||||
this(target.originalTarget, target.additionalFilter, false);
|
||||
}
|
||||
|
||||
public TargetWithAdditionalFilter(Target originalTarget, FilterInPlay<T> additionalFilter) {
|
||||
this(originalTarget, additionalFilter, false);
|
||||
}
|
||||
|
||||
public TargetWithAdditionalFilter(Target originalTarget, FilterInPlay<T> additionalFilter, boolean notTarget) {
|
||||
this.originalTarget = originalTarget.copy();
|
||||
this.originalTarget.clearChosen();
|
||||
this.targetName = originalTarget.getFilter().getMessage();
|
||||
this.notTarget = notTarget;
|
||||
this.additionalFilter = additionalFilter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Target getOriginalTarget() {
|
||||
return originalTarget;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getNumberOfTargets() {
|
||||
return originalTarget.getNumberOfTargets();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMinNumberOfTargets() {
|
||||
return originalTarget.getMinNumberOfTargets();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMinNumberOfTargets(int minNumberOfTargets) {
|
||||
originalTarget.setMinNumberOfTargets(minNumberOfTargets);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaxNumberOfTargets() {
|
||||
return originalTarget.getMaxNumberOfTargets();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMaxNumberOfTargets(int maxNumberOfTargets) {
|
||||
originalTarget.setMaxNumberOfTargets(maxNumberOfTargets);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Zone getZone() {
|
||||
return originalTarget.getZone();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canTarget(UUID id, Game game) {
|
||||
MageItem obj = game.getObject(id);
|
||||
if (obj == null) {
|
||||
obj = game.getPlayer(id);
|
||||
}
|
||||
|
||||
try {
|
||||
return obj != null
|
||||
&& originalTarget.canTarget(id, game)
|
||||
&& additionalFilter.match((T) obj, game);
|
||||
} catch (ClassCastException e) {
|
||||
if (actingPlayer == null || spell == null) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canTarget(UUID id, Ability source, Game game) {
|
||||
MageItem obj = game.getObject(id);
|
||||
if (obj == null) {
|
||||
obj = game.getPlayer(id);
|
||||
}
|
||||
|
||||
try {
|
||||
return obj != null
|
||||
&& originalTarget.canTarget(id, source, game)
|
||||
&& additionalFilter.match((T) obj, source.getSourceId(), source.getControllerId(), game);
|
||||
} catch (ClassCastException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) {
|
||||
MageItem obj = game.getObject(id);
|
||||
if (obj == null) {
|
||||
obj = game.getPlayer(id);
|
||||
}
|
||||
|
||||
try {
|
||||
return obj != null
|
||||
&& originalTarget.canTarget(controllerId, id, source, game)
|
||||
&& additionalFilter.match((T) obj, source.getSourceId(), controllerId, game);
|
||||
} catch (ClassCastException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public FilterInPlay<T> getFilter() {
|
||||
return new CompoundFilter((FilterInPlay<T>) originalTarget.getFilter(),
|
||||
additionalFilter, originalTarget.getFilter().getMessage());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canChoose(UUID sourceId, UUID sourceControllerId, Game game) {
|
||||
int remainingTargets = getNumberOfTargets() - targets.size();
|
||||
if (remainingTargets <= 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
int count = 0;
|
||||
for (UUID objId : originalTarget.possibleTargets(sourceId, sourceControllerId, game)) {
|
||||
MageItem obj = game.getObject(objId);
|
||||
if (obj == null) {
|
||||
obj = game.getPlayer(objId);
|
||||
}
|
||||
try {
|
||||
if (!targets.containsKey(objId)
|
||||
&& obj != null
|
||||
&& additionalFilter.match((T) obj, sourceId, sourceControllerId, game)) {
|
||||
count++;
|
||||
if (count >= remainingTargets) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} catch (ClassCastException e) {
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canChoose(UUID sourceControllerId, Game game) {
|
||||
int remainingTargets = getNumberOfTargets() - targets.size();
|
||||
if (remainingTargets <= 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
int count = 0;
|
||||
for (UUID objId : originalTarget.possibleTargets(sourceControllerId, game)) {
|
||||
MageItem obj = game.getObject(objId);
|
||||
if (obj == null) {
|
||||
obj = game.getPlayer(objId);
|
||||
}
|
||||
try {
|
||||
if (!targets.containsKey(objId)
|
||||
&& obj != null
|
||||
&& additionalFilter.match((T) obj, game)) {
|
||||
count++;
|
||||
if (count >= remainingTargets) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} catch (ClassCastException e) {
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<UUID> possibleTargets(UUID sourceId, UUID sourceControllerId, Game game) {
|
||||
Set<UUID> ret = new HashSet<>();
|
||||
for (UUID id : originalTarget.possibleTargets(sourceId, sourceControllerId, game)) {
|
||||
MageItem obj = game.getObject(id);
|
||||
if (obj == null) {
|
||||
obj = game.getPlayer(id);
|
||||
}
|
||||
try {
|
||||
if (obj != null
|
||||
&& additionalFilter.match((T) obj, sourceId, sourceControllerId, game)) {
|
||||
ret.add(id);
|
||||
}
|
||||
} catch (ClassCastException e) {
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<UUID> possibleTargets(UUID sourceControllerId, Game game) {
|
||||
Set<UUID> ret = new HashSet<>();
|
||||
for (UUID id : originalTarget.possibleTargets(sourceControllerId, game)) {
|
||||
MageItem obj = game.getObject(id);
|
||||
if (obj == null) {
|
||||
obj = game.getPlayer(id);
|
||||
}
|
||||
try {
|
||||
if (obj != null
|
||||
&& additionalFilter.match((T) obj, game)) {
|
||||
ret.add(id);
|
||||
}
|
||||
} catch (ClassCastException e) {
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TargetWithAdditionalFilter copy() {
|
||||
return new TargetWithAdditionalFilter(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTargetedName(Game game) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (UUID targetId : getTargets()) {
|
||||
MageObject object = game.getObject(targetId);
|
||||
if (object != null) {
|
||||
sb.append(object.getLogName()).append(' ');
|
||||
} else {
|
||||
Player player = game.getPlayer(targetId);
|
||||
if (player != null) {
|
||||
sb.append(player.getLogName()).append(' ');
|
||||
}
|
||||
}
|
||||
}
|
||||
return sb.toString().trim();
|
||||
List<MageObjectReferencePredicate> predicates = getPossibleTargets(spell, actingPlayer, source, game);
|
||||
spell.createCopyOnStack(
|
||||
game, source, actingPlayer.getId(), false,
|
||||
predicates.size(), new CopyApplier(predicates)
|
||||
);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -65,17 +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(), chooseTargets);
|
||||
Player player = game.getPlayer(source.getControllerId());
|
||||
if (player != null && newStackObject instanceof Spell) {
|
||||
String activateMessage = ((Spell) newStackObject).getActivatedMessage(game);
|
||||
if (activateMessage.startsWith(" casts ")) {
|
||||
activateMessage = activateMessage.substring(6);
|
||||
}
|
||||
if (!game.isSimulation()) {
|
||||
game.informPlayers(player.getLogName() + activateMessage);
|
||||
}
|
||||
}
|
||||
spell.createCopyOnStack(game, source, useController ? spell.getControllerId() : source.getControllerId(), chooseTargets);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -1,26 +1,17 @@
|
|||
/*
|
||||
* To change this template, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package mage.abilities.effects.common;
|
||||
|
||||
import mage.MageObject;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.DelayedTriggeredAbility;
|
||||
import mage.abilities.common.delayed.AtTheBeginOfYourNextUpkeepDelayedTriggeredAbility;
|
||||
import mage.abilities.effects.ContinuousRuleModifyingEffectImpl;
|
||||
import mage.abilities.effects.Effect;
|
||||
import mage.abilities.effects.OneShotEffect;
|
||||
import mage.constants.Duration;
|
||||
import mage.constants.Outcome;
|
||||
import mage.game.Game;
|
||||
import mage.game.events.GameEvent;
|
||||
import mage.game.stack.Spell;
|
||||
import mage.game.stack.StackObject;
|
||||
import mage.players.Player;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* @author jeffwadsworth
|
||||
* <p>
|
||||
|
|
@ -35,42 +26,36 @@ import java.util.Objects;
|
|||
*/
|
||||
public class EpicEffect extends OneShotEffect {
|
||||
|
||||
static final String rule = "<br>Epic <i>(For the rest of the game, you can't cast spells. At the beginning of each of your upkeeps for the rest of the game, copy this spell except for its epic ability. If the spell has targets, you may choose new targets for the copy)";
|
||||
private static final String rule = "Epic <i>(For the rest of the game, you can't cast spells. " +
|
||||
"At the beginning of each of your upkeeps for the rest of the game, copy this spell " +
|
||||
"except for its epic ability. If the spell has targets, you may choose new targets for the copy)";
|
||||
|
||||
public EpicEffect() {
|
||||
super(Outcome.Benefit);
|
||||
staticText = rule;
|
||||
staticText = "<br>" + rule;
|
||||
}
|
||||
|
||||
public EpicEffect(final EpicEffect effect) {
|
||||
private EpicEffect(final EpicEffect effect) {
|
||||
super(effect);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
Player controller = game.getPlayer(source.getControllerId());
|
||||
if (controller != null) {
|
||||
StackObject stackObject = game.getStack().getStackObject(source.getId());
|
||||
Spell spell = (Spell) stackObject;
|
||||
if (spell == null) {
|
||||
return false;
|
||||
}
|
||||
spell = spell.copySpell(game, source, source.getControllerId()); // it's a fake copy, real copy with events in EpicPushEffect
|
||||
// Remove Epic effect from the spell
|
||||
Effect epicEffect = null;
|
||||
for (Effect effect : spell.getSpellAbility().getEffects()) {
|
||||
if (effect instanceof EpicEffect) {
|
||||
epicEffect = effect;
|
||||
break;
|
||||
}
|
||||
}
|
||||
spell.getSpellAbility().getEffects().remove(epicEffect);
|
||||
DelayedTriggeredAbility ability = new AtTheBeginOfYourNextUpkeepDelayedTriggeredAbility(new EpicPushEffect(spell, rule), Duration.EndOfGame, false);
|
||||
game.addDelayedTriggeredAbility(ability, source);
|
||||
game.addEffect(new EpicReplacementEffect(), source);
|
||||
return true;
|
||||
if (controller == null) {
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
Spell spell = (Spell) source.getSourceObject(game);
|
||||
if (spell == null) {
|
||||
return false;
|
||||
}
|
||||
spell = spell.copy();
|
||||
spell.getSpellAbility().getEffects().removeIf(EpicEffect.class::isInstance);
|
||||
game.addDelayedTriggeredAbility(new AtTheBeginOfYourNextUpkeepDelayedTriggeredAbility(
|
||||
new EpicPushEffect(spell, rule), Duration.EndOfGame, false
|
||||
), source);
|
||||
game.addEffect(new EpicReplacementEffect(), source);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -81,12 +66,12 @@ public class EpicEffect extends OneShotEffect {
|
|||
|
||||
class EpicReplacementEffect extends ContinuousRuleModifyingEffectImpl {
|
||||
|
||||
public EpicReplacementEffect() {
|
||||
EpicReplacementEffect() {
|
||||
super(Duration.EndOfGame, Outcome.Neutral);
|
||||
staticText = "For the rest of the game, you can't cast spells";
|
||||
}
|
||||
|
||||
public EpicReplacementEffect(final EpicReplacementEffect effect) {
|
||||
private EpicReplacementEffect(final EpicReplacementEffect effect) {
|
||||
super(effect);
|
||||
}
|
||||
|
||||
|
|
@ -116,25 +101,22 @@ class EpicReplacementEffect extends ContinuousRuleModifyingEffectImpl {
|
|||
|
||||
@Override
|
||||
public boolean applies(GameEvent event, Ability source, Game game) {
|
||||
if (Objects.equals(source.getControllerId(), event.getPlayerId())) {
|
||||
MageObject object = game.getObject(event.getSourceId());
|
||||
return object != null;
|
||||
}
|
||||
return false;
|
||||
return source.isControlledBy(event.getPlayerId())
|
||||
&& game.getObject(event.getSourceId()) != null;
|
||||
}
|
||||
}
|
||||
|
||||
class EpicPushEffect extends OneShotEffect {
|
||||
|
||||
final private Spell spell;
|
||||
private final Spell spell;
|
||||
|
||||
public EpicPushEffect(Spell spell, String ruleText) {
|
||||
EpicPushEffect(Spell spell, String ruleText) {
|
||||
super(Outcome.Copy);
|
||||
this.spell = spell;
|
||||
staticText = ruleText;
|
||||
}
|
||||
|
||||
public EpicPushEffect(final EpicPushEffect effect) {
|
||||
private EpicPushEffect(final EpicPushEffect effect) {
|
||||
super(effect);
|
||||
this.spell = effect.spell;
|
||||
}
|
||||
|
|
@ -145,7 +127,6 @@ class EpicPushEffect extends OneShotEffect {
|
|||
spell.createCopyOnStack(game, source, source.getControllerId(), true);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -22,7 +22,6 @@ import mage.filter.predicate.permanent.TappedPredicate;
|
|||
import mage.game.Game;
|
||||
import mage.game.events.GameEvent;
|
||||
import mage.game.stack.Spell;
|
||||
import mage.game.stack.StackObject;
|
||||
import mage.players.Player;
|
||||
import mage.target.common.TargetControlledPermanent;
|
||||
|
||||
|
|
@ -269,10 +268,7 @@ class ConspireEffect extends OneShotEffect {
|
|||
if (controller != null && conspiredSpell != null) {
|
||||
Card card = game.getCard(conspiredSpell.getSourceId());
|
||||
if (card != null) {
|
||||
StackObject newStackObject = conspiredSpell.createCopyOnStack(game, source, source.getControllerId(), true);
|
||||
if (newStackObject instanceof Spell && !game.isSimulation()) {
|
||||
game.informPlayers(controller.getLogName() + ((Spell) newStackObject).getActivatedMessage(game));
|
||||
}
|
||||
conspiredSpell.createCopyOnStack(game, source, source.getControllerId(), true);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -96,10 +96,10 @@ public class ReplicateAbility extends StaticAbility implements OptionalAdditiona
|
|||
// canPay checks only single mana available, not total mana usage
|
||||
if (additionalCost.canPay(ability, this, ability.getControllerId(), game)
|
||||
&& player.chooseUse(/*Outcome.Benefit*/Outcome.AIDontUseIt,
|
||||
new StringBuilder("Pay ").append(times).append(
|
||||
additionalCost.getText(false)).append(" ?").toString(), ability, game)) {
|
||||
new StringBuilder("Pay ").append(times).append(
|
||||
additionalCost.getText(false)).append(" ?").toString(), ability, game)) {
|
||||
additionalCost.activate();
|
||||
for (Iterator it = ((Costs) additionalCost).iterator(); it.hasNext();) {
|
||||
for (Iterator it = ((Costs) additionalCost).iterator(); it.hasNext(); ) {
|
||||
Cost cost = (Cost) it.next();
|
||||
if (cost instanceof ManaCostsImpl) {
|
||||
ability.getManaCostsToPay().add((ManaCostsImpl) cost.copy());
|
||||
|
|
@ -225,8 +225,7 @@ class ReplicateCopyEffect extends OneShotEffect {
|
|||
}
|
||||
}
|
||||
// create the copies
|
||||
StackObject newStackObject = spell.createCopyOnStack(game, source,
|
||||
source.getControllerId(), true, replicateCount);
|
||||
spell.createCopyOnStack(game, source, source.getControllerId(), true, replicateCount);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,16 +1,17 @@
|
|||
|
||||
package mage.filter.predicate.mageobject;
|
||||
|
||||
import mage.MageItem;
|
||||
import mage.MageObject;
|
||||
import mage.MageObjectReference;
|
||||
import mage.filter.predicate.Predicate;
|
||||
import mage.game.Game;
|
||||
import mage.game.permanent.Permanent;
|
||||
import mage.players.Player;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author TheElk801
|
||||
*/
|
||||
public class MageObjectReferencePredicate implements Predicate<MageObject> {
|
||||
public class MageObjectReferencePredicate implements Predicate<MageItem> {
|
||||
|
||||
private final MageObjectReference mor;
|
||||
|
||||
|
|
@ -19,8 +20,23 @@ public class MageObjectReferencePredicate implements Predicate<MageObject> {
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(MageObject input, Game game) {
|
||||
return mor.refersTo(input, game);
|
||||
public boolean apply(MageItem input, Game game) {
|
||||
if (input instanceof Player) {
|
||||
return mor.getSourceId().equals(input.getId());
|
||||
}
|
||||
return input instanceof MageObject && mor.refersTo((MageObject) input, game);
|
||||
}
|
||||
|
||||
public String getName(Game game) {
|
||||
Permanent permanent = mor.getPermanent(game);
|
||||
if (permanent != null) {
|
||||
return permanent.getIdName();
|
||||
}
|
||||
Player player = game.getPlayer(mor.getSourceId());
|
||||
if (player != null) {
|
||||
return player.getName();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -13,10 +13,14 @@ 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;
|
||||
|
|
@ -32,9 +36,11 @@ import mage.util.CardUtil;
|
|||
import mage.util.GameLog;
|
||||
import mage.util.ManaUtil;
|
||||
import mage.util.SubTypes;
|
||||
import mage.util.functions.SpellCopyApplier;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author BetaSteward_at_googlemail.com
|
||||
|
|
@ -1057,27 +1063,128 @@ public class Spell extends StackObjImpl implements Card {
|
|||
}
|
||||
|
||||
@Override
|
||||
public StackObject createCopyOnStack(Game game, Ability source, UUID newControllerId, boolean chooseNewTargets) {
|
||||
return createCopyOnStack(game, source, newControllerId, chooseNewTargets, 1);
|
||||
public void createCopyOnStack(Game game, Ability source, UUID newControllerId, boolean chooseNewTargets) {
|
||||
createCopyOnStack(game, source, newControllerId, chooseNewTargets, 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public StackObject createCopyOnStack(Game game, Ability source, UUID newControllerId, boolean chooseNewTargets, int amount) {
|
||||
Spell spellCopy = null;
|
||||
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;
|
||||
}
|
||||
|
||||
@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 null;
|
||||
return;
|
||||
}
|
||||
Iterator<MageObjectReferencePredicate> predicates = new PredicateIterator(game, newControllerId, gameEvent.getAmount(), applier);
|
||||
for (int i = 0; i < gameEvent.getAmount(); i++) {
|
||||
spellCopy = this.copySpell(game, source, newControllerId);
|
||||
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);
|
||||
if (chooseNewTargets) {
|
||||
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));
|
||||
}
|
||||
return spellCopy;
|
||||
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()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
package mage.game.stack;
|
||||
|
||||
import mage.*;
|
||||
import mage.MageIdentifier;
|
||||
import mage.MageInt;
|
||||
import mage.MageObject;
|
||||
import mage.ObjectColor;
|
||||
import mage.abilities.*;
|
||||
import mage.abilities.costs.Cost;
|
||||
import mage.abilities.costs.CostAdjuster;
|
||||
|
|
@ -27,8 +30,10 @@ 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.watchers.Watcher;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
|
@ -593,15 +598,20 @@ public class StackAbility extends StackObjImpl implements Ability {
|
|||
}
|
||||
|
||||
@Override
|
||||
public StackObject createCopyOnStack(Game game, Ability source, UUID newControllerId, boolean chooseNewTargets) {
|
||||
return createCopyOnStack(game, source, newControllerId, chooseNewTargets, 1);
|
||||
public void createCopyOnStack(Game game, Ability source, UUID newControllerId, boolean chooseNewTargets) {
|
||||
createCopyOnStack(game, source, newControllerId, chooseNewTargets, 1);
|
||||
}
|
||||
|
||||
public StackObject createCopyOnStack(Game game, Ability source, UUID newControllerId, boolean chooseNewTargets, int amount) {
|
||||
@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 null;
|
||||
return;
|
||||
}
|
||||
for (int i = 0; i < gameEvent.getAmount(); i++) {
|
||||
Ability newAbility = this.copy();
|
||||
|
|
@ -618,7 +628,13 @@ public class StackAbility extends StackObjImpl implements Ability {
|
|||
}
|
||||
game.fireEvent(new CopiedStackObjectEvent(this, newStackAbility, newControllerId));
|
||||
}
|
||||
return newStackAbility;
|
||||
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()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -7,13 +7,14 @@ import mage.abilities.Ability;
|
|||
import mage.abilities.Mode;
|
||||
import mage.abilities.dynamicvalue.common.StaticValue;
|
||||
import mage.constants.Outcome;
|
||||
import mage.filter.FilterPermanent;
|
||||
import mage.filter.predicate.Predicate;
|
||||
import mage.game.Game;
|
||||
import mage.game.permanent.Permanent;
|
||||
import mage.players.Player;
|
||||
import mage.target.Target;
|
||||
import mage.target.TargetAmount;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
|
|
@ -94,12 +95,12 @@ public abstract class StackObjImpl implements StackObject {
|
|||
* targetId
|
||||
* @param onlyOneTarget - 114.6b one target must be changed to another
|
||||
* target
|
||||
* @param filterNewTarget restriction for the new target, if null nothing is
|
||||
* @param extraPredicate restriction for the new target, if null nothing is
|
||||
* cheched
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public boolean chooseNewTargets(Game game, UUID targetControllerId, boolean forceChange, boolean onlyOneTarget, FilterPermanent filterNewTarget) {
|
||||
public boolean chooseNewTargets(Game game, UUID targetControllerId, boolean forceChange, boolean onlyOneTarget, Predicate extraPredicate) {
|
||||
Player targetController = game.getPlayer(targetControllerId);
|
||||
if (targetController != null) {
|
||||
StringBuilder oldTargetDescription = new StringBuilder();
|
||||
|
|
@ -118,7 +119,7 @@ public abstract class StackObjImpl implements StackObject {
|
|||
ability.getModes().setActiveMode(mode);
|
||||
oldTargetDescription.append(ability.getTargetDescription(mode.getTargets(), game));
|
||||
for (Target target : mode.getTargets()) {
|
||||
Target newTarget = chooseNewTarget(targetController, ability, mode, target, forceChange, filterNewTarget, game);
|
||||
Target newTarget = chooseNewTarget(targetController, ability, mode, target, forceChange, extraPredicate, game);
|
||||
// clear the old target and copy all targets from new target
|
||||
target.clearChosen();
|
||||
for (UUID targetId : newTarget.getTargets()) {
|
||||
|
|
@ -149,8 +150,13 @@ public abstract class StackObjImpl implements StackObject {
|
|||
* @param game
|
||||
* @return
|
||||
*/
|
||||
private Target chooseNewTarget(Player targetController, Ability ability, Mode mode, Target target, boolean forceChange, FilterPermanent filterNewTarget, Game game) {
|
||||
private Target chooseNewTarget(Player targetController, Ability ability, Mode mode, Target target, boolean forceChange, Predicate predicate, Game game) {
|
||||
Target newTarget = target.copy();
|
||||
if (predicate != null) {
|
||||
newTarget.getFilter().add(predicate);
|
||||
// If adding a predicate, there will only be one choice and therefore target can be automatic
|
||||
newTarget.setRandom(true);
|
||||
}
|
||||
newTarget.setEventReporting(false);
|
||||
if (!targetController.getId().equals(getControllerId())) {
|
||||
newTarget.setTargetController(targetController.getId()); // target controller for the change is different from spell controller
|
||||
|
|
@ -181,15 +187,6 @@ public abstract class StackObjImpl implements StackObject {
|
|||
|
||||
newTarget.chooseTarget(outcome, getControllerId(), ability, game);
|
||||
|
||||
// check target restriction TODO: add multiple target checks
|
||||
if (newTarget.getFirstTarget() != null && filterNewTarget != null) {
|
||||
Permanent newTargetPermanent = game.getPermanent(newTarget.getFirstTarget());
|
||||
if (newTargetPermanent == null || !filterNewTarget.match(newTargetPermanent, game)) {
|
||||
game.informPlayer(targetController, "Target does not fullfil the target requirements (" + filterNewTarget.getMessage() + ')');
|
||||
newTarget.clearChosen();
|
||||
}
|
||||
}
|
||||
|
||||
// workaround to stop infinite AI choose (remove after chooseTarget can be called with extra filter to disable some ids)
|
||||
if (iteration > 10) {
|
||||
break;
|
||||
|
|
@ -200,6 +197,11 @@ public abstract class StackObjImpl implements StackObject {
|
|||
} else {
|
||||
// build a target definition with exactly one possible target to select that replaces old target
|
||||
Target tempTarget = target.copy();
|
||||
if (predicate != null) {
|
||||
tempTarget.getFilter().add(predicate);
|
||||
// If adding a predicate, there will only be one choice and therefore target can be automatic
|
||||
tempTarget.setRandom(true);
|
||||
}
|
||||
tempTarget.setEventReporting(false);
|
||||
if (target instanceof TargetAmount) {
|
||||
((TargetAmount) tempTarget).setAmountDefinition(StaticValue.get(target.getTargetAmount(targetId)));
|
||||
|
|
@ -242,12 +244,6 @@ public abstract class StackObjImpl implements StackObject {
|
|||
// keep the old
|
||||
newTarget.addTarget(targetId, target.getTargetAmount(targetId), ability, game, true);
|
||||
}
|
||||
} else if (newTarget.getFirstTarget() != null && filterNewTarget != null) {
|
||||
Permanent newTargetPermanent = game.getPermanent(newTarget.getFirstTarget());
|
||||
if (newTargetPermanent == null || !filterNewTarget.match(newTargetPermanent, game)) {
|
||||
game.informPlayer(targetController, "This target does not fullfil the target requirements (" + filterNewTarget.getMessage() + ')');
|
||||
again = true;
|
||||
}
|
||||
} else {
|
||||
// valid target was selected, add it to the new target definition
|
||||
newTarget.addTarget(tempTarget.getFirstTarget(), target.getTargetAmount(targetId), ability, game, true);
|
||||
|
|
@ -263,6 +259,30 @@ public abstract class StackObjImpl implements StackObject {
|
|||
return newTarget;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canTarget(Game game, UUID targetId) {
|
||||
Abilities<Ability> objectAbilities = new AbilitiesImpl<>();
|
||||
if (this instanceof Spell) {
|
||||
objectAbilities.addAll(((Spell) this).getSpellAbilities());
|
||||
} else {
|
||||
objectAbilities.add(getStackAbility());
|
||||
}
|
||||
for (Ability ability : objectAbilities) {
|
||||
if (ability.getModes()
|
||||
.getSelectedModes()
|
||||
.stream()
|
||||
.map(ability.getModes()::get)
|
||||
.filter(Objects::nonNull)
|
||||
.map(Mode::getTargets)
|
||||
.flatMap(Collection::stream)
|
||||
.filter(t -> !t.isNotTarget())
|
||||
.anyMatch(t -> t.canTarget(ability.getControllerId(), targetId, ability, game))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private String getNamesOftargets(UUID targetId, Game game) {
|
||||
MageObject object = game.getObject(targetId);
|
||||
String name = null;
|
||||
|
|
|
|||
|
|
@ -4,9 +4,10 @@ import mage.MageObject;
|
|||
import mage.abilities.Ability;
|
||||
import mage.constants.Zone;
|
||||
import mage.constants.ZoneDetail;
|
||||
import mage.filter.FilterPermanent;
|
||||
import mage.filter.predicate.Predicate;
|
||||
import mage.game.Controllable;
|
||||
import mage.game.Game;
|
||||
import mage.util.functions.SpellCopyApplier;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
|
|
@ -17,7 +18,6 @@ public interface StackObject extends MageObject, Controllable {
|
|||
UUID getSourceId();
|
||||
|
||||
/**
|
||||
*
|
||||
* @param source null for fizzled events (sourceId will be null)
|
||||
* @param game
|
||||
*/
|
||||
|
|
@ -27,11 +27,15 @@ public interface StackObject extends MageObject, Controllable {
|
|||
|
||||
Ability getStackAbility();
|
||||
|
||||
boolean chooseNewTargets(Game game, UUID playerId, boolean forceChange, boolean onlyOneTarget, FilterPermanent filterNewTarget);
|
||||
boolean chooseNewTargets(Game game, UUID playerId, boolean forceChange, boolean onlyOneTarget, Predicate extraPredicate);
|
||||
|
||||
StackObject createCopyOnStack(Game game, Ability source, UUID newControllerId, boolean chooseNewTargets);
|
||||
boolean canTarget(Game game, UUID targetId);
|
||||
|
||||
StackObject createCopyOnStack(Game game, Ability source, UUID newControllerId, boolean chooseNewTargets, int amount);
|
||||
void createCopyOnStack(Game game, Ability source, UUID newControllerId, boolean chooseNewTargets);
|
||||
|
||||
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);
|
||||
|
||||
boolean isTargetChanged();
|
||||
|
||||
|
|
|
|||
17
Mage/src/main/java/mage/util/functions/SpellCopyApplier.java
Normal file
17
Mage/src/main/java/mage/util/functions/SpellCopyApplier.java
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
package mage.util.functions;
|
||||
|
||||
import mage.filter.predicate.mageobject.MageObjectReferencePredicate;
|
||||
import mage.game.Game;
|
||||
import mage.game.stack.Spell;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* @author TheElk801
|
||||
*/
|
||||
public interface SpellCopyApplier extends Serializable {
|
||||
|
||||
void modifySpell(Spell spell, Game game);
|
||||
|
||||
MageObjectReferencePredicate getNextPredicate();
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue