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

@ -47,6 +47,7 @@ import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import mage.abilities.Mode;
import mage.abilities.Modes;
import mage.abilities.SpellAbility;
import mage.abilities.costs.mana.ManaCosts;
import mage.cards.SplitCard;
@ -184,15 +185,19 @@ public class CardView extends SimpleCardView {
if (card instanceof Spell) {
Spell<?> spell = (Spell<?>) card;
for (SpellAbility spellAbility: spell.getSpellAbilities()) {
if (spellAbility.getTargets().size() > 0) {
setTargets(spellAbility.getTargets());
for(UUID modeId : spellAbility.getModes().getSelectedModes()) {
spellAbility.getModes().setMode(spellAbility.getModes().get(modeId));
if (spellAbility.getTargets().size() > 0) {
setTargets(spellAbility.getTargets());
}
}
}
// show for modal spell, which mode was choosen
if (spell.getSpellAbility().isModal()) {
Mode activeMode = spell.getSpellAbility().getModes().getMode();
if (activeMode != null) {
this.rules.add("<span color='green'><i>Chosen mode: " + activeMode.getEffects().getText(activeMode)+"</i></span>");
Modes modes = spell.getSpellAbility().getModes();
for(UUID modeId : modes.getSelectedModes()) {
modes.setMode(modes.get(modeId));
this.rules.add("<span color='green'><i>Chosen mode: " + spell.getSpellAbility().getEffects().getText(modes.get(modeId))+"</i></span>");
}
}
}

View file

@ -28,17 +28,17 @@
package mage.view;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import mage.MageObject;
import mage.abilities.Modes;
import mage.abilities.effects.Effect;
import mage.game.Game;
import mage.game.stack.StackAbility;
import mage.target.targetpointer.FixedTarget;
import mage.target.targetpointer.TargetPointer;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
/**
*
* @author BetaSteward_at_googlemail.com
@ -68,30 +68,43 @@ public class StackAbilityView extends CardView {
}
private void updateTargets(Game game, StackAbility ability) {
if (ability.getTargets().size() > 0) {
setTargets(ability.getTargets());
} else {
List<UUID> targetList = new ArrayList<UUID>();
for (Effect effect : ability.getEffects()) {
TargetPointer targetPointer = effect.getTargetPointer();
if (targetPointer instanceof FixedTarget) {
targetList.add(((FixedTarget) targetPointer).getTarget());
}
}
if (targetList.size() > 0) {
overrideTargets(targetList);
List<String> names = new ArrayList<String>();
for (UUID uuid : targetList) {
MageObject mageObject = game.getObject(uuid);
if (mageObject != null) {
names.add(mageObject.getName());
List<String> names = new ArrayList<String>();
for(UUID modeId : ability.getModes().getSelectedModes()) {
ability.getModes().setMode(ability.getModes().get(modeId));
if (ability.getTargets().size() > 0) {
setTargets(ability.getTargets());
} else {
List<UUID> targetList = new ArrayList<UUID>();
for (Effect effect : ability.getEffects()) {
TargetPointer targetPointer = effect.getTargetPointer();
if (targetPointer instanceof FixedTarget) {
targetList.add(((FixedTarget) targetPointer).getTarget());
}
}
if (!names.isEmpty()) {
getRules().add("<i>Targets: " + names.toString() + "</i>");
if (targetList.size() > 0) {
overrideTargets(targetList);
for (UUID uuid : targetList) {
MageObject mageObject = game.getObject(uuid);
if (mageObject != null) {
names.add(mageObject.getName());
}
}
}
}
}
if (!names.isEmpty()) {
getRules().add("<i>Targets: " + names.toString() + "</i>");
}
// show for modal ability, which mode was choosen
if (ability.isModal()) {
Modes modes = ability.getModes();
for(UUID modeId : modes.getSelectedModes()) {
modes.setMode(modes.get(modeId));
this.rules.add("<span color='green'><i>Chosen mode: " + ability.getEffects().getText(modes.get(modeId))+"</i></span>");
}
}
}
public CardView getSourceCard() {

View file

@ -1230,7 +1230,13 @@ public class ComputerPlayer<T extends ComputerPlayer<T>> extends PlayerImpl<T> i
return modes.getMode();
}
//TODO: improve this;
return modes.values().iterator().next();
for (Mode mode: modes.values()) {
if (!modes.getSelectedModes().contains(mode.getId()) // select only modes not already selected
&& mode.getTargets().canChoose(source.getSourceId(), source.getControllerId(), game)) { // and where targets are available
return mode;
}
}
return null;
}
@Override

View file

@ -782,18 +782,23 @@ public class HumanPlayer extends PlayerImpl<HumanPlayer> {
MageObject obj = game.getObject(source.getSourceId());
Map<UUID, String> modeMap = new LinkedHashMap<UUID, String>();
for (Mode mode: modes.values()) {
String modeText = mode.getEffects().getText(mode);
if (obj != null) {
modeText = modeText.replace("{source}", obj.getName());
if (!modes.getSelectedModes().contains(mode.getId()) // show only modes not already selected
&& mode.getTargets().canChoose(source.getSourceId(), source.getControllerId(), game)) { // and where targets are available
String modeText = mode.getEffects().getText(mode);
if (obj != null) {
modeText = modeText.replace("{source}", obj.getName());
}
modeMap.put(mode.getId(), modeText);
}
modeMap.put(mode.getId(), modeText);
}
game.fireGetModeEvent(playerId, "Choose Mode", modeMap);
waitForResponse(game);
if (response.getUUID() != null) {
for (Mode mode: modes.values()) {
if (mode.getId().equals(response.getUUID())) {
return mode;
if (modeMap.size() > 0) {
game.fireGetModeEvent(playerId, "Choose Mode", modeMap);
waitForResponse(game);
if (response.getUUID() != null) {
for (Mode mode: modes.values()) {
if (mode.getId().equals(response.getUUID())) {
return mode;
}
}
}
}

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);