InputSelectCards made synchronous, select cards to sacrifice uses that input instead on GuiChoose...

Added check to MagicStack add to prevent spells being added by EDT.
This commit is contained in:
Maxmtg
2013-03-25 09:13:42 +00:00
parent 7de818de6f
commit ddc2315699
17 changed files with 304 additions and 473 deletions

7
.gitattributes vendored
View File

@@ -13814,10 +13814,11 @@ src/main/java/forge/control/input/InputPayManaOfCostPayment.java -text
src/main/java/forge/control/input/InputPayManaSimple.java svneol=native#text/plain src/main/java/forge/control/input/InputPayManaSimple.java svneol=native#text/plain
src/main/java/forge/control/input/InputPayManaX.java -text src/main/java/forge/control/input/InputPayManaX.java -text
src/main/java/forge/control/input/InputPayReturnCost.java -text src/main/java/forge/control/input/InputPayReturnCost.java -text
src/main/java/forge/control/input/InputPaySacCost.java -text
src/main/java/forge/control/input/InputPayment.java -text src/main/java/forge/control/input/InputPayment.java -text
src/main/java/forge/control/input/InputSelectMany.java -text src/main/java/forge/control/input/InputSelectCards.java -text
src/main/java/forge/control/input/InputSelectManyCards.java -text src/main/java/forge/control/input/InputSelectCardsFromList.java -text
src/main/java/forge/control/input/InputSelectList.java -text
src/main/java/forge/control/input/InputSelectListBase.java -text
src/main/java/forge/control/input/InputSynchronized.java -text src/main/java/forge/control/input/InputSynchronized.java -text
src/main/java/forge/control/input/InputSyncronizedBase.java -text src/main/java/forge/control/input/InputSyncronizedBase.java -text
src/main/java/forge/control/input/package-info.java svneol=native#text/plain src/main/java/forge/control/input/package-info.java svneol=native#text/plain

View File

@@ -29,6 +29,7 @@ import com.google.common.collect.Iterables;
import forge.Card; import forge.Card;
import forge.CardLists; import forge.CardLists;
import forge.CardPredicates; import forge.CardPredicates;
import forge.FThreads;
import forge.CardPredicates.Presets; import forge.CardPredicates.Presets;
import forge.Command; import forge.Command;
import forge.CounterType; import forge.CounterType;
@@ -44,8 +45,7 @@ import forge.card.spellability.SpellPermanent;
import forge.card.spellability.Target; import forge.card.spellability.Target;
import forge.card.trigger.Trigger; import forge.card.trigger.Trigger;
import forge.card.trigger.TriggerHandler; import forge.card.trigger.TriggerHandler;
import forge.control.input.InputBase; import forge.control.input.InputSelectCards;
import forge.control.input.InputSelectManyCards;
import forge.game.ai.ComputerUtilCard; import forge.game.ai.ComputerUtilCard;
import forge.game.ai.ComputerUtilCombat; import forge.game.ai.ComputerUtilCombat;
import forge.game.event.TokenCreatedEvent; import forge.game.event.TokenCreatedEvent;
@@ -477,59 +477,51 @@ public class CardFactoryCreatures {
private static void getCard_PhyrexianDreadnought(final Card card, final String cardName) { private static void getCard_PhyrexianDreadnought(final Card card, final String cardName) {
final Player player = card.getController(); final Player player = card.getController();
final InputBase target = new InputSelectManyCards(0, Integer.MAX_VALUE) {
private static final long serialVersionUID = 2698036349873486664L;
@Override
public String getMessage() {
String toDisplay = cardName + " - Select any number of creatures to sacrifice. ";
toDisplay += "Currently, (" + selected.size() + ") selected with a total power of: "
+ getTotalPower();
toDisplay += " Click OK when Done.";
return toDisplay;
}
@Override
protected boolean canCancelWithSomethingSelected() {
return true;
}
@Override
protected void onCancel() {
Singletons.getModel().getGame().getAction().sacrifice(card, null);
}
@Override
protected boolean isValidChoice(Card c) {
Zone zone = Singletons.getModel().getGame().getZoneOf(c);
return c.isCreature() && zone.is(ZoneType.Battlefield, player);
} // selectCard()
@Override
protected void onDone() {
for (final Card sac : selected) {
Singletons.getModel().getGame().getAction().sacrifice(sac, null);
}
}
@Override
protected boolean hasEnoughTargets() {
return getTotalPower() >= 12;
};
private int getTotalPower() {
int sum = 0;
for (final Card c : selected) {
sum += c.getNetAttack();
}
return sum;
}
}; // Input
final Ability sacOrSac = new Ability(card, ManaCost.NO_COST) { final Ability sacOrSac = new Ability(card, ManaCost.NO_COST) {
@Override @Override
public void resolve() { public void resolve() {
if (player.isHuman()) { if (player.isHuman()) {
final InputSelectCards target = new InputSelectCards(0, Integer.MAX_VALUE) {
private static final long serialVersionUID = 2698036349873486664L;
@Override
public String getMessage() {
String toDisplay = cardName + " - Select any number of creatures to sacrifice. ";
toDisplay += "Currently, (" + selected.size() + ") selected with a total power of: " + getTotalPower();
toDisplay += " Click OK when Done.";
return toDisplay;
}
@Override
protected boolean isValidChoice(Card c) {
Zone zone = Singletons.getModel().getGame().getZoneOf(c);
return c.isCreature() && zone.is(ZoneType.Battlefield, player);
} // selectCard()
@Override
protected boolean hasEnoughTargets() {
return getTotalPower() >= 12;
};
private int getTotalPower() {
int sum = 0;
for (final Card c : selected) {
sum += c.getNetAttack();
}
return sum;
}
}; // Input
target.setCancelWithSelectedAllowed(true);
FThreads.setInputAndWait(target);
if(!target.hasCancelled()) {
for (final Card sac : target.getSelected()) {
Singletons.getModel().getGame().getAction().sacrifice(sac, null);
}
} else {
Singletons.getModel().getGame().getAction().sacrifice(card, null);
}
Singletons.getModel().getMatch().getInput().setInput(target); Singletons.getModel().getMatch().getInput().setInput(target);
} }
} // end resolve } // end resolve

View File

@@ -24,8 +24,9 @@ import javax.swing.JOptionPane;
import forge.Card; import forge.Card;
import forge.CardLists; import forge.CardLists;
import forge.Command; import forge.Command;
import forge.FThreads;
import forge.Singletons; import forge.Singletons;
import forge.control.input.InputSelectManyCards; import forge.control.input.InputSelectCards;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.zone.Zone; import forge.game.zone.Zone;
import forge.game.zone.ZoneType; import forge.game.zone.ZoneType;
@@ -45,7 +46,7 @@ class CardFactoryLands {
* TODO: Write javadoc for this type. * TODO: Write javadoc for this type.
* *
*/ */
private static final class InputRevealCardType extends InputSelectManyCards { private static final class InputRevealCardType extends InputSelectCards {
private final String type; private final String type;
private final Card card; private final Card card;
private static final long serialVersionUID = -2774066137824255680L; private static final long serialVersionUID = -2774066137824255680L;
@@ -73,21 +74,6 @@ class CardFactoryLands {
Zone zone = Singletons.getModel().getGame().getZoneOf(c); Zone zone = Singletons.getModel().getGame().getZoneOf(c);
return zone.is(ZoneType.Hand) && c.isType(type) && c.getController() == card.getController(); return zone.is(ZoneType.Hand) && c.isType(type) && c.getController() == card.getController();
} }
@Override
protected void onDone() {
if (selected.isEmpty()) {
onCancel();
}
String cardName = selected.get(0).getName();
JOptionPane.showMessageDialog(null, "Revealed card: " + cardName, cardName, JOptionPane.PLAIN_MESSAGE);
}
@Override
public void onCancel() {
card.setTapped(true);
}
} }
/** /**
@@ -226,7 +212,16 @@ class CardFactoryLands {
} }
public void humanExecute() { public void humanExecute() {
Singletons.getModel().getMatch().getInput().setInput(new InputRevealCardType(0, 1, type, card)); InputSelectCards inp = new InputRevealCardType(0, 1, type, card);
FThreads.setInputAndWait(inp);
if ( inp.hasCancelled() || inp.getSelected().isEmpty() ) {
card.setTapped(true);
} else {
String cardName = inp.getSelected().get(0).getName();
JOptionPane.showMessageDialog(null, "Revealed card: " + cardName, cardName, JOptionPane.PLAIN_MESSAGE);
}
} // execute() } // execute()
private void revealCard(final Card c) { private void revealCard(final Card c) {

View File

@@ -38,6 +38,7 @@ import forge.CardUtil;
import forge.Command; import forge.Command;
import forge.Constant; import forge.Constant;
import forge.CounterType; import forge.CounterType;
import forge.FThreads;
import forge.GameEntity; import forge.GameEntity;
import forge.Singletons; import forge.Singletons;
import forge.card.ability.AbilityFactory; import forge.card.ability.AbilityFactory;
@@ -64,7 +65,7 @@ import forge.card.trigger.TriggerHandler;
import forge.card.trigger.TriggerType; import forge.card.trigger.TriggerType;
import forge.control.input.Input; import forge.control.input.Input;
import forge.control.input.InputBase; import forge.control.input.InputBase;
import forge.control.input.InputSelectManyCards; import forge.control.input.InputSelectCards;
import forge.game.GameState; import forge.game.GameState;
import forge.game.ai.ComputerUtil; import forge.game.ai.ComputerUtil;
import forge.game.ai.ComputerUtilCard; import forge.game.ai.ComputerUtilCard;
@@ -3152,26 +3153,6 @@ public class CardFactoryUtil {
}; };
haunterDiesWork.setDescription(hauntDescription); haunterDiesWork.setDescription(hauntDescription);
final InputSelectManyCards target = new InputSelectManyCards(1, 1) {
private static final long serialVersionUID = 1981791992623774490L;
@Override
protected void onDone() {
haunterDiesWork.setTargetCard(selected.get(0));
Singletons.getModel().getGame().getStack().add(haunterDiesWork);
}
@Override
protected boolean isValidChoice(Card c) {
Zone zone = Singletons.getModel().getGame().getZoneOf(c);
if (!zone.is(ZoneType.Battlefield) || !c.isCreature()) {
return false;
}
return c.canBeTargetedBy(haunterDiesWork);
}
};
target.setMessage("Choose target creature to haunt.");
final Ability haunterDiesSetup = new Ability(card, ManaCost.ZERO) { final Ability haunterDiesSetup = new Ability(card, ManaCost.ZERO) {
@Override @Override
public void resolve() { public void resolve() {
@@ -3189,7 +3170,25 @@ public class CardFactoryUtil {
// need to do it this way because I don't know quite how to // need to do it this way because I don't know quite how to
// make TriggerHandler respect BeforePayMana. // make TriggerHandler respect BeforePayMana.
if (card.getController().isHuman()) { if (card.getController().isHuman()) {
Singletons.getModel().getMatch().getInput().setInput(target);
final InputSelectCards target = new InputSelectCards(1, 1) {
private static final long serialVersionUID = 1981791992623774490L;
@Override
protected boolean isValidChoice(Card c) {
Zone zone = Singletons.getModel().getGame().getZoneOf(c);
if (!zone.is(ZoneType.Battlefield) || !c.isCreature()) {
return false;
}
return c.canBeTargetedBy(haunterDiesWork);
}
};
target.setMessage("Choose target creature to haunt.");
FThreads.setInputAndWait(target);
if (!target.hasCancelled()) {
haunterDiesWork.setTargetCard(target.getSelected().get(0));
Singletons.getModel().getGame().getStack().add(haunterDiesWork);
}
} else { } else {
// AI choosing what to haunt // AI choosing what to haunt
final List<Card> oppCreats = CardLists.filterControlledBy(creats, card.getController().getOpponent()); final List<Card> oppCreats = CardLists.filterControlledBy(creats, card.getController().getOpponent());

View File

@@ -25,7 +25,6 @@ import forge.card.cardfactory.CardFactoryUtil;
import forge.card.cost.CostDiscard; import forge.card.cost.CostDiscard;
import forge.card.spellability.SpellAbility; import forge.card.spellability.SpellAbility;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.zone.PlayerZone;
import forge.game.zone.ZoneType; import forge.game.zone.ZoneType;
import forge.gui.match.CMatchUI; import forge.gui.match.CMatchUI;
import forge.view.ButtonUtil; import forge.view.ButtonUtil;
@@ -108,74 +107,40 @@ public class InputPayDiscardCostWithCommands extends InputSyncronizedBase implem
/** {@inheritDoc} */ /** {@inheritDoc} */
@Override @Override
public void selectButtonCancel() { public void selectButtonCancel() {
this.cancel(); for (Card selected : this.discardCost.getList()) {
selected.setUsedToPay(false);
}
this.bPaid = false;
this.stop();
} }
/** {@inheritDoc} */ /** {@inheritDoc} */
@Override @Override
public void selectButtonOK() { public void selectButtonOK() {
this.done(); for (Card selected : this.discardCost.getList()) {
selected.setUsedToPay(false);
Singletons.getControl().getPlayer().discard(selected, this.ability);
}
this.discardCost.addListToHash(this.ability, "Discarded");
this.bPaid = true;
this.stop();
} }
/** {@inheritDoc} */ /** {@inheritDoc} */
@Override @Override
public void selectCard(final Card card) { public void selectCard(final Card card) {
if (this.choiceList.contains(card) && this.numChosen < numRequired) {
this.numChosen++;
this.discardCost.addToList(card);
card.setUsedToPay(true);
this.choiceList.remove(card);
this.showMessage();
}
}
/**
* <p>
* unselectCard.
* </p>
*
* @param card
* a {@link forge.Card} object.
* @param zone
* a {@link forge.game.zone.PlayerZone} object.
*/
public void unselectCard(final Card card, final PlayerZone zone) {
if (this.discardCost.getList().contains(card)) { if (this.discardCost.getList().contains(card)) {
this.numChosen--; this.numChosen--;
card.setUsedToPay(false); card.setUsedToPay(false);
this.discardCost.getList().remove(card); this.discardCost.getList().remove(card);
this.choiceList.add(card); this.choiceList.add(card);
this.showMessage();
} }
} else if (this.choiceList.contains(card) && this.numChosen < numRequired) {
this.numChosen++;
/** this.discardCost.addToList(card);
* <p> card.setUsedToPay(true);
* executes paid commmand. this.choiceList.remove(card);
* </p>
*
*/
public void done() {
for (Card selected : this.discardCost.getList()) {
selected.setUsedToPay(false);
Singletons.getControl().getPlayer().discard(selected, this.ability);
} }
this.discardCost.addListToHash(ability, "Discarded"); this.showMessage();
bPaid = true;
this.stop();
}
/**
* <p>
* executes unpaid commmand.
* </p>
*
*/
public void cancel() {
for (Card selected : this.discardCost.getList()) {
selected.setUsedToPay(false);
}
bPaid = false;
this.stop();
} }
} }

View File

@@ -1,188 +0,0 @@
/*
* Forge: Play Magic: the Gathering.
* Copyright (C) 2011 Forge Team
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package forge.control.input;
import java.util.List;
import forge.Card;
import forge.CardLists;
import forge.Command;
import forge.Singletons;
import forge.card.cardfactory.CardFactoryUtil;
import forge.card.cost.CostSacrifice;
import forge.card.spellability.SpellAbility;
import forge.game.player.Player;
import forge.game.zone.PlayerZone;
import forge.game.zone.ZoneType;
import forge.gui.match.CMatchUI;
import forge.view.ButtonUtil;
//if cost is paid, Command.execute() is called
/**
* <p>
* Input_PayManaCost_Ability class.
* </p>
*
* @author Forge
* @version $Id: InputPayManaCostAbility.java 15673 2012-05-23 14:01:35Z ArsenalNut $
*/
public class InputPaySacCost extends InputBase {
/**
* Constant <code>serialVersionUID=2685832214529141991L</code>.
*/
private static final long serialVersionUID = 2685832214529141991L;
private int numChosen = 0;
private int numRequired = 0;
private List<Card> choiceList;
private CostSacrifice sacCost;
private SpellAbility ability;
private Command paid;
private Command unpaid;
/**
* <p>
* Constructor for Input_PayManaCost_Ability.
* </p>
*
* @param cost
* a {@link forge.card.cost.CostSacrifice} object.
* @param sa
* a {@link forge.card.spellability.SpellAbility} object.
* @param paidCommand
* a {@link forge.Command} object.
* @param unpaidCommand
* a {@link forge.Command} object.
*/
public InputPaySacCost(final CostSacrifice cost, final SpellAbility sa, final Command paidCommand,
final Command unpaidCommand) {
final Card source = sa.getSourceCard();
this.ability = sa;
this.sacCost = cost;
Player human = Singletons.getControl().getPlayer();
this.choiceList = CardLists.getValidCards(human.getCardsIn(ZoneType.Battlefield), cost.getType().split(";"), human, source);
String amountString = cost.getAmount();
this.numRequired = amountString.matches("[0-9][0-9]?") ? Integer.parseInt(amountString)
: CardFactoryUtil.xCount(source, source.getSVar(amountString));
this.paid = paidCommand;
this.unpaid = unpaidCommand;
}
/** {@inheritDoc} */
@Override
public void showMessage() {
final StringBuilder msg = new StringBuilder("Sacrifice ");
final int nLeft = this.numRequired - this.numChosen;
msg.append(nLeft).append(" ");
msg.append(this.sacCost.getDescriptiveType());
if (nLeft > 1) {
msg.append("s");
}
if (!this.sacCost.getList().isEmpty()) {
msg.append("\r\nSelected:\r\n");
for (Card selected : this.sacCost.getList()) {
msg.append(selected + "\r\n");
}
}
CMatchUI.SINGLETON_INSTANCE.showMessage(msg.toString());
if (nLeft > 0) {
ButtonUtil.enableOnlyCancel();
}
else {
ButtonUtil.enableAllFocusOk();
}
}
/** {@inheritDoc} */
@Override
public void selectButtonCancel() {
this.cancel();
}
/** {@inheritDoc} */
@Override
public void selectButtonOK() {
this.done();
}
/** {@inheritDoc} */
@Override
public void selectCard(final Card card) {
if (this.choiceList.contains(card) && this.numChosen < numRequired) {
this.numChosen++;
this.sacCost.addToList(card);
card.setUsedToPay(true);
this.choiceList.remove(card);
this.showMessage();
}
}
/**
* <p>
* unselectCard.
* </p>
*
* @param card
* a {@link forge.Card} object.
* @param zone
* a {@link forge.game.zone.PlayerZone} object.
*/
public void unselectCard(final Card card, final PlayerZone zone) {
if (this.sacCost.getList().contains(card)) {
this.numChosen--;
card.setUsedToPay(false);
this.sacCost.getList().remove(card);
this.choiceList.add(card);
this.showMessage();
}
}
/**
* <p>
* executes paid commmand.
* </p>
*
*/
public void done() {
this.stop();
// actually sacrifice the cards
for (Card selected : this.sacCost.getList()) {
selected.setUsedToPay(false);
Singletons.getModel().getGame().getAction().sacrifice(selected, this.ability);
}
this.sacCost.addListToHash(ability, "Sacrificed");
this.paid.execute();
}
/**
* <p>
* executes unpaid commmand.
* </p>
*
*/
public void cancel() {
this.stop();
for (Card selected : this.sacCost.getList()) {
selected.setUsedToPay(false);
}
this.unpaid.execute();
}
}

View File

@@ -0,0 +1,37 @@
package forge.control.input;
import forge.Card;
public abstract class InputSelectCards extends InputSelectListBase<Card> {
private static final long serialVersionUID = -6609493252672573139L;
protected InputSelectCards(int min, int max) {
super(min, max);
}
@Override
public final void selectCard(final Card c) {
selectEntity(c);
}
/* (non-Javadoc)
* @see forge.control.input.InputSelectListBase#onSelectStateChanged(forge.GameEntity, boolean)
*/
@Override
protected void onSelectStateChanged(Card c, boolean newState) {
c.setUsedToPay(newState); // UI supports card highlighting though this abstraction-breaking mechanism
}
/* (non-Javadoc)
* @see forge.control.input.InputSyncronizedBase#afterStop()
*/
@Override
protected void afterStop() {
for(Card c : selected)
c.setUsedToPay(false);
super.afterStop(); // It's ultimatelly important to keep call to super class!
}
}

View File

@@ -0,0 +1,22 @@
package forge.control.input;
import java.util.List;
import forge.Card;
public class InputSelectCardsFromList extends InputSelectCards {
private static final long serialVersionUID = 6230360322294805986L;
private final List<Card> validChoices;
public InputSelectCardsFromList(int min, int max, List<Card> validCards) {
super(min, max);
this.validChoices = validCards;
}
@Override
protected final boolean isValidChoice(Card choice) {
return validChoices.contains(choice);
}
}

View File

@@ -0,0 +1,12 @@
package forge.control.input;
import java.util.List;
/**
* TODO: Write javadoc for this type.
*
*/
public interface InputSelectList<T> extends InputSynchronized {
boolean hasCancelled();
List<T> getSelected();
}

View File

@@ -7,22 +7,23 @@ import forge.GameEntity;
import forge.gui.match.CMatchUI; import forge.gui.match.CMatchUI;
import forge.view.ButtonUtil; import forge.view.ButtonUtil;
/** public abstract class InputSelectListBase<T extends GameEntity> extends InputSyncronizedBase implements InputSelectList<T> {
* TODO: Write javadoc for this type.
*
*/
public abstract class InputSelectMany<T extends GameEntity> extends InputBase {
private static final long serialVersionUID = -2305549394512889450L; private static final long serialVersionUID = -2305549394512889450L;
protected final List<T> selected = new ArrayList<T>(); protected final List<T> selected;
protected boolean bCancelled = false;
protected final int min; protected final int min;
protected final int max; protected final int max;
public boolean allowUnselect = false;
private boolean allowCancelWithNotEmptyList = false;
private String message = "Source-Card-Name - Select %d more card(s)"; private String message = "Source-Card-Name - Select %d more card(s)";
protected InputSelectMany(int min, int max) {
protected InputSelectListBase(int min, int max) {
selected = new ArrayList<T>();
if (min > max) { if (min > max) {
throw new IllegalArgumentException("Min must not be greater than Max"); throw new IllegalArgumentException("Min must not be greater than Max");
} }
@@ -35,7 +36,7 @@ public abstract class InputSelectMany<T extends GameEntity> extends InputBase {
String msgToShow = getMessage(); String msgToShow = getMessage();
CMatchUI.SINGLETON_INSTANCE.showMessage(msgToShow); CMatchUI.SINGLETON_INSTANCE.showMessage(msgToShow);
boolean canCancel = (min == 0 && selected.isEmpty()) || canCancelWithSomethingSelected(); boolean canCancel = (min == 0 && selected.isEmpty()) || isCancelWithSelectedAllowed();
boolean canOk = hasEnoughTargets(); boolean canOk = hasEnoughTargets();
if (canOk && canCancel) { if (canOk && canCancel) {
@@ -65,9 +66,18 @@ public abstract class InputSelectMany<T extends GameEntity> extends InputBase {
@Override @Override
public final void selectButtonCancel() { public final void selectButtonCancel() {
bCancelled = true;
this.stop(); this.stop();
// for a next use }
selected.clear();
@Override
public final boolean hasCancelled() {
return bCancelled;
}
@Override
public final List<T> getSelected() {
return selected;
} }
@Override @Override
@@ -77,8 +87,6 @@ public abstract class InputSelectMany<T extends GameEntity> extends InputBase {
// if it does, uncomment the 5 lines below, use them as method body // if it does, uncomment the 5 lines below, use them as method body
this.stop(); this.stop();
// for a next use
selected.clear();
} }
public void setMessage(String message0) { public void setMessage(String message0) {
@@ -86,27 +94,41 @@ public abstract class InputSelectMany<T extends GameEntity> extends InputBase {
} }
// must define these // must define these
protected abstract void onDone();
protected abstract boolean isValidChoice(T choice); protected abstract boolean isValidChoice(T choice);
// might re-define later // might re-define later
protected void onCancel() {}
protected boolean canCancelWithSomethingSelected() { return false; }
protected boolean hasEnoughTargets() { return selected.size() >= min; } protected boolean hasEnoughTargets() { return selected.size() >= min; }
protected boolean hasAllTargets() { return selected.size() >= max; } protected boolean hasAllTargets() { return selected.size() >= max; }
protected void onSelectStateChanged(T c, boolean newState) {} // Select card inputs may highlight selected cards with this method
protected void selectEntity(T c) { protected void selectEntity(T c) {
if (!isValidChoice(c)) {
if (selected.contains(c) || !isValidChoice(c)) {
return; return;
} }
this.selected.add(c); if ( selected.contains(c) ) {
this.showMessage(); if ( allowUnselect ) {
this.selected.remove(c);
onSelectStateChanged(c, false);
}
} else {
this.selected.add(c);
onSelectStateChanged(c, true);
}
if (hasAllTargets()) { if (hasAllTargets()) {
selectButtonOK(); selectButtonOK();
} else {
this.showMessage();
} }
} }
public final boolean isUnselectAllowed() { return allowUnselect; }
public final void setUnselectAllowed(boolean allow) { this.allowUnselect = allow; }
public final boolean isCancelWithSelectedAllowed() { return allowCancelWithNotEmptyList; }
public final void setCancelWithSelectedAllowed(boolean allow) { this.allowCancelWithNotEmptyList = allow ; }
} }

View File

@@ -1,17 +0,0 @@
package forge.control.input;
import forge.Card;
public abstract class InputSelectManyCards extends InputSelectMany<Card> {
private static final long serialVersionUID = -6609493252672573139L;
protected InputSelectManyCards(int min, int max) {
super(min, max);
}
@Override
public final void selectCard(final Card c) {
selectEntity(c);
}
}

View File

@@ -66,6 +66,7 @@ public class GameActionPlay {
* a {@link forge.card.spellability.SpellAbility} object. * a {@link forge.card.spellability.SpellAbility} object.
*/ */
public final void playSpellAbilityWithoutPayingManaCost(final SpellAbility sa) { public final void playSpellAbilityWithoutPayingManaCost(final SpellAbility sa) {
FThreads.checkEDT("GameActionPlay.playSpellAbilityWithoutPayingManaCost", false);
final Card source = sa.getSourceCard(); final Card source = sa.getSourceCard();
setSplitCardState(source, sa); // Split card support setSplitCardState(source, sa); // Split card support

View File

@@ -41,7 +41,7 @@ import forge.card.spellability.AbilityStatic;
import forge.card.spellability.SpellAbility; import forge.card.spellability.SpellAbility;
import forge.control.input.InputBase; import forge.control.input.InputBase;
import forge.control.input.InputPayManaExecuteCommands; import forge.control.input.InputPayManaExecuteCommands;
import forge.control.input.InputSelectManyCards; import forge.control.input.InputSelectCards;
import forge.game.GameActionUtil; import forge.game.GameActionUtil;
import forge.game.GameState; import forge.game.GameState;
import forge.game.ai.ComputerUtil; import forge.game.ai.ComputerUtil;
@@ -460,26 +460,20 @@ public class Upkeep extends Phase {
@Override @Override
public void resolve() { public void resolve() {
final List<Card> targets = CardLists.getTargetableCards(abyssGetTargets, this); final List<Card> targets = CardLists.getTargetableCards(abyssGetTargets, this);
final InputBase chooseArt = new InputSelectManyCards(1, 1) {
private static final long serialVersionUID = 4820011040853968644L;
@Override
public String getMessage() {
return abyss.getName() + " - Select one nonartifact creature to destroy";
}
@Override
protected boolean isValidChoice(Card choice) {
return choice.isCreature() && !choice.isArtifact() && canTarget(choice) && choice.getController() == player;
};
@Override
protected void onDone() {
game.getAction().destroyNoRegeneration(selected.get(0));
}
};
if (player.isHuman() && targets.size() > 0) { if (player.isHuman() && targets.size() > 0) {
Singletons.getModel().getMatch().getInput().setInput(chooseArt); // Input final InputSelectCards chooseArt = new InputSelectCards(1, 1) {
private static final long serialVersionUID = 4820011040853968644L;
@Override
protected boolean isValidChoice(Card choice) {
return choice.isCreature() && !choice.isArtifact() && canTarget(choice) && choice.getController() == player;
};
};
chooseArt.setMessage(abyss.getName() + " - Select one nonartifact creature to destroy");
FThreads.setInputAndWait(chooseArt); // Input
if (!chooseArt.hasCancelled()) {
game.getAction().destroyNoRegeneration(chooseArt.getSelected().get(0));
}
} else { // computer } else { // computer
final List<Card> indestruct = CardLists.getKeyword(targets, "Indestructible"); final List<Card> indestruct = CardLists.getKeyword(targets, "Indestructible");

View File

@@ -11,12 +11,15 @@ import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.ImmutablePair; import org.apache.commons.lang3.tuple.ImmutablePair;
import forge.Card; import forge.Card;
import forge.FThreads;
import forge.GameEntity; import forge.GameEntity;
import forge.card.spellability.SpellAbility; import forge.card.spellability.SpellAbility;
import forge.control.input.Input; import forge.control.input.Input;
import forge.control.input.InputBlock; import forge.control.input.InputBlock;
import forge.control.input.InputCleanup; import forge.control.input.InputCleanup;
import forge.control.input.InputPassPriority; import forge.control.input.InputPassPriority;
import forge.control.input.InputSelectCards;
import forge.control.input.InputSelectCardsFromList;
import forge.deck.CardPool; import forge.deck.CardPool;
import forge.deck.Deck; import forge.deck.Deck;
import forge.deck.DeckSection; import forge.deck.DeckSection;
@@ -212,26 +215,18 @@ public class PlayerControllerHuman extends PlayerController {
*/ */
@Override @Override
public List<Card> choosePermanentsToSacrifice(List<Card> validTargets, int amount, SpellAbility sa, boolean destroy, boolean isOptional) { public List<Card> choosePermanentsToSacrifice(List<Card> validTargets, int amount, SpellAbility sa, boolean destroy, boolean isOptional) {
List<Card> result = new ArrayList<Card>(); int max = Math.min(amount, validTargets.size());
if (max == 0)
return new ArrayList<Card>();
for (int i = 0; i < amount; i++) { InputSelectCards inp = new InputSelectCardsFromList(isOptional ? 0 : amount, max, validTargets);
if (validTargets.isEmpty()) { // TODO: Either compose a message here, or pass it as parameter from caller.
break; inp.setMessage("Select cards to sacrifice");
}
Card c; FThreads.setInputAndWait(inp);
if (isOptional) { if( inp.hasCancelled() )
c = GuiChoose.oneOrNone("Select a card to sacrifice", validTargets); return new ArrayList<Card>();
} else { else return inp.getSelected();
c = GuiChoose.one("Select a card to sacrifice", validTargets);
}
if (c != null) {
result.add(c);
validTargets.remove(c);
} else {
return result;
}
}
return result;
} }
@Override @Override

View File

@@ -29,7 +29,6 @@ import forge.CardLists;
import forge.CardPredicates; import forge.CardPredicates;
import forge.FThreads; import forge.FThreads;
import forge.CardPredicates.Presets; import forge.CardPredicates.Presets;
import forge.Command;
import forge.Singletons; import forge.Singletons;
import forge.card.ability.AbilityUtils; import forge.card.ability.AbilityUtils;
import forge.card.cardfactory.CardFactory; import forge.card.cardfactory.CardFactory;
@@ -393,6 +392,7 @@ public class MagicStack extends MyObservable {
* a {@link forge.card.spellability.SpellAbility} object. * a {@link forge.card.spellability.SpellAbility} object.
*/ */
public final void add(final SpellAbility sp) { public final void add(final SpellAbility sp) {
FThreads.checkEDT("MagicStack.add", false);
final ArrayList<TargetChoices> chosenTargets = sp.getAllTargetChoices(); final ArrayList<TargetChoices> chosenTargets = sp.getAllTargetChoices();
if (sp.isManaAbility()) { // Mana Abilities go straight through if (sp.isManaAbility()) { // Mana Abilities go straight through
@@ -530,17 +530,15 @@ public class MagicStack extends MyObservable {
if (activating.isHuman()) { if (activating.isHuman()) {
sa.getSourceCard().addMultiKickerMagnitude(-1); sa.getSourceCard().addMultiKickerMagnitude(-1);
final Command paidCommand = new Command() { final Runnable paidCommand = new Runnable() {
private static final long serialVersionUID = -6037161763374971106L;
@Override @Override
public void execute() { public void run() {
abilityIncreaseMultikicker.resolve(); abilityIncreaseMultikicker.resolve();
final ManaCostBeingPaid manaCost = MagicStack.this.getMultiKickerSpellCostChange(abilityIncreaseMultikicker); final ManaCostBeingPaid manaCost = MagicStack.this.getMultiKickerSpellCostChange(abilityIncreaseMultikicker);
if (manaCost.isPaid()) { if (manaCost.isPaid()) {
this.execute(); this.run();
} else { } else {
String prompt; String prompt;
int mkCostPaid = game.getActionPlay().getCostCuttingGetMultiKickerManaCostPaid(); int mkCostPaid = game.getActionPlay().getCostCuttingGetMultiKickerManaCostPaid();
@@ -555,13 +553,13 @@ public class MagicStack extends MyObservable {
InputPayManaExecuteCommands toSet = new InputPayManaExecuteCommands(game, prompt, manaCost.toString()); InputPayManaExecuteCommands toSet = new InputPayManaExecuteCommands(game, prompt, manaCost.toString());
FThreads.setInputAndWait(toSet); FThreads.setInputAndWait(toSet);
if ( toSet.isPaid() ) { if ( toSet.isPaid() ) {
this.execute(); this.run();
} else } else
MagicStack.this.push(sa); MagicStack.this.push(sa);
} }
} }
}; };
paidCommand.execute(); paidCommand.run();
} else { } else {
// computer // computer

View File

@@ -44,6 +44,7 @@ import forge.CardPredicates;
import forge.CardUtil; import forge.CardUtil;
import forge.Constant; import forge.Constant;
import forge.CounterType; import forge.CounterType;
import forge.FThreads;
import forge.Singletons; import forge.Singletons;
import forge.card.CardType; import forge.card.CardType;
import forge.card.spellability.AbilityManaPart; import forge.card.spellability.AbilityManaPart;
@@ -611,7 +612,7 @@ public final class GuiDisplayUtil {
return; return;
} }
Card forgeCard = c.toForgeCard(p); final Card forgeCard = c.toForgeCard(p);
final GameState game = Singletons.getModel().getGame(); final GameState game = Singletons.getModel().getGame();
if (forgeCard.getType().contains("Land")) { if (forgeCard.getType().contains("Land")) {
@@ -628,9 +629,14 @@ public final class GuiDisplayUtil {
return; // happens if cancelled return; // happens if cancelled
} }
sa.setActivatingPlayer(p); FThreads.invokeInNewThread(new Runnable() {
game.getAction().moveToHand(forgeCard); // this is really needed @Override
game.getActionPlay().playSpellAbilityWithoutPayingManaCost(sa); public void run() {
sa.setActivatingPlayer(p);
game.getAction().moveToHand(forgeCard); // this is really needed
game.getActionPlay().playSpellAbilityWithoutPayingManaCost(sa);
}
});
} }

View File

@@ -41,9 +41,6 @@ import forge.control.input.Input;
import forge.control.input.InputAttack; import forge.control.input.InputAttack;
import forge.control.input.InputBlock; import forge.control.input.InputBlock;
import forge.control.input.InputPayManaBase; import forge.control.input.InputPayManaBase;
import forge.control.input.InputPayManaExecuteCommands;
import forge.control.input.InputPayManaSimple;
import forge.control.input.InputPaySacCost;
import forge.game.GameState; import forge.game.GameState;
import forge.game.phase.CombatUtil; import forge.game.phase.CombatUtil;
import forge.game.player.Player; import forge.game.player.Player;
@@ -56,6 +53,7 @@ import forge.gui.framework.ICDoc;
import forge.gui.match.CMatchUI; import forge.gui.match.CMatchUI;
import forge.gui.match.controllers.CMessage; import forge.gui.match.controllers.CMessage;
import forge.gui.toolbox.FLabel; import forge.gui.toolbox.FLabel;
import forge.view.arcane.CardPanel;
/** /**
* Controls Swing components of a player's field instance. * Controls Swing components of a player's field instance.
@@ -390,54 +388,53 @@ public class CField implements ICDoc {
final Input input = CMessage.SINGLETON_INSTANCE.getInputControl().getInput(); final Input input = CMessage.SINGLETON_INSTANCE.getInputControl().getInput();
if (c != null && c.isInZone(ZoneType.Battlefield)) { if (c == null || !c.isInZone(ZoneType.Battlefield)) {
if (c.isTapped() && (input instanceof InputPayManaSimple || input instanceof InputPayManaExecuteCommands)) { return;
final forge.view.arcane.CardPanel cardPanel = CField.this.view.getTabletop().getCardPanel(c.getUniqueNumber()); }
for (final forge.view.arcane.CardPanel cp : cardPanel.getAttachedPanels()) {
if (cp.getCard().isUntapped()) {
break;
}
}
}
final List<Card> att = Singletons.getModel().getGame().getCombat().getAttackerList(); // Why does CField filter cards here? That's Input's responsibility to detect incorrect choices!
if ((c.isTapped() || c.hasSickness() || (c.hasKeyword("Vigilance") && att.contains(c))) && (input instanceof InputAttack)) { if (c.isTapped() && input instanceof InputPayManaBase) {
final forge.view.arcane.CardPanel cardPanel = CField.this.view.getTabletop().getCardPanel( final CardPanel cardPanel = CField.this.view.getTabletop().getCardPanel(c.getUniqueNumber());
c.getUniqueNumber()); for (final CardPanel cp : cardPanel.getAttachedPanels()) {
for (final forge.view.arcane.CardPanel cp : cardPanel.getAttachedPanels()) { if (cp.getCard().isUntapped()) {
if (cp.getCard().isUntapped() && !cp.getCard().hasSickness()) { break;
break;
}
} }
} }
if (e.isMetaDown()) {
if (att.contains(c) && (input instanceof InputAttack)
&& !c.hasKeyword("CARDNAME attacks each turn if able.")) {
c.untap();
Singletons.getModel().getGame().getCombat().removeFromCombat(c);
CombatUtil.showCombat();
} else if (input instanceof InputBlock) {
if (c.getController() == Singletons.getControl().getPlayer() ) {
Singletons.getModel().getGame().getCombat().removeFromCombat(c);
}
((InputBlock) input).removeFromAllBlocking(c);
CombatUtil.showCombat();
}
else if (input instanceof InputPaySacCost) {
((InputPaySacCost) input).unselectCard(c, Singletons.getControl().getPlayer().getZone(ZoneType.Battlefield));
}
} else {
//Yosei, the Morning Star required cards to be chosen on computer side
//earlier it was enforced that cards must be in player zone
//this can potentially break some other functionality
//(tapping lands works ok but some custom cards may not...)
//in weird case card has no controller revert to default behaviour
input.selectCard(c);
}
} }
final List<Card> att = Singletons.getModel().getGame().getCombat().getAttackerList();
if ((c.isTapped() || c.hasSickness() || (c.hasKeyword("Vigilance") && att.contains(c))) && (input instanceof InputAttack)) {
final CardPanel cardPanel = CField.this.view.getTabletop().getCardPanel(c.getUniqueNumber());
for (final CardPanel cp : cardPanel.getAttachedPanels()) {
if (cp.getCard().isUntapped() && !cp.getCard().hasSickness()) {
break;
}
}
}
if (e.isMetaDown()) {
if (att.contains(c) && input instanceof InputAttack && !c.hasKeyword("CARDNAME attacks each turn if able.")) {
c.untap();
Singletons.getModel().getGame().getCombat().removeFromCombat(c);
CombatUtil.showCombat();
} else if (input instanceof InputBlock) {
if (c.getController() == Singletons.getControl().getPlayer() ) {
Singletons.getModel().getGame().getCombat().removeFromCombat(c);
}
((InputBlock) input).removeFromAllBlocking(c);
CombatUtil.showCombat();
}
} else {
//Yosei, the Morning Star required cards to be chosen on computer side
//earlier it was enforced that cards must be in player zone
//this can potentially break some other functionality
//(tapping lands works ok but some custom cards may not...)
//in weird case card has no controller revert to default behaviour
input.selectCard(c);
}
} }
/** */ /** */