targeting is also synchronous now

This commit is contained in:
Maxmtg
2013-03-23 12:06:30 +00:00
parent 0ce3aba250
commit 76960e7ea8
20 changed files with 332 additions and 425 deletions

View File

@@ -1,11 +1,15 @@
package forge;
import java.lang.reflect.InvocationTargetException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import javax.swing.SwingUtilities;
import forge.control.input.Input;
import forge.error.BugReporter;
/**
* TODO: Write javadoc for this type.
*
@@ -81,8 +85,8 @@ public class FThreads {
}
public static void invokeInNewThread(Runnable proc) {
invokeInNewThread(proc, false);
public static void invokeInNewThread(Runnable toRun) {
getCachedPool().execute(toRun);
}
public static void invokeInNewThread(final Runnable proc, boolean lockUI) {
@@ -94,13 +98,20 @@ public class FThreads {
@Override
public void run() {
proc.run();
// may try special unlock method here
Singletons.getModel().getMatch().getInput().unlock();
}
};
}
invokeInNewThread(toRun);
}
getCachedPool().execute(toRun);
public static final void setInputAndWait(Input inp, CountDownLatch cdl) {
Singletons.getModel().getMatch().getInput().setInputInterrupt(inp);
try {
cdl.await();
} catch (InterruptedException e) {
BugReporter.reportException(e);
}
}
}

View File

@@ -26,6 +26,7 @@ import com.google.common.base.Predicate;
import forge.Card;
import forge.CardLists;
import forge.CardPredicates;
import forge.FThreads;
import forge.Singletons;
import forge.card.ability.AbilityUtils;
import forge.card.spellability.SpellAbility;
@@ -339,7 +340,7 @@ public class CostDiscard extends CostPartWithList {
CountDownLatch cdl = new CountDownLatch(1);
final Input inp = new InputPayCostDiscard(cdl, ability, handList, this, payment, c, discardType);
setInputAndWait(inp, cdl);
FThreads.setInputAndWait(inp, cdl);
}
}
if ( !payment.isCanceled())

View File

@@ -25,6 +25,7 @@ import java.util.concurrent.CountDownLatch;
import forge.Card;
import forge.CardLists;
import forge.CardPredicates;
import forge.FThreads;
import forge.Singletons;
import forge.card.ability.AbilityUtils;
import forge.card.spellability.SpellAbility;
@@ -602,7 +603,7 @@ public class CostExile extends CostPartWithList {
} else {
target = new InputExileFrom(cdl,ability, this.getType(), c, payment, this);
}
setInputAndWait(target, cdl);
FThreads.setInputAndWait(target, cdl);
if(!payment.isCanceled())
addListToHash(ability, "Exiled");
}

View File

@@ -17,13 +17,9 @@
*/
package forge.card.cost;
import java.util.concurrent.CountDownLatch;
import forge.Card;
import forge.Singletons;
import forge.card.spellability.SpellAbility;
import forge.control.input.Input;
import forge.error.BugReporter;
import forge.game.GameState;
import forge.game.player.AIPlayer;
import forge.game.player.Player;
@@ -146,15 +142,6 @@ public abstract class CostPart {
return i;
}
public final void setInputAndWait(Input inp, CountDownLatch cdl) {
Singletons.getModel().getMatch().getInput().setInputInterrupt(inp);
try {
cdl.await();
} catch (InterruptedException e) {
BugReporter.reportException(e);
}
}
/**
* Can pay.
*

View File

@@ -22,6 +22,7 @@ import java.util.concurrent.CountDownLatch;
import com.google.common.base.Strings;
import forge.Card;
import forge.FThreads;
import forge.card.ability.AbilityUtils;
import forge.card.spellability.SpellAbility;
import forge.control.input.Input;
@@ -112,8 +113,8 @@ public class CostPartMana extends CostPart {
/**
* @return the xCantBe0
*/
public boolean isxCantBe0() {
return xCantBe0;
public boolean canXbe0() {
return !xCantBe0;
}
/**
@@ -227,7 +228,7 @@ public class CostPartMana extends CostPart {
}
else inp = null;
if ( null != inp) {
setInputAndWait(inp, cdl);
FThreads.setInputAndWait(inp, cdl);
}
}

View File

@@ -205,12 +205,7 @@ public class CostPayment {
*
* @return a boolean.
*/
public final boolean payCost() {
// Nothing actually ever checks this return value, is it needed?
if (this.bCancel) {
return false;
}
public void payCost() {
for (final CostPart part : this.cost.getCostParts()) {
// This portion of the cost is already paid for, keep moving
if (this.paidCostParts.contains(part)) {
@@ -218,13 +213,11 @@ public class CostPayment {
}
part.payHuman(this.ability, this.card, this, game);
if ( isCanceled() ) break;
if ( isCanceled() ) return;
setPaidPart(part);
}
this.resetUndoList(); // ??
return true;
this.resetUndoList();
}
/**

View File

@@ -23,6 +23,7 @@ import java.util.concurrent.CountDownLatch;
import forge.Card;
import forge.CardLists;
import forge.CounterType;
import forge.FThreads;
import forge.card.ability.AbilityUtils;
import forge.card.spellability.SpellAbility;
import forge.game.GameState;
@@ -255,7 +256,7 @@ public class CostPutCounter extends CostPartWithList {
this.addToList(source);
} else {
CountDownLatch cdl = new CountDownLatch(1);
setInputAndWait(new InputPayCostPutCounter(cdl, this.getType(), this, c, payment, ability), cdl);
FThreads.setInputAndWait(new InputPayCostPutCounter(cdl, this.getType(), this, c, payment, ability), cdl);
}
if ( !payment.isCanceled())
addListToHash(ability, "CounterPut");

View File

@@ -24,6 +24,7 @@ import java.util.concurrent.CountDownLatch;
import forge.Card;
import forge.CardLists;
import forge.CounterType;
import forge.FThreads;
import forge.card.ability.AbilityUtils;
import forge.card.spellability.SpellAbility;
import forge.control.input.Input;
@@ -386,7 +387,7 @@ public class CostRemoveCounter extends CostPartWithList {
} else {
inp = new InputPayCostRemoveCounterFrom(cdl, this, this.getType(), ability, c, payment);
}
setInputAndWait(inp, cdl);
FThreads.setInputAndWait(inp, cdl);
if ( !payment.isCanceled() )
addListToHash(ability, "CounterRemove");

View File

@@ -23,6 +23,7 @@ import java.util.concurrent.CountDownLatch;
import forge.Card;
import forge.CardLists;
import forge.FThreads;
import forge.Singletons;
import forge.card.ability.AbilityUtils;
import forge.card.spellability.SpellAbility;
@@ -254,7 +255,7 @@ public class CostReturn extends CostPartWithList {
CountDownLatch cdl = new CountDownLatch(1);
final Input target = new InputPayReturnType(cdl, ability, this, c, this.getType(), payment);
final Input inp = target;
setInputAndWait(inp, cdl);
FThreads.setInputAndWait(inp, cdl);
}
if (!payment.isCanceled())

View File

@@ -23,6 +23,7 @@ import java.util.concurrent.CountDownLatch;
import forge.Card;
import forge.CardLists;
import forge.FThreads;
import forge.Singletons;
import forge.card.ability.AbilityUtils;
import forge.card.spellability.SpellAbility;
@@ -279,7 +280,7 @@ public class CostReveal extends CostPartWithList {
if (num > 0) {
final CountDownLatch cdl = new CountDownLatch(1);
final Input inp = new InputPayReveal(cdl, this, this.getType(), handList, ability, payment, num);;
setInputAndWait(inp, cdl);
FThreads.setInputAndWait(inp, cdl);
}
}
if ( !payment.isCanceled())

View File

@@ -23,6 +23,7 @@ import java.util.concurrent.CountDownLatch;
import forge.Card;
import forge.CardLists;
import forge.FThreads;
import forge.Singletons;
import forge.card.ability.AbilityUtils;
import forge.card.spellability.SpellAbility;
@@ -252,7 +253,7 @@ public class CostSacrifice extends CostPartWithList {
return;
}
final CountDownLatch cdl = new CountDownLatch(1);
setInputAndWait(new InputPayCostSacrificeFromList(cdl, this, ability, c, payment, list), cdl);
FThreads.setInputAndWait(new InputPayCostSacrificeFromList(cdl, this, ability, c, payment, list), cdl);
}
if( !payment.isCanceled())
addListToHash(ability, "Sacrificed");

View File

@@ -24,6 +24,7 @@ import java.util.concurrent.CountDownLatch;
import forge.Card;
import forge.CardLists;
import forge.CardPredicates.Presets;
import forge.FThreads;
import forge.Singletons;
import forge.card.ability.AbilityUtils;
import forge.card.spellability.SpellAbility;
@@ -248,7 +249,7 @@ public class CostTapType extends CostPartWithList {
}
}
CountDownLatch cdl = new CountDownLatch(1);
setInputAndWait(new InputPayCostTapType(cdl, this, c, typeList, payment), cdl);
FThreads.setInputAndWait(new InputPayCostTapType(cdl, this, c, typeList, payment), cdl);
if( !payment.isCanceled())
addListToHash(ability, "Tapped");
}

View File

@@ -23,6 +23,7 @@ import java.util.concurrent.CountDownLatch;
import forge.Card;
import forge.CardLists;
import forge.CardPredicates.Presets;
import forge.FThreads;
import forge.Singletons;
import forge.card.ability.AbilityUtils;
import forge.card.spellability.SpellAbility;
@@ -249,7 +250,7 @@ public class CostUntapType extends CostPartWithList {
}
}
CountDownLatch cdl = new CountDownLatch(1);
setInputAndWait(new InputPayCostUntapY(cdl, c, typeList, this, payment), cdl);
FThreads.setInputAndWait(new InputPayCostUntapY(cdl, c, typeList, this, payment), cdl);
if ( !payment.isCanceled() )
addListToHash(ability, "Untapped");

View File

@@ -23,8 +23,6 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.lang.StringUtils;
import forge.Card;
import forge.GameEntity;
import forge.Singletons;
@@ -1682,25 +1680,4 @@ public abstract class SpellAbility implements ISpellAbility {
public void setCopied(boolean isCopied0) {
this.isCopied = isCopied0;
}
public boolean announceRequirements() {
// Announcing Requirements like Choosing X or Multikicker
// SA Params as comma delimited list
String announce = this.getParam("Announce");
if (announce != null) {
String[] announceVars = announce.split(",");
for(String aVar : announceVars) {
String value = this.getActivatingPlayer().getController().announceRequirements(this, aVar);
if (value == null || !StringUtils.isNumeric(value)) {
return false;
} else if (this.getPayCosts().getCostMana() != null && this.getPayCosts().getCostMana().isxCantBe0()
&& Integer.parseInt(value) == 0) {
return false;
}
this.setSVar(aVar, "Number$" + value);
this.getSourceCard().setSVar(aVar, "Number$" + value);
}
}
return true;
}
}

View File

@@ -19,6 +19,8 @@ package forge.card.spellability;
import java.util.ArrayList;
import org.apache.commons.lang3.StringUtils;
import forge.Card;
import forge.CardCharacteristicName;
import forge.Singletons;
@@ -44,67 +46,26 @@ public class SpellAbilityRequirements {
private Zone fromZone = null;
private Integer zonePosition = null;
/**
* <p>
* Setter for the field <code>skipStack</code>.
* </p>
*
* @param bSkip
* a boolean.
*/
public final void setSkipStack(final boolean bSkip) {
this.skipStack = bSkip;
}
/**
* <p>
* setFree.
* </p>
*
* @param bFree
* a boolean.
*/
public final void setFree(final boolean bFree) {
this.isFree = bFree;
}
/**
* <p>
* Constructor for SpellAbility_Requirements.
* </p>
*
* @param sa
* a {@link forge.card.spellability.SpellAbility} object.
* @param ts
* a {@link forge.card.spellability.TargetSelection} object.
* @param cp
* a {@link forge.card.cost.CostPayment} object.
*/
public SpellAbilityRequirements(final SpellAbility sa, final TargetSelection ts, final CostPayment cp) {
this.ability = sa;
this.select = ts;
this.payment = cp;
}
/**
* <p>
* fillRequirements.
* </p>
*/
public final void fillRequirements() {
this.fillRequirements(false);
}
/**
* <p>
* fillRequirements.
* </p>
*
* @param skipTargeting
* a boolean.
*/
public final void fillRequirements(final boolean skipTargeting) {
if ((this.ability instanceof Spell) && !this.bCasting) {
// remove from hand
@@ -118,14 +79,13 @@ public class SpellAbilityRequirements {
}
}
// freeze Stack. No abilities should go onto the stack while I'm filling
// requirements.
// freeze Stack. No abilities should go onto the stack while I'm filling requirements.
Singletons.getModel().getGame().getStack().freezeStack();
// Announce things like how many times you want to Multikick or the value of X
if (!this.ability.announceRequirements()) {
if (!this.announceRequirements()) {
this.select.setCancel(true);
this.finishedTargeting();
rollbackAbility();
return;
}
@@ -134,47 +94,15 @@ public class SpellAbilityRequirements {
// (or trigger case where its already targeted)
if (!skipTargeting && (this.select.doesTarget() || (this.ability.getSubAbility() != null))) {
this.select.setRequirements(this);
this.select.resetTargets();
this.select.clearTargets();
this.select.chooseTargets();
} else {
this.needPayment();
}
}
/**
* <p>
* finishedTargeting.
* </p>
*/
public final void finishedTargeting() {
if (this.select.isCanceled()) {
// cancel ability during target choosing
final Card c = this.ability.getSourceCard();
// split cards transform back to full form if targeting is canceled
if (c.isSplitCard()) {
c.setState(CardCharacteristicName.Original);
if (this.select.isCanceled()) {
rollbackAbility();
return;
}
if (this.bCasting && !c.isCopiedSpell()) { // and not a copy
// add back to where it came from
Singletons.getModel().getGame().getAction().moveTo(this.fromZone, c, this.zonePosition);
}
this.select.resetTargets();
Singletons.getModel().getGame().getStack().removeFromFrozenStack(this.ability);
return;
} else {
this.needPayment();
}
}
/**
* <p>
* needPayment.
* </p>
*/
public final void needPayment() {
// Payment
if (!this.isFree) {
this.payment.setRequirements(this);
this.payment.changeCost();
@@ -182,62 +110,88 @@ public class SpellAbilityRequirements {
}
if (this.payment.isCanceled()) {
final Card c = this.ability.getSourceCard();
// split cards transform back to full form if mana cost is not paid
if (c.isSplitCard()) {
c.setState(CardCharacteristicName.Original);
}
if (this.bCasting && !c.isCopiedSpell()) { // and not a copy
// add back to Previous Zone
Singletons.getModel().getGame().getAction().moveTo(this.fromZone, c, this.zonePosition);
}
if (this.select != null) {
this.select.resetTargets();
}
this.ability.resetOnceResolved();
this.payment.cancelPayment();
Singletons.getModel().getGame().getStack().clearFrozen();
rollbackAbility();
return;
}
else if (this.isFree || this.payment.isAllPaid()) {
if (this.skipStack) {
AbilityUtils.resolve(this.ability, false);
} else {
this.addAbilityToStack();
this.enusureAbilityHasDescription(this.ability);
this.ability.getActivatingPlayer().getManaPool().clearManaPaid(this.ability, false);
Singletons.getModel().getGame().getStack().addAndUnfreeze(this.ability);
}
this.select.resetTargets();
// Warning about this - resolution may come in another thread, and it would still need its targets
this.select.clearTargets();
Singletons.getModel().getGame().getAction().checkStateEffects();
}
}
/**
* <p>
* addAbilityToStack.
* </p>
*/
public final void addAbilityToStack() {
// For older abilities that don't setStackDescription set it here
if (this.ability.getStackDescription().equals("")) {
final StringBuilder sb = new StringBuilder();
sb.append(this.ability.getSourceCard().getName());
if (this.ability.getTarget() != null) {
final ArrayList<Object> targets = this.ability.getTarget().getTargets();
if (targets.size() > 0) {
sb.append(" - Targeting ");
for (final Object o : targets) {
sb.append(o.toString()).append(" ");
}
}
}
private void rollbackAbility() {
// cancel ability during target choosing
final Card c = this.ability.getSourceCard();
this.ability.setStackDescription(sb.toString());
// split cards transform back to full form if targeting is canceled
if (c.isSplitCard()) {
c.setState(CardCharacteristicName.Original);
}
this.ability.getActivatingPlayer().getManaPool().clearManaPaid(this.ability, false);
Singletons.getModel().getGame().getStack().addAndUnfreeze(this.ability);
if (this.bCasting && !c.isCopiedSpell()) { // and not a copy
// add back to where it came from
Singletons.getModel().getGame().getAction().moveTo(this.fromZone, c, this.zonePosition);
}
if (this.select != null) {
this.select.clearTargets();
}
this.ability.resetOnceResolved();
this.payment.cancelPayment();
Singletons.getModel().getGame().getStack().clearFrozen();
// Singletons.getModel().getGame().getStack().removeFromFrozenStack(this.ability);
}
public boolean announceRequirements() {
// Announcing Requirements like Choosing X or Multikicker
// SA Params as comma delimited list
String announce = ability.getParam("Announce");
if (announce != null) {
for(String aVar : announce.split(",")) {
String value = ability.getActivatingPlayer().getController().announceRequirements(ability, aVar);
if (value == null || !StringUtils.isNumeric(value)) {
return false;
} else if (ability.getPayCosts().getCostMana() != null && !ability.getPayCosts().getCostMana().canXbe0()
&& Integer.parseInt(value) == 0) {
return false;
}
ability.setSVar(aVar, "Number$" + value);
ability.getSourceCard().setSVar(aVar, "Number$" + value);
}
}
return true;
}
private void enusureAbilityHasDescription(SpellAbility ability) {
if (!StringUtils.isBlank(ability.getStackDescription()))
return;
// For older abilities that don't setStackDescription set it here
final StringBuilder sb = new StringBuilder();
sb.append(ability.getSourceCard().getName());
if (ability.getTarget() != null) {
final ArrayList<Object> targets = ability.getTarget().getTargets();
if (targets.size() > 0) {
sb.append(" - Targeting ");
for (final Object o : targets) {
sb.append(o.toString()).append(" ");
}
}
}
ability.setStackDescription(sb.toString());
}
}

View File

@@ -20,11 +20,13 @@ package forge.card.spellability;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import com.google.common.base.Predicate;
import forge.Card;
import forge.CardLists;
import forge.FThreads;
import forge.Singletons;
import forge.card.ability.AbilityUtils;
import forge.card.ability.ApiType;
@@ -44,6 +46,177 @@ import forge.view.ButtonUtil;
* @version $Id$
*/
public class TargetSelection {
/**
* TODO: Write javadoc for this type.
*
*/
public final class InputSelectTargets extends Input {
private final TargetSelection select;
private final List<Card> choices;
private final ArrayList<Object> alreadyTargeted;
private final boolean targeted;
private final Target tgt;
private final SpellAbility sa;
private final boolean mandatory;
private final CountDownLatch cdlDone;
private static final long serialVersionUID = -1091595663541356356L;
/**
* TODO: Write javadoc for Constructor.
* @param select
* @param choices
* @param req
* @param alreadyTargeted
* @param targeted
* @param tgt
* @param sa
* @param mandatory
*/
public InputSelectTargets(CountDownLatch cdl, TargetSelection select, List<Card> choices, ArrayList<Object> alreadyTargeted, boolean targeted, Target tgt, SpellAbility sa, boolean mandatory) {
cdlDone = cdl;
this.select = select;
this.choices = choices;
this.alreadyTargeted = alreadyTargeted;
this.targeted = targeted;
this.tgt = tgt;
this.sa = sa;
this.mandatory = mandatory;
}
@Override
public void showMessage() {
final StringBuilder sb = new StringBuilder();
sb.append("Targeted: ");
for (final Object o : alreadyTargeted) {
sb.append(o).append(" ");
}
sb.append(tgt.getTargetedString());
sb.append("\n");
sb.append(tgt.getVTSelection());
CMatchUI.SINGLETON_INSTANCE.showMessage(sb.toString());
// If reached Minimum targets, enable OK button
if (!tgt.isMinTargetsChosen(sa.getSourceCard(), sa) || tgt.isDividedAsYouChoose()) {
if (mandatory && tgt.hasCandidates(sa, true)) {
// Player has to click on a target
ButtonUtil.disableAll();
} else {
ButtonUtil.enableOnlyCancel();
}
} else {
if (mandatory && tgt.hasCandidates(sa, true)) {
// Player has to click on a target or ok
ButtonUtil.enableOnlyOk();
} else {
ButtonUtil.enableAllFocusOk();
}
}
}
@Override
public void selectButtonCancel() {
select.setCancel(true);
this.done();
}
@Override
public void selectButtonOK() {
this.done();
}
@Override
public void selectCard(final Card card) {
// leave this in temporarily, there some seriously wrong things
// going on here
if (targeted && !card.canBeTargetedBy(sa)) {
CMatchUI.SINGLETON_INSTANCE.showMessage("Cannot target this card (Shroud? Protection? Restrictions?).");
} else if (choices.contains(card)) {
if (tgt.isDividedAsYouChoose()) {
final int stillToDivide = tgt.getStillToDivide();
int allocatedPortion = 0;
// allow allocation only if the max targets isn't reached and there are more candidates
if ((tgt.getNumTargeted() + 1 < tgt.getMaxTargets(sa.getSourceCard(), sa))
&& (tgt.getNumCandidates(sa, true) - 1 > 0) && stillToDivide > 1) {
final Integer[] choices = new Integer[stillToDivide];
for (int i = 1; i <= stillToDivide; i++) {
choices[i - 1] = i;
}
String apiBasedMessage = "Distribute how much to ";
if (sa.getApi() == ApiType.DealDamage) {
apiBasedMessage = "Select how much damage to deal to ";
} else if (sa.getApi() == ApiType.PreventDamage) {
apiBasedMessage = "Select how much damage to prevent to ";
} else if (sa.getApi() == ApiType.PutCounter) {
apiBasedMessage = "Select how many counters to distribute to ";
}
final StringBuilder sb = new StringBuilder();
sb.append(apiBasedMessage);
sb.append(card.toString());
Integer chosen = GuiChoose.oneOrNone(sb.toString(), choices);
if (null == chosen) {
return;
}
allocatedPortion = chosen;
} else { // otherwise assign the rest of the damage/protection
allocatedPortion = stillToDivide;
}
tgt.setStillToDivide(stillToDivide - allocatedPortion);
tgt.addDividedAllocation(card, allocatedPortion);
}
tgt.addTarget(card);
this.done();
}
} // selectCard()
@Override
public void selectPlayer(final Player player) {
if (alreadyTargeted.contains(player)) {
return;
}
if (sa.canTarget(player)) {
if (tgt.isDividedAsYouChoose()) {
final int stillToDivide = tgt.getStillToDivide();
int allocatedPortion = 0;
// allow allocation only if the max targets isn't reached and there are more candidates
if ((alreadyTargeted.size() + 1 < tgt.getMaxTargets(sa.getSourceCard(), sa))
&& (tgt.getNumCandidates(sa, true) - 1 > 0) && stillToDivide > 1) {
final Integer[] choices = new Integer[stillToDivide];
for (int i = 1; i <= stillToDivide; i++) {
choices[i - 1] = i;
}
String apiBasedMessage = "Distribute how much to ";
if (sa.getApi() == ApiType.DealDamage) {
apiBasedMessage = "Select how much damage to deal to ";
} else if (sa.getApi() == ApiType.PreventDamage) {
apiBasedMessage = "Select how much damage to prevent to ";
}
final StringBuilder sb = new StringBuilder();
sb.append(apiBasedMessage);
sb.append(player.getName());
Integer chosen = GuiChoose.oneOrNone(sb.toString(), choices);
if (null == chosen) {
return;
}
allocatedPortion = chosen;
} else { // otherwise assign the rest of the damage/protection
allocatedPortion = stillToDivide;
}
tgt.setStillToDivide(stillToDivide - allocatedPortion);
tgt.addDividedAllocation(player, allocatedPortion);
}
tgt.addTarget(player);
this.done();
}
}
void done() {
this.stop();
cdlDone.countDown();
}
}
private Target target = null;
private SpellAbility ability = null;
private Card card = null;
@@ -98,6 +271,7 @@ public class TargetSelection {
}
private boolean bCancel = false;
private boolean bTargetingDone = false;
/**
* <p>
@@ -130,20 +304,6 @@ public class TargetSelection {
return this.subSelection.isCanceled();
}
private boolean bDoneTarget = false;
/**
* <p>
* setDoneTarget.
* </p>
*
* @param done
* a boolean.
*/
public final void setDoneTarget(final boolean done) {
this.bDoneTarget = done;
}
/**
* <p>
* Constructor for Target_Selection.
@@ -179,7 +339,7 @@ public class TargetSelection {
* resetTargets.
* </p>
*/
public final void resetTargets() {
public final void clearTargets() {
if (this.target != null) {
this.target.resetTargets();
this.target.calculateStillToDivide(this.ability.getParam("DividedAsYouChoose"), this.getCard(), this.ability);
@@ -195,24 +355,25 @@ public class TargetSelection {
*/
public final boolean chooseTargets() {
// if not enough targets chosen, reset and cancel Ability
if (this.bCancel || (this.bDoneTarget && !this.target.isMinTargetsChosen(this.card, this.ability))) {
if (this.bCancel || (this.bTargetingDone && !this.target.isMinTargetsChosen(this.card, this.ability))) {
this.bCancel = true;
this.req.finishedTargeting();
return false;
} else if (!this.doesTarget() || (this.bDoneTarget && this.target.isMinTargetsChosen(this.card, this.ability))
}
if (!this.doesTarget()
|| this.bTargetingDone && this.target.isMinTargetsChosen(this.card, this.ability)
|| this.target.isMaxTargetsChosen(this.card, this.ability)
|| (this.target.isDividedAsYouChoose() && this.target.getStillToDivide() == 0)) {
|| this.target.isDividedAsYouChoose() && this.target.getStillToDivide() == 0) {
final AbilitySub abSub = this.ability.getSubAbility();
if (abSub == null) {
// if no more SubAbilities finish targeting
this.req.finishedTargeting();
return true;
} else {
// Has Sub Ability
this.subSelection = new TargetSelection(abSub.getTarget(), abSub);
this.subSelection.setRequirements(this.req);
this.subSelection.resetTargets();
this.subSelection.clearTargets();
return this.subSelection.chooseTargets();
}
}
@@ -220,11 +381,12 @@ public class TargetSelection {
if (!this.target.hasCandidates(this.ability, true) && !this.target.isMinTargetsChosen(this.card, this.ability)) {
// Cancel ability if there aren't any valid Candidates
this.bCancel = true;
this.req.finishedTargeting();
return false;
}
this.chooseValidInput();
if ( !bCancel )
return chooseTargets();
return false;
}
@@ -362,178 +524,15 @@ public class TargetSelection {
}
if (zone.contains(ZoneType.Battlefield) && zone.size() == 1) {
Singletons.getModel().getMatch().getInput().setInput(this.inputTargetSpecific(choices, true, mandatory, objects));
CountDownLatch cdl = new CountDownLatch(1);
Input inp = new InputSelectTargets(cdl, this, choices, objects, true, this.target, this.ability, mandatory);
FThreads.setInputAndWait(inp, cdl);
bTargetingDone = !bCancel;
} else {
this.chooseCardFromList(choices, true, mandatory);
}
} // input_targetValid
// List<Card> choices are the only cards the user can successful select
/**
* <p>
* input_targetSpecific.
* </p>
*
* @param choices
* a {@link forge.CardList} object.
* @param targeted
* a boolean.
* @param mandatory
* a boolean.
* @param alreadyTargeted
* the already targeted
* @return a {@link forge.control.input.Input} object.
*/
public final Input inputTargetSpecific(final List<Card> choices, final boolean targeted, final boolean mandatory,
final ArrayList<Object> alreadyTargeted) {
final SpellAbility sa = this.ability;
final TargetSelection select = this;
final Target tgt = this.target;
final SpellAbilityRequirements req = this.req;
final Input target = new Input() {
private static final long serialVersionUID = -1091595663541356356L;
@Override
public void showMessage() {
final StringBuilder sb = new StringBuilder();
sb.append("Targeted: ");
for (final Object o : alreadyTargeted) {
sb.append(o).append(" ");
}
sb.append(tgt.getTargetedString());
sb.append("\n");
sb.append(tgt.getVTSelection());
CMatchUI.SINGLETON_INSTANCE.showMessage(sb.toString());
// If reached Minimum targets, enable OK button
if (!tgt.isMinTargetsChosen(sa.getSourceCard(), sa) || tgt.isDividedAsYouChoose()) {
if (mandatory && tgt.hasCandidates(sa, true)) {
// Player has to click on a target
ButtonUtil.disableAll();
} else {
ButtonUtil.enableOnlyCancel();
}
} else {
if (mandatory && tgt.hasCandidates(sa, true)) {
// Player has to click on a target or ok
ButtonUtil.enableOnlyOk();
} else {
ButtonUtil.enableAllFocusOk();
}
}
}
@Override
public void selectButtonCancel() {
select.setCancel(true);
this.stop();
req.finishedTargeting();
}
@Override
public void selectButtonOK() {
select.setDoneTarget(true);
this.done();
}
@Override
public void selectCard(final Card card) {
// leave this in temporarily, there some seriously wrong things
// going on here
if (targeted && !card.canBeTargetedBy(sa)) {
CMatchUI.SINGLETON_INSTANCE.showMessage("Cannot target this card (Shroud? Protection? Restrictions?).");
} else if (choices.contains(card)) {
if (tgt.isDividedAsYouChoose()) {
final int stillToDivide = tgt.getStillToDivide();
int allocatedPortion = 0;
// allow allocation only if the max targets isn't reached and there are more candidates
if ((tgt.getNumTargeted() + 1 < tgt.getMaxTargets(sa.getSourceCard(), sa))
&& (tgt.getNumCandidates(sa, true) - 1 > 0) && stillToDivide > 1) {
final Integer[] choices = new Integer[stillToDivide];
for (int i = 1; i <= stillToDivide; i++) {
choices[i - 1] = i;
}
String apiBasedMessage = "Distribute how much to ";
if (sa.getApi() == ApiType.DealDamage) {
apiBasedMessage = "Select how much damage to deal to ";
} else if (sa.getApi() == ApiType.PreventDamage) {
apiBasedMessage = "Select how much damage to prevent to ";
} else if (sa.getApi() == ApiType.PutCounter) {
apiBasedMessage = "Select how many counters to distribute to ";
}
final StringBuilder sb = new StringBuilder();
sb.append(apiBasedMessage);
sb.append(card.toString());
Integer chosen = GuiChoose.oneOrNone(sb.toString(), choices);
if (null == chosen) {
return;
}
allocatedPortion = chosen;
} else { // otherwise assign the rest of the damage/protection
allocatedPortion = stillToDivide;
}
tgt.setStillToDivide(stillToDivide - allocatedPortion);
tgt.addDividedAllocation(card, allocatedPortion);
}
tgt.addTarget(card);
this.done();
}
} // selectCard()
@Override
public void selectPlayer(final Player player) {
if (alreadyTargeted.contains(player)) {
return;
}
if (sa.canTarget(player)) {
if (tgt.isDividedAsYouChoose()) {
final int stillToDivide = tgt.getStillToDivide();
int allocatedPortion = 0;
// allow allocation only if the max targets isn't reached and there are more candidates
if ((alreadyTargeted.size() + 1 < tgt.getMaxTargets(sa.getSourceCard(), sa))
&& (tgt.getNumCandidates(sa, true) - 1 > 0) && stillToDivide > 1) {
final Integer[] choices = new Integer[stillToDivide];
for (int i = 1; i <= stillToDivide; i++) {
choices[i - 1] = i;
}
String apiBasedMessage = "Distribute how much to ";
if (sa.getApi() == ApiType.DealDamage) {
apiBasedMessage = "Select how much damage to deal to ";
} else if (sa.getApi() == ApiType.PreventDamage) {
apiBasedMessage = "Select how much damage to prevent to ";
}
final StringBuilder sb = new StringBuilder();
sb.append(apiBasedMessage);
sb.append(player.getName());
Integer chosen = GuiChoose.oneOrNone(sb.toString(), choices);
if (null == chosen) {
return;
}
allocatedPortion = chosen;
} else { // otherwise assign the rest of the damage/protection
allocatedPortion = stillToDivide;
}
tgt.setStillToDivide(stillToDivide - allocatedPortion);
tgt.addDividedAllocation(player, allocatedPortion);
}
tgt.addTarget(player);
this.done();
}
}
void done() {
this.stop();
select.chooseTargets();
}
};
return target;
} // input_targetSpecific()
/**
* <p>
* chooseCardFromList.
@@ -624,7 +623,7 @@ public class TargetSelection {
if (!c.equals(divBattlefield) && !c.equals(divExile) && !c.equals(divGrave)
&& !c.equals(divLibrary) && !c.equals(divStack)) {
if (c.equals(dummy)) {
this.setDoneTarget(true);
bTargetingDone = true;
} else {
tgt.addTarget(c);
}
@@ -632,8 +631,6 @@ public class TargetSelection {
} else {
this.setCancel(true);
}
this.chooseTargets();
}
/**
@@ -675,7 +672,7 @@ public class TargetSelection {
if (madeChoice != null) {
if (madeChoice.equals(doneDummy)) {
this.setDoneTarget(true);
bTargetingDone = true;
} else {
tgt.addTarget(map.get(madeChoice));
}
@@ -683,8 +680,6 @@ public class TargetSelection {
select.setCancel(true);
}
}
select.chooseTargets();
}
// TODO The following three functions are Utility functions for

View File

@@ -328,4 +328,15 @@ public abstract class InputPayManaBase extends Input {
return "PayManaBase (" + manaCost.toString() + ")";
}
protected void handleConvokedCards(boolean isCancelled) {
if (saPaidFor.getTappedForConvoke() != null) {
for (final Card c : saPaidFor.getTappedForConvoke()) {
c.setTapped(false);
if (!isCancelled)
c.tap();
}
saPaidFor.clearTappedForConvoke();
}
}
}

View File

@@ -82,17 +82,7 @@ public class InputPayManaOfCostPayment extends InputPayManaBase {
// any mana tapabilities can't be used in payment as well as being tapped for convoke)
handleConvokedCards(false);
}
protected void handleConvokedCards(boolean isCancelled) {
if (saPaidFor.getTappedForConvoke() != null) {
for (final Card c : saPaidFor.getTappedForConvoke()) {
c.setTapped(false);
if (!isCancelled)
c.tap();
}
saPaidFor.clearTappedForConvoke();
}
cdlFinished.countDown();
}
@Override
@@ -102,6 +92,7 @@ public class InputPayManaOfCostPayment extends InputPayManaBase {
this.stop();
this.resetManaCost();
payment.cancelCost();
cdlFinished.countDown();
}
@Override

View File

@@ -103,21 +103,7 @@ public class InputPayManaSimple extends InputPayManaBase {
this.saPaidFor.setSourceCard(game.getAction().moveToStack(this.originalCard));
}
// If this is a spell with convoke, re-tap all creatures used for
// it.
// This is done to make sure Taps triggers go off at the right time
// (i.e. AFTER cost payment, they are tapped previously as well so
// that
// any mana tapabilities can't be used in payment as well as being
// tapped for convoke)
if (this.saPaidFor.getTappedForConvoke() != null) {
for (final Card c : this.saPaidFor.getTappedForConvoke()) {
c.setTapped(false);
c.tap();
}
this.saPaidFor.clearTappedForConvoke();
}
handleConvokedCards(false);
}
Singletons.getModel().getMatch().getInput().resetInput();
@@ -127,14 +113,7 @@ public class InputPayManaSimple extends InputPayManaBase {
/** {@inheritDoc} */
@Override
public final void selectButtonCancel() {
// If this is a spell with convoke, untap all creatures used for it.
if (this.saPaidFor.getTappedForConvoke() != null) {
for (final Card c : this.saPaidFor.getTappedForConvoke()) {
c.setTapped(false);
}
this.saPaidFor.clearTappedForConvoke();
}
handleConvokedCards(true);
this.resetManaCost();
whoPays.getManaPool().refundManaPaid(this.saPaidFor, true);

View File

@@ -38,8 +38,7 @@ public class InputPayManaX extends InputPayManaBase {
@Override
public void showMessage() {
if ((xPaid == 0 && costMana.isxCantBe0()) || (this.colorX.equals("")
&& !this.manaCost.toString().equals(strX))) {
if (xPaid == 0 && !costMana.canXbe0() || this.colorX.equals("") && !this.manaCost.toString().equals(strX)) {
ButtonUtil.enableOnlyCancel();
// only cancel if partially paid an X value
// or X is 0, and x can't be 0
@@ -50,7 +49,7 @@ public class InputPayManaX extends InputPayManaBase {
StringBuilder msg = new StringBuilder("Pay X Mana Cost for ");
msg.append(saPaidFor.getSourceCard().getName()).append("\n").append(this.xPaid);
msg.append(" Paid so far.");
if (costMana.isxCantBe0()) {
if (!costMana.canXbe0()) {
msg.append(" X Can't be 0.");
}