mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-16 18:58:00 +00:00
iteration of all subabilities to select targets is made in spellability/SpellAbilityRequirements.java
input/InputSelectTargets.java doesn't ask for more targets if all damage (or other thing) has been already 'divided as you choose' TargetSelection does not use cancel, it returns false instead
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -1687,16 +1687,15 @@ public abstract class SpellAbility implements ISpellAbility {
|
||||
* the ability
|
||||
* @return the unique targets
|
||||
*/
|
||||
public final ArrayList<Object> getUniqueTargets() {
|
||||
final ArrayList<Object> targets = new ArrayList<Object>();
|
||||
SpellAbility child = this;
|
||||
while (child instanceof AbilitySub) {
|
||||
child = ((AbilitySub) child).getParent();
|
||||
if (child != null && child.getTarget() != null) {
|
||||
public final List<Object> getUniqueTargets() {
|
||||
final List<Object> targets = new ArrayList<Object>();
|
||||
SpellAbility child = this.getParent();
|
||||
while (child != null) {
|
||||
if (child.getTarget() != null) {
|
||||
targets.addAll(child.getTarget().getTargets());
|
||||
}
|
||||
child = child.getParent();
|
||||
}
|
||||
|
||||
return targets;
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* setCancel.
|
||||
* </p>
|
||||
*
|
||||
* @param done
|
||||
* a boolean.
|
||||
*/
|
||||
public final void setCancel(final boolean done) {
|
||||
this.bCancel = done;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* isCanceled.
|
||||
* </p>
|
||||
*
|
||||
* @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();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* resetTargets.
|
||||
* </p>
|
||||
*/
|
||||
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<ZoneType> 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<Card> validTargets = this.chooseValidInput();
|
||||
List<Card> 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 {
|
||||
* </p>
|
||||
* @return
|
||||
*/
|
||||
private final List<Card> chooseValidInput() {
|
||||
private final List<Card> getValidCardsToTarget() {
|
||||
final Target tgt = this.getTgt();
|
||||
final GameState game = ability.getActivatingPlayer().getGame();
|
||||
final List<ZoneType> zone = tgt.getZone();
|
||||
|
||||
final boolean canTgtStack = zone.contains(ZoneType.Stack);
|
||||
List<Card> choices = CardLists.getTargetableCards(CardLists.getValidCards(game.getCardsIn(zone), tgt.getValidTgts(), this.ability.getActivatingPlayer(), this.ability.getSourceCard()), this.ability);
|
||||
List<Card> validCards = CardLists.getValidCards(game.getCardsIn(zone), tgt.getValidTgts(), this.ability.getActivatingPlayer(), this.ability.getSourceCard());
|
||||
List<Card> 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<Object> objects = this.ability.getUniqueTargets();
|
||||
List<Object> 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<Card> list = new ArrayList<Card>();
|
||||
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<Player> pl = AbilityUtils.getDefinedPlayers(getCard(), tgt.getDefinedController(), this.ability);
|
||||
List<Player> 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
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
@@ -274,7 +229,7 @@ public class TargetSelection {
|
||||
* @param mandatory
|
||||
* a boolean.
|
||||
*/
|
||||
private final void chooseCardFromList(final List<Card> choices, final boolean targeted, final boolean mandatory) {
|
||||
private final boolean chooseCardFromList(final List<Card> 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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user