diff --git a/Mage/src/main/java/mage/game/stack/StackObjectImpl.java b/Mage/src/main/java/mage/game/stack/StackObjectImpl.java index a3a2c486e36..5bd3621b749 100644 --- a/Mage/src/main/java/mage/game/stack/StackObjectImpl.java +++ b/Mage/src/main/java/mage/game/stack/StackObjectImpl.java @@ -288,53 +288,73 @@ 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 target original target * @param forceChange - * @param newTargetFilterPredicate + * @param newTargetFilterPredicate additional filter for target selection (e.g. force to select 1 specific permanent) * @param game * @return */ private Target chooseNewTarget(Player targetController, Ability ability, Mode mode, Target target, boolean forceChange, Predicate newTargetFilterPredicate, Game game) { Target newTarget = target.copy(); + newTarget.clearChosen(); - // filter targets + // prepare temp target + + // additional filter for possible targets if (newTargetFilterPredicate != null) { newTarget.getFilter().add(newTargetFilterPredicate); - // If adding a predicate, there will only be one choice and therefore target can be automatic + // if adding a predicate, there will only be one choice and therefore target can be automatic + // TODO: it's a card specific code like Radiate and limit usage of newTargetFilterPredicate (only for 1 to 1 target replace), + // so must must be refactored to a standard way to check like possibleTargets/currentTargets compare newTarget.setRandom(true); } - + // TODO: new targets must be selected without TARGETED events - is it ok by rules (triggers like whenever xxx becomes the target)? newTarget.setEventReporting(false); if (!targetController.getId().equals(getControllerId())) { newTarget.setTargetController(targetController.getId()); // target controller for the change is different from spell controller newTarget.setAbilityController(getControllerId()); } - newTarget.clearChosen(); - for (UUID targetId : target.getTargets()) { - String targetNames = getNamesOftargets(targetId, game); - String targetAmount = ""; - if (target.getTargetAmount(targetId) > 0) { - targetAmount = " (amount: " + target.getTargetAmount(targetId) + ")"; - } - // change the target? - Outcome outcome = mode.getEffects().getOutcome(ability); - if (targetNames != null - && (forceChange || targetController.chooseUse(outcome, "Change this target: " + targetNames + targetAmount + '?', ability, game))) { - Set possibleTargets = target.possibleTargets(getControllerId(), ability, game); - // choose exactly one other target - already targeted objects are not counted + // try to replace each targeted with a new + // * target - original target, do not change it + // * newTarget - final target, must store result from a tempTarget, do not clear it + // * tempTarget - temporary target for choosing a single replacement + int targetNumber = 0; + for (UUID oldTargetId : target.getTargets()) { + targetNumber++; + String targetName = getNameOftarget(oldTargetId, game); + String targetAmount = ""; + if (target.getTargetAmount(oldTargetId) > 0) { + targetAmount = " (amount: " + target.getTargetAmount(oldTargetId) + ")"; + } + + Outcome outcome = mode.getEffects().getOutcome(ability); + if (targetName != null + && (forceChange || targetController.chooseUse( + outcome, + String.format("Change this %d of %d target: %s?", + targetNumber, + target.getTargets().size(), + targetName + targetAmount), + ability, + game))) { + + // try single target replacement by a new + Set possibleTargets = newTarget.possibleTargets(getControllerId(), ability, game); if (forceChange && possibleTargets != null && possibleTargets.size() > 1) { // controller of spell must be used (e.g. TargetOpponent) + // required replacement of 1 target int iteration = 0; do { if (iteration > 0 && !game.isSimulation()) { game.informPlayer(targetController, "You may only select exactly one target that must be different from the origin target!"); } iteration++; - newTarget.clearChosen(); + // TODO: bugged, must use tempTarget here also must keep amount value? + newTarget.clearChosen(); newTarget.chooseTarget(outcome, getControllerId(), ability, game); // workaround to stop infinite AI choose (remove after chooseTarget can be called with extra filter to disable some ids) @@ -342,10 +362,12 @@ public abstract class StackObjectImpl implements StackObject { break; } } - while (targetController.canRespond() && (targetId.equals(newTarget.getFirstTarget()) || newTarget.getTargets().size() != 1)); - // choose a new target + while (targetController.canRespond() && (oldTargetId.equals(newTarget.getFirstTarget()) || newTarget.getTargets().size() != 1)); } else { - // build a target definition with exactly one possible target to select that replaces old target + // optional replacement of 1 target (amount value must be same) + + // prepare temp target + // TODO: must use shared code for target prepare (see comments at the start) Target tempTarget = target.copy(); if (newTargetFilterPredicate != null) { tempTarget.getFilter().add(newTargetFilterPredicate); @@ -354,7 +376,7 @@ public abstract class StackObjectImpl implements StackObject { } tempTarget.setEventReporting(false); if (target instanceof TargetAmount) { - ((TargetAmount) tempTarget).setAmountDefinition(StaticValue.get(target.getTargetAmount(targetId))); + ((TargetAmount) tempTarget).setAmountDefinition(StaticValue.get(target.getTargetAmount(oldTargetId))); } tempTarget.setMinNumberOfTargets(1); tempTarget.setMaxNumberOfTargets(1); @@ -362,48 +384,51 @@ public abstract class StackObjectImpl implements StackObject { tempTarget.setTargetController(targetController.getId()); tempTarget.setAbilityController(getControllerId()); } + boolean again; do { + // TODO: bugged, must have workaround for infinite AI choose (see above) again = false; tempTarget.clearChosen(); if (!tempTarget.chooseTarget(outcome, getControllerId(), ability, game)) { if (targetController.chooseUse(Outcome.Benefit, "No target object selected. Reset to original target?", ability, game)) { // use previous target no target was selected - newTarget.addTarget(targetId, target.getTargetAmount(targetId), ability, game, true); + newTarget.addTarget(oldTargetId, target.getTargetAmount(oldTargetId), ability, game, true); } else { again = true; } - } else // if possible add the alternate Target - it may not be included in the old definition nor in the already selected targets of the new definition - { + } else { + // if possible add the alternate Target - it may not be included in the old definition nor in the already selected targets of the new definition if (newTarget.getTargets().contains(tempTarget.getFirstTarget()) || target.getTargets().contains(tempTarget.getFirstTarget())) { if (targetController.isHuman()) { if (targetController.chooseUse(Outcome.Benefit, "This target was already selected from origin spell. Reset to original target?", ability, game)) { // use previous target no target was selected - newTarget.addTarget(targetId, target.getTargetAmount(targetId), ability, game, true); + newTarget.addTarget(oldTargetId, target.getTargetAmount(oldTargetId), ability, game, true); } else { again = true; } } else { - newTarget.addTarget(targetId, target.getTargetAmount(targetId), ability, game, true); + newTarget.addTarget(oldTargetId, target.getTargetAmount(oldTargetId), ability, game, true); } } else if (!target.canTarget(getControllerId(), tempTarget.getFirstTarget(), ability, game)) { + // TODO: bugged, must use newTarget.canTarget instead target.canTarget? if (targetController.isHuman()) { game.informPlayer(targetController, "This target is not valid!"); again = true; } else { // keep the old - newTarget.addTarget(targetId, target.getTargetAmount(targetId), ability, game, true); + newTarget.addTarget(oldTargetId, target.getTargetAmount(oldTargetId), ability, game, true); } } else { // valid target was selected, add it to the new target definition - newTarget.addTarget(tempTarget.getFirstTarget(), target.getTargetAmount(targetId), ability, game, true); + newTarget.addTarget(tempTarget.getFirstTarget(), target.getTargetAmount(oldTargetId), ability, game, true); } } } while (again && targetController.canRespond()); } - } // keep the target - else { - newTarget.addTarget(targetId, target.getTargetAmount(targetId), ability, game, true); + } else { + // keep old target + newTarget.addTarget(oldTargetId, target.getTargetAmount(oldTargetId), ability, game, true); } } return newTarget; @@ -433,18 +458,18 @@ public abstract class StackObjectImpl implements StackObject { return false; } - private String getNamesOftargets(UUID targetId, Game game) { - MageObject object = game.getObject(targetId); - String name = null; - if (object == null) { - Player targetPlayer = game.getPlayer(targetId); - if (targetPlayer != null) { - name = targetPlayer.getLogName(); - } - } else { - name = object.getIdName(); + private String getNameOftarget(UUID targetId, Game game) { + Player targetPlayer = game.getPlayer(targetId); + if (targetPlayer != null) { + return targetPlayer.getLogName(); } - return name; + + MageObject object = game.getObject(targetId); + if (object != null) { + return object.getIdName(); + } + + return null; } @Override diff --git a/Mage/src/main/java/mage/target/TargetImpl.java b/Mage/src/main/java/mage/target/TargetImpl.java index 28376250e2e..f6adbeb4e0b 100644 --- a/Mage/src/main/java/mage/target/TargetImpl.java +++ b/Mage/src/main/java/mage/target/TargetImpl.java @@ -40,7 +40,7 @@ public abstract class TargetImpl implements Target { protected int targetTag; // can be set if other target check is needed (AnotherTargetPredicate) protected String chooseHint = null; // UI choose hints after target name - protected boolean shouldReportEvents = true; + protected boolean shouldReportEvents = true; // generates TARGET and TARGETED events (can be disabled in non targeting mode, e.g. on target change) @Override public abstract TargetImpl copy();