mirror of
https://github.com/magefree/mage.git
synced 2025-12-26 13:32:06 -08:00
Refactor: added docs for a copy stack object code (#7662);
This commit is contained in:
parent
c328c71ef9
commit
f21c492ce2
16 changed files with 136 additions and 85 deletions
|
|
@ -17,12 +17,12 @@ import java.util.List;
|
|||
*/
|
||||
public abstract class CopySpellForEachItCouldTargetEffect extends OneShotEffect {
|
||||
|
||||
private static final class CopyApplier implements StackObjectCopyApplier {
|
||||
private static final class ForEachCopyApplier implements StackObjectCopyApplier {
|
||||
|
||||
private final Iterator<MageObjectReferencePredicate> iterator;
|
||||
private final Iterator<MageObjectReferencePredicate> newTargetTypes;
|
||||
|
||||
private CopyApplier(List<MageObjectReferencePredicate> predicates) {
|
||||
this.iterator = predicates.iterator();
|
||||
private ForEachCopyApplier(List<MageObjectReferencePredicate> copiesWithTargets) {
|
||||
this.newTargetTypes = copiesWithTargets.iterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -30,9 +30,9 @@ public abstract class CopySpellForEachItCouldTargetEffect extends OneShotEffect
|
|||
}
|
||||
|
||||
@Override
|
||||
public MageObjectReferencePredicate getNextPredicate() {
|
||||
if (iterator.hasNext()) {
|
||||
return iterator.next();
|
||||
public MageObjectReferencePredicate getNextNewTargetType(int copyNumber) {
|
||||
if (newTargetTypes.hasNext()) {
|
||||
return newTargetTypes.next();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
|
@ -50,7 +50,16 @@ public abstract class CopySpellForEachItCouldTargetEffect extends OneShotEffect
|
|||
|
||||
protected abstract Player getPlayer(Game game, Ability source);
|
||||
|
||||
protected abstract List<MageObjectReferencePredicate> getPossibleTargets(StackObject stackObject, Player player, Ability source, Game game);
|
||||
/**
|
||||
* Prepare copies list. Each item must contain filter for new target (target type).
|
||||
*
|
||||
* @param stackObject
|
||||
* @param player
|
||||
* @param source
|
||||
* @param game
|
||||
* @return
|
||||
*/
|
||||
protected abstract List<MageObjectReferencePredicate> prepareCopiesWithTargets(StackObject stackObject, Player player, Ability source, Game game);
|
||||
|
||||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
|
|
@ -59,10 +68,10 @@ public abstract class CopySpellForEachItCouldTargetEffect extends OneShotEffect
|
|||
if (actingPlayer == null || stackObject == null) {
|
||||
return false;
|
||||
}
|
||||
List<MageObjectReferencePredicate> predicates = getPossibleTargets(stackObject, actingPlayer, source, game);
|
||||
List<MageObjectReferencePredicate> copies = prepareCopiesWithTargets(stackObject, actingPlayer, source, game);
|
||||
stackObject.createCopyOnStack(
|
||||
game, source, actingPlayer.getId(), false,
|
||||
predicates.size(), new CopyApplier(predicates)
|
||||
copies.size(), new ForEachCopyApplier(copies)
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1058,18 +1058,21 @@ public class Spell extends StackObjectImpl implements Card {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void createSingleCopy(UUID newControllerId, StackObjectCopyApplier applier, MageObjectReferencePredicate predicate, Game game, Ability source, boolean chooseNewTargets) {
|
||||
public void createSingleCopy(UUID newControllerId, StackObjectCopyApplier applier, MageObjectReferencePredicate newTargetFilterPredicate, Game game, Ability source, boolean chooseNewTargets) {
|
||||
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 (predicate != null) {
|
||||
spellCopy.chooseNewTargets(game, newControllerId, true, false, predicate);
|
||||
|
||||
// new targets
|
||||
if (newTargetFilterPredicate != null) {
|
||||
spellCopy.chooseNewTargets(game, newControllerId, true, false, newTargetFilterPredicate);
|
||||
} 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));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -607,16 +607,19 @@ public class StackAbility extends StackObjectImpl implements Ability {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void createSingleCopy(UUID newControllerId, StackObjectCopyApplier applier, MageObjectReferencePredicate predicate, Game game, Ability source, boolean chooseNewTargets) {
|
||||
public void createSingleCopy(UUID newControllerId, StackObjectCopyApplier applier, MageObjectReferencePredicate newTargetFilterPredicate, 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);
|
||||
|
||||
// new targets
|
||||
if (newTargetFilterPredicate != null) {
|
||||
newStackAbility.chooseNewTargets(game, newControllerId, true, false, newTargetFilterPredicate);
|
||||
} 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));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ public interface StackObject extends MageObject, Controllable {
|
|||
|
||||
Ability getStackAbility();
|
||||
|
||||
boolean chooseNewTargets(Game game, UUID playerId, boolean forceChange, boolean onlyOneTarget, Predicate<MageItem> extraPredicate);
|
||||
boolean chooseNewTargets(Game game, UUID playerId, boolean forceChange, boolean onlyOneTarget, Predicate<MageItem> newTargetFilterPredicate);
|
||||
|
||||
boolean canTarget(Game game, UUID targetId);
|
||||
|
||||
|
|
@ -39,7 +39,7 @@ public interface StackObject extends MageObject, Controllable {
|
|||
|
||||
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);
|
||||
void createSingleCopy(UUID newControllerId, StackObjectCopyApplier applier, MageObjectReferencePredicate newTargetFilterPredicate, Game game, Ability source, boolean chooseNewTargets);
|
||||
|
||||
boolean isTargetChanged();
|
||||
|
||||
|
|
|
|||
|
|
@ -42,18 +42,30 @@ public abstract class StackObjectImpl implements StackObject {
|
|||
createCopyOnStack(game, source, newControllerId, chooseNewTargets, amount, null);
|
||||
}
|
||||
|
||||
private static final class PredicateIterator implements Iterator<MageObjectReferencePredicate> {
|
||||
/**
|
||||
* Copy logic:
|
||||
* - multiple copies allows
|
||||
* - new targets for each copy allows
|
||||
* - each next copy can have new target type/filter
|
||||
* - player can choose copies order on stack
|
||||
* <p>
|
||||
* Code logic:
|
||||
* - find all possible target types (any target or custom)
|
||||
* - user can choose next target type to put on stack
|
||||
* - put all copies with that target type and request next choice
|
||||
* - implemented by iterator mechanic (target type -> target filter)
|
||||
*/
|
||||
private static final class NewTargetTypeIterator 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 Map<String, MageObjectReferencePredicate> newTargetTypes = null;
|
||||
private Iterator<MageObjectReferencePredicate> currentNewTargetType = null;
|
||||
private Choice newTargetTypeChoiceDialog = null;
|
||||
|
||||
private PredicateIterator(Game game, UUID newControllerId, int amount, StackObjectCopyApplier applier) {
|
||||
private NewTargetTypeIterator(Game game, UUID newControllerId, int amount, StackObjectCopyApplier applier) {
|
||||
this.applier = applier;
|
||||
this.player = game.getPlayer(newControllerId);
|
||||
this.amount = amount;
|
||||
|
|
@ -65,39 +77,47 @@ public abstract class StackObjectImpl implements StackObject {
|
|||
return true;
|
||||
}
|
||||
|
||||
private void makeMap() {
|
||||
if (predicateMap != null) {
|
||||
private void prepareNewTargetTypes() {
|
||||
if (newTargetTypes != null) {
|
||||
// already prepared
|
||||
return;
|
||||
}
|
||||
predicateMap = new HashMap<>();
|
||||
|
||||
int currentAnyTargetNumber = 0;
|
||||
int currentFilteredTargetNumber = 0;
|
||||
|
||||
newTargetTypes = new HashMap<>();
|
||||
for (int i = 0; i < amount; i++) {
|
||||
MageObjectReferencePredicate predicate = applier.getNextPredicate();
|
||||
if (predicate == null) {
|
||||
anyCount++;
|
||||
MageObjectReferencePredicate newTargetType = applier.getNextNewTargetType(i + 1);
|
||||
if (newTargetType == null) {
|
||||
currentAnyTargetNumber++;
|
||||
String message = "Any target";
|
||||
if (anyCount > 1) {
|
||||
message += " (" + anyCount + ")";
|
||||
if (currentAnyTargetNumber > 1) {
|
||||
message += " (" + currentAnyTargetNumber + ")";
|
||||
}
|
||||
predicateMap.put(message, predicate);
|
||||
newTargetTypes.put(message, null);
|
||||
continue;
|
||||
}
|
||||
setCount++;
|
||||
predicateMap.put(predicate.getName(game), predicate);
|
||||
currentFilteredTargetNumber++;
|
||||
newTargetTypes.put(newTargetType.getName(game), newTargetType);
|
||||
}
|
||||
if ((setCount == 1 && anyCount == 0) || setCount == 0) {
|
||||
iterator = predicateMap.values().stream().collect(Collectors.toList()).iterator();
|
||||
|
||||
// if only one target type then choose it by default
|
||||
if ((currentFilteredTargetNumber == 1 && currentAnyTargetNumber == 0) || currentFilteredTargetNumber == 0) {
|
||||
currentNewTargetType = newTargetTypes.values().stream().collect(Collectors.toList()).iterator();
|
||||
}
|
||||
}
|
||||
|
||||
private void makeChoice() {
|
||||
if (choice != null) {
|
||||
private void prepareFilterChooseDialog() {
|
||||
// used choices removes from the dialog
|
||||
if (newTargetTypeChoiceDialog != null) {
|
||||
// already prepared
|
||||
return;
|
||||
}
|
||||
choice = new ChoiceImpl(false, ChoiceHintType.CARD);
|
||||
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()));
|
||||
newTargetTypeChoiceDialog = new ChoiceImpl(false, ChoiceHintType.CARD);
|
||||
newTargetTypeChoiceDialog.setMessage("Choose the order of copies to go on the stack");
|
||||
newTargetTypeChoiceDialog.setSubMessage("Press cancel to put the rest in any order");
|
||||
newTargetTypeChoiceDialog.setChoices(new HashSet<>(newTargetTypes.keySet()));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -105,24 +125,30 @@ public abstract class StackObjectImpl implements StackObject {
|
|||
if (player == null || applier == null) {
|
||||
return null;
|
||||
}
|
||||
makeMap();
|
||||
if (iterator != null) {
|
||||
return iterator.hasNext() ? iterator.next() : null;
|
||||
|
||||
prepareNewTargetTypes();
|
||||
if (currentNewTargetType != null) {
|
||||
// target type already selected
|
||||
return currentNewTargetType.hasNext() ? currentNewTargetType.next() : null;
|
||||
}
|
||||
makeChoice();
|
||||
if (choice.getChoices().size() < 2) {
|
||||
iterator = choice.getChoices().stream().map(predicateMap::get).iterator();
|
||||
|
||||
prepareFilterChooseDialog();
|
||||
if (newTargetTypeChoiceDialog.getChoices().size() < 2) {
|
||||
// no more unused target types - select the last one
|
||||
currentNewTargetType = newTargetTypeChoiceDialog.getChoices().stream().map(newTargetTypes::get).iterator();
|
||||
return next();
|
||||
}
|
||||
choice.clearChoice();
|
||||
player.choose(Outcome.AIDontUseIt, choice, game);
|
||||
String chosen = choice.getChoice();
|
||||
|
||||
// choose next target type for usage
|
||||
newTargetTypeChoiceDialog.clearChoice();
|
||||
player.choose(Outcome.AIDontUseIt, newTargetTypeChoiceDialog, game);
|
||||
String chosen = newTargetTypeChoiceDialog.getChoice();
|
||||
if (chosen == null) {
|
||||
iterator = choice.getChoices().stream().map(predicateMap::get).iterator();
|
||||
currentNewTargetType = newTargetTypeChoiceDialog.getChoices().stream().map(newTargetTypes::get).iterator();
|
||||
return next();
|
||||
}
|
||||
choice.getChoices().remove(chosen);
|
||||
return predicateMap.get(chosen);
|
||||
newTargetTypeChoiceDialog.getChoices().remove(chosen);
|
||||
return newTargetTypes.get(chosen);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -132,9 +158,9 @@ public abstract class StackObjectImpl implements StackObject {
|
|||
if (game.replaceEvent(gameEvent)) {
|
||||
return;
|
||||
}
|
||||
Iterator<MageObjectReferencePredicate> predicates = new PredicateIterator(game, newControllerId, gameEvent.getAmount(), applier);
|
||||
Iterator<MageObjectReferencePredicate> newTargetTypeIterator = new NewTargetTypeIterator(game, newControllerId, gameEvent.getAmount(), applier);
|
||||
for (int i = 0; i < gameEvent.getAmount(); i++) {
|
||||
createSingleCopy(newControllerId, applier, predicates.next(), game, source, chooseNewTargets);
|
||||
createSingleCopy(newControllerId, applier, newTargetTypeIterator.next(), game, source, chooseNewTargets);
|
||||
}
|
||||
Player player = game.getPlayer(newControllerId);
|
||||
if (player == null) {
|
||||
|
|
@ -210,18 +236,17 @@ public abstract class StackObjectImpl implements StackObject {
|
|||
* targets will be, the copy is put onto the stack with those targets.
|
||||
*
|
||||
* @param game
|
||||
* @param targetControllerId - player that can/has to change the target of
|
||||
* the spell
|
||||
* @param forceChange - does only work for targets with maximum of one
|
||||
* targetId
|
||||
* @param onlyOneTarget - 114.6b one target must be changed to another
|
||||
* target
|
||||
* @param extraPredicate restriction for the new target, if null nothing is
|
||||
* cheched
|
||||
* @param targetControllerId - player that can/has to change the target of
|
||||
* the spell
|
||||
* @param forceChange - does only work for targets with maximum of one
|
||||
* targetId
|
||||
* @param onlyOneTarget - 114.6b one target must be changed to another
|
||||
* target
|
||||
* @param newTargetFilterPredicate restriction for the new target (null - can select same targets)
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public boolean chooseNewTargets(Game game, UUID targetControllerId, boolean forceChange, boolean onlyOneTarget, Predicate<MageItem> extraPredicate) {
|
||||
public boolean chooseNewTargets(Game game, UUID targetControllerId, boolean forceChange, boolean onlyOneTarget, Predicate<MageItem> newTargetFilterPredicate) {
|
||||
Player targetController = game.getPlayer(targetControllerId);
|
||||
if (targetController != null) {
|
||||
StringBuilder oldTargetDescription = new StringBuilder();
|
||||
|
|
@ -240,7 +265,7 @@ public abstract class StackObjectImpl 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, extraPredicate, game);
|
||||
Target newTarget = chooseNewTarget(targetController, ability, mode, target, forceChange, newTargetFilterPredicate, game);
|
||||
// clear the old target and copy all targets from new target
|
||||
target.clearChosen();
|
||||
for (UUID targetId : newTarget.getTargets()) {
|
||||
|
|
@ -263,21 +288,25 @@ public abstract class StackObjectImpl implements StackObject {
|
|||
/**
|
||||
* Handles the change of one target instance of a mode
|
||||
*
|
||||
* @param targetController - player that can choose the new target
|
||||
* @param targetController - player that can choose the new target
|
||||
* @param ability
|
||||
* @param mode
|
||||
* @param target
|
||||
* @param forceChange
|
||||
* @param newTargetFilterPredicate
|
||||
* @param game
|
||||
* @return
|
||||
*/
|
||||
private Target chooseNewTarget(Player targetController, Ability ability, Mode mode, Target target, boolean forceChange, Predicate predicate, Game game) {
|
||||
private Target chooseNewTarget(Player targetController, Ability ability, Mode mode, Target target, boolean forceChange, Predicate newTargetFilterPredicate, Game game) {
|
||||
Target newTarget = target.copy();
|
||||
if (predicate != null) {
|
||||
newTarget.getFilter().add(predicate);
|
||||
|
||||
// filter targets
|
||||
if (newTargetFilterPredicate != null) {
|
||||
newTarget.getFilter().add(newTargetFilterPredicate);
|
||||
// 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
|
||||
|
|
@ -318,8 +347,8 @@ public abstract class StackObjectImpl 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 (newTargetFilterPredicate != null) {
|
||||
tempTarget.getFilter().add(newTargetFilterPredicate);
|
||||
// If adding a predicate, there will only be one choice and therefore target can be automatic
|
||||
tempTarget.setRandom(true);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,5 +13,12 @@ public interface StackObjectCopyApplier extends Serializable {
|
|||
|
||||
void modifySpell(StackObject stackObject, Game game);
|
||||
|
||||
MageObjectReferencePredicate getNextPredicate();
|
||||
/**
|
||||
* For multi copies: allows change new target filter for each next copy (e.g. add some restict)
|
||||
* Return null to use same target type as original spell
|
||||
*
|
||||
* @param copyNumber current number of copy, starts with 1
|
||||
* @return
|
||||
*/
|
||||
MageObjectReferencePredicate getNextNewTargetType(int copyNumber);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue