payCostDuringAbilityResolve moved to HumanPlay

MagicStack is Iterable<SpellAbilityStackInstance>, get(i) methods removed, many invocations of size() replaced with isEmpty or iteration when appropriate/
GameActionUtil - some other methods inlined for being 2-lines long or used only once
This commit is contained in:
Maxmtg
2013-05-13 12:18:28 +00:00
parent af4c2154d4
commit a9fb1fa85d
33 changed files with 445 additions and 579 deletions

View File

@@ -53,6 +53,7 @@ import forge.card.mana.ManaCost;
import forge.card.replacement.ReplaceMoved; import forge.card.replacement.ReplaceMoved;
import forge.card.replacement.ReplacementEffect; import forge.card.replacement.ReplacementEffect;
import forge.card.replacement.ReplacementResult; import forge.card.replacement.ReplacementResult;
import forge.card.spellability.Ability;
import forge.card.spellability.AbilityTriggered; import forge.card.spellability.AbilityTriggered;
import forge.card.spellability.OptionalCost; import forge.card.spellability.OptionalCost;
import forge.card.spellability.SpellAbility; import forge.card.spellability.SpellAbility;
@@ -62,9 +63,9 @@ import forge.card.staticability.StaticAbility;
import forge.card.trigger.Trigger; import forge.card.trigger.Trigger;
import forge.card.trigger.TriggerType; import forge.card.trigger.TriggerType;
import forge.card.trigger.ZCTrigger; import forge.card.trigger.ZCTrigger;
import forge.game.GameActionUtil;
import forge.game.GameState; import forge.game.GameState;
import forge.game.GlobalRuleChange; import forge.game.GlobalRuleChange;
import forge.game.event.CardDamagedEvent;
import forge.game.event.CardEquippedEvent; import forge.game.event.CardEquippedEvent;
import forge.game.event.CounterAddedEvent; import forge.game.event.CounterAddedEvent;
import forge.game.event.CounterRemovedEvent; import forge.game.event.CounterRemovedEvent;
@@ -7406,7 +7407,9 @@ public class Card extends GameEntity implements Comparable<Card> {
this.addReceivedDamageFromThisTurn(source, damageToAdd); this.addReceivedDamageFromThisTurn(source, damageToAdd);
source.addDealtDamageToThisTurn(this, damageToAdd); source.addDealtDamageToThisTurn(this, damageToAdd);
GameActionUtil.executeDamageDealingEffects(source, damageToAdd); if (source.hasKeyword("Lifelink")) {
source.getController().gainLife(damageToAdd, source);
}
// Run triggers // Run triggers
final Map<String, Object> runParams = new TreeMap<String, Object>(); final Map<String, Object> runParams = new TreeMap<String, Object>();
@@ -7421,25 +7424,57 @@ public class Card extends GameEntity implements Comparable<Card> {
this.subtractCounter(CounterType.LOYALTY, damageToAdd); this.subtractCounter(CounterType.LOYALTY, damageToAdd);
additionalLog = String.format("(Removing %d Loyalty Counters)", damageToAdd); additionalLog = String.format("(Removing %d Loyalty Counters)", damageToAdd);
} else { } else {
final GameState game = source.getGame();
final String s = this + " - destroy";
final int amount = this.getAmountOfKeyword("When CARDNAME is dealt damage, destroy it.");
if(amount > 0) {
final Ability abDestroy = new Ability(source, ManaCost.ZERO){
@Override public void resolve() { game.getAction().destroy(Card.this, this); }
};
abDestroy.setStackDescription(s + ", it cannot be regenerated.");
for (int i = 0; i < amount; i++) {
game.getStack().addSimultaneousStackEntry(abDestroy);
}
}
final int amount2 = this.getAmountOfKeyword("When CARDNAME is dealt damage, destroy it. It can't be regenerated.");
if( amount2 > 0 ) {
final Ability abDestoryNoRegen = new Ability(source, ManaCost.ZERO){
@Override public void resolve() { game.getAction().destroyNoRegeneration(Card.this, this); }
};
abDestoryNoRegen.setStackDescription(s);
for (int i = 0; i < amount2; i++) {
game.getStack().addSimultaneousStackEntry(abDestoryNoRegen);
}
}
boolean wither = (getGame().getStaticEffects().getGlobalRuleChange(GlobalRuleChange.alwaysWither) boolean wither = (getGame().getStaticEffects().getGlobalRuleChange(GlobalRuleChange.alwaysWither)
|| source.hasKeyword("Wither") || source.hasKeyword("Infect")); || source.hasKeyword("Wither") || source.hasKeyword("Infect"));
GameActionUtil.executeDamageToCreatureEffects(source, this, damageToAdd); if (this.isInPlay()) {
if (wither) {
if (this.isInPlay() && wither) { this.addCounter(CounterType.M1M1, damageToAdd, true);
this.addCounter(CounterType.M1M1, damageToAdd, true); additionalLog = "(As -1/-1 Counters)";
additionalLog = "(As -1/-1 Counters)"; } else
this.damage += damageToAdd;
} }
if (source.hasKeyword("Deathtouch") && this.isCreature()) { if (source.hasKeyword("Deathtouch") && this.isCreature()) {
getGame().getAction().destroy(this, null); getGame().getAction().destroy(this, null);
additionalLog = "(Deathtouch)"; additionalLog = "(Deathtouch)";
} else if (this.isInPlay() && !wither) {
this.damage += damageToAdd;
} }
// Play the Damage sound
game.getEvents().post(new CardDamagedEvent());
} }
getGame().getGameLog().add("Damage", String.format("Dealing %d damage to %s. %s", getGame().getGameLog().add("Damage", String.format("Dealing %d damage to %s. %s", damageToAdd, this.getName(), additionalLog), 3);
damageToAdd, this.getName(), additionalLog), 3);
return true; return true;
} }

View File

@@ -21,10 +21,10 @@ import forge.card.spellability.Ability;
import forge.card.spellability.AbilityStatic; import forge.card.spellability.AbilityStatic;
import forge.card.spellability.AbilitySub; import forge.card.spellability.AbilitySub;
import forge.card.spellability.SpellAbility; import forge.card.spellability.SpellAbility;
import forge.game.GameActionUtil;
import forge.game.GameState; import forge.game.GameState;
import forge.game.ai.ComputerUtil; import forge.game.ai.ComputerUtil;
import forge.game.ai.ComputerUtilCost; import forge.game.ai.ComputerUtilCost;
import forge.game.player.HumanPlay;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.zone.ZoneType; import forge.game.zone.ZoneType;
import forge.util.Expressions; import forge.util.Expressions;
@@ -1107,13 +1107,13 @@ public class AbilityUtils {
for (Player payer : payers) { for (Player payer : payers) {
ability.setActivatingPlayer(payer); ability.setActivatingPlayer(payer);
if (payer.isComputer()) { if (payer.isComputer()) {
if (ComputerUtilCost.willPayUnlessCost(sa, payer, ability, paid, payers)) { if (ComputerUtilCost.willPayUnlessCost(sa, payer, ability, paid, payers) && ComputerUtilCost.canPayCost(ability, payer)) {
ComputerUtil.playNoStack(payer, ability, game); // Unless cost was payed - no resolve ComputerUtil.playNoStack(payer, ability, game); // Unless cost was payed - no resolve
paid = true; paid = true;
} }
} else { } else {
// if it's paid by the AI already the human can pay, but it won't change anything // if it's paid by the AI already the human can pay, but it won't change anything
paid |= GameActionUtil.payCostDuringAbilityResolve(ability, cost, sa, game); paid |= HumanPlay.payCostDuringAbilityResolve(ability, cost, sa, game);
} }
} }

View File

@@ -588,7 +588,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
// in general this should only be used to protect from Imminent Harm // in general this should only be used to protect from Imminent Harm
// (dying or losing control of) // (dying or losing control of)
if (origin.equals(ZoneType.Battlefield)) { if (origin.equals(ZoneType.Battlefield)) {
if (ai.getGame().getStack().size() == 0) { if (ai.getGame().getStack().isEmpty()) {
return false; return false;
} }
@@ -720,7 +720,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
// check stack for something on the stack that will kill // check stack for something on the stack that will kill
// anything i control // anything i control
if (ai.getGame().getStack().size() > 0) { if (!ai.getGame().getStack().isEmpty()) {
final ArrayList<Object> objects = ComputerUtil.predictThreatenedObjects(ai, sa); final ArrayList<Object> objects = ComputerUtil.predictThreatenedObjects(ai, sa);
final List<Card> threatenedTargets = new ArrayList<Card>(); final List<Card> threatenedTargets = new ArrayList<Card>();

View File

@@ -56,7 +56,7 @@ public class DamagePreventAi extends SpellAbilityAi {
sa.getParam("Defined"), sa); sa.getParam("Defined"), sa);
// react to threats on the stack // react to threats on the stack
if (game.getStack().size() > 0) { if (!game.getStack().isEmpty()) {
final ArrayList<Object> threatenedObjects = ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), sa); final ArrayList<Object> threatenedObjects = ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), sa);
for (final Object o : objects) { for (final Object o : objects) {
if (threatenedObjects.contains(o)) { if (threatenedObjects.contains(o)) {
@@ -90,7 +90,7 @@ public class DamagePreventAi extends SpellAbilityAi {
} // targeted } // targeted
// react to threats on the stack // react to threats on the stack
else if (game.getStack().size() > 0) { else if (!game.getStack().isEmpty()) {
tgt.resetTargets(); tgt.resetTargets();
// check stack for something on the stack will kill anything i // check stack for something on the stack will kill anything i
// control // control

View File

@@ -38,7 +38,7 @@ public class DamagePreventAllAi extends SpellAbilityAi {
return false; return false;
} }
if (ai.getGame().getStack().size() > 0) { if (!ai.getGame().getStack().isEmpty()) {
// TODO check stack for something on the stack will kill anything i // TODO check stack for something on the stack will kill anything i
// control // control

View File

@@ -49,7 +49,7 @@ public class EffectAi extends SpellAbilityAi {
if (!game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS_INSTANT_ABILITY)) { if (!game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS_INSTANT_ABILITY)) {
return false; return false;
} }
if (game.getStack().size() != 0) { if (!game.getStack().isEmpty()) {
return false; return false;
} }
if (game.getPhaseHandler().isPreventCombatDamageThisTurn()) { if (game.getPhaseHandler().isPreventCombatDamageThisTurn()) {

View File

@@ -137,13 +137,13 @@ public class ProtectAi extends SpellAbilityAi {
} }
// Phase Restrictions // Phase Restrictions
if ((game.getStack().size() == 0) && game.getPhaseHandler().getPhase().isBefore(PhaseType.COMBAT_FIRST_STRIKE_DAMAGE)) { if (game.getStack().isEmpty() && game.getPhaseHandler().getPhase().isBefore(PhaseType.COMBAT_FIRST_STRIKE_DAMAGE)) {
// Instant-speed protections should not be cast outside of combat // Instant-speed protections should not be cast outside of combat
// when the stack is empty // when the stack is empty
if (!SpellAbilityAi.isSorcerySpeed(sa)) { if (!SpellAbilityAi.isSorcerySpeed(sa)) {
return false; return false;
} }
} else if (game.getStack().size() > 0) { } else if (!game.getStack().isEmpty()) {
// TODO protection something only if the top thing on the stack will // TODO protection something only if the top thing on the stack will
// kill it via damage or destroy // kill it via damage or destroy
return false; return false;

View File

@@ -83,13 +83,13 @@ public class PumpAi extends PumpAiBase {
} }
// Phase Restrictions // Phase Restrictions
if ((game.getStack().size() == 0) && ph.getPhase().isBefore(PhaseType.COMBAT_BEGIN)) { if (game.getStack().isEmpty() && ph.getPhase().isBefore(PhaseType.COMBAT_BEGIN)) {
// Instant-speed pumps should not be cast outside of combat when the // Instant-speed pumps should not be cast outside of combat when the
// stack is empty // stack is empty
if (!sa.isCurse() && !SpellAbilityAi.isSorcerySpeed(sa)) { if (!sa.isCurse() && !SpellAbilityAi.isSorcerySpeed(sa)) {
return false; return false;
} }
} else if (game.getStack().size() > 0) { } else if (!game.getStack().isEmpty()) {
if (!keywords.contains("Shroud") && !keywords.contains("Hexproof")) { if (!keywords.contains("Shroud") && !keywords.contains("Hexproof")) {
return false; return false;
} }
@@ -228,7 +228,7 @@ public class PumpAi extends PumpAiBase {
} }
list = CardLists.getValidCards(list, tgt.getValidTgts(), ai, sa.getSourceCard()); list = CardLists.getValidCards(list, tgt.getValidTgts(), ai, sa.getSourceCard());
if (game.getStack().size() == 0) { if (game.getStack().isEmpty()) {
// If the cost is tapping, don't activate before declare // If the cost is tapping, don't activate before declare
// attack/block // attack/block
if ((sa.getPayCosts() != null) && sa.getPayCosts().hasTapCost()) { if ((sa.getPayCosts() != null) && sa.getPayCosts().hasTapCost()) {

View File

@@ -83,7 +83,7 @@ public class RegenerateAi extends SpellAbilityAi {
// them // them
final List<Card> list = AbilityUtils.getDefinedCards(hostCard, sa.getParam("Defined"), sa); final List<Card> list = AbilityUtils.getDefinedCards(hostCard, sa.getParam("Defined"), sa);
if (game.getStack().size() > 0) { if (!game.getStack().isEmpty()) {
final List<Object> objects = ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), sa); final List<Object> objects = ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), sa);
for (final Card c : list) { for (final Card c : list) {
@@ -117,7 +117,7 @@ public class RegenerateAi extends SpellAbilityAi {
return false; return false;
} }
if (game.getStack().size() > 0) { if (!game.getStack().isEmpty()) {
// check stack for something on the stack will kill anything i // check stack for something on the stack will kill anything i
// control // control
final ArrayList<Object> objects = ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), sa); final ArrayList<Object> objects = ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), sa);

View File

@@ -56,7 +56,7 @@ public class RegenerateAllAi extends SpellAbilityAi {
} }
int numSaved = 0; int numSaved = 0;
if (game.getStack().size() > 0) { if (!game.getStack().isEmpty()) {
final ArrayList<Object> objects = ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), sa); final ArrayList<Object> objects = ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), sa);
for (final Card c : list) { for (final Card c : list) {

View File

@@ -178,34 +178,35 @@ public class ChooseSourceEffect extends SpellAbilityEffect {
} }
private Card ChooseCardOnStack(SpellAbility sa, Player ai, GameState game) { private Card ChooseCardOnStack(SpellAbility sa, Player ai, GameState game) {
int size = game.getStack().size(); for (SpellAbilityStackInstance si : game.getStack()) {
for (int i = 0; i < size; i++) { final Card source = si.getSourceCard();
final SpellAbility topStack = game.getStack().peekAbility(i); final SpellAbility abilityOnStack = si.getSpellAbility();
if (sa.hasParam("Choices") && !topStack.getSourceCard().isValid(sa.getParam("Choices"), ai, sa.getSourceCard())) {
if (sa.hasParam("Choices") && !abilityOnStack.getSourceCard().isValid(sa.getParam("Choices"), ai, sa.getSourceCard())) {
continue; continue;
} }
final ApiType threatApi = topStack.getApi(); final ApiType threatApi = abilityOnStack.getApi();
if (threatApi != ApiType.DealDamage && threatApi != ApiType.DamageAll) { if (threatApi != ApiType.DealDamage && threatApi != ApiType.DamageAll) {
continue; continue;
} }
final Card source = topStack.getSourceCard();
ArrayList<Object> objects = new ArrayList<Object>(); ArrayList<Object> objects = new ArrayList<Object>();
final Target threatTgt = topStack.getTarget(); final Target threatTgt = abilityOnStack.getTarget();
if (threatTgt == null) { if (threatTgt == null) {
if (topStack.hasParam("Defined")) { if (abilityOnStack.hasParam("Defined")) {
objects = AbilityUtils.getDefinedObjects(source, topStack.getParam("Defined"), topStack); objects = AbilityUtils.getDefinedObjects(source, abilityOnStack.getParam("Defined"), abilityOnStack);
} else if (topStack.hasParam("ValidPlayers")) { } else if (abilityOnStack.hasParam("ValidPlayers")) {
objects.addAll(AbilityUtils.getDefinedPlayers(source, topStack.getParam("ValidPlayers"), topStack)); objects.addAll(AbilityUtils.getDefinedPlayers(source, abilityOnStack.getParam("ValidPlayers"), abilityOnStack));
} }
} else { } else {
objects.addAll(threatTgt.getTargetPlayers()); objects.addAll(threatTgt.getTargetPlayers());
} }
if (!objects.contains(ai) || topStack.hasParam("NoPrevention")) { if (!objects.contains(ai) || abilityOnStack.hasParam("NoPrevention")) {
continue; continue;
} }
int dmg = AbilityUtils.calculateAmount(source, topStack.getParam("NumDmg"), topStack); int dmg = AbilityUtils.calculateAmount(source, abilityOnStack.getParam("NumDmg"), abilityOnStack);
if (ComputerUtilCombat.predictDamageTo(ai, dmg, source, false) <= 0) { if (ComputerUtilCombat.predictDamageTo(ai, dmg, source, false) <= 0) {
continue; continue;
} }

View File

@@ -24,8 +24,8 @@ public class CounterEffect extends SpellAbilityEffect {
if (sa.hasParam("AllType")) { if (sa.hasParam("AllType")) {
sas = new ArrayList<SpellAbility>(); sas = new ArrayList<SpellAbility>();
for (int i = 0; i < game.getStack().size(); i++) { for (SpellAbilityStackInstance si : game.getStack()) {
SpellAbility spell = game.getStack().peekAbility(i); SpellAbility spell = si.getSpellAbility();
if (sa.getParam("AllType").equals("Spell") && !spell.isSpell()) { if (sa.getParam("AllType").equals("Spell") && !spell.isSpell()) {
continue; continue;
} }
@@ -69,8 +69,8 @@ public class CounterEffect extends SpellAbilityEffect {
if (sa.hasParam("AllType")) { if (sa.hasParam("AllType")) {
sas = new ArrayList<SpellAbility>(); sas = new ArrayList<SpellAbility>();
for (int i = 0; i < game.getStack().size(); i++) { for (SpellAbilityStackInstance si : game.getStack()) {
SpellAbility spell = game.getStack().peekAbility(i); SpellAbility spell = si.getSpellAbility();
if (sa.getParam("AllType").equals("Spell") && !spell.isSpell()) { if (sa.getParam("AllType").equals("Spell") && !spell.isSpell()) {
continue; continue;
} }

View File

@@ -32,7 +32,6 @@ import forge.control.input.InputSelectCardsFromList;
import forge.game.GameState; import forge.game.GameState;
import forge.game.ai.ComputerUtil; import forge.game.ai.ComputerUtil;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.zone.MagicStack;
import forge.game.zone.ZoneType; import forge.game.zone.ZoneType;
import forge.gui.GuiChoose; import forge.gui.GuiChoose;
import forge.gui.GuiDialog; import forge.gui.GuiDialog;
@@ -162,8 +161,8 @@ public class CostExile extends CostPartWithList {
return true; // this will always work return true; // this will always work
} }
if (this.getFrom().equals(ZoneType.Stack)) { if (this.getFrom().equals(ZoneType.Stack)) {
for (int i = 0; i < game.getStack().size(); i++) { for (SpellAbilityStackInstance si : game.getStack()) {
typeList.add(game.getStack().peekAbility(i).getSourceCard()); typeList.add(si.getSourceCard());
} }
} else { } else {
if (this.sameZone) { if (this.sameZone) {
@@ -336,13 +335,13 @@ public class CostExile extends CostPartWithList {
return true; return true;
} }
final GameState game = sa.getActivatingPlayer().getGame();
ArrayList<SpellAbility> saList = new ArrayList<SpellAbility>(); ArrayList<SpellAbility> saList = new ArrayList<SpellAbility>();
ArrayList<String> descList = new ArrayList<String>(); ArrayList<String> descList = new ArrayList<String>();
final MagicStack stack = sa.getActivatingPlayer().getGame().getStack();
for (int i = 0; i < stack.size(); i++) { for (SpellAbilityStackInstance si : game.getStack()) {
final Card stC = stack.peekAbility(i).getSourceCard(); final Card stC = si.getSourceCard();
final SpellAbility stSA = stack.peekAbility(i).getRootAbility(); final SpellAbility stSA = si.getSpellAbility().getRootAbility();
if (stC.isValid(getType().split(";"), sa.getActivatingPlayer(), sa.getSourceCard()) && stSA.isSpell()) { if (stC.isValid(getType().split(";"), sa.getActivatingPlayer(), sa.getSourceCard()) && stSA.isSpell()) {
saList.add(stSA); saList.add(stSA);
if (stC.isCopiedSpell()) { if (stC.isCopiedSpell()) {
@@ -373,8 +372,8 @@ public class CostExile extends CostPartWithList {
} else { } else {
addToList(c); addToList(c);
} }
final SpellAbilityStackInstance si = stack.getInstanceFromSpellAbility(toExile); final SpellAbilityStackInstance si = game.getStack().getInstanceFromSpellAbility(toExile);
stack.remove(si); game.getStack().remove(si);
} else { } else {
return false; return false;
} }

View File

@@ -51,9 +51,6 @@ public class SpellAbilityStackInstance {
private TargetChoices tc = null; private TargetChoices tc = null;
private List<Card> splicedCards = null; private List<Card> splicedCards = null;
/** The activating player. */
private Player activatingPlayer = null;
/** The stack description. */ /** The stack description. */
private String stackDescription = null; private String stackDescription = null;
@@ -89,7 +86,6 @@ public class SpellAbilityStackInstance {
// Base SA info // Base SA info
this.ability = sa; this.ability = sa;
this.stackDescription = this.ability.getStackDescription(); this.stackDescription = this.ability.getStackDescription();
this.activatingPlayer = sa.getActivatingPlayer();
// Payment info // Payment info
this.paidHash = this.ability.getPaidHash(); this.paidHash = this.ability.getPaidHash();
@@ -140,7 +136,6 @@ public class SpellAbilityStackInstance {
this.ability.getTarget().resetTargets(); this.ability.getTarget().resetTargets();
this.ability.getTarget().setTargetChoices(this.tc); this.ability.getTarget().setTargetChoices(this.tc);
} }
this.ability.setActivatingPlayer(this.activatingPlayer);
// Saved sub-SA needs to be reset on the way out // Saved sub-SA needs to be reset on the way out
if (this.subInstace != null) { if (this.subInstace != null) {
@@ -191,17 +186,6 @@ public class SpellAbilityStackInstance {
return this.ability.getSourceCard(); return this.ability.getSourceCard();
} }
/**
* <p>
* Getter for the field <code>activatingPlayer</code>.
* </p>
*
* @return a {@link forge.game.player.Player} object.
*/
public final Player getActivatingPlayer() {
return this.activatingPlayer;
}
/** /**
* <p> * <p>
* isSpell. * isSpell.

View File

@@ -310,14 +310,14 @@ public class TargetSelection {
final List<Object> selectOptions = new ArrayList<Object>(); final List<Object> selectOptions = new ArrayList<Object>();
final GameState game = ability.getActivatingPlayer().getGame(); final GameState game = ability.getActivatingPlayer().getGame();
for (int i = 0; i < game.getStack().size(); i++) { for (SpellAbilityStackInstance si : game.getStack()) {
SpellAbility stackItem = game.getStack().peekAbility(i); SpellAbility abilityOnStack = si.getSpellAbility();
if (ability.equals(stackItem)) { if (ability.equals(abilityOnStack)) {
// By peeking at stack item, target is set to its SI state. So set it back before adding targets // By peeking at stack item, target is set to its SI state. So set it back before adding targets
tgt.resetTargets(); tgt.resetTargets();
} }
else if (ability.canTargetSpellAbility(stackItem)) { else if (ability.canTargetSpellAbility(abilityOnStack)) {
selectOptions.add(stackItem); selectOptions.add(abilityOnStack);
} }
} }

View File

@@ -91,7 +91,7 @@ public class TriggerSpellAbilityCast extends Trigger {
} }
if (this.mapParams.containsKey("ValidActivatingPlayer")) { if (this.mapParams.containsKey("ValidActivatingPlayer")) {
if (si == null || !matchesValid(si.getActivatingPlayer(), this.mapParams.get("ValidActivatingPlayer") if (si == null || !matchesValid(si.getSpellAbility().getActivatingPlayer(), this.mapParams.get("ValidActivatingPlayer")
.split(","), this.getHostCard())) { .split(","), this.getHostCard())) {
return false; return false;
} }

View File

@@ -61,7 +61,7 @@ public class InputPassPriority extends InputBase {
sb.append("Turn : ").append(ph.getPlayerTurn()).append("\n"); sb.append("Turn : ").append(ph.getPlayerTurn()).append("\n");
sb.append("Phase: ").append(ph.getPhase().Name).append("\n"); sb.append("Phase: ").append(ph.getPhase().Name).append("\n");
sb.append("Stack: "); sb.append("Stack: ");
if (player.getGame().getStack().size() != 0) { if (!player.getGame().getStack().isEmpty()) {
sb.append(player.getGame().getStack().size()).append(" to Resolve."); sb.append(player.getGame().getStack().size()).append(" to Resolve.");
} else { } else {
sb.append("Empty"); sb.append("Empty");

View File

@@ -522,7 +522,7 @@ public class GameAction {
Player p = recoverable.getController(); Player p = recoverable.getController();
if (p.isHuman()) { if (p.isHuman()) {
if ( GameActionUtil.payCostDuringAbilityResolve(abRecover, abRecover.getPayCosts(), null, game) ) if ( HumanPlay.payCostDuringAbilityResolve(abRecover, abRecover.getPayCosts(), null, game) )
moveToHand(recoverable); moveToHand(recoverable);
else else
exile(recoverable); exile(recoverable);

View File

@@ -22,7 +22,6 @@ import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import com.google.common.base.Function; import com.google.common.base.Function;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
@@ -36,27 +35,12 @@ import forge.CardUtil;
import forge.Command; import forge.Command;
import forge.Constant; import forge.Constant;
import forge.CounterType; import forge.CounterType;
import forge.FThreads;
import forge.card.ability.AbilityFactory; import forge.card.ability.AbilityFactory;
import forge.card.ability.AbilityFactory.AbilityRecordType; import forge.card.ability.AbilityFactory.AbilityRecordType;
import forge.card.ability.AbilityUtils; import forge.card.ability.AbilityUtils;
import forge.card.ability.ApiType; import forge.card.ability.ApiType;
import forge.card.cardfactory.CardFactoryUtil; import forge.card.cardfactory.CardFactoryUtil;
import forge.card.cost.Cost; import forge.card.cost.Cost;
import forge.card.cost.CostDamage;
import forge.card.cost.CostDiscard;
import forge.card.cost.CostExile;
import forge.card.cost.CostMill;
import forge.card.cost.CostPart;
import forge.card.cost.CostPartMana;
import forge.card.cost.CostPartWithList;
import forge.card.cost.CostPayLife;
import forge.card.cost.CostPutCounter;
import forge.card.cost.CostRemoveCounter;
import forge.card.cost.CostReturn;
import forge.card.cost.CostReveal;
import forge.card.cost.CostSacrifice;
import forge.card.cost.CostTapType;
import forge.card.mana.ManaCost; import forge.card.mana.ManaCost;
import forge.card.mana.ManaCostBeingPaid; import forge.card.mana.ManaCostBeingPaid;
import forge.card.spellability.Ability; import forge.card.spellability.Ability;
@@ -66,13 +50,7 @@ import forge.card.spellability.AbilitySub;
import forge.card.spellability.OptionalCost; import forge.card.spellability.OptionalCost;
import forge.card.spellability.SpellAbility; import forge.card.spellability.SpellAbility;
import forge.card.spellability.SpellAbilityRestriction; import forge.card.spellability.SpellAbilityRestriction;
import forge.control.input.InputPayManaExecuteCommands;
import forge.control.input.InputPayment;
import forge.control.input.InputSelectCards;
import forge.control.input.InputSelectCardsFromList;
import forge.game.ai.AiController; import forge.game.ai.AiController;
import forge.game.event.CardDamagedEvent;
import forge.game.event.LifeLossEvent;
import forge.game.player.HumanPlay; import forge.game.player.HumanPlay;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.player.PlayerControllerAi; import forge.game.player.PlayerControllerAi;
@@ -93,31 +71,6 @@ import forge.util.TextUtil;
*/ */
public final class GameActionUtil { public final class GameActionUtil {
/**
* TODO: Write javadoc for this type.
*
*/
private static final class AbilityDestroy extends Ability {
private final Card affected;
private final boolean canRegenerate;
public AbilityDestroy(Card sourceCard, Card affected, boolean canRegenerate) {
super(sourceCard, ManaCost.ZERO);
this.affected = affected;
this.canRegenerate = canRegenerate;
}
@Override
public void resolve() {
final GameState game = affected.getGame();
if ( canRegenerate )
game.getAction().destroy(affected, this);
else
game.getAction().destroyNoRegeneration(affected, this);
}
}
private static final class CascadeAbility extends Ability { private static final class CascadeAbility extends Ability {
private final Player controller; private final Player controller;
private final Card cascCard; private final Card cascCard;
@@ -368,7 +321,7 @@ public final class GameActionUtil {
* a {@link forge.card.spellability.SpellAbility} object. * a {@link forge.card.spellability.SpellAbility} object.
*/ */
public static void executePlayCardEffects(final SpellAbility sa) { public static void executePlayCardEffects(final SpellAbility sa) {
// (called in MagicStack.java) // (called from MagicStack.java)
final GameState game = sa.getActivatingPlayer().getGame(); final GameState game = sa.getActivatingPlayer().getGame();
final Command cascade = new CascadeExecutor(sa.getActivatingPlayer(), sa.getSourceCard(), game); final Command cascade = new CascadeExecutor(sa.getActivatingPlayer(), sa.getSourceCard(), game);
@@ -377,307 +330,6 @@ public final class GameActionUtil {
ripple.run(); ripple.run();
} }
private static int getAmountFromPart(CostPart part, Card source, SpellAbility sourceAbility) {
String amountString = part.getAmount();
return StringUtils.isNumeric(amountString) ? Integer.parseInt(amountString) : AbilityUtils.calculateAmount(source, amountString, sourceAbility);
}
/**
* TODO: Write javadoc for this method.
* @param part
* @param source
* @param sourceAbility
* @return
*/
private static int getAmountFromPartX(CostPart part, Card source, SpellAbility sourceAbility) {
String amountString = part.getAmount();
return StringUtils.isNumeric(amountString) ? Integer.parseInt(amountString) : CardFactoryUtil.xCount(source, source.getSVar(amountString));
}
/**
* <p>
* payCostDuringAbilityResolve.
* </p>
*
* @param ability
* a {@link forge.card.spellability.SpellAbility} object.
* @param cost
* a {@link forge.card.cost.Cost} object.
* @param paid
* a {@link forge.Command} object.
* @param unpaid
* a {@link forge.Command} object.
* @param sourceAbility TODO
*/
public static boolean payCostDuringAbilityResolve(final SpellAbility ability, final Cost cost, SpellAbility sourceAbility, final GameState game) {
// Only human player pays this way
final Player p = ability.getActivatingPlayer();
final Card source = ability.getSourceCard();
Card current = null; // Used in spells with RepeatEach effect to distinguish cards, Cut the Tethers
if (!source.getRemembered().isEmpty()) {
if (source.getRemembered().get(0) instanceof Card) {
current = (Card) source.getRemembered().get(0);
}
}
if (!source.getImprinted().isEmpty()) {
current = source.getImprinted().get(0);
}
final List<CostPart> parts = cost.getCostParts();
ArrayList<CostPart> remainingParts = new ArrayList<CostPart>(cost.getCostParts());
CostPart costPart = null;
if (!parts.isEmpty()) {
costPart = parts.get(0);
}
final String orString = sourceAbility == null ? "" : " (or: " + sourceAbility.getStackDescription() + ")";
if (parts.isEmpty() || costPart.getAmount().equals("0")) {
return GuiDialog.confirm(source, "Do you want to pay 0?" + orString);
}
//the following costs do not need inputs
for (CostPart part : parts) {
boolean mayRemovePart = true;
if (part instanceof CostPayLife) {
final int amount = getAmountFromPart(part, source, sourceAbility);
if (!p.canPayLife(amount))
return false;
if (false == GuiDialog.confirm(source, "Do you want to pay " + amount + " life?" + orString))
return false;
p.payLife(amount, null);
}
else if (part instanceof CostMill) {
final int amount = getAmountFromPart(part, source, sourceAbility);
final List<Card> list = p.getCardsIn(ZoneType.Library);
if (list.size() < amount) return false;
if (!GuiDialog.confirm(source, "Do you want to mill " + amount + " card(s)?" + orString))
return false;
List<Card> listmill = p.getCardsIn(ZoneType.Library, amount);
((CostMill) part).executePayment(sourceAbility, listmill);
}
else if (part instanceof CostDamage) {
int amount = getAmountFromPartX(part, source, sourceAbility);
if (!p.canPayLife(amount))
return false;
if (false == GuiDialog.confirm(source, "Do you want " + source + " to deal " + amount + " damage to you?"))
return false;
p.addDamage(amount, source);
}
else if (part instanceof CostPutCounter) {
CounterType counterType = ((CostPutCounter) part).getCounter();
int amount = getAmountFromPartX(part, source, sourceAbility);
if (false == source.canReceiveCounters(counterType)) {
String message = String.format("Won't be able to pay upkeep for %s but it can't have %s counters put on it.", source, counterType.getName());
p.getGame().getGameLog().add("ResolveStack", message, 2);
return false;
}
String plural = amount > 1 ? "s" : "";
if (false == GuiDialog.confirm(source, "Do you want to put " + amount + " " + counterType.getName() + " counter" + plural + " on " + source + "?"))
return false;
source.addCounter(counterType, amount, false);
}
else if (part instanceof CostRemoveCounter) {
CounterType counterType = ((CostRemoveCounter) part).getCounter();
int amount = getAmountFromPartX(part, source, sourceAbility);
String plural = amount > 1 ? "s" : "";
if (!part.canPay(sourceAbility))
return false;
if ( false == GuiDialog.confirm(source, "Do you want to remove " + amount + " " + counterType.getName() + " counter" + plural + " from " + source + "?"))
return false;
source.subtractCounter(counterType, amount);
}
else if (part instanceof CostExile) {
if ("All".equals(part.getType())) {
if (false == GuiDialog.confirm(source, "Do you want to exile all cards in your graveyard?"))
return false;
List<Card> cards = new ArrayList<Card>(p.getCardsIn(ZoneType.Graveyard));
for (final Card card : cards) {
p.getGame().getAction().exile(card);
}
} else {
CostExile costExile = (CostExile) part;
ZoneType from = costExile.getFrom();
List<Card> list = CardLists.getValidCards(p.getCardsIn(from), part.getType().split(";"), p, source);
final int nNeeded = AbilityUtils.calculateAmount(source, part.getAmount(), ability);
if (list.size() < nNeeded)
return false;
// replace this with input
for (int i = 0; i < nNeeded; i++) {
final Card c = GuiChoose.oneOrNone("Exile from " + from, list);
if (c == null)
return false;
list.remove(c);
p.getGame().getAction().exile(c);
}
}
}
else if (part instanceof CostSacrifice) {
int amount = Integer.parseInt(((CostSacrifice)part).getAmount());
List<Card> list = CardLists.getValidCards(p.getCardsIn(ZoneType.Battlefield), part.getType(), p, source);
boolean hasPaid = payCostPart(sourceAbility, (CostPartWithList)part, amount, list, "sacrifice." + orString);
if(!hasPaid) return false;
} else if (part instanceof CostReturn) {
List<Card> list = CardLists.getValidCards(p.getCardsIn(ZoneType.Battlefield), part.getType(), p, source);
int amount = getAmountFromPartX(part, source, sourceAbility);
boolean hasPaid = payCostPart(sourceAbility, (CostPartWithList)part, amount, list, "return to hand." + orString);
if(!hasPaid) return false;
} else if (part instanceof CostDiscard) {
List<Card> list = CardLists.getValidCards(p.getCardsIn(ZoneType.Hand), part.getType(), p, source);
int amount = getAmountFromPartX(part, source, sourceAbility);
boolean hasPaid = payCostPart(sourceAbility, (CostPartWithList)part, amount, list, "discard." + orString);
if(!hasPaid) return false;
} else if (part instanceof CostReveal) {
List<Card> list = CardLists.getValidCards(p.getCardsIn(ZoneType.Hand), part.getType(), p, source);
int amount = getAmountFromPartX(part, source, sourceAbility);
boolean hasPaid = payCostPart(sourceAbility, (CostPartWithList)part, amount, list, "reveal." + orString);
if(!hasPaid) return false;
} else if (part instanceof CostTapType) {
List<Card> list = CardLists.getValidCards(p.getCardsIn(ZoneType.Battlefield), part.getType(), p, source);
list = CardLists.filter(list, Presets.UNTAPPED);
int amount = getAmountFromPartX(part, source, sourceAbility);
boolean hasPaid = payCostPart(sourceAbility, (CostPartWithList)part, amount, list, "tap." + orString);
if(!hasPaid) return false;
}
else if (part instanceof CostPartMana ) {
if (!((CostPartMana) part).getManaToPay().isZero()) // non-zero costs require input
mayRemovePart = false;
} else
throw new RuntimeException("GameActionUtil.payCostDuringAbilityResolve - An unhandled type of cost was met: " + part.getClass());
if( mayRemovePart )
remainingParts.remove(part);
}
if (remainingParts.isEmpty()) {
return true;
}
if (remainingParts.size() > 1) {
throw new RuntimeException("GameActionUtil.payCostDuringAbilityResolve - Too many payment types - " + source);
}
costPart = remainingParts.get(0);
// check this is a mana cost
if (!(costPart instanceof CostPartMana ))
throw new RuntimeException("GameActionUtil.payCostDuringAbilityResolve - The remaining payment type is not Mana.");
InputPayment toSet = current == null
? new InputPayManaExecuteCommands(p, source + "\r\n", cost.getCostMana().getManaToPay())
: new InputPayManaExecuteCommands(p, source + "\r\n" + "Current Card: " + current + "\r\n" , cost.getCostMana().getManaToPay());
FThreads.setInputAndWait(toSet);
return toSet.isPaid();
}
private static boolean payCostPart(SpellAbility sourceAbility, CostPartWithList cpl, int amount, List<Card> list, String actionName) {
if (list.size() < amount) return false; // unable to pay (not enough cards)
InputSelectCards inp = new InputSelectCardsFromList(amount, amount, list);
inp.setMessage("Select %d " + cpl.getDescriptiveType() + " card(s) to " + actionName);
inp.setCancelAllowed(true);
FThreads.setInputAndWait(inp);
if( inp.hasCancelled() || inp.getSelected().size() != amount)
return false;
for(Card c : inp.getSelected()) {
cpl.executePayment(sourceAbility, c);
}
if (sourceAbility != null) {
cpl.reportPaidCardsTo(sourceAbility);
}
return true;
}
// not restricted to combat damage, not restricted to dealing damage to
// creatures/players
/**
* <p>
* executeDamageDealingEffects.
* </p>
*
* @param source
* a {@link forge.Card} object.
* @param damage
* a int.
*/
public static void executeDamageDealingEffects(final Card source, final int damage) {
if (damage <= 0) {
return;
}
if (source.hasKeyword("Lifelink")) {
source.getController().gainLife(damage, source);
}
}
// not restricted to combat damage, restricted to dealing damage to
// creatures
/**
* <p>
* executeDamageToCreatureEffects.
* </p>
*
* @param source
* a {@link forge.Card} object.
* @param affected
* a {@link forge.Card} object.
* @param damage
* a int.
*/
public static void executeDamageToCreatureEffects(final Card source, final Card affected, final int damage) {
if (damage <= 0) {
return;
}
final GameState game = source.getGame();
if (affected.hasStartOfKeyword("When CARDNAME is dealt damage, destroy it.")) {
final Ability ability = new AbilityDestroy(source, affected, true);
final Ability ability2 = new AbilityDestroy(source, affected, false);
final StringBuilder sb = new StringBuilder();
sb.append(affected).append(" - destroy");
ability.setStackDescription(sb.toString());
ability2.setStackDescription(sb.toString());
final int amount = affected.getAmountOfKeyword("When CARDNAME is dealt damage, destroy it. It can't be regenerated.");
for (int i = 0; i < amount; i++) {
game.getStack().addSimultaneousStackEntry(ability2);
}
final int amount2 = affected.getAmountOfKeyword("When CARDNAME is dealt damage, destroy it.");
for (int i = 0; i < amount2; i++) {
game.getStack().addSimultaneousStackEntry(ability);
}
}
// Play the Damage sound
game.getEvents().post(new CardDamagedEvent());
}
// this is for cards like Sengir Vampire // this is for cards like Sengir Vampire
/** /**
* <p> * <p>
@@ -723,30 +375,6 @@ public final class GameActionUtil {
} }
} }
// not restricted to just combat damage, restricted to players
/**
* <p>
* executeDamageToPlayerEffects.
* </p>
*
* @param player
* a {@link forge.game.player.Player} object.
* @param c
* a {@link forge.Card} object.
* @param damage
* a int.
*/
public static void executeDamageToPlayerEffects(final Player player, final Card c, final int damage) {
if (damage <= 0) {
return;
}
c.getDamageHistory().registerDamage(player);
// Play the Life Loss sound
player.getGame().getEvents().post(new LifeLossEvent());
}
// restricted to combat damage, restricted to players // restricted to combat damage, restricted to players
/** /**
* <p> * <p>
@@ -771,14 +399,11 @@ public final class GameActionUtil {
final String parse = c.getKeyword().get(keywordPosition).toString(); final String parse = c.getKeyword().get(keywordPosition).toString();
final String[] k = parse.split(" "); final String[] k = parse.split(" ");
final int poison = Integer.parseInt(k[1]); final int poison = Integer.parseInt(k[1]);
final Card crd = c;
final Ability ability = new Ability(c, ManaCost.ZERO) { final Ability ability = new Ability(c, ManaCost.ZERO) {
@Override @Override
public void resolve() { public void resolve() {
final Player player = crd.getController(); player.addPoisonCounters(poison, c);
final Player opponent = player.getOpponent();
opponent.addPoisonCounters(poison, c);
} }
}; };
@@ -786,9 +411,7 @@ public final class GameActionUtil {
sb.append(c); sb.append(c);
sb.append(" - Poisonous: "); sb.append(" - Poisonous: ");
sb.append(c.getController().getOpponent()); sb.append(c.getController().getOpponent());
sb.append(" gets "); sb.append(" gets ").append(poison).append(" poison counter");
sb.append(poison);
sb.append(" poison counter");
if (poison != 1) { if (poison != 1) {
sb.append("s"); sb.append("s");
} }

View File

@@ -180,7 +180,7 @@ public class MatchController {
// The UI controls should use these game data as models // The UI controls should use these game data as models
CMatchUI.SINGLETON_INSTANCE.initMatch(currentGame.getRegisteredPlayers(), localHuman); CMatchUI.SINGLETON_INSTANCE.initMatch(currentGame.getRegisteredPlayers(), localHuman);
CDock.SINGLETON_INSTANCE.setModel(currentGame, localHuman); CDock.SINGLETON_INSTANCE.setModel(currentGame, localHuman);
CStack.SINGLETON_INSTANCE.setModel(currentGame.getStack()); CStack.SINGLETON_INSTANCE.setModel(currentGame.getStack(), localHuman);
CLog.SINGLETON_INSTANCE.setModel(currentGame.getGameLog()); CLog.SINGLETON_INSTANCE.setModel(currentGame.getGameLog());
CCombat.SINGLETON_INSTANCE.setModel(currentGame); CCombat.SINGLETON_INSTANCE.setModel(currentGame);
CMessage.SINGLETON_INSTANCE.setModel(match); CMessage.SINGLETON_INSTANCE.setModel(match);

View File

@@ -86,7 +86,7 @@ public class AiController {
public final SpellAbility getSpellAbilityToPlay() { public final SpellAbility getSpellAbilityToPlay() {
// if top of stack is owned by me // if top of stack is owned by me
if (!game.getStack().isEmpty() && game.getStack().peekInstance().getActivatingPlayer().equals(player)) { if (!game.getStack().isEmpty() && game.getStack().peekAbility().getActivatingPlayer().equals(player)) {
// probably should let my stuff resolve // probably should let my stuff resolve
return null; return null;
} }
@@ -792,7 +792,7 @@ public class AiController {
public void onPriorityRecieved() { public void onPriorityRecieved() {
final PhaseType phase = game.getPhaseHandler().getPhase(); final PhaseType phase = game.getPhaseHandler().getPhase();
if (game.getStack().size() > 0) { if (!game.getStack().isEmpty()) {
playSpellAbilities(game); playSpellAbilities(game);
} else { } else {
switch(phase) { switch(phase) {

View File

@@ -42,6 +42,7 @@ import forge.card.cost.CostPayment;
import forge.card.spellability.AbilityManaPart; import forge.card.spellability.AbilityManaPart;
import forge.card.spellability.AbilityStatic; import forge.card.spellability.AbilityStatic;
import forge.card.spellability.SpellAbility; import forge.card.spellability.SpellAbility;
import forge.card.spellability.SpellAbilityStackInstance;
import forge.card.spellability.Target; import forge.card.spellability.Target;
import forge.error.BugReporter; import forge.error.BugReporter;
import forge.game.GameState; import forge.game.GameState;
@@ -271,7 +272,7 @@ public class ComputerUtil {
final SpellAbility newSA = sa.copyWithNoManaCost(); final SpellAbility newSA = sa.copyWithNoManaCost();
newSA.setActivatingPlayer(ai); newSA.setActivatingPlayer(ai);
if (!ComputerUtilCost.canPayAdditionalCosts(newSA, ai)) { if (!CostPayment.canPayAdditionalCosts(newSA.getPayCosts(), newSA)) {
return; return;
} }
@@ -462,13 +463,14 @@ public class ComputerUtil {
final GameState game = ai.getGame(); final GameState game = ai.getGame();
List<Card> typeList = new ArrayList<Card>(); List<Card> typeList = new ArrayList<Card>();
if (zone.equals(ZoneType.Stack)) { if (zone.equals(ZoneType.Stack)) {
for (int i = 0; i < game.getStack().size(); i++) { for (SpellAbilityStackInstance si : game.getStack()) {
typeList.add(game.getStack().peekAbility(i).getSourceCard()); typeList.add(si.getSourceCard());
typeList = CardLists.getValidCards(typeList, type.split(","), activate.getController(), activate);
} }
} else { } else {
typeList = CardLists.getValidCards(ai.getCardsIn(zone), type.split(","), activate.getController(), activate); typeList = ai.getCardsIn(zone);
} }
typeList = CardLists.getValidCards(typeList, type.split(","), activate.getController(), activate);
if ((target != null) && target.getController() == ai && typeList.contains(target)) { if ((target != null) && target.getController() == ai && typeList.contains(target)) {
typeList.remove(target); // don't exile the card we're pumping typeList.remove(target); // don't exile the card we're pumping
} }

View File

@@ -364,30 +364,8 @@ public class ComputerUtilCost {
return false; return false;
} }
return ComputerUtilCost.canPayAdditionalCosts(sa, player);
} // canPayCost()
/**
* <p>
* canPayAdditionalCosts.
* </p>
*
* @param sa
* a {@link forge.card.spellability.SpellAbility} object.
* @param player
* a {@link forge.game.player.Player} object.
* @return a boolean.
*/
public static boolean canPayAdditionalCosts(final SpellAbility sa, final Player player) {
if (sa.getActivatingPlayer() == null) {
final StringBuilder sb = new StringBuilder();
sb.append(sa.getSourceCard());
sb.append(" in ComputerUtil.canPayAdditionalCosts() without an activating player");
System.out.println(sb.toString());
sa.setActivatingPlayer(player);
}
return CostPayment.canPayAdditionalCosts(sa.getPayCosts(), sa); return CostPayment.canPayAdditionalCosts(sa.getPayCosts(), sa);
} } // canPayCost()
public static boolean willPayUnlessCost(SpellAbility sa, Player payer, SpellAbility ability, boolean alreadyPaid, List<Player> payers) { public static boolean willPayUnlessCost(SpellAbility sa, Player payer, SpellAbility ability, boolean alreadyPaid, List<Player> payers) {
final Card source = sa.getSourceCard(); final Card source = sa.getSourceCard();
@@ -426,20 +404,17 @@ public class ComputerUtilCost {
if (alreadyPaid || (payers.size() > 1 && (isMine && !payForOwnOnly))) { if (alreadyPaid || (payers.size() > 1 && (isMine && !payForOwnOnly))) {
return false; return false;
} }
if (canPayCost(ability, payer)
&& checkLifeCost(payer, ability.getPayCosts(), source, 4, sa) // AI was crashing because the blank ability used to pay costs
&& checkDamageCost(payer, ability.getPayCosts(), source, 4) // Didn't have any of the data on the original SA to pay dependant costs
&& (isMine || checkDiscardCost(payer, ability.getPayCosts(), source))
&& (!source.getName().equals("Tyrannize") || payer.getCardsIn(ZoneType.Hand).size() > 2) return checkLifeCost(payer, ability.getPayCosts(), source, 4, sa)
&& (!source.getName().equals("Perplex") || payer.getCardsIn(ZoneType.Hand).size() < 2) && checkDamageCost(payer, ability.getPayCosts(), source, 4)
&& (!source.getName().equals("Breaking Point") || payer.getCreaturesInPlay().size() > 1) && (isMine || checkDiscardCost(payer, ability.getPayCosts(), source))
&& (!source.getName().equals("Chain of Vapor") && (!source.getName().equals("Tyrannize") || payer.getCardsIn(ZoneType.Hand).size() > 2)
|| (payer.getOpponent().getCreaturesInPlay().size() > 0 && payer.getLandsInPlay().size() > 3))) { && (!source.getName().equals("Perplex") || payer.getCardsIn(ZoneType.Hand).size() < 2)
// AI was crashing because the blank ability used to pay costs && (!source.getName().equals("Breaking Point") || payer.getCreaturesInPlay().size() > 1)
// Didn't have any of the data on the original SA to pay dependant costs && (!source.getName().equals("Chain of Vapor") || (payer.getOpponent().getCreaturesInPlay().size() > 0 && payer.getLandsInPlay().size() > 3));
return true;
}
return false;
} }
} }

View File

@@ -131,7 +131,7 @@ public class ComputerUtilMana {
ma.setActivatingPlayer(ai); ma.setActivatingPlayer(ai);
// if the AI can't pay the additional costs skip the mana ability // if the AI can't pay the additional costs skip the mana ability
if (ma.getPayCosts() != null && checkPlayable) { if (ma.getPayCosts() != null && checkPlayable) {
if (!ComputerUtilCost.canPayAdditionalCosts(ma, ai)) { if (!CostPayment.canPayAdditionalCosts(ma.getPayCosts(), ma)) {
continue; continue;
} }
} else if (sourceCard.isTapped() && checkPlayable) { } else if (sourceCard.isTapped() && checkPlayable) {
@@ -535,7 +535,7 @@ public class ComputerUtilMana {
// ability // ability
m.setActivatingPlayer(ai); m.setActivatingPlayer(ai);
if (cost != null) { if (cost != null) {
if (!ComputerUtilCost.canPayAdditionalCosts(m, ai)) { if (!CostPayment.canPayAdditionalCosts(m.getPayCosts(), m)) {
continue; continue;
} }
} }

View File

@@ -46,12 +46,12 @@ import forge.card.spellability.Ability;
import forge.card.spellability.AbilityStatic; import forge.card.spellability.AbilityStatic;
import forge.card.staticability.StaticAbility; import forge.card.staticability.StaticAbility;
import forge.card.trigger.TriggerType; import forge.card.trigger.TriggerType;
import forge.game.GameActionUtil;
import forge.game.GameState; import forge.game.GameState;
import forge.game.GlobalRuleChange; import forge.game.GlobalRuleChange;
import forge.game.ai.ComputerUtil; import forge.game.ai.ComputerUtil;
import forge.game.ai.ComputerUtilCard; import forge.game.ai.ComputerUtilCard;
import forge.game.ai.ComputerUtilCost; import forge.game.ai.ComputerUtilCost;
import forge.game.player.HumanPlay;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.zone.PlayerZone; import forge.game.zone.PlayerZone;
import forge.game.zone.ZoneType; import forge.game.zone.ZoneType;
@@ -1078,7 +1078,7 @@ public class CombatUtil {
ability.setActivatingPlayer(c.getController()); ability.setActivatingPlayer(c.getController());
if (c.getController().isHuman()) { if (c.getController().isHuman()) {
hasPaid = GameActionUtil.payCostDuringAbilityResolve(ability, attackCost, null, game); hasPaid = HumanPlay.payCostDuringAbilityResolve(ability, attackCost, null, game);
} else { // computer } else { // computer
if (ComputerUtilCost.canPayCost(ability, c.getController())) { if (ComputerUtilCost.canPayCost(ability, c.getController())) {
ComputerUtil.playNoStack(c.getController(), ability, game); ComputerUtil.playNoStack(c.getController(), ability, game);

View File

@@ -429,7 +429,7 @@ public class PhaseHandler extends MyObservable implements java.io.Serializable {
this.setPlayersPriorityPermission(true); // PlayerPriorityAllowed = false; this.setPlayersPriorityPermission(true); // PlayerPriorityAllowed = false;
// If the Stack isn't empty why is nextPhase being called? // If the Stack isn't empty why is nextPhase being called?
if (game.getStack().size() != 0) { if (!game.getStack().isEmpty()) {
Log.debug("Phase.nextPhase() is called, but Stack isn't empty."); Log.debug("Phase.nextPhase() is called, but Stack isn't empty.");
return; return;
} }

View File

@@ -32,10 +32,10 @@ import forge.card.spellability.Ability;
import forge.card.spellability.AbilityStatic; import forge.card.spellability.AbilityStatic;
import forge.card.staticability.StaticAbility; import forge.card.staticability.StaticAbility;
import forge.card.trigger.TriggerType; import forge.card.trigger.TriggerType;
import forge.game.GameActionUtil;
import forge.game.GameState; import forge.game.GameState;
import forge.game.ai.ComputerUtil; import forge.game.ai.ComputerUtil;
import forge.game.ai.ComputerUtilCost; import forge.game.ai.ComputerUtilCost;
import forge.game.player.HumanPlay;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.zone.ZoneType; import forge.game.zone.ZoneType;
import forge.gui.match.CMatchUI; import forge.gui.match.CMatchUI;
@@ -225,7 +225,7 @@ public class PhaseUtil {
ability.setActivatingPlayer(blocker.getController()); ability.setActivatingPlayer(blocker.getController());
if (blocker.getController().isHuman()) { if (blocker.getController().isHuman()) {
hasPaid = GameActionUtil.payCostDuringAbilityResolve(ability, blockCost, null, game); hasPaid = HumanPlay.payCostDuringAbilityResolve(ability, blockCost, null, game);
} else { // computer } else { // computer
if (ComputerUtilCost.canPayCost(ability, blocker.getController())) { if (ComputerUtilCost.canPayCost(ability, blocker.getController())) {
ComputerUtil.playNoStack(blocker.getController(), ability, game); ComputerUtil.playNoStack(blocker.getController(), ability, game);

View File

@@ -41,13 +41,13 @@ import forge.control.input.InputPayManaExecuteCommands;
import forge.control.input.InputPayment; import forge.control.input.InputPayment;
import forge.control.input.InputSelectCards; import forge.control.input.InputSelectCards;
import forge.control.input.InputSelectCardsFromList; import forge.control.input.InputSelectCardsFromList;
import forge.game.GameActionUtil;
import forge.game.GameState; import forge.game.GameState;
import forge.game.ai.ComputerUtil; import forge.game.ai.ComputerUtil;
import forge.game.ai.ComputerUtilCard; import forge.game.ai.ComputerUtilCard;
import forge.game.ai.ComputerUtilCombat; import forge.game.ai.ComputerUtilCombat;
import forge.game.ai.ComputerUtilCost; import forge.game.ai.ComputerUtilCost;
import forge.game.ai.ComputerUtilMana; import forge.game.ai.ComputerUtilMana;
import forge.game.player.HumanPlay;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.zone.PlayerZone; import forge.game.zone.PlayerZone;
import forge.game.zone.ZoneType; import forge.game.zone.ZoneType;
@@ -182,7 +182,7 @@ public class Upkeep extends Phase {
Player controller = c.getController(); Player controller = c.getController();
if (controller.isHuman()) { if (controller.isHuman()) {
Cost cost = new Cost(c.getEchoCost().trim(), true); Cost cost = new Cost(c.getEchoCost().trim(), true);
if ( !GameActionUtil.payCostDuringAbilityResolve(blankAbility, cost, null, game) ) if ( !HumanPlay.payCostDuringAbilityResolve(blankAbility, cost, null, game) )
game.getAction().sacrifice(c, null);; game.getAction().sacrifice(c, null);;
} else { // computer } else { // computer
@@ -324,7 +324,7 @@ public class Upkeep extends Phase {
@Override @Override
public void resolve() { public void resolve() {
if (controller.isHuman()) { if (controller.isHuman()) {
if ( !GameActionUtil.payCostDuringAbilityResolve(blankAbility, blankAbility.getPayCosts(), this, game)) if ( !HumanPlay.payCostDuringAbilityResolve(blankAbility, blankAbility.getPayCosts(), this, game))
game.getAction().sacrifice(c, null); game.getAction().sacrifice(c, null);
} else { // computer } else { // computer
if (ComputerUtilCost.shouldPayCost(controller, c, upkeepCost) && ComputerUtilCost.canPayCost(blankAbility, controller)) { if (ComputerUtilCost.shouldPayCost(controller, c, upkeepCost) && ComputerUtilCost.canPayCost(blankAbility, controller)) {

View File

@@ -1,14 +1,35 @@
package forge.game.player; package forge.game.player;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.apache.commons.lang3.StringUtils;
import forge.Card; import forge.Card;
import forge.CardLists;
import forge.CardPredicates.Presets;
import forge.CounterType;
import forge.FThreads; import forge.FThreads;
import forge.card.ability.AbilityUtils; import forge.card.ability.AbilityUtils;
import forge.card.ability.ApiType; import forge.card.ability.ApiType;
import forge.card.ability.effects.CharmEffect; import forge.card.ability.effects.CharmEffect;
import forge.card.cardfactory.CardFactoryUtil;
import forge.card.cost.Cost; import forge.card.cost.Cost;
import forge.card.cost.CostDamage;
import forge.card.cost.CostDiscard;
import forge.card.cost.CostExile;
import forge.card.cost.CostMill;
import forge.card.cost.CostPart;
import forge.card.cost.CostPartMana;
import forge.card.cost.CostPartWithList;
import forge.card.cost.CostPayLife;
import forge.card.cost.CostPayment; import forge.card.cost.CostPayment;
import forge.card.cost.CostPutCounter;
import forge.card.cost.CostRemoveCounter;
import forge.card.cost.CostReturn;
import forge.card.cost.CostReveal;
import forge.card.cost.CostSacrifice;
import forge.card.cost.CostTapType;
import forge.card.mana.ManaCost; import forge.card.mana.ManaCost;
import forge.card.mana.ManaCostBeingPaid; import forge.card.mana.ManaCostBeingPaid;
import forge.card.mana.ManaCostShard; import forge.card.mana.ManaCostShard;
@@ -17,8 +38,16 @@ import forge.card.spellability.HumanPlaySpellAbility;
import forge.card.spellability.SpellAbility; import forge.card.spellability.SpellAbility;
import forge.card.spellability.Target; import forge.card.spellability.Target;
import forge.control.input.InputPayManaBase; import forge.control.input.InputPayManaBase;
import forge.control.input.InputPayManaExecuteCommands;
import forge.control.input.InputPayManaSimple; import forge.control.input.InputPayManaSimple;
import forge.control.input.InputPayment;
import forge.control.input.InputSelectCards;
import forge.control.input.InputSelectCardsFromList;
import forge.game.GameActionUtil; import forge.game.GameActionUtil;
import forge.game.GameState;
import forge.game.zone.ZoneType;
import forge.gui.GuiChoose;
import forge.gui.GuiDialog;
/** /**
* TODO: Write javadoc for this type. * TODO: Write javadoc for this type.
@@ -223,4 +252,238 @@ public class HumanPlay {
} }
} }
// ------------------------------------------------------------------------
private static int getAmountFromPart(CostPart part, Card source, SpellAbility sourceAbility) {
String amountString = part.getAmount();
return StringUtils.isNumeric(amountString) ? Integer.parseInt(amountString) : AbilityUtils.calculateAmount(source, amountString, sourceAbility);
}
/**
* TODO: Write javadoc for this method.
* @param part
* @param source
* @param sourceAbility
* @return
*/
private static int getAmountFromPartX(CostPart part, Card source, SpellAbility sourceAbility) {
String amountString = part.getAmount();
return StringUtils.isNumeric(amountString) ? Integer.parseInt(amountString) : CardFactoryUtil.xCount(source, source.getSVar(amountString));
}
/**
* <p>
* payCostDuringAbilityResolve.
* </p>
*
* @param ability
* a {@link forge.card.spellability.SpellAbility} object.
* @param cost
* a {@link forge.card.cost.Cost} object.
* @param paid
* a {@link forge.Command} object.
* @param unpaid
* a {@link forge.Command} object.
* @param sourceAbility TODO
*/
public static boolean payCostDuringAbilityResolve(final SpellAbility ability, final Cost cost, SpellAbility sourceAbility, final GameState game) {
// Only human player pays this way
final Player p = ability.getActivatingPlayer();
final Card source = ability.getSourceCard();
Card current = null; // Used in spells with RepeatEach effect to distinguish cards, Cut the Tethers
if (!source.getRemembered().isEmpty()) {
if (source.getRemembered().get(0) instanceof Card) {
current = (Card) source.getRemembered().get(0);
}
}
if (!source.getImprinted().isEmpty()) {
current = source.getImprinted().get(0);
}
final List<CostPart> parts = cost.getCostParts();
ArrayList<CostPart> remainingParts = new ArrayList<CostPart>(cost.getCostParts());
CostPart costPart = null;
if (!parts.isEmpty()) {
costPart = parts.get(0);
}
final String orString = sourceAbility == null ? "" : " (or: " + sourceAbility.getStackDescription() + ")";
if (parts.isEmpty() || costPart.getAmount().equals("0")) {
return GuiDialog.confirm(source, "Do you want to pay 0?" + orString);
}
//the following costs do not need inputs
for (CostPart part : parts) {
boolean mayRemovePart = true;
if (part instanceof CostPayLife) {
final int amount = getAmountFromPart(part, source, sourceAbility);
if (!p.canPayLife(amount))
return false;
if (false == GuiDialog.confirm(source, "Do you want to pay " + amount + " life?" + orString))
return false;
p.payLife(amount, null);
}
else if (part instanceof CostMill) {
final int amount = getAmountFromPart(part, source, sourceAbility);
final List<Card> list = p.getCardsIn(ZoneType.Library);
if (list.size() < amount) return false;
if (!GuiDialog.confirm(source, "Do you want to mill " + amount + " card(s)?" + orString))
return false;
List<Card> listmill = p.getCardsIn(ZoneType.Library, amount);
((CostMill) part).executePayment(sourceAbility, listmill);
}
else if (part instanceof CostDamage) {
int amount = getAmountFromPartX(part, source, sourceAbility);
if (!p.canPayLife(amount))
return false;
if (false == GuiDialog.confirm(source, "Do you want " + source + " to deal " + amount + " damage to you?"))
return false;
p.addDamage(amount, source);
}
else if (part instanceof CostPutCounter) {
CounterType counterType = ((CostPutCounter) part).getCounter();
int amount = getAmountFromPartX(part, source, sourceAbility);
if (false == source.canReceiveCounters(counterType)) {
String message = String.format("Won't be able to pay upkeep for %s but it can't have %s counters put on it.", source, counterType.getName());
p.getGame().getGameLog().add("ResolveStack", message, 2);
return false;
}
String plural = amount > 1 ? "s" : "";
if (false == GuiDialog.confirm(source, "Do you want to put " + amount + " " + counterType.getName() + " counter" + plural + " on " + source + "?"))
return false;
source.addCounter(counterType, amount, false);
}
else if (part instanceof CostRemoveCounter) {
CounterType counterType = ((CostRemoveCounter) part).getCounter();
int amount = getAmountFromPartX(part, source, sourceAbility);
String plural = amount > 1 ? "s" : "";
if (!part.canPay(sourceAbility))
return false;
if ( false == GuiDialog.confirm(source, "Do you want to remove " + amount + " " + counterType.getName() + " counter" + plural + " from " + source + "?"))
return false;
source.subtractCounter(counterType, amount);
}
else if (part instanceof CostExile) {
if ("All".equals(part.getType())) {
if (false == GuiDialog.confirm(source, "Do you want to exile all cards in your graveyard?"))
return false;
List<Card> cards = new ArrayList<Card>(p.getCardsIn(ZoneType.Graveyard));
for (final Card card : cards) {
p.getGame().getAction().exile(card);
}
} else {
CostExile costExile = (CostExile) part;
ZoneType from = costExile.getFrom();
List<Card> list = CardLists.getValidCards(p.getCardsIn(from), part.getType().split(";"), p, source);
final int nNeeded = AbilityUtils.calculateAmount(source, part.getAmount(), ability);
if (list.size() < nNeeded)
return false;
// replace this with input
for (int i = 0; i < nNeeded; i++) {
final Card c = GuiChoose.oneOrNone("Exile from " + from, list);
if (c == null)
return false;
list.remove(c);
p.getGame().getAction().exile(c);
}
}
}
else if (part instanceof CostSacrifice) {
int amount = Integer.parseInt(((CostSacrifice)part).getAmount());
List<Card> list = CardLists.getValidCards(p.getCardsIn(ZoneType.Battlefield), part.getType(), p, source);
boolean hasPaid = payCostPart(sourceAbility, (CostPartWithList)part, amount, list, "sacrifice." + orString);
if(!hasPaid) return false;
} else if (part instanceof CostReturn) {
List<Card> list = CardLists.getValidCards(p.getCardsIn(ZoneType.Battlefield), part.getType(), p, source);
int amount = getAmountFromPartX(part, source, sourceAbility);
boolean hasPaid = payCostPart(sourceAbility, (CostPartWithList)part, amount, list, "return to hand." + orString);
if(!hasPaid) return false;
} else if (part instanceof CostDiscard) {
List<Card> list = CardLists.getValidCards(p.getCardsIn(ZoneType.Hand), part.getType(), p, source);
int amount = getAmountFromPartX(part, source, sourceAbility);
boolean hasPaid = payCostPart(sourceAbility, (CostPartWithList)part, amount, list, "discard." + orString);
if(!hasPaid) return false;
} else if (part instanceof CostReveal) {
List<Card> list = CardLists.getValidCards(p.getCardsIn(ZoneType.Hand), part.getType(), p, source);
int amount = getAmountFromPartX(part, source, sourceAbility);
boolean hasPaid = payCostPart(sourceAbility, (CostPartWithList)part, amount, list, "reveal." + orString);
if(!hasPaid) return false;
} else if (part instanceof CostTapType) {
List<Card> list = CardLists.getValidCards(p.getCardsIn(ZoneType.Battlefield), part.getType(), p, source);
list = CardLists.filter(list, Presets.UNTAPPED);
int amount = getAmountFromPartX(part, source, sourceAbility);
boolean hasPaid = payCostPart(sourceAbility, (CostPartWithList)part, amount, list, "tap." + orString);
if(!hasPaid) return false;
}
else if (part instanceof CostPartMana ) {
if (!((CostPartMana) part).getManaToPay().isZero()) // non-zero costs require input
mayRemovePart = false;
} else
throw new RuntimeException("GameActionUtil.payCostDuringAbilityResolve - An unhandled type of cost was met: " + part.getClass());
if( mayRemovePart )
remainingParts.remove(part);
}
if (remainingParts.isEmpty()) {
return true;
}
if (remainingParts.size() > 1) {
throw new RuntimeException("GameActionUtil.payCostDuringAbilityResolve - Too many payment types - " + source);
}
costPart = remainingParts.get(0);
// check this is a mana cost
if (!(costPart instanceof CostPartMana ))
throw new RuntimeException("GameActionUtil.payCostDuringAbilityResolve - The remaining payment type is not Mana.");
InputPayment toSet = current == null
? new InputPayManaExecuteCommands(p, source + "\r\n", cost.getCostMana().getManaToPay())
: new InputPayManaExecuteCommands(p, source + "\r\n" + "Current Card: " + current + "\r\n" , cost.getCostMana().getManaToPay());
FThreads.setInputAndWait(toSet);
return toSet.isPaid();
}
private static boolean payCostPart(SpellAbility sourceAbility, CostPartWithList cpl, int amount, List<Card> list, String actionName) {
if (list.size() < amount) return false; // unable to pay (not enough cards)
InputSelectCards inp = new InputSelectCardsFromList(amount, amount, list);
inp.setMessage("Select %d " + cpl.getDescriptiveType() + " card(s) to " + actionName);
inp.setCancelAllowed(true);
FThreads.setInputAndWait(inp);
if( inp.hasCancelled() || inp.getSelected().size() != amount)
return false;
for(Card c : inp.getSelected()) {
cpl.executePayment(sourceAbility, c);
}
if (sourceAbility != null) {
cpl.reportPaidCardsTo(sourceAbility);
}
return true;
}
} }

View File

@@ -612,7 +612,7 @@ public class Player extends GameEntity implements Comparable<Player> {
public final boolean addDamageAfterPrevention(final int damage, final Card source, final boolean isCombat) { public final boolean addDamageAfterPrevention(final int damage, final Card source, final boolean isCombat) {
final int damageToDo = damage; final int damageToDo = damage;
if (damageToDo == 0) { if (damageToDo <= 0) {
return false; return false;
} }
String additionalLog = ""; String additionalLog = "";
@@ -639,8 +639,11 @@ public class Player extends GameEntity implements Comparable<Player> {
} }
this.assignedDamage.put(source, damageToDo); this.assignedDamage.put(source, damageToDo);
GameActionUtil.executeDamageDealingEffects(source, damageToDo); if (source.hasKeyword("Lifelink")) {
GameActionUtil.executeDamageToPlayerEffects(this, source, damageToDo); source.getController().gainLife(damageToDo, source);
}
source.getDamageHistory().registerDamage(this);
this.getGame().getEvents().post(new LifeLossEvent());
if (isCombat) { if (isCombat) {
final ArrayList<String> types = source.getType(); final ArrayList<String> types = source.getType();
@@ -2822,7 +2825,7 @@ public class Player extends GameEntity implements Comparable<Player> {
public boolean canCastSorcery() { public boolean canCastSorcery() {
PhaseHandler now = game.getPhaseHandler(); PhaseHandler now = game.getPhaseHandler();
return now.isPlayerTurn(this) && now.getPhase().isMain() && game.getStack().size() == 0; return now.isPlayerTurn(this) && now.getPhase().isMain() && game.getStack().isEmpty();
} }

View File

@@ -20,6 +20,7 @@ package forge.game.zone;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Stack; import java.util.Stack;
import com.esotericsoftware.minlog.Log; import com.esotericsoftware.minlog.Log;
@@ -66,7 +67,7 @@ import forge.util.MyObservable;
* @author Forge * @author Forge
* @version $Id$ * @version $Id$
*/ */
public class MagicStack extends MyObservable { public class MagicStack extends MyObservable implements Iterable<SpellAbilityStackInstance> {
private final List<SpellAbility> simultaneousStackEntryList = new ArrayList<SpellAbility>(); private final List<SpellAbility> simultaneousStackEntryList = new ArrayList<SpellAbility>();
private final Stack<SpellAbilityStackInstance> stack = new Stack<SpellAbilityStackInstance>(); private final Stack<SpellAbilityStackInstance> stack = new Stack<SpellAbilityStackInstance>();
@@ -539,7 +540,7 @@ public class MagicStack extends MyObservable {
* @return a boolean. * @return a boolean.
*/ */
public final boolean isEmpty() { public final boolean isEmpty() {
return this.getStack().size() == 0; return this.getStack().isEmpty();
} }
// Push should only be used by add. // Push should only be used by add.
@@ -854,53 +855,12 @@ public class MagicStack extends MyObservable {
} }
} }
public final SpellAbility top() { private final SpellAbility top() {
final SpellAbilityStackInstance si = this.getStack().peek(); final SpellAbilityStackInstance si = this.getStack().peek();
final SpellAbility sa = si.getSpellAbility(); final SpellAbility sa = si.getSpellAbility();
return sa; return sa;
} }
// CAREFUL! Peeking while an SAs Targets are being choosen may cause issues
// index = 0 is the top, index = 1 is the next to top, etc...
/**
* <p>
* peekInstance.
* </p>
*
* @param index
* a int.
* @return a {@link forge.card.spellability.SpellAbilityStackInstance}
* object.
*/
public final SpellAbilityStackInstance peekInstance(final int index) {
return this.getStack().get(index);
}
/**
* <p>
* peekAbility.
* </p>
*
* @param index
* a int.
* @return a {@link forge.card.spellability.SpellAbility} object.
*/
public final SpellAbility peekAbility(final int index) {
return this.getStack().get(index).getSpellAbility();
}
/**
* <p>
* peekInstance.
* </p>
*
* @return a {@link forge.card.spellability.SpellAbilityStackInstance}
* object.
*/
public final SpellAbilityStackInstance peekInstance() {
return this.getStack().peek();
}
/** /**
* <p> * <p>
* peekAbility. * peekAbility.
@@ -1168,4 +1128,13 @@ public class MagicStack extends MyObservable {
return c.equals(this.curResolvingCard); return c.equals(this.curResolvingCard);
} }
/* (non-Javadoc)
* @see java.lang.Iterable#iterator()
*/
@Override
public Iterator<SpellAbilityStackInstance> iterator() {
// TODO Auto-generated method stub
return stack.iterator();
}
} }

View File

@@ -5,6 +5,7 @@ import java.util.Observer;
import forge.Command; import forge.Command;
import forge.FThreads; import forge.FThreads;
import forge.game.player.Player;
import forge.game.zone.MagicStack; import forge.game.zone.MagicStack;
import forge.gui.framework.EDocID; import forge.gui.framework.EDocID;
import forge.gui.framework.ICDoc; import forge.gui.framework.ICDoc;
@@ -22,6 +23,7 @@ public enum CStack implements ICDoc, Observer {
SINGLETON_INSTANCE; SINGLETON_INSTANCE;
private MagicStack model; private MagicStack model;
private Player viewer;
/* (non-Javadoc) /* (non-Javadoc)
* @see forge.gui.framework.ICDoc#getCommandOnSelect() * @see forge.gui.framework.ICDoc#getCommandOnSelect()
@@ -40,7 +42,7 @@ public enum CStack implements ICDoc, Observer {
private final Runnable upd = new Runnable() { @Override public void run() { private final Runnable upd = new Runnable() { @Override public void run() {
SDisplayUtil.showTab(EDocID.REPORT_STACK.getDoc()); SDisplayUtil.showTab(EDocID.REPORT_STACK.getDoc());
VStack.SINGLETON_INSTANCE.updateStack(model); VStack.SINGLETON_INSTANCE.updateStack(model, viewer);
} }; } };
/* (non-Javadoc) /* (non-Javadoc)
@@ -59,6 +61,9 @@ public enum CStack implements ICDoc, Observer {
FThreads.invokeInEdtNowOrLater(upd); FThreads.invokeInEdtNowOrLater(upd);
} }
public void setModel(MagicStack model) { this.model = model; } public void setModel(MagicStack model, Player guiPlayer) {
this.model = model;
this.viewer = guiPlayer;
}
} }

View File

@@ -23,6 +23,7 @@ import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter; import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent; import java.awt.event.MouseEvent;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
import javax.swing.JCheckBoxMenuItem; import javax.swing.JCheckBoxMenuItem;
@@ -35,6 +36,7 @@ import net.miginfocom.swing.MigLayout;
import forge.CardUtil; import forge.CardUtil;
import forge.card.spellability.SpellAbilityStackInstance; import forge.card.spellability.SpellAbilityStackInstance;
import forge.control.FControl; import forge.control.FControl;
import forge.game.player.Player;
import forge.game.player.PlayerController; import forge.game.player.PlayerController;
import forge.game.zone.MagicStack; import forge.game.zone.MagicStack;
import forge.gui.framework.DragCell; import forge.gui.framework.DragCell;
@@ -115,14 +117,15 @@ public enum VStack implements IVDoc<CStack> {
//========== Observer update methods //========== Observer update methods
/** /**
* @param stack */ * @param stack
public void updateStack(final MagicStack stack) { * @param viewer */
public void updateStack(final MagicStack stack, Player viewer) {
// No need to update this unless it's showing // No need to update this unless it's showing
if (!parentCell.getSelected().equals(this)) { return; } if (!parentCell.getSelected().equals(this)) { return; }
int count = 1; int count = 1;
JTextArea tar;
String txt, isOptional; List<JTextArea> list = new ArrayList<JTextArea>();
parentCell.getBody().removeAll(); parentCell.getBody().removeAll();
parentCell.getBody().setLayout(new MigLayout("insets 1%, gap 1%, wrap")); parentCell.getBody().setLayout(new MigLayout("insets 1%, gap 1%, wrap"));
@@ -133,15 +136,13 @@ public enum VStack implements IVDoc<CStack> {
Color[] scheme; Color[] scheme;
stackTARs.clear(); stackTARs.clear();
for (int i = stack.size() - 1; 0 <= i; i--) { boolean isFirst = true;
final SpellAbilityStackInstance spell = stack.peekInstance(i); for (final SpellAbilityStackInstance spell : stack) {
scheme = getSpellColor(spell); scheme = getSpellColor(spell);
isOptional = stack.peekAbility(i).isOptionalTrigger() String isOptional = spell.getSpellAbility().isOptionalTrigger() && spell.getSourceCard().getController().equals(viewer) ? "(OPTIONAL) " : "";
&& stack.peekAbility(i).getSourceCard().getController().isHuman() ? "(OPTIONAL) " : ""; String txt = (count++) + ". " + isOptional + spell.getStackDescription();
txt = (count++) + ". " + isOptional + spell.getStackDescription(); JTextArea tar = new JTextArea(txt);
tar = new JTextArea(txt);
tar.setToolTipText(txt); tar.setToolTipText(txt);
tar.setOpaque(true); tar.setOpaque(true);
tar.setBorder(border); tar.setBorder(border);
@@ -183,6 +184,7 @@ public enum VStack implements IVDoc<CStack> {
} }
}); });
} }
list.add(tar);
/* /*
* This updates the Card Picture/Detail when the spell is added to * This updates the Card Picture/Detail when the spell is added to
@@ -190,10 +192,15 @@ public enum VStack implements IVDoc<CStack> {
* *
* Problem is described in TODO right above this. * Problem is described in TODO right above this.
*/ */
if (i == 0 && !spell.getStackDescription().startsWith("Morph ")) { if (isFirst && !spell.getStackDescription().startsWith("Morph ")) {
CMatchUI.SINGLETON_INSTANCE.setCard(spell.getSourceCard()); CMatchUI.SINGLETON_INSTANCE.setCard(spell.getSourceCard());
} }
isFirst = false;
}
Collections.reverse(list);
for(JTextArea tar : list) {
parentCell.getBody().add(tar, "w 98%!"); parentCell.getBody().add(tar, "w 98%!");
stackTARs.add(tar); stackTARs.add(tar);
} }