diff --git a/src/main/java/forge/card/CardRules.java b/src/main/java/forge/card/CardRules.java index 2eb63c92df0..093581f542c 100644 --- a/src/main/java/forge/card/CardRules.java +++ b/src/main/java/forge/card/CardRules.java @@ -23,7 +23,6 @@ import java.util.Map.Entry; import java.util.Set; import java.util.TreeMap; -import forge.CardColor; import forge.card.mana.ManaCost; /** @@ -76,32 +75,28 @@ public final class CardRules implements ICardCharacteristics { byte res = face.getManaCost() == null ? 0 : face.getManaCost().getColorProfile(); boolean isReminder = false; boolean isSymbol = false; - for(char c : face.getOracleText().toCharArray()) { - switch(c) - { - case('('): isReminder = true; break; - case(')'): isReminder = false; break; - case('{'): isSymbol = true; break; - case('}'): isSymbol = false; break; - default: + String oracleText = face.getOracleText(); + int len = oracleText.length(); + for(int i = 0; i < len; i++) { + char c = oracleText.charAt(i); // This is to avoid needless allocations performed by toCharArray() + switch(c) { + case('('): isReminder = true; break; + case(')'): isReminder = false; break; + case('{'): isSymbol = true; break; + case('}'): isSymbol = false; break; + default: if(isSymbol && !isReminder) { - switch(c) - { - case('W'): res |= MagicColor.WHITE; break; - case('U'): res |= MagicColor.BLUE; break; - case('B'): res |= MagicColor.BLACK; break; - case('R'): res |= MagicColor.RED; break; - case('G'): res |= MagicColor.GREEN; break; + switch(c) { + case('W'): res |= MagicColor.WHITE; break; + case('U'): res |= MagicColor.BLUE; break; + case('B'): res |= MagicColor.BLACK; break; + case('R'): res |= MagicColor.RED; break; + case('G'): res |= MagicColor.GREEN; break; } } - else - { - continue; - } break; } } - return res; } diff --git a/src/main/java/forge/card/spellability/SpellAbility.java b/src/main/java/forge/card/spellability/SpellAbility.java index e8b29b17765..872e0de87ea 100644 --- a/src/main/java/forge/card/spellability/SpellAbility.java +++ b/src/main/java/forge/card/spellability/SpellAbility.java @@ -1687,16 +1687,15 @@ public abstract class SpellAbility implements ISpellAbility { * the ability * @return the unique targets */ - public final ArrayList getUniqueTargets() { - final ArrayList targets = new ArrayList(); - SpellAbility child = this; - while (child instanceof AbilitySub) { - child = ((AbilitySub) child).getParent(); - if (child != null && child.getTarget() != null) { + public final List getUniqueTargets() { + final List targets = new ArrayList(); + SpellAbility child = this.getParent(); + while (child != null) { + if (child.getTarget() != null) { targets.addAll(child.getTarget().getTargets()); } + child = child.getParent(); } - return targets; } diff --git a/src/main/java/forge/card/spellability/SpellAbilityRequirements.java b/src/main/java/forge/card/spellability/SpellAbilityRequirements.java index ab6505b2f3a..114297c078e 100644 --- a/src/main/java/forge/card/spellability/SpellAbilityRequirements.java +++ b/src/main/java/forge/card/spellability/SpellAbilityRequirements.java @@ -66,22 +66,28 @@ public class SpellAbilityRequirements { // Announce things like how many times you want to Multikick or the value of X if (!this.announceRequirements()) { - rollbackAbility(fromZone, zonePosition, null); + rollbackAbility(fromZone, zonePosition); return; } - final TargetSelection select = new TargetSelection(ability); // Skip to paying if parent ability doesn't target and has no // subAbilities. // (or trigger case where its already targeted) - boolean acceptsTargets = select.doesTarget() || this.ability.getSubAbility() != null; - if (!isAlreadyTargeted && acceptsTargets) { - select.clearTargets(); - select.chooseTargets(); - if (select.isCanceled()) { - rollbackAbility(fromZone, zonePosition, select); - return; - } + if (!isAlreadyTargeted) { + + SpellAbility beingTargeted = ability; + do { + Target tgt = beingTargeted.getTarget(); + if( tgt != null && tgt.doesTarget()) { + clearTargets(beingTargeted); + final TargetSelection select = new TargetSelection(beingTargeted); + if (!select.chooseTargets() ) { + rollbackAbility(fromZone, zonePosition); + return; + } + } + beingTargeted = beingTargeted.getSubAbility(); + } while (beingTargeted != null); } // Payment @@ -93,7 +99,7 @@ public class SpellAbilityRequirements { } if (!paymentMade) { - rollbackAbility(fromZone, zonePosition, select); + rollbackAbility(fromZone, zonePosition); return; } @@ -101,19 +107,26 @@ public class SpellAbilityRequirements { if (skipStack) { AbilityUtils.resolve(this.ability, false); } else { - this.enusureAbilityHasDescription(this.ability); this.ability.getActivatingPlayer().getManaPool().clearManaPaid(this.ability, false); game.getStack().addAndUnfreeze(this.ability); } // no worries here. The same thread must resolve, and by this moment ability will have been resolved already - select.clearTargets(); + clearTargets(ability); game.getAction().checkStateEffects(); } } - private void rollbackAbility(Zone fromZone, int zonePosition, TargetSelection select) { + public final void clearTargets(SpellAbility ability) { + Target tg = ability.getTarget(); + if (tg != null) { + tg.resetTargets(); + tg.calculateStillToDivide(ability.getParam("DividedAsYouChoose"), ability.getSourceCard(), ability); + } + } + + private void rollbackAbility(Zone fromZone, int zonePosition) { // cancel ability during target choosing final Card c = this.ability.getSourceCard(); @@ -127,9 +140,7 @@ public class SpellAbilityRequirements { Singletons.getModel().getGame().getAction().moveTo(fromZone, c, zonePosition >= 0 ? Integer.valueOf(zonePosition) : null); } - if (select != null) { - select.clearTargets(); - } + clearTargets(ability); this.ability.resetOnceResolved(); this.payment.refundPayment(); diff --git a/src/main/java/forge/card/spellability/TargetSelection.java b/src/main/java/forge/card/spellability/TargetSelection.java index 36414ec9e29..d9363abf0c4 100644 --- a/src/main/java/forge/card/spellability/TargetSelection.java +++ b/src/main/java/forge/card/spellability/TargetSelection.java @@ -18,7 +18,6 @@ package forge.card.spellability; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; import com.google.common.base.Predicate; @@ -54,108 +53,63 @@ public class TargetSelection { return this.ability.getTarget(); } - private final Card getCard() { - return this.ability.getSourceCard(); - } - - private TargetSelection subSelection = null; - - private boolean bCancel = false; private boolean bTargetingDone = false; - /** - *

- * setCancel. - *

- * - * @param done - * a boolean. - */ - public final void setCancel(final boolean done) { - this.bCancel = done; - } - - /** - *

- * isCanceled. - *

- * - * @return a boolean. - */ - public final boolean isCanceled() { - return this.bCancel || this.subSelection != null && this.subSelection.isCanceled(); - } - - public final boolean doesTarget() { - Target tg = getTgt(); - return tg != null && tg.doesTarget(); - } /** *

* resetTargets. *

*/ - public final void clearTargets() { - Target tg = getTgt(); - if (tg != null) { - tg.resetTargets(); - tg.calculateStillToDivide(this.ability.getParam("DividedAsYouChoose"), this.getCard(), this.ability); - } - } public final boolean chooseTargets() { Target tgt = getTgt(); - final boolean canTarget = tgt == null ? false : doesTarget(); - final int minTargets = canTarget ? tgt.getMinTargets(getCard(), ability) : 0; - final int maxTargets = canTarget ? tgt.getMaxTargets(getCard(), ability) : 0; - final int numTargeted = canTarget ? tgt.getNumTargeted() : 0; + final boolean canTarget = tgt != null && tgt.doesTarget(); + if( !canTarget ) + throw new RuntimeException("TargetSelection.chooseTargets called for ability that does not target - " + ability); + + final int minTargets = tgt.getMinTargets(ability.getSourceCard(), ability); + final int maxTargets = tgt.getMaxTargets(ability.getSourceCard(), ability); + final int numTargeted = tgt.getNumTargeted(); boolean hasEnoughTargets = minTargets == 0 || numTargeted >= minTargets; boolean hasAllTargets = numTargeted == maxTargets && maxTargets > 0; - // if not enough targets chosen, reset and cancel Ability - if (this.bTargetingDone && !hasEnoughTargets) this.bCancel = true; - if (this.bCancel) return false; + // if not enough targets chosen, cancel Ability + if (this.bTargetingDone && !hasEnoughTargets) + return false; - if (!canTarget || this.bTargetingDone && hasEnoughTargets || hasAllTargets || tgt.isDividedAsYouChoose() && tgt.getStillToDivide() == 0) { - final AbilitySub abSub = this.ability.getSubAbility(); - if (abSub == null) // if no more SubAbilities finish targeting - return true; - - // Has Sub Ability - this.subSelection = new TargetSelection(abSub); - this.subSelection.clearTargets(); - return this.subSelection.chooseTargets(); + if (this.bTargetingDone && hasEnoughTargets || hasAllTargets || tgt.isDividedAsYouChoose() && tgt.getStillToDivide() == 0) { + return true; } if (!tgt.hasCandidates(this.ability, true) && !hasEnoughTargets) { // Cancel ability if there aren't any valid Candidates - this.bCancel = true; return false; } final List zone = tgt.getZone(); final boolean mandatory = tgt.getMandatory() && tgt.hasCandidates(this.ability, true); + final boolean choiceResult; if (zone.size() == 1 && zone.get(0) == ZoneType.Stack) { // If Zone is Stack, the choices are handled slightly differently - this.chooseCardFromStack(mandatory); + choiceResult = this.chooseCardFromStack(mandatory); } else { - List validTargets = this.chooseValidInput(); + List validTargets = this.getValidCardsToTarget(); if (zone.size() == 1 && zone.get(0) == ZoneType.Battlefield) { InputSelectTargets inp = new InputSelectTargets(validTargets, ability, mandatory); FThreads.setInputAndWait(inp); - bCancel = inp.hasCancelled(); + choiceResult = !inp.hasCancelled(); bTargetingDone = inp.hasPressedOk(); } else { - this.chooseCardFromList(validTargets, true, mandatory); + // for every other case an all-purpose GuiChoose + choiceResult = this.chooseCardFromList(validTargets, true, mandatory); } } - // some inputs choose cards 1-by-1 and need to be called again, - // moreover there are sub-abilities that also need targets - return chooseTargets(); + // some inputs choose cards one-by-one and need to be called again + return choiceResult && chooseTargets(); } @@ -171,13 +125,14 @@ public class TargetSelection { *

* @return */ - private final List chooseValidInput() { + private final List getValidCardsToTarget() { final Target tgt = this.getTgt(); final GameState game = ability.getActivatingPlayer().getGame(); final List zone = tgt.getZone(); final boolean canTgtStack = zone.contains(ZoneType.Stack); - List choices = CardLists.getTargetableCards(CardLists.getValidCards(game.getCardsIn(zone), tgt.getValidTgts(), this.ability.getActivatingPlayer(), this.ability.getSourceCard()), this.ability); + List validCards = CardLists.getValidCards(game.getCardsIn(zone), tgt.getValidTgts(), this.ability.getActivatingPlayer(), this.ability.getSourceCard()); + List choices = CardLists.getTargetableCards(validCards, this.ability); if (canTgtStack) { // Since getTargetableCards doesn't have additional checks if one of the Zones is stack // Remove the activating card from targeting itself if its on the Stack @@ -186,11 +141,11 @@ public class TargetSelection { choices.remove(tgt.getSourceCard()); } } - ArrayList objects = this.ability.getUniqueTargets(); + List targetedObjects = this.ability.getUniqueTargets(); if (tgt.isUniqueTargets()) { - for (final Object o : objects) { - if ((o instanceof Card) && objects.contains(o)) { + for (final Object o : targetedObjects) { + if ((o instanceof Card) && targetedObjects.contains(o)) { choices.remove(o); } } @@ -205,9 +160,9 @@ public class TargetSelection { } // If all cards (including subability targets) must have the same controller - if (tgt.isSameController() && !objects.isEmpty()) { + if (tgt.isSameController() && !targetedObjects.isEmpty()) { final List list = new ArrayList(); - for (final Object o : objects) { + for (final Object o : targetedObjects) { if (o instanceof Card) { list.add((Card) o); } @@ -251,7 +206,7 @@ public class TargetSelection { } // If the cards must have a specific controller if (tgt.getDefinedController() != null) { - List pl = AbilityUtils.getDefinedPlayers(getCard(), tgt.getDefinedController(), this.ability); + List pl = AbilityUtils.getDefinedPlayers(ability.getSourceCard(), tgt.getDefinedController(), this.ability); if (pl != null && !pl.isEmpty()) { Player controller = pl.get(0); choices = CardLists.filterControlledBy(choices, controller); @@ -260,7 +215,7 @@ public class TargetSelection { } } return choices; - } // input_targetValid + } /** *

@@ -274,7 +229,7 @@ public class TargetSelection { * @param mandatory * a boolean. */ - private final void chooseCardFromList(final List choices, final boolean targeted, final boolean mandatory) { + private final boolean chooseCardFromList(final List choices, final boolean targeted, final boolean mandatory) { // Send in a list of valid cards, and popup a choice box to target final GameState game = ability.getActivatingPlayer().getGame(); @@ -321,16 +276,16 @@ public class TargetSelection { final Object chosen = GuiChoose.oneOrNone(getTgt().getVTSelection(), choicesFiltered); if (chosen == null) { - this.setCancel(true); - return; + return false; } if (msgDone.equals(chosen)) { bTargetingDone = true; - return; + return true; } if (chosen instanceof Card ) this.getTgt().addTarget(chosen); + return true; } /** @@ -341,7 +296,7 @@ public class TargetSelection { * @param mandatory * a boolean. */ - private final void chooseCardFromStack(final boolean mandatory) { + private final boolean chooseCardFromStack(final boolean mandatory) { final Target tgt = this.getTgt(); final String message = tgt.getVTSelection(); // Find what's targetable, then allow human to choose @@ -359,18 +314,18 @@ public class TargetSelection { } if (selectOptions.isEmpty()) { - setCancel(true); + return false; } else { final Object madeChoice = GuiChoose.oneOrNone(message, selectOptions); if (madeChoice == null) { - setCancel(true); - return; + return false; } if (madeChoice instanceof SpellAbility) { tgt.addTarget(madeChoice); } else // only 'FINISH TARGETING' remains bTargetingDone = true; } + return true; } /** diff --git a/src/main/java/forge/control/input/InputSelectTargets.java b/src/main/java/forge/control/input/InputSelectTargets.java index 3d5462a7a0b..91e16e00d23 100644 --- a/src/main/java/forge/control/input/InputSelectTargets.java +++ b/src/main/java/forge/control/input/InputSelectTargets.java @@ -207,6 +207,6 @@ public final class InputSelectTargets extends InputSyncronizedBase { } boolean hasAllTargets() { - return tgt.isMaxTargetsChosen(sa.getSourceCard(), sa); + return tgt.isMaxTargetsChosen(sa.getSourceCard(), sa) || tgt.getStillToDivide() == 0; } } \ No newline at end of file