Targeting code fixed to support fireballs and other spell with multiple targets.

Some classes had references to ArrayList instead of list, this was also changed
This commit is contained in:
Maxmtg
2013-04-01 09:56:12 +00:00
parent 41f77ba745
commit 9d4a18f0c2
22 changed files with 326 additions and 421 deletions

2
.gitattributes vendored
View File

@@ -13766,7 +13766,7 @@ src/main/java/forge/card/spellability/SpellAbilityVariables.java svneol=native#t
src/main/java/forge/card/spellability/SpellPermanent.java svneol=native#text/plain
src/main/java/forge/card/spellability/Target.java svneol=native#text/plain
src/main/java/forge/card/spellability/TargetChoices.java svneol=native#text/plain
src/main/java/forge/card/spellability/TargetSelection.java svneol=native#text/plain
src/main/java/forge/card/spellability/TargetChooser.java svneol=native#text/plain
src/main/java/forge/card/spellability/package-info.java svneol=native#text/plain
src/main/java/forge/card/staticability/StaticAbility.java svneol=native#text/plain
src/main/java/forge/card/staticability/StaticAbilityCantAttackBlock.java -text

View File

@@ -623,7 +623,7 @@ public class AbilityUtils {
} else if (type.startsWith("Targeted")) {
source = null;
ArrayList<Card> tgts = sa.findTargetedCards();
List<Card> tgts = sa.findTargetedCards();
if (!tgts.isEmpty()) {
source = tgts.get(0);
}

View File

@@ -8,7 +8,7 @@ import forge.card.cardfactory.CardFactoryUtil;
import forge.card.cost.Cost;
import forge.card.spellability.SpellAbility;
import forge.card.spellability.Target;
import forge.card.spellability.TargetSelection;
import forge.card.spellability.TargetChooser;
import forge.game.ai.ComputerUtilCard;
import forge.game.ai.ComputerUtilCost;
import forge.game.ai.ComputerUtilMana;
@@ -50,7 +50,7 @@ public class CounterAi extends SpellAbilityAi {
}
tgt.resetTargets();
if (TargetSelection.matchSpellAbility(sa, topSA, tgt)) {
if (TargetChooser.matchSpellAbility(sa, topSA, tgt)) {
tgt.addTarget(topSA);
} else {
return false;
@@ -120,7 +120,7 @@ public class CounterAi extends SpellAbilityAi {
}
tgt.resetTargets();
if (TargetSelection.matchSpellAbility(sa, topSA, tgt)) {
if (TargetChooser.matchSpellAbility(sa, topSA, tgt)) {
tgt.addTarget(topSA);
} else {
return false;

View File

@@ -15,7 +15,7 @@ import forge.card.cost.Cost;
import forge.card.spellability.AbilitySub;
import forge.card.spellability.SpellAbility;
import forge.card.spellability.Target;
import forge.card.spellability.TargetSelection;
import forge.card.spellability.TargetChooser;
import forge.game.ai.ComputerUtil;
import forge.game.ai.ComputerUtilCard;
import forge.game.ai.ComputerUtilCombat;
@@ -103,8 +103,7 @@ public class DamageDealAi extends DamageAiBase {
if (tgt != null && tgt.getTargetPlayers().isEmpty() && !sa.hasParam("DividedAsYouChoose")) {
int actualPay = 0;
final boolean noPrevention = sa.hasParam("NoPrevention");
final ArrayList<Card> cards = tgt.getTargetCards();
for (final Card c : cards) {
for (final Card c : tgt.getTargetCards()) {
final int adjDamage = ComputerUtilCombat.getEnoughDamageToKill(c, dmg, source, false, noPrevention);
if ((adjDamage > actualPay) && (adjDamage <= dmg)) {
actualPay = adjDamage;
@@ -144,7 +143,7 @@ public class DamageDealAi extends DamageAiBase {
final ArrayList<Object> objects = tgt.getTargets();
if (saMe.hasParam("TargetUnique")) {
objects.addAll(TargetSelection.getUniqueTargets(saMe));
objects.addAll(TargetChooser.getUniqueTargets(saMe));
}
for (final Object o : objects) {
if (o instanceof Card) {
@@ -472,7 +471,7 @@ public class DamageDealAi extends DamageAiBase {
// If I can kill my target by paying less mana, do it
int actualPay = 0;
final boolean noPrevention = sa.hasParam("NoPrevention");
final ArrayList<Card> cards = tgt.getTargetCards();
final List<Card> cards = tgt.getTargetCards();
//target is a player
if (cards.isEmpty()) {
actualPay = dmg;

View File

@@ -18,7 +18,7 @@
*/
package forge.card.ability.ai;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import forge.Card;
@@ -96,7 +96,7 @@ public class DrawAi extends SpellAbilityAi {
}
if (tgt != null) {
final ArrayList<Player> players = tgt.getTargetPlayers();
final List<Player> players = tgt.getTargetPlayers();
if ((players.size() > 0) && players.get(0).isOpponentOf(ai)) {
return true;
}

View File

@@ -171,7 +171,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
final StringBuilder sbTargets = new StringBuilder();
ArrayList<Card> tgts;
List<Card> tgts;
if (sa.getTarget() != null) {
tgts = sa.getTarget().getTargetCards();
} else {
@@ -301,8 +301,8 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
* a {@link forge.card.spellability.SpellAbility} object.
*/
private static void changeKnownOriginResolve(final SpellAbility sa) {
ArrayList<Card> tgtCards;
ArrayList<SpellAbility> sas;
List<Card> tgtCards;
List<SpellAbility> sas;
final Target tgt = sa.getTarget();
final Player player = sa.getActivatingPlayer();
@@ -552,7 +552,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
final Target tgt = sa.getTarget();
if (tgt != null) {
final ArrayList<Player> players = tgt.getTargetPlayers();
final List<Player> players = tgt.getTargetPlayers();
player = player != null ? player : players.get(0);
if (players.contains(player) && !player.canBeTargetedBy(sa)) {
return;

View File

@@ -1,6 +1,6 @@
package forge.card.ability.effects;
import java.util.ArrayList;
import java.util.List;
import forge.Card;
import forge.Singletons;
@@ -21,7 +21,7 @@ public class ControlExchangeEffect extends SpellAbilityEffect {
Card object1 = null;
Card object2 = null;
final Target tgt = sa.getTarget();
ArrayList<Card> tgts = tgt.getTargetCards();
List<Card> tgts = tgt.getTargetCards();
if (tgts.size() > 0) {
object1 = tgts.get(0);
}
@@ -42,7 +42,7 @@ public class ControlExchangeEffect extends SpellAbilityEffect {
Card object1 = null;
Card object2 = null;
final Target tgt = sa.getTarget();
ArrayList<Card> tgts = tgt.getTargetCards();
List<Card> tgts = tgt.getTargetCards();
if (tgts.size() > 0) {
object1 = tgts.get(0);
}

View File

@@ -21,7 +21,7 @@ public class DestroyAllEffect extends SpellAbilityEffect {
final StringBuilder sb = new StringBuilder();
final boolean noRegen = sa.hasParam("NoRegen");
ArrayList<Card> tgtCards;
List<Card> tgtCards;
final Target tgt = sa.getTarget();
if (tgt != null) {

View File

@@ -34,7 +34,6 @@ import forge.CardPredicates.Presets;
import forge.Command;
import forge.CounterType;
import forge.Singletons;
import forge.card.ability.AbilityFactory;
import forge.card.cost.Cost;
import forge.card.mana.ManaCost;
import forge.card.spellability.Ability;

View File

@@ -2919,7 +2919,7 @@ public class CardFactoryUtil {
}
if (card.getController().isHuman()) {
game.getActionPlay().playSpellAbilityNoStack(card.getController(), origSA, false);
game.getActionPlay().playSpellAbilityNoStack(card.getController(), origSA);
} else {
ComputerUtil.playNoStack((AIPlayer) card.getController(), origSA, game);
}

View File

@@ -24,7 +24,6 @@ import com.google.common.base.Predicate;
import forge.Card;
import forge.CardLists;
import forge.CardPredicates;
import forge.CardPredicates.Presets;
import forge.FThreads;
import forge.card.ability.AbilityUtils;

View File

@@ -239,7 +239,7 @@ public class ReplacementHandler {
Player player = replacementEffect.getHostCard().getController();
//player.getController().playNoStack()
if (player.isHuman()) {
game.getActionPlay().playSpellAbilityNoStack(player, effectSA, false);
game.getActionPlay().playSpellAbilityNoStack(player, effectSA);
} else {
ComputerUtil.playNoStack((AIPlayer) player, effectSA, game);
}

View File

@@ -1053,7 +1053,7 @@ public abstract class SpellAbility implements ISpellAbility {
if (this.targetCard == null) {
final Target tgt = this.getTarget();
if (tgt != null) {
final ArrayList<Card> list = tgt.getTargetCards();
final List<Card> list = tgt.getTargetCards();
if (!list.isEmpty()) {
return list.get(0);
@@ -1106,7 +1106,7 @@ public abstract class SpellAbility implements ISpellAbility {
public Player getTargetPlayer() {
final Target tgt = this.getTarget();
if (tgt != null) {
final ArrayList<Player> list = tgt.getTargetPlayers();
final List<Player> list = tgt.getTargetPlayers();
if (!list.isEmpty()) {
return list.get(0);
@@ -1313,7 +1313,7 @@ public abstract class SpellAbility implements ISpellAbility {
return false;
}
if (entity.isValid(this.getTarget().getValidTgts(), this.getActivatingPlayer(), this.getSourceCard())
&& (!this.getTarget().isUniqueTargets() || !TargetSelection.getUniqueTargets(this).contains(entity))
&& (!this.getTarget().isUniqueTargets() || !TargetChooser.getUniqueTargets(this).contains(entity))
&& entity.canBeTargetedBy(this)) {
return true;
}
@@ -1494,23 +1494,22 @@ public abstract class SpellAbility implements ISpellAbility {
*
* @return a {@link forge.card.spellability.SpellAbility} object.
*/
public ArrayList<Card> findTargetedCards() {
ArrayList<Card> list = new ArrayList<Card>();
public List<Card> findTargetedCards() {
Target tgt = this.getTarget();
// First search for targeted cards associated with current ability
if (tgt != null && tgt.getTargetCards() != null && !tgt.getTargetCards().isEmpty()) {
return tgt.getTargetCards();
}
List<Card> list = new ArrayList<Card>();
// Next search for source cards of targeted SAs associated with current ability
else if (tgt != null && tgt.getTargetSAs() != null && !tgt.getTargetSAs().isEmpty()) {
if (tgt != null && tgt.getTargetSAs() != null && !tgt.getTargetSAs().isEmpty()) {
for (final SpellAbility ability : tgt.getTargetSAs()) {
list.add(ability.getSourceCard());
}
return list;
}
// Lastly Search parent SAs for target cards
else {
// Check for a parent that targets a card
SpellAbility parent = this.getParentTargetingCard();
if (null != parent) {
@@ -1523,7 +1522,7 @@ public abstract class SpellAbility implements ISpellAbility {
list.add(ability.getSourceCard());
}
}
}
return list;
}

View File

@@ -38,49 +38,37 @@ import forge.game.zone.Zone;
* @version $Id$
*/
public class SpellAbilityRequirements {
private SpellAbility ability = null;
private TargetSelection select = null;
private CostPayment payment = null;
private boolean isFree = false;
private final SpellAbility ability;
private final TargetChooser select;
private final CostPayment payment;
private boolean isFree;
private boolean skipStack = false;
private boolean bCasting = false;
private Zone fromZone = null;
private Integer zonePosition = null;
private boolean isAlreadyTargeted = false;
public void setAlreadyTargeted() { isAlreadyTargeted = true; }
public final void setSkipStack() { this.skipStack = true; }
public void setFree() { this.isFree = true; }
public final void setSkipStack(final boolean bSkip) {
this.skipStack = bSkip;
}
public final void setFree(final boolean bFree) {
this.isFree = bFree;
}
public SpellAbilityRequirements(final SpellAbility sa, final TargetSelection ts, final CostPayment cp) {
public SpellAbilityRequirements(final SpellAbility sa, final CostPayment cp) {
this.ability = sa;
this.select = ts;
this.select = new TargetChooser(sa);
this.payment = cp;
}
public final void fillRequirements() {
this.fillRequirements(false);
}
public final void fillRequirements(final boolean skipTargeting) {
public final void fillRequirements() {
final GameState game = Singletons.getModel().getGame();
if ((this.ability instanceof Spell) && !this.bCasting) {
// remove from hand
this.bCasting = true;
if (!this.ability.getSourceCard().isCopiedSpell()) {
final Card c = this.ability.getSourceCard();
// used to rollback
Zone fromZone = null;
int zonePosition = 0;
this.fromZone = game.getZoneOf(c);
this.zonePosition = this.fromZone.getPosition(c);
final Card c = this.ability.getSourceCard();
if (this.ability instanceof Spell && !c.isCopiedSpell()) {
fromZone = game.getZoneOf(c);
zonePosition = fromZone.getPosition(c);
this.ability.setSourceCard(game.getAction().moveToStack(c));
}
}
// freeze Stack. No abilities should go onto the stack while I'm filling requirements.
game.getStack().freezeStack();
@@ -88,19 +76,19 @@ public class SpellAbilityRequirements {
// Announce things like how many times you want to Multikick or the value of X
if (!this.announceRequirements()) {
this.select.setCancel(true);
rollbackAbility();
rollbackAbility(fromZone, zonePosition);
return;
}
// Skip to paying if parent ability doesn't target and has no
// subAbilities.
// (or trigger case where its already targeted)
if (!skipTargeting && (this.select.doesTarget() || (this.ability.getSubAbility() != null))) {
this.select.setRequirements(this);
boolean acceptsTargets = this.select.doesTarget() || this.ability.getSubAbility() != null;
if (!isAlreadyTargeted && acceptsTargets) {
this.select.clearTargets();
this.select.chooseTargets();
if (this.select.isCanceled()) {
rollbackAbility();
rollbackAbility(fromZone, zonePosition);
return;
}
}
@@ -114,7 +102,7 @@ public class SpellAbilityRequirements {
}
if (!paymentMade) {
rollbackAbility();
rollbackAbility(fromZone, zonePosition);
return;
}
@@ -134,7 +122,7 @@ public class SpellAbilityRequirements {
}
}
private void rollbackAbility() {
private void rollbackAbility(Zone fromZone, int zonePosition) {
// cancel ability during target choosing
final Card c = this.ability.getSourceCard();
@@ -143,9 +131,9 @@ public class SpellAbilityRequirements {
c.setState(CardCharacteristicName.Original);
}
if (this.bCasting && !c.isCopiedSpell()) { // and not a copy
if (fromZone != null) { // and not a copy
// add back to where it came from
Singletons.getModel().getGame().getAction().moveTo(this.fromZone, c, this.zonePosition);
Singletons.getModel().getGame().getAction().moveTo(fromZone, c, zonePosition >= 0 ? Integer.valueOf(zonePosition) : null);
}
if (this.select != null) {

View File

@@ -464,7 +464,7 @@ public class Target {
*
* @return a {@link java.util.ArrayList} object.
*/
public final ArrayList<Card> getTargetCards() {
public final List<Card> getTargetCards() {
if (this.choice == null) {
return new ArrayList<Card>();
}
@@ -479,7 +479,7 @@ public class Target {
*
* @return a {@link java.util.ArrayList} object.
*/
public final ArrayList<Player> getTargetPlayers() {
public final List<Player> getTargetPlayers() {
if (this.choice == null) {
return new ArrayList<Player>();
}
@@ -494,7 +494,7 @@ public class Target {
*
* @return a {@link java.util.ArrayList} object.
*/
public final ArrayList<SpellAbility> getTargetSAs() {
public final List<SpellAbility> getTargetSAs() {
if (this.choice == null) {
return new ArrayList<SpellAbility>();
}

View File

@@ -18,6 +18,7 @@
package forge.card.spellability;
import java.util.ArrayList;
import java.util.List;
import forge.Card;
import forge.game.player.Player;
@@ -45,9 +46,9 @@ public class TargetChoices {
}
// Card or Player are legal targets.
private final ArrayList<Card> targetCards = new ArrayList<Card>();
private final ArrayList<Player> targetPlayers = new ArrayList<Player>();
private final ArrayList<SpellAbility> targetSAs = new ArrayList<SpellAbility>();
private final List<Card> targetCards = new ArrayList<Card>();
private final List<Player> targetPlayers = new ArrayList<Player>();
private final List<SpellAbility> targetSAs = new ArrayList<SpellAbility>();
/**
* <p>
@@ -199,7 +200,7 @@ public class TargetChoices {
*
* @return a {@link java.util.ArrayList} object.
*/
public final ArrayList<Card> getTargetCards() {
public final List<Card> getTargetCards() {
return this.targetCards;
}
@@ -210,7 +211,7 @@ public class TargetChoices {
*
* @return a {@link java.util.ArrayList} object.
*/
public final ArrayList<Player> getTargetPlayers() {
public final List<Player> getTargetPlayers() {
return this.targetPlayers;
}
@@ -221,7 +222,7 @@ public class TargetChoices {
*
* @return a {@link java.util.ArrayList} object.
*/
public final ArrayList<SpellAbility> getTargetSAs() {
public final List<SpellAbility> getTargetSAs() {
return this.targetSAs;
}

View File

@@ -20,20 +20,23 @@ package forge.card.spellability;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import com.google.common.base.Predicate;
import forge.Card;
import forge.CardLists;
import forge.FThreads;
import forge.Singletons;
import forge.GameEntity;
import forge.card.ability.AbilityUtils;
import forge.card.ability.ApiType;
import forge.control.input.InputSynchronized;
import forge.control.input.InputSyncronizedBase;
import forge.game.GameState;
import forge.game.player.Player;
import forge.game.zone.Zone;
import forge.game.zone.ZoneType;
import forge.gui.GuiChoose;
import forge.gui.match.CMatchUI;
import forge.view.ButtonUtil;
/**
@@ -44,21 +47,24 @@ import forge.view.ButtonUtil;
* @author Forge
* @version $Id$
*/
public class TargetSelection {
public class TargetChooser {
/**
* TODO: Write javadoc for this type.
*
*/
public final class InputSelectTargets extends InputSyncronizedBase {
private final TargetSelection select;
public static final class InputSelectTargets extends InputSyncronizedBase {
private final List<Card> choices;
private final ArrayList<Object> alreadyTargeted;
private final boolean targeted;
// some cards can be targeted several times (eg: distribute damage as you choose)
private final Map<GameEntity, Integer> targetDepth = new HashMap<GameEntity, Integer>();
private final Target tgt;
private final SpellAbility sa;
private boolean bCancel = false;
private boolean bOk = false;
private final boolean mandatory;
private static final long serialVersionUID = -1091595663541356356L;
public final boolean hasCancelled() { return bCancel; }
public final boolean hasPressedOk() { return bOk; }
/**
* TODO: Write javadoc for Constructor.
* @param select
@@ -70,13 +76,10 @@ public class TargetSelection {
* @param sa
* @param mandatory
*/
public InputSelectTargets(TargetSelection select, List<Card> choices, ArrayList<Object> alreadyTargeted, boolean targeted, Target tgt, SpellAbility sa, boolean mandatory) {
public InputSelectTargets(List<Card> choices, SpellAbility sa, boolean mandatory) {
super(sa.getActivatingPlayer());
this.select = select;
this.choices = choices;
this.alreadyTargeted = alreadyTargeted;
this.targeted = targeted;
this.tgt = tgt;
this.tgt = sa.getTarget();
this.sa = sa;
this.mandatory = mandatory;
}
@@ -84,12 +87,14 @@ public class TargetSelection {
@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("Targeted:\n");
for (final Entry<GameEntity, Integer> o : targetDepth.entrySet()) {
sb.append(o.getKey());
if( o.getValue() > 1 )
sb.append(" (").append(o.getValue()).append(" times)");
sb.append("\n");
}
//sb.append(tgt.getTargetedString()).append("\n");
sb.append(tgt.getVTSelection());
showMessage(sb.toString());
@@ -114,22 +119,32 @@ public class TargetSelection {
@Override
public void selectButtonCancel() {
select.setCancel(true);
bCancel = true;
this.done();
}
@Override
public void selectButtonOK() {
bOk = 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.isUniqueTargets() && targetDepth.containsKey(card)) {
return;
}
// leave this in temporarily, there some seriously wrong things going on here
if (!card.canBeTargetedBy(sa)) {
showMessage("Cannot target this card (Shroud? Protection? Restrictions?).");
return;
}
if (!choices.contains(card)) {
showMessage("This card is not a valid choice for some other reason besides (Shroud? Protection? Restrictions?).");
return;
}
if (tgt.isDividedAsYouChoose()) {
final int stillToDivide = tgt.getStillToDivide();
int allocatedPortion = 0;
@@ -162,24 +177,25 @@ public class TargetSelection {
tgt.setStillToDivide(stillToDivide - allocatedPortion);
tgt.addDividedAllocation(card, allocatedPortion);
}
tgt.addTarget(card);
this.done();
}
addTarget(card);
} // selectCard()
@Override
public void selectPlayer(final Player player) {
if (alreadyTargeted.contains(player)) {
if (!tgt.isUniqueTargets() && targetDepth.containsKey(player)) {
return;
}
if (!sa.canTarget(player)) {
showMessage("Cannot target this player (Hexproof? Protection? Restrictions?).");
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) {
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;
@@ -204,20 +220,46 @@ public class TargetSelection {
tgt.setStillToDivide(stillToDivide - allocatedPortion);
tgt.addDividedAllocation(player, allocatedPortion);
}
tgt.addTarget(player);
addTarget(player);
}
void addTarget(GameEntity ge) {
tgt.addTarget(ge);
Integer val = targetDepth.get(ge);
targetDepth.put(ge, val == null ? Integer.valueOf(1) : Integer.valueOf(val.intValue() + 1) );
if(hasAllTargets()) {
bOk = true;
this.done();
}
else
this.showMessage();
}
void done() {
this.stop();
}
boolean hasAllTargets() {
return tgt.isMaxTargetsChosen(sa.getSourceCard(), sa);
}
}
private final Target target;
private final SpellAbility ability;
private TargetSelection subSelection = null;
/**
* <p>
* Constructor for Target_Selection.
* </p>
*
* @param tgt
* a {@link forge.card.spellability.Target} object.
* @param sa
* a {@link forge.card.spellability.SpellAbility} object.
*/
public TargetChooser(final SpellAbility sa) {
this.ability = sa;
}
/**
* <p>
@@ -227,7 +269,7 @@ public class TargetSelection {
* @return a {@link forge.card.spellability.Target} object.
*/
public final Target getTgt() {
return this.target;
return this.ability.getTarget();
}
/**
@@ -252,20 +294,7 @@ public class TargetSelection {
return this.ability.getSourceCard();
}
private SpellAbilityRequirements req = null;
/**
* <p>
* setRequirements.
* </p>
*
* @param reqs
* a {@link forge.card.spellability.SpellAbilityRequirements}
* object.
*/
public final void setRequirements(final SpellAbilityRequirements reqs) {
this.req = reqs;
}
private TargetChooser subSelection = null;
private boolean bCancel = false;
private boolean bTargetingDone = false;
@@ -290,44 +319,12 @@ public class TargetSelection {
* @return a boolean.
*/
public final boolean isCanceled() {
if (this.bCancel) {
return this.bCancel;
return this.bCancel || this.subSelection != null && this.subSelection.isCanceled();
}
if (this.subSelection == null) {
return false;
}
return this.subSelection.isCanceled();
}
/**
* <p>
* Constructor for Target_Selection.
* </p>
*
* @param tgt
* a {@link forge.card.spellability.Target} object.
* @param sa
* a {@link forge.card.spellability.SpellAbility} object.
*/
public TargetSelection(final Target tgt, final SpellAbility sa) {
this.target = tgt;
this.ability = sa;
}
/**
* <p>
* doesTarget.
* </p>
*
* @return a boolean.
*/
public final boolean doesTarget() {
if (this.target == null) {
return false;
}
return this.target.doesTarget();
Target tg = getTgt();
return tg != null && tg.doesTarget();
}
/**
@@ -336,55 +333,65 @@ public class TargetSelection {
* </p>
*/
public final void clearTargets() {
if (this.target != null) {
this.target.resetTargets();
this.target.calculateStillToDivide(this.ability.getParam("DividedAsYouChoose"), this.getCard(), this.ability);
Target tg = getTgt();
if (tg != null) {
tg.resetTargets();
tg.calculateStillToDivide(this.ability.getParam("DividedAsYouChoose"), this.getCard(), this.ability);
}
}
/**
* <p>
* chooseTargets.
* </p>
*
* @return a boolean.
*/
public final boolean chooseTargets() {
Target tgt = getTgt();
final boolean canTarget = tgt.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;
boolean hasEnoughTargets = minTargets == 0 || numTargeted >= minTargets;
boolean hasAllTargets = numTargeted == maxTargets && maxTargets > 0;
// if not enough targets chosen, reset and cancel Ability
if (this.bCancel || (this.bTargetingDone && !this.target.isMinTargetsChosen(this.getCard(), this.ability))) {
this.bCancel = true;
return false;
}
if (this.bTargetingDone && !hasEnoughTargets) this.bCancel = true;
if (this.bCancel) return false;
if (!this.doesTarget()
|| this.bTargetingDone && this.target.isMinTargetsChosen(this.getCard(), this.ability)
|| this.target.isMaxTargetsChosen(this.getCard(), this.ability)
|| this.target.isDividedAsYouChoose() && this.target.getStillToDivide() == 0) {
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
if (abSub == null) // if no more SubAbilities finish targeting
return true;
} else {
// Has Sub Ability
this.subSelection = new TargetSelection(abSub.getTarget(), abSub);
this.subSelection.setRequirements(this.req);
this.subSelection = new TargetChooser(abSub);
this.subSelection.clearTargets();
return this.subSelection.chooseTargets();
}
}
if (!this.target.hasCandidates(this.ability, true) && !this.target.isMinTargetsChosen(this.getCard(), this.ability)) {
if (!tgt.hasCandidates(this.ability, true) && !hasEnoughTargets) {
// Cancel ability if there aren't any valid Candidates
this.bCancel = true;
return false;
}
this.chooseValidInput();
if ( !bCancel )
return chooseTargets();
final List<ZoneType> zone = tgt.getZone();
final boolean mandatory = tgt.getMandatory() && tgt.hasCandidates(this.ability, true);
return false;
if (zone.size() == 1 && zone.get(0) == ZoneType.Stack) {
// If Zone is Stack, the choices are handled slightly differently
this.chooseCardFromStack(mandatory);
} else {
List<Card> validTargets = this.chooseValidInput();
if (zone.size() == 1 && zone.get(0) == ZoneType.Battlefield) {
InputSelectTargets inp = new InputSelectTargets(validTargets, ability, mandatory);
FThreads.setInputAndWait(inp);
bCancel = inp.hasCancelled();
bTargetingDone = inp.hasPressedOk();
} else {
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();
}
/**
@@ -416,21 +423,15 @@ public class TargetSelection {
* <p>
* chooseValidInput.
* </p>
* @return
*/
public final void chooseValidInput() {
public final List<Card> chooseValidInput() {
final Target tgt = this.getTgt();
final GameState game = ability.getActivatingPlayer().getGame();
final List<ZoneType> zone = tgt.getZone();
final boolean mandatory = this.target.getMandatory() ? this.target.hasCandidates(this.ability, true) : false;
final boolean canTgtStack = zone.contains(ZoneType.Stack);
if (canTgtStack && (zone.size() == 1)) {
// If Zone is Stack, the choices are handled slightly differently
this.chooseCardFromStack(mandatory);
return;
}
List<Card> choices = CardLists.getTargetableCards(CardLists.getValidCards(Singletons.getModel().getGame().getCardsIn(zone), this.target.getValidTgts(), this.ability.getActivatingPlayer(), this.ability.getSourceCard()), this.ability);
List<Card> choices = CardLists.getTargetableCards(CardLists.getValidCards(game.getCardsIn(zone), tgt.getValidTgts(), this.ability.getActivatingPlayer(), this.ability.getSourceCard()), 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
@@ -450,7 +451,7 @@ public class TargetSelection {
}
// Remove cards already targeted
final ArrayList<Card> targeted = tgt.getTargetCards();
final List<Card> targeted = tgt.getTargetCards();
for (final Card c : targeted) {
if (choices.contains(c)) {
choices.remove(c);
@@ -486,7 +487,7 @@ public class TargetSelection {
}
// If all cards must have different controllers
if (tgt.isDifferentControllers() && !targeted.isEmpty()) {
final List<Player> availableControllers = new ArrayList<Player>(Singletons.getModel().getGame().getPlayers());
final List<Player> availableControllers = new ArrayList<Player>(game.getPlayers());
for (int i = 0; i < targeted.size(); i++) {
availableControllers.remove(targeted.get(i).getController());
}
@@ -512,20 +513,7 @@ public class TargetSelection {
choices.clear();
}
}
if (!tgt.isUniqueTargets()) {
// Previously targeted objects needed to be used for same controller above, but causes problems
// if passed through with certain card functionality to inputTargetSpecific so resetting now
objects = new ArrayList<Object>();
}
if (zone.contains(ZoneType.Battlefield) && zone.size() == 1) {
InputSynchronized inp = new InputSelectTargets(this, choices, objects, true, this.target, this.ability, mandatory);
FThreads.setInputAndWait(inp);
bTargetingDone = !bCancel;
} else {
this.chooseCardFromList(choices, true, mandatory);
}
return choices;
} // input_targetValid
/**
@@ -540,92 +528,63 @@ public class TargetSelection {
* @param mandatory
* a boolean.
*/
public final void chooseCardFromList(final List<Card> choices, final boolean targeted, final boolean mandatory) {
private final void 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 Card dummy = new Card();
dummy.setName("[FINISH TARGETING]");
final SpellAbility sa = this.ability;
final String message = this.target.getVTSelection();
final GameState game = ability.getActivatingPlayer().getGame();
final Card divBattlefield = new Card();
divBattlefield.setName("--CARDS ON BATTLEFIELD:--");
final Card divExile = new Card();
divExile.setName("--CARDS IN EXILE:--");
final Card divGrave = new Card();
divGrave.setName("--CARDS IN GRAVEYARD:--");
final Card divLibrary = new Card();
divLibrary.setName("--CARDS IN LIBRARY:--");
final Card divStack = new Card();
divStack.setName("--CARDS IN STACK:--");
List<Card> choicesZoneUnfiltered = choices;
final List<Card> crdsBattle = new ArrayList<Card>();
final List<Card> crdsExile = new ArrayList<Card>();
final List<Card> crdsGrave = new ArrayList<Card>();
final List<Card> crdsLibrary = new ArrayList<Card>();
final List<Card> crdsStack = new ArrayList<Card>();
for (final Card inZone : choicesZoneUnfiltered) {
if (Singletons.getModel().getGame().getCardsIn(ZoneType.Battlefield).contains(inZone)) {
crdsBattle.add(inZone);
} else if (Singletons.getModel().getGame().getCardsIn(ZoneType.Exile).contains(inZone)) {
crdsExile.add(inZone);
} else if (Singletons.getModel().getGame().getCardsIn(ZoneType.Graveyard).contains(inZone)) {
crdsGrave.add(inZone);
} else if (Singletons.getModel().getGame().getCardsIn(ZoneType.Library).contains(inZone)) {
crdsLibrary.add(inZone);
} else if (Singletons.getModel().getGame().getCardsIn(ZoneType.Stack).contains(inZone)) {
crdsStack.add(inZone);
for (final Card inZone : choices) {
Zone zz = game.getZoneOf(inZone);
if (zz.is(ZoneType.Battlefield)) crdsBattle.add(inZone);
else if (zz.is(ZoneType.Exile)) crdsExile.add(inZone);
else if (zz.is(ZoneType.Graveyard)) crdsGrave.add(inZone);
else if (zz.is(ZoneType.Library)) crdsLibrary.add(inZone);
else if (zz.is(ZoneType.Stack)) crdsStack.add(inZone);
}
}
List<Card> choicesFiltered = new ArrayList<Card>();
if (crdsBattle.size() >= 1) {
choicesFiltered.add(divBattlefield);
List<Object> choicesFiltered = new ArrayList<Object>();
if (!crdsBattle.isEmpty()) {
choicesFiltered.add("--CARDS ON BATTLEFIELD:--");
choicesFiltered.addAll(crdsBattle);
crdsBattle.clear();
}
if (crdsExile.size() >= 1) {
choicesFiltered.add(divExile);
if (!crdsExile.isEmpty()) {
choicesFiltered.add("--CARDS IN EXILE:--");
choicesFiltered.addAll(crdsExile);
crdsExile.clear();
}
if (crdsGrave.size() >= 1) {
choicesFiltered.add(divGrave);
if (!crdsGrave.isEmpty()) {
choicesFiltered.add("--CARDS IN GRAVEYARD:--");
choicesFiltered.addAll(crdsGrave);
crdsGrave.clear();
}
if (crdsLibrary.size() >= 1) {
choicesFiltered.add(divLibrary);
if (!crdsLibrary.isEmpty()) {
choicesFiltered.add("--CARDS IN LIBRARY:--");
choicesFiltered.addAll(crdsLibrary);
crdsLibrary.clear();
}
if (crdsStack.size() >= 1) {
choicesFiltered.add(divStack);
if (!crdsStack.isEmpty()) {
choicesFiltered.add("--CARDS IN STACK:--");
choicesFiltered.addAll(crdsStack);
crdsStack.clear();
}
final Target tgt = this.getTgt();
final List<Card> choicesWithDone = choicesFiltered;
if (tgt.isMinTargetsChosen(sa.getSourceCard(), sa)) {
final String msgDone = "[FINISH TARGETING]";
if (this.getTgt().isMinTargetsChosen(this.ability.getSourceCard(), this.ability)) {
// is there a more elegant way of doing this?
choicesWithDone.add(dummy);
choicesFiltered.add(msgDone);
}
final Card check = GuiChoose.oneOrNone(message, choicesWithDone);
if (check != null) {
final Card c = check;
if (!c.equals(divBattlefield) && !c.equals(divExile) && !c.equals(divGrave)
&& !c.equals(divLibrary) && !c.equals(divStack)) {
if (c.equals(dummy)) {
bTargetingDone = true;
} else {
tgt.addTarget(c);
}
}
} else {
final Object chosen = GuiChoose.oneOrNone(getTgt().getVTSelection(), choicesFiltered);
if (chosen == null) {
this.setCancel(true);
return;
}
if (msgDone.equals(chosen)) {
bTargetingDone = true;
return;
}
if (chosen instanceof Card )
this.getTgt().addTarget(chosen);
}
/**
@@ -636,14 +595,13 @@ public class TargetSelection {
* @param mandatory
* a boolean.
*/
public final void chooseCardFromStack(final boolean mandatory) {
final Target tgt = this.target;
private final void chooseCardFromStack(final boolean mandatory) {
final Target tgt = this.getTgt();
final String message = tgt.getVTSelection();
final TargetSelection select = this;
final String doneDummy = "[FINISH TARGETING]";
// Find what's targetable, then allow human to choose
final ArrayList<SpellAbility> choosables = TargetSelection.getTargetableOnStack(this.ability, select.getTgt());
final ArrayList<SpellAbility> choosables = getTargetableOnStack();
final HashMap<String, SpellAbility> map = new HashMap<String, SpellAbility>();
@@ -657,14 +615,11 @@ public class TargetSelection {
map.put(doneDummy, null);
}
String[] choices = new String[map.keySet().size()];
choices = map.keySet().toArray(choices);
if (choices.length == 0) {
select.setCancel(true);
if (map.isEmpty()) {
setCancel(true);
} else {
final String madeChoice = GuiChoose.oneOrNone(message, choices);
final String madeChoice = GuiChoose.oneOrNone(message, map.keySet());
if (madeChoice != null) {
if (madeChoice.equals(doneDummy)) {
bTargetingDone = true;
@@ -672,7 +627,7 @@ public class TargetSelection {
tgt.addTarget(map.get(madeChoice));
}
} else {
select.setCancel(true);
setCancel(true);
}
}
}
@@ -691,15 +646,16 @@ public class TargetSelection {
* a {@link forge.card.spellability.Target} object.
* @return a {@link java.util.ArrayList} object.
*/
public static ArrayList<SpellAbility> getTargetableOnStack(final SpellAbility sa, final Target tgt) {
private ArrayList<SpellAbility> getTargetableOnStack() {
final ArrayList<SpellAbility> choosables = new ArrayList<SpellAbility>();
for (int i = 0; i < Singletons.getModel().getGame().getStack().size(); i++) {
choosables.add(Singletons.getModel().getGame().getStack().peekAbility(i));
final GameState game = ability.getActivatingPlayer().getGame();
for (int i = 0; i < game.getStack().size(); i++) {
choosables.add(game.getStack().peekAbility(i));
}
for (int i = 0; i < choosables.size(); i++) {
if (!TargetSelection.matchSpellAbility(sa, choosables.get(i), tgt)) {
if (!TargetChooser.matchSpellAbility(ability, choosables.get(i), getTgt())) {
choosables.remove(i);
}
}
@@ -753,7 +709,7 @@ public class TargetSelection {
boolean result = false;
for (final Object o : matchTgt.getTargets()) {
if (TargetSelection.matchesValid(o, splitTargetRestrictions.split(","), sa)) {
if (TargetChooser.matchesValid(o, splitTargetRestrictions.split(","), sa)) {
result = true;
break;
}
@@ -764,26 +720,10 @@ public class TargetSelection {
}
}
if (!TargetSelection.matchesValidSA(topSA, tgt.getValidTgts(), sa)) {
return false;
return topSA.getSourceCard().isValid(tgt.getValidTgts(), sa.getActivatingPlayer(), sa.getSourceCard());
}
return true;
}
/**
* <p>
* matchesValid.
* </p>
*
* @param o
* a {@link java.lang.Object} object.
* @param valids
* an array of {@link java.lang.String} objects.
* @param sa
* a {@link forge.card.spellability.SpellAbility} object.
* @return a boolean.
*/
private static boolean matchesValid(final Object o, final String[] valids, final SpellAbility sa) {
final Card srcCard = sa.getSourceCard();
final Player activatingPlayer = sa.getActivatingPlayer();
@@ -801,24 +741,4 @@ public class TargetSelection {
return false;
}
/**
* <p>
* matchesValidSA.
* </p>
*
* @param sa
* a {@link forge.card.spellability.SpellAbility} object.
* @param valids
* an array of {@link java.lang.String} objects.
* @param source
* a {@link forge.card.spellability.SpellAbility} object.
* @return a boolean.
*/
private static boolean matchesValidSA(final SpellAbility sa, final String[] valids, final SpellAbility source) {
final Card srcCard = source.getSourceCard();
final Player activatingPlayer = source.getActivatingPlayer();
final Card c = sa.getSourceCard();
return c.isValid(valids, activatingPlayer, srcCard);
}
}

View File

@@ -419,7 +419,7 @@ public class TriggerHandler {
if (regtrig.isStatic()) {
if (wrapperAbility.getActivatingPlayer().isHuman()) {
game.getActionPlay().playSpellAbilityNoStack(wrapperAbility.getActivatingPlayer(), wrapperAbility, false);
game.getActionPlay().playSpellAbilityNoStack(wrapperAbility.getActivatingPlayer(), wrapperAbility);
} else {
wrapperAbility.doTrigger(isMandatory, (AIPlayer)wrapperAbility.getActivatingPlayer());
ComputerUtil.playNoStack((AIPlayer)wrapperAbility.getActivatingPlayer(), wrapperAbility, game);

View File

@@ -78,7 +78,7 @@ public class InputMulligan extends InputBase {
}
sb.append("Do you want to Mulligan?");
CMatchUI.SINGLETON_INSTANCE.showMessage(sb.toString());
showMessage(sb.toString());
}
/** {@inheritDoc} */
@@ -130,7 +130,7 @@ public class InputMulligan extends InputBase {
if (GuiDialog.confirm(c, "Use " + c +"'s ability?")) {
// If we ever let the AI memorize cards in the players
// hand, this would be a place to do so.
game.getActionPlay().playSpellAbilityNoStack(p, effect, false);
game.getActionPlay().playSpellAbilityNoStack(p, effect);
}
}
}

View File

@@ -21,7 +21,6 @@ import forge.card.mana.ManaCostShard;
import forge.card.spellability.SpellAbility;
import forge.card.spellability.SpellAbilityRequirements;
import forge.card.spellability.Target;
import forge.card.spellability.TargetSelection;
import forge.card.staticability.StaticAbility;
import forge.control.input.InputPayManaSimple;
import forge.game.ai.ComputerUtilCard;
@@ -73,11 +72,10 @@ public class GameActionPlay {
if (sa.getApi() == ApiType.Charm && !sa.isWrapper()) {
CharmEffect.makeChoices(sa);
}
final TargetSelection ts = new TargetSelection(sa.getTarget(), sa);
final CostPayment payment = new CostPayment(sa.getPayCosts(), sa);
final SpellAbilityRequirements req = new SpellAbilityRequirements(sa, ts, payment);
req.setFree(true);
final SpellAbilityRequirements req = new SpellAbilityRequirements(sa, payment);
req.setFree();
req.fillRequirements();
} else {
if (sa.isSpell()) {
@@ -383,7 +381,6 @@ public class GameActionPlay {
// System.out.println("Playing:" + sa.getDescription() + " of " + sa.getSourceCard() + " new = " + newAbility);
if (newAbility) {
final TargetSelection ts = new TargetSelection(sa.getTarget(), sa);
CostPayment payment = null;
if (sa.getPayCosts() == null) {
payment = new CostPayment(new Cost(sa.getSourceCard(), "0", sa.isAbility()), sa);
@@ -391,7 +388,7 @@ public class GameActionPlay {
payment = new CostPayment(sa.getPayCosts(), sa);
}
final SpellAbilityRequirements req = new SpellAbilityRequirements(sa, ts, payment);
final SpellAbilityRequirements req = new SpellAbilityRequirements(sa, payment);
req.fillRequirements();
} else {
ManaCostBeingPaid manaCost = new ManaCostBeingPaid(sa.getManaCost());
@@ -426,20 +423,24 @@ public class GameActionPlay {
* @param skipTargeting
* a boolean.
*/
public final void playSpellAbilityNoStack(final Player human, final SpellAbility sa, final boolean skipTargeting) {
public final void playSpellAbilityNoStack(final Player human, final SpellAbility sa) {
playSpellAbilityNoStack(human, sa, false);
}
public final void playSpellAbilityNoStack(final Player human, final SpellAbility sa, boolean useOldTargets) {
sa.setActivatingPlayer(human);
if (sa.getPayCosts() != null) {
final TargetSelection ts = new TargetSelection(sa.getTarget(), sa);
final CostPayment payment = new CostPayment(sa.getPayCosts(), sa);
if (!sa.isTrigger()) {
payment.changeCost();
}
final SpellAbilityRequirements req = new SpellAbilityRequirements(sa, ts, payment);
req.setSkipStack(true);
req.fillRequirements(skipTargeting);
final SpellAbilityRequirements req = new SpellAbilityRequirements(sa, payment);
if( useOldTargets )
req.setAlreadyTargeted();
req.setSkipStack();
req.fillRequirements();
} else {
ManaCostBeingPaid manaCost = new ManaCostBeingPaid(sa.getManaCost());
if (sa.getSourceCard().isCopiedSpell() && sa.isSpell()) {

View File

@@ -42,7 +42,7 @@ import forge.card.spellability.SpellAbility;
import forge.card.spellability.SpellAbilityStackInstance;
import forge.card.spellability.Target;
import forge.card.spellability.TargetChoices;
import forge.card.spellability.TargetSelection;
import forge.card.spellability.TargetChooser;
import forge.card.trigger.Trigger;
import forge.card.trigger.TriggerType;
import forge.control.input.InputPayManaExecuteCommands;
@@ -885,7 +885,7 @@ public class MagicStack extends MyObservable {
}
else if (o instanceof SpellAbility) {
final SpellAbility tgtSA = (SpellAbility) o;
invalidTarget = !(TargetSelection.matchSpellAbility(sa, tgtSA, tgt));
invalidTarget = !(TargetChooser.matchSpellAbility(sa, tgtSA, tgt));
// TODO Remove target?
if (invalidTarget) {
choices.removeTarget(tgtSA);

View File

@@ -205,7 +205,6 @@ public final class CEditorConstructed extends ACEditorBase<CardPrinted, Deck> {
/**
* Switch between the main deck and the sideboard editor.
*/
@SuppressWarnings("incomplete-switch")
public void cycleEditorMode() {
int curindex = allSections.indexOf(sectionMode);