/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package mage.game.stack;
import mage.MageObject;
import mage.abilities.Abilities;
import mage.abilities.AbilitiesImpl;
import mage.abilities.Ability;
import mage.abilities.Mode;
import mage.abilities.dynamicvalue.common.StaticValue;
import mage.constants.Outcome;
import mage.filter.FilterPermanent;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.players.Player;
import mage.target.Target;
import mage.target.TargetAmount;
import java.util.Set;
import java.util.UUID;
/**
*
* @author LevelX2
*/
public abstract class StackObjImpl implements StackObject {
/**
* Choose new targets for a stack Object
*
* @param game
* @param playerId Player UUID who changes the targets.
* @return
*/
public boolean chooseNewTargets(Game game, UUID playerId) {
return chooseNewTargets(game, playerId, false, false, null);
}
/**
* 114.6. Some effects allow a player to change the target(s) of a spell or
* ability, and other effects allow a player to choose new targets for a
* spell or ability.
*
* 114.6a If an effect allows a player to "change the target(s)" of a spell
* or ability, each target can be changed only to another legal target. If a
* target can't be changed to another legal target, the original target is
* unchanged, even if the original target is itself illegal by then. If all
* the targets aren't changed to other legal targets, none of them are
* changed.
*
* 114.6b If an effect allows a player to "change a target" of a spell or
* ability, the process described in rule 114.6a is followed, except that
* only one of those targets may be changed (rather than all of them or none
* of them).
*
* 114.6c If an effect allows a player to "change any targets" of a spell or
* ability, the process described in rule 114.6a is followed, except that
* any number of those targets may be changed (rather than all of them or
* none of them).
*
* 114.6d If an effect allows a player to "choose new targets" for a spell
* or ability, the player may leave any number of the targets unchanged,
* even if those targets would be illegal. If the player chooses to change
* some or all of the targets, the new targets must be legal and must not
* cause any unchanged targets to become illegal.
*
* 114.6e When changing targets or choosing new targets for a spell or
* ability, only the final set of targets is evaluated to determine whether
* the change is legal.
*
* Example: Arc Trail is a sorcery that reads "Arc Trail deals 2 damage to
* target creature or player and 1 damage to another target creature or
* player." The current targets of Arc Trail are Runeclaw Bear and Llanowar
* Elves, in that order. You cast Redirect, an instant that reads "You may
* choose new targets for target spell," targeting Arc Trail. You can change
* the first target to Llanowar Elves and change the second target to
* Runeclaw Bear.
*
* 114.7. Modal spells and abilities may have different targeting
* requirements for each mode. An effect that allows a player to change the
* target(s) of a modal spell or ability, or to choose new targets for a
* modal spell or ability, doesn't allow that player to change its mode.
* (See rule 700.2.)
*
* 706.10c Some effects copy a spell or ability and state that its
* controller may choose new targets for the copy. The player may leave any
* number of the targets unchanged, even if those targets would be illegal.
* If the player chooses to change some or all of the targets, the new
* targets must be legal. Once the player has decided what the copy's
* 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 filterNewTarget 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) {
Player targetController = game.getPlayer(targetControllerId);
if (targetController != null) {
StringBuilder oldTargetDescription = new StringBuilder();
StringBuilder newTargetDescription = new StringBuilder();
// Fused split spells or spells where "Splice on Arcane" was used can have more than one ability
Abilities<Ability> objectAbilities = new AbilitiesImpl<>();
if (this instanceof Spell) {
objectAbilities.addAll(((Spell) this).getSpellAbilities());
} else {
objectAbilities.add(getStackAbility());
}
for (Ability ability : objectAbilities) {
// Some spells can have more than one mode
for (UUID modeId : ability.getModes().getSelectedModes()) {
Mode mode = ability.getModes().get(modeId);
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);
// clear the old target and copy all targets from new target
target.clearChosen();
for (UUID targetId : newTarget.getTargets()) {
target.addTarget(targetId, newTarget.getTargetAmount(targetId), ability, game, false);
}
}
newTargetDescription.append(ability.getTargetDescription(mode.getTargets(), game));
}
}
if (!newTargetDescription.toString().equals(oldTargetDescription.toString()) && !game.isSimulation()) {
game.informPlayers(this.getLogName() + " is now " + newTargetDescription);
}
return true;
}
return false;
}
/**
* Handles the change of one target instance of a mode
*
* @param targetController - player that can choose the new target
* @param ability
* @param mode
* @param target
* @param forceChange
* @param game
* @return
*/
private Target chooseNewTarget(Player targetController, Ability ability, Mode mode, Target target, boolean forceChange, FilterPermanent filterNewTarget, Game game) {
Target newTarget = target.copy();
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);
// change the target?
Outcome outcome = mode.getEffects().isEmpty() ? Outcome.Detriment : mode.getEffects().get(0).getOutcome();
if (targetNames != null
&& (forceChange || targetController.chooseUse(outcome, "Change this target: " + targetNames + '?', ability, game))) {
Set<UUID> possibleTargets = target.possibleTargets(this.getSourceId(), getControllerId(), game);
// choose exactly one other target - already targeted objects are not counted
if (forceChange && possibleTargets != null && possibleTargets.size() > 1) { // controller of spell must be used (e.g. TargetOpponent)
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();
newTarget.chooseTarget(outcome, getControllerId(), ability, game);
// check target restriction
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();
}
}
} while (targetController.canRespond() && (targetId.equals(newTarget.getFirstTarget()) || newTarget.getTargets().size() != 1));
// choose a new target
} else {
// build a target definition with exactly one possible target to select that replaces old target
Target tempTarget = target.copy();
if (target instanceof TargetAmount) {
((TargetAmount) tempTarget).setAmountDefinition(new StaticValue(target.getTargetAmount(targetId)));
}
tempTarget.setMinNumberOfTargets(1);
tempTarget.setMaxNumberOfTargets(1);
if (!targetController.getId().equals(getControllerId())) {
tempTarget.setTargetController(targetController.getId());
tempTarget.setAbilityController(getControllerId());
}
boolean again;
do {
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, false);
} 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
{
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, false);
} else {
again = true;
}
} else {
newTarget.addTarget(targetId, target.getTargetAmount(targetId), ability, game, false);
}
} else if (!target.canTarget(getControllerId(), tempTarget.getFirstTarget(), ability, game)) {
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, false);
}
} 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, false);
}
}
} while (again && targetController.canRespond());
}
} // keep the target
else {
newTarget.addTarget(targetId, target.getTargetAmount(targetId), ability, game, false);
}
}
return newTarget;
}
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.getName();
}
return name;
}
}