Added support for casting modal spells or activating modal abilities with more than one mode to choose.

This commit is contained in:
LevelX2 2013-06-22 19:35:22 +02:00
parent d520d63e2c
commit 853810ce45
8 changed files with 201 additions and 90 deletions

View file

@ -170,9 +170,12 @@ public abstract class AbilityImpl<T extends AbilityImpl<T>> implements Ability {
if (card != null) {
card.adjustChoices(this, game);
}
if (getChoices().size() > 0 && getChoices().choose(game, this) == false) {
logger.debug("activate failed - choice");
return false;
for (UUID modeId :this.getModes().getSelectedModes()) {
this.getModes().setMode(this.getModes().get(modeId));
if (getChoices().size() > 0 && getChoices().choose(game, this) == false) {
logger.debug("activate failed - choice");
return false;
}
}
// 20121001 - 601.2b
@ -198,33 +201,37 @@ public abstract class AbilityImpl<T extends AbilityImpl<T>> implements Ability {
// its mana cost; see rule 107.3), the player announces the value of that variable.
VariableManaCost variableManaCost = handleXCosts(game, noMana);
//20121001 - 601.2c
// 601.2c The player announces his or her choice of an appropriate player, object, or zone for
// each target the spell requires. A spell may require some targets only if an alternative or
// additional cost (such as a buyback or kicker cost), or a particular mode, was chosen for it;
// otherwise, the spell is cast as though it did not require those targets. If the spell has a
// variable number of targets, the player announces how many targets he or she will choose before
// he or she announces those targets. The same target can't be chosen multiple times for any one
// instance of the word "target" on the spell. However, if the spell uses the word "target" in
// multiple places, the same object, player, or zone can be chosen once for each instance of the
// word "target" (as long as it fits the targeting criteria). If any effects say that an object
// or player must be chosen as a target, the player chooses targets so that he or she obeys the
// maximum possible number of such effects without violating any rules or effects that say that
// an object or player can't be chosen as a target. The chosen players, objects, and/or zones
// each become a target of that spell. (Any abilities that trigger when those players, objects,
// and/or zones become the target of a spell trigger at this point; they'll wait to be put on
// the stack until the spell has finished being cast.)
if (card != null) {
card.adjustTargets(this, game);
}
if (getTargets().size() > 0 && getTargets().chooseTargets(getEffects().get(0).getOutcome(), this.controllerId, this, game) == false) {
if (variableManaCost != null) {
game.informPlayers(new StringBuilder(card.getName()).append(": no valid targets with this value of X").toString());
} else {
logger.debug("activate failed - target");
for (UUID modeId :this.getModes().getSelectedModes()) {
this.getModes().setMode(this.getModes().get(modeId));
//20121001 - 601.2c
// 601.2c The player announces his or her choice of an appropriate player, object, or zone for
// each target the spell requires. A spell may require some targets only if an alternative or
// additional cost (such as a buyback or kicker cost), or a particular mode, was chosen for it;
// otherwise, the spell is cast as though it did not require those targets. If the spell has a
// variable number of targets, the player announces how many targets he or she will choose before
// he or she announces those targets. The same target can't be chosen multiple times for any one
// instance of the word "target" on the spell. However, if the spell uses the word "target" in
// multiple places, the same object, player, or zone can be chosen once for each instance of the
// word "target" (as long as it fits the targeting criteria). If any effects say that an object
// or player must be chosen as a target, the player chooses targets so that he or she obeys the
// maximum possible number of such effects without violating any rules or effects that say that
// an object or player can't be chosen as a target. The chosen players, objects, and/or zones
// each become a target of that spell. (Any abilities that trigger when those players, objects,
// and/or zones become the target of a spell trigger at this point; they'll wait to be put on
// the stack until the spell has finished being cast.)
if (card != null) {
card.adjustTargets(this, game);
}
return false;
}
if (getTargets().size() > 0 && getTargets().chooseTargets(getEffects().get(0).getOutcome(), this.controllerId, this, game) == false) {
if (variableManaCost != null) {
game.informPlayers(new StringBuilder(card != null ? card.getName(): "").append(": no valid targets with this value of X").toString());
} else {
logger.debug("activate failed - target");
}
return false;
}
} // end modes
// TODO: Handle optionalCosts at the same time as already OptionalAdditionalSourceCosts are handled.
for (Cost cost : optionalCosts) {

View file

@ -261,6 +261,22 @@ public abstract class ActivatedAbilityImpl<T extends ActivatedAbilityImpl<T>> ex
}
}
}
} else if (object instanceof Spell && ((Spell) object).getSpellAbility().getModes().size() > 1) {
Modes modes = ((Spell) object).getSpellAbility().getModes();
int item = 0;
for (Mode mode : modes.values()) {
item++;
if (modes.getSelectedModes().contains(mode.getId())) {
modes.setMode(mode);
sb.append(" (mode ").append(item).append(")");
if (getTargets().size() > 0) {
sb.append(" targeting ");
for (Target target: getTargets()) {
sb.append(target.getTargetedName(game));
}
}
}
}
} else {
if (getTargets().size() > 0) {
sb.append(" targeting ");

View file

@ -28,7 +28,10 @@
package mage.abilities;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import mage.game.Game;
import mage.players.Player;
@ -40,11 +43,18 @@ import mage.players.Player;
public class Modes extends LinkedHashMap<UUID, Mode> {
private UUID modeId;
private Set<UUID> selectedModes = new LinkedHashSet<UUID>();
private int minModes;
private int maxModes;
public Modes() {
Mode mode = new Mode();
this.put(mode.getId(), mode);
this.modeId = mode.getId();
this.minModes = 1;
this.maxModes = 1;
this.selectedModes.add(modeId);
}
public Modes(Modes modes) {
@ -52,6 +62,9 @@ public class Modes extends LinkedHashMap<UUID, Mode> {
for (Map.Entry<UUID, Mode> entry: modes.entrySet()) {
this.put(entry.getKey(), entry.getValue().copy());
}
this.minModes = modes.minModes;
this.maxModes = modes.maxModes;
this.selectedModes.addAll(modes.selectedModes);
}
public Modes copy() {
@ -62,9 +75,30 @@ public class Modes extends LinkedHashMap<UUID, Mode> {
return get(modeId);
}
public Set<UUID> getSelectedModes() {
return selectedModes;
}
public void setMinModes(int minModes) {
this.minModes = minModes;
}
public int getMinModes() {
return this.minModes;
}
public void setMaxModes(int maxModes) {
this.maxModes = maxModes;
}
public int getMaxModes() {
return this.maxModes;
}
public void setMode(Mode mode) {
if (this.containsKey(mode.getId())) {
this.modeId = mode.getId();
this.selectedModes.add(mode.getId());
}
}
@ -74,40 +108,59 @@ public class Modes extends LinkedHashMap<UUID, Mode> {
public boolean choose(Game game, Ability source) {
if (this.size() > 1) {
this.selectedModes.clear();
Player player = game.getPlayer(source.getControllerId());
Mode choice = player.chooseMode(this, source, game);
if (choice == null) {
return false;
while (this.selectedModes.size() < this.getMaxModes()) {
Mode choice = player.chooseMode(this, source, game);
if (choice == null) {
return this.selectedModes.size() >= this.getMinModes();
}
setMode(choice);
this.selectedModes.add(choice.getId());
}
setMode(choice);
return true;
}
this.modeId = this.values().iterator().next().getId();
this.selectedModes.add(modeId);
return true;
}
public String getText() {
String andOr = "";
StringBuilder sb = new StringBuilder();
if (this.size() > 1) {
sb.append("Choose one - ");
if (this.getMinModes() == 1 && this.getMaxModes() == 3) {
sb.append("Choose one or more - ");
andOr = "; and/or ";
}else if (this.getMinModes() == 1 && this.getMaxModes() == 2) {
sb.append("Choose one or both - ");
andOr = "; and/or ";
} else if (this.getMinModes() == 2 && this.getMaxModes() == 2) {
sb.append("Choose two - ");
andOr = "; or ";
} else {
sb.append("Choose one - ");
andOr = "; or ";
}
}
for (Mode mode: this.values()) {
sb.append(mode.getEffects().getText(mode)).append("; or ");
sb.append(mode.getEffects().getText(mode));
if (this.size() > 1) {
if (sb.length() > 2 && sb.substring(sb.length()-2, sb.length()).equals(". ")) {
sb.delete(sb.length()-2, sb.length());
}
sb.append(andOr);
}
}
if (this.size() > 1) {
sb.delete(sb.length() - andOr.length(), sb.length());
}
sb.delete(sb.length() - 5, sb.length());
return sb.toString();
}
public String getText(String sourceName) {
StringBuilder sb = new StringBuilder();
if (this.size() > 1) {
sb.append("Choose one - ");
}
for (Mode mode: this.values()) {
sb.append(mode.getEffects().getText(mode)).append("; or ");
}
sb.delete(sb.length() - 5, sb.length());
String text = sb.toString();
String text = getText();
text = text.replace("{this}", sourceName);
text = text.replace("{source}", sourceName);
return text;

View file

@ -138,10 +138,13 @@ public class Spell<T extends Spell<T>> implements StackObject, Card {
result = false;
boolean legalParts = false;
for(SpellAbility spellAbility: this.spellAbilities) {
if (spellAbility.getTargets().stillLegal(spellAbility, game)) {
legalParts = true;
updateOptionalCosts(index);
result |= spellAbility.resolve(game);
for (UUID modeId :spellAbility.getModes().getSelectedModes()) {
spellAbility.getModes().setMode(spellAbility.getModes().get(modeId));
if (spellAbility.getTargets().stillLegal(spellAbility, game)) {
legalParts = true;
updateOptionalCosts(index);
result |= spellAbility.resolve(game);
}
}
index++;
}
@ -226,13 +229,16 @@ public class Spell<T extends Spell<T>> implements StackObject, Card {
String name = null;
if (object == null) {
Player targetPlayer = game.getPlayer(targetId);
if (targetPlayer != null) name = targetPlayer.getName();
if (targetPlayer != null) {
name = targetPlayer.getName();
}
} else {
name = object.getName();
}
if (name != null && player.chooseUse(spellAbility.getEffects().get(0).getOutcome(), "Change target from " + name + "?", game)) {
if (!player.chooseTarget(spellAbility.getEffects().get(0).getOutcome(), newTarget, spellAbility, game))
if (!player.chooseTarget(spellAbility.getEffects().get(0).getOutcome(), newTarget, spellAbility, game)) {
newTarget.addTarget(targetId, spellAbility, game, false);
}
}
else {
newTarget.addTarget(targetId, spellAbility, game, false);