From a9fb1fa85d7261a80e68b75333fb336910a4c6ae Mon Sep 17 00:00:00 2001 From: Maxmtg Date: Mon, 13 May 2013 12:18:28 +0000 Subject: [PATCH] payCostDuringAbilityResolve moved to HumanPlay MagicStack is Iterable, 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 --- src/main/java/forge/Card.java | 59 ++- .../java/forge/card/ability/AbilityUtils.java | 6 +- .../forge/card/ability/ai/ChangeZoneAi.java | 4 +- .../card/ability/ai/DamagePreventAi.java | 4 +- .../card/ability/ai/DamagePreventAllAi.java | 2 +- .../java/forge/card/ability/ai/EffectAi.java | 2 +- .../java/forge/card/ability/ai/ProtectAi.java | 4 +- .../java/forge/card/ability/ai/PumpAi.java | 6 +- .../forge/card/ability/ai/RegenerateAi.java | 4 +- .../card/ability/ai/RegenerateAllAi.java | 2 +- .../ability/effects/ChooseSourceEffect.java | 27 +- .../card/ability/effects/CounterEffect.java | 8 +- src/main/java/forge/card/cost/CostExile.java | 19 +- .../SpellAbilityStackInstance.java | 16 - .../card/spellability/TargetSelection.java | 10 +- .../card/trigger/TriggerSpellAbilityCast.java | 2 +- .../control/input/InputPassPriority.java | 2 +- src/main/java/forge/game/GameAction.java | 2 +- src/main/java/forge/game/GameActionUtil.java | 383 +----------------- src/main/java/forge/game/MatchController.java | 2 +- src/main/java/forge/game/ai/AiController.java | 4 +- src/main/java/forge/game/ai/ComputerUtil.java | 12 +- .../java/forge/game/ai/ComputerUtilCost.java | 49 +-- .../java/forge/game/ai/ComputerUtilMana.java | 4 +- .../java/forge/game/phase/CombatUtil.java | 4 +- .../java/forge/game/phase/PhaseHandler.java | 2 +- src/main/java/forge/game/phase/PhaseUtil.java | 4 +- src/main/java/forge/game/phase/Upkeep.java | 6 +- .../java/forge/game/player/HumanPlay.java | 263 ++++++++++++ src/main/java/forge/game/player/Player.java | 11 +- src/main/java/forge/game/zone/MagicStack.java | 57 +-- .../forge/gui/match/controllers/CStack.java | 9 +- .../java/forge/gui/match/views/VStack.java | 35 +- 33 files changed, 445 insertions(+), 579 deletions(-) diff --git a/src/main/java/forge/Card.java b/src/main/java/forge/Card.java index 3090eb51e88..c1e92f3effb 100644 --- a/src/main/java/forge/Card.java +++ b/src/main/java/forge/Card.java @@ -53,6 +53,7 @@ import forge.card.mana.ManaCost; import forge.card.replacement.ReplaceMoved; import forge.card.replacement.ReplacementEffect; import forge.card.replacement.ReplacementResult; +import forge.card.spellability.Ability; import forge.card.spellability.AbilityTriggered; import forge.card.spellability.OptionalCost; import forge.card.spellability.SpellAbility; @@ -62,9 +63,9 @@ import forge.card.staticability.StaticAbility; import forge.card.trigger.Trigger; import forge.card.trigger.TriggerType; import forge.card.trigger.ZCTrigger; -import forge.game.GameActionUtil; import forge.game.GameState; import forge.game.GlobalRuleChange; +import forge.game.event.CardDamagedEvent; import forge.game.event.CardEquippedEvent; import forge.game.event.CounterAddedEvent; import forge.game.event.CounterRemovedEvent; @@ -7406,7 +7407,9 @@ public class Card extends GameEntity implements Comparable { this.addReceivedDamageFromThisTurn(source, damageToAdd); source.addDealtDamageToThisTurn(this, damageToAdd); - GameActionUtil.executeDamageDealingEffects(source, damageToAdd); + if (source.hasKeyword("Lifelink")) { + source.getController().gainLife(damageToAdd, source); + } // Run triggers final Map runParams = new TreeMap(); @@ -7421,25 +7424,57 @@ public class Card extends GameEntity implements Comparable { this.subtractCounter(CounterType.LOYALTY, damageToAdd); additionalLog = String.format("(Removing %d Loyalty Counters)", damageToAdd); } 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) || source.hasKeyword("Wither") || source.hasKeyword("Infect")); - GameActionUtil.executeDamageToCreatureEffects(source, this, damageToAdd); - - if (this.isInPlay() && wither) { - this.addCounter(CounterType.M1M1, damageToAdd, true); - additionalLog = "(As -1/-1 Counters)"; + if (this.isInPlay()) { + if (wither) { + this.addCounter(CounterType.M1M1, damageToAdd, true); + additionalLog = "(As -1/-1 Counters)"; + } else + this.damage += damageToAdd; } + if (source.hasKeyword("Deathtouch") && this.isCreature()) { getGame().getAction().destroy(this, null); 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", - damageToAdd, this.getName(), additionalLog), 3); + getGame().getGameLog().add("Damage", String.format("Dealing %d damage to %s. %s", damageToAdd, this.getName(), additionalLog), 3); return true; } diff --git a/src/main/java/forge/card/ability/AbilityUtils.java b/src/main/java/forge/card/ability/AbilityUtils.java index fc1d471af28..339c47d2c08 100644 --- a/src/main/java/forge/card/ability/AbilityUtils.java +++ b/src/main/java/forge/card/ability/AbilityUtils.java @@ -21,10 +21,10 @@ import forge.card.spellability.Ability; import forge.card.spellability.AbilityStatic; import forge.card.spellability.AbilitySub; import forge.card.spellability.SpellAbility; -import forge.game.GameActionUtil; import forge.game.GameState; import forge.game.ai.ComputerUtil; import forge.game.ai.ComputerUtilCost; +import forge.game.player.HumanPlay; import forge.game.player.Player; import forge.game.zone.ZoneType; import forge.util.Expressions; @@ -1107,13 +1107,13 @@ public class AbilityUtils { for (Player payer : payers) { ability.setActivatingPlayer(payer); 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 paid = true; } } else { // 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); } } diff --git a/src/main/java/forge/card/ability/ai/ChangeZoneAi.java b/src/main/java/forge/card/ability/ai/ChangeZoneAi.java index 16547b93cce..61596f9523c 100644 --- a/src/main/java/forge/card/ability/ai/ChangeZoneAi.java +++ b/src/main/java/forge/card/ability/ai/ChangeZoneAi.java @@ -588,7 +588,7 @@ public class ChangeZoneAi extends SpellAbilityAi { // in general this should only be used to protect from Imminent Harm // (dying or losing control of) if (origin.equals(ZoneType.Battlefield)) { - if (ai.getGame().getStack().size() == 0) { + if (ai.getGame().getStack().isEmpty()) { return false; } @@ -720,7 +720,7 @@ public class ChangeZoneAi extends SpellAbilityAi { // check stack for something on the stack that will kill // anything i control - if (ai.getGame().getStack().size() > 0) { + if (!ai.getGame().getStack().isEmpty()) { final ArrayList objects = ComputerUtil.predictThreatenedObjects(ai, sa); final List threatenedTargets = new ArrayList(); diff --git a/src/main/java/forge/card/ability/ai/DamagePreventAi.java b/src/main/java/forge/card/ability/ai/DamagePreventAi.java index a795422234f..d931be29026 100644 --- a/src/main/java/forge/card/ability/ai/DamagePreventAi.java +++ b/src/main/java/forge/card/ability/ai/DamagePreventAi.java @@ -56,7 +56,7 @@ public class DamagePreventAi extends SpellAbilityAi { sa.getParam("Defined"), sa); // react to threats on the stack - if (game.getStack().size() > 0) { + if (!game.getStack().isEmpty()) { final ArrayList threatenedObjects = ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), sa); for (final Object o : objects) { if (threatenedObjects.contains(o)) { @@ -90,7 +90,7 @@ public class DamagePreventAi extends SpellAbilityAi { } // targeted // react to threats on the stack - else if (game.getStack().size() > 0) { + else if (!game.getStack().isEmpty()) { tgt.resetTargets(); // check stack for something on the stack will kill anything i // control diff --git a/src/main/java/forge/card/ability/ai/DamagePreventAllAi.java b/src/main/java/forge/card/ability/ai/DamagePreventAllAi.java index a284ec22f72..c32dbbbd4d5 100644 --- a/src/main/java/forge/card/ability/ai/DamagePreventAllAi.java +++ b/src/main/java/forge/card/ability/ai/DamagePreventAllAi.java @@ -38,7 +38,7 @@ public class DamagePreventAllAi extends SpellAbilityAi { 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 // control diff --git a/src/main/java/forge/card/ability/ai/EffectAi.java b/src/main/java/forge/card/ability/ai/EffectAi.java index 19a33d81b08..0a3e8c910a3 100644 --- a/src/main/java/forge/card/ability/ai/EffectAi.java +++ b/src/main/java/forge/card/ability/ai/EffectAi.java @@ -49,7 +49,7 @@ public class EffectAi extends SpellAbilityAi { if (!game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS_INSTANT_ABILITY)) { return false; } - if (game.getStack().size() != 0) { + if (!game.getStack().isEmpty()) { return false; } if (game.getPhaseHandler().isPreventCombatDamageThisTurn()) { diff --git a/src/main/java/forge/card/ability/ai/ProtectAi.java b/src/main/java/forge/card/ability/ai/ProtectAi.java index 89fa8d24bb2..d747e9abf34 100644 --- a/src/main/java/forge/card/ability/ai/ProtectAi.java +++ b/src/main/java/forge/card/ability/ai/ProtectAi.java @@ -137,13 +137,13 @@ public class ProtectAi extends SpellAbilityAi { } // 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 // when the stack is empty if (!SpellAbilityAi.isSorcerySpeed(sa)) { 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 // kill it via damage or destroy return false; diff --git a/src/main/java/forge/card/ability/ai/PumpAi.java b/src/main/java/forge/card/ability/ai/PumpAi.java index b70e4d02a89..937bb86c91f 100644 --- a/src/main/java/forge/card/ability/ai/PumpAi.java +++ b/src/main/java/forge/card/ability/ai/PumpAi.java @@ -83,13 +83,13 @@ public class PumpAi extends PumpAiBase { } // 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 // stack is empty if (!sa.isCurse() && !SpellAbilityAi.isSorcerySpeed(sa)) { return false; } - } else if (game.getStack().size() > 0) { + } else if (!game.getStack().isEmpty()) { if (!keywords.contains("Shroud") && !keywords.contains("Hexproof")) { return false; } @@ -228,7 +228,7 @@ public class PumpAi extends PumpAiBase { } 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 // attack/block if ((sa.getPayCosts() != null) && sa.getPayCosts().hasTapCost()) { diff --git a/src/main/java/forge/card/ability/ai/RegenerateAi.java b/src/main/java/forge/card/ability/ai/RegenerateAi.java index 6ecda5e764a..06f99e4a436 100644 --- a/src/main/java/forge/card/ability/ai/RegenerateAi.java +++ b/src/main/java/forge/card/ability/ai/RegenerateAi.java @@ -83,7 +83,7 @@ public class RegenerateAi extends SpellAbilityAi { // them final List list = AbilityUtils.getDefinedCards(hostCard, sa.getParam("Defined"), sa); - if (game.getStack().size() > 0) { + if (!game.getStack().isEmpty()) { final List objects = ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), sa); for (final Card c : list) { @@ -117,7 +117,7 @@ public class RegenerateAi extends SpellAbilityAi { return false; } - if (game.getStack().size() > 0) { + if (!game.getStack().isEmpty()) { // check stack for something on the stack will kill anything i // control final ArrayList objects = ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), sa); diff --git a/src/main/java/forge/card/ability/ai/RegenerateAllAi.java b/src/main/java/forge/card/ability/ai/RegenerateAllAi.java index 06f35a2aed5..ba24543ebb4 100644 --- a/src/main/java/forge/card/ability/ai/RegenerateAllAi.java +++ b/src/main/java/forge/card/ability/ai/RegenerateAllAi.java @@ -56,7 +56,7 @@ public class RegenerateAllAi extends SpellAbilityAi { } int numSaved = 0; - if (game.getStack().size() > 0) { + if (!game.getStack().isEmpty()) { final ArrayList objects = ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), sa); for (final Card c : list) { diff --git a/src/main/java/forge/card/ability/effects/ChooseSourceEffect.java b/src/main/java/forge/card/ability/effects/ChooseSourceEffect.java index e373c1273c0..42cb0f9b64a 100644 --- a/src/main/java/forge/card/ability/effects/ChooseSourceEffect.java +++ b/src/main/java/forge/card/ability/effects/ChooseSourceEffect.java @@ -178,34 +178,35 @@ public class ChooseSourceEffect extends SpellAbilityEffect { } private Card ChooseCardOnStack(SpellAbility sa, Player ai, GameState game) { - int size = game.getStack().size(); - for (int i = 0; i < size; i++) { - final SpellAbility topStack = game.getStack().peekAbility(i); - if (sa.hasParam("Choices") && !topStack.getSourceCard().isValid(sa.getParam("Choices"), ai, sa.getSourceCard())) { + for (SpellAbilityStackInstance si : game.getStack()) { + final Card source = si.getSourceCard(); + final SpellAbility abilityOnStack = si.getSpellAbility(); + + if (sa.hasParam("Choices") && !abilityOnStack.getSourceCard().isValid(sa.getParam("Choices"), ai, sa.getSourceCard())) { continue; } - final ApiType threatApi = topStack.getApi(); + final ApiType threatApi = abilityOnStack.getApi(); if (threatApi != ApiType.DealDamage && threatApi != ApiType.DamageAll) { continue; } - final Card source = topStack.getSourceCard(); + ArrayList objects = new ArrayList(); - final Target threatTgt = topStack.getTarget(); + final Target threatTgt = abilityOnStack.getTarget(); if (threatTgt == null) { - if (topStack.hasParam("Defined")) { - objects = AbilityUtils.getDefinedObjects(source, topStack.getParam("Defined"), topStack); - } else if (topStack.hasParam("ValidPlayers")) { - objects.addAll(AbilityUtils.getDefinedPlayers(source, topStack.getParam("ValidPlayers"), topStack)); + if (abilityOnStack.hasParam("Defined")) { + objects = AbilityUtils.getDefinedObjects(source, abilityOnStack.getParam("Defined"), abilityOnStack); + } else if (abilityOnStack.hasParam("ValidPlayers")) { + objects.addAll(AbilityUtils.getDefinedPlayers(source, abilityOnStack.getParam("ValidPlayers"), abilityOnStack)); } } else { objects.addAll(threatTgt.getTargetPlayers()); } - if (!objects.contains(ai) || topStack.hasParam("NoPrevention")) { + if (!objects.contains(ai) || abilityOnStack.hasParam("NoPrevention")) { 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) { continue; } diff --git a/src/main/java/forge/card/ability/effects/CounterEffect.java b/src/main/java/forge/card/ability/effects/CounterEffect.java index 5bdc63ffc72..e01b09a67c8 100644 --- a/src/main/java/forge/card/ability/effects/CounterEffect.java +++ b/src/main/java/forge/card/ability/effects/CounterEffect.java @@ -24,8 +24,8 @@ public class CounterEffect extends SpellAbilityEffect { if (sa.hasParam("AllType")) { sas = new ArrayList(); - for (int i = 0; i < game.getStack().size(); i++) { - SpellAbility spell = game.getStack().peekAbility(i); + for (SpellAbilityStackInstance si : game.getStack()) { + SpellAbility spell = si.getSpellAbility(); if (sa.getParam("AllType").equals("Spell") && !spell.isSpell()) { continue; } @@ -69,8 +69,8 @@ public class CounterEffect extends SpellAbilityEffect { if (sa.hasParam("AllType")) { sas = new ArrayList(); - for (int i = 0; i < game.getStack().size(); i++) { - SpellAbility spell = game.getStack().peekAbility(i); + for (SpellAbilityStackInstance si : game.getStack()) { + SpellAbility spell = si.getSpellAbility(); if (sa.getParam("AllType").equals("Spell") && !spell.isSpell()) { continue; } diff --git a/src/main/java/forge/card/cost/CostExile.java b/src/main/java/forge/card/cost/CostExile.java index 0938d169a3a..5adeda94fbe 100644 --- a/src/main/java/forge/card/cost/CostExile.java +++ b/src/main/java/forge/card/cost/CostExile.java @@ -32,7 +32,6 @@ import forge.control.input.InputSelectCardsFromList; import forge.game.GameState; import forge.game.ai.ComputerUtil; import forge.game.player.Player; -import forge.game.zone.MagicStack; import forge.game.zone.ZoneType; import forge.gui.GuiChoose; import forge.gui.GuiDialog; @@ -162,8 +161,8 @@ public class CostExile extends CostPartWithList { return true; // this will always work } if (this.getFrom().equals(ZoneType.Stack)) { - for (int i = 0; i < game.getStack().size(); i++) { - typeList.add(game.getStack().peekAbility(i).getSourceCard()); + for (SpellAbilityStackInstance si : game.getStack()) { + typeList.add(si.getSourceCard()); } } else { if (this.sameZone) { @@ -336,13 +335,13 @@ public class CostExile extends CostPartWithList { return true; } + final GameState game = sa.getActivatingPlayer().getGame(); ArrayList saList = new ArrayList(); ArrayList descList = new ArrayList(); - final MagicStack stack = sa.getActivatingPlayer().getGame().getStack(); - - for (int i = 0; i < stack.size(); i++) { - final Card stC = stack.peekAbility(i).getSourceCard(); - final SpellAbility stSA = stack.peekAbility(i).getRootAbility(); + + for (SpellAbilityStackInstance si : game.getStack()) { + final Card stC = si.getSourceCard(); + final SpellAbility stSA = si.getSpellAbility().getRootAbility(); if (stC.isValid(getType().split(";"), sa.getActivatingPlayer(), sa.getSourceCard()) && stSA.isSpell()) { saList.add(stSA); if (stC.isCopiedSpell()) { @@ -373,8 +372,8 @@ public class CostExile extends CostPartWithList { } else { addToList(c); } - final SpellAbilityStackInstance si = stack.getInstanceFromSpellAbility(toExile); - stack.remove(si); + final SpellAbilityStackInstance si = game.getStack().getInstanceFromSpellAbility(toExile); + game.getStack().remove(si); } else { return false; } diff --git a/src/main/java/forge/card/spellability/SpellAbilityStackInstance.java b/src/main/java/forge/card/spellability/SpellAbilityStackInstance.java index 68aa97cc2ac..aa40c21af2b 100644 --- a/src/main/java/forge/card/spellability/SpellAbilityStackInstance.java +++ b/src/main/java/forge/card/spellability/SpellAbilityStackInstance.java @@ -51,9 +51,6 @@ public class SpellAbilityStackInstance { private TargetChoices tc = null; private List splicedCards = null; - /** The activating player. */ - private Player activatingPlayer = null; - /** The stack description. */ private String stackDescription = null; @@ -89,7 +86,6 @@ public class SpellAbilityStackInstance { // Base SA info this.ability = sa; this.stackDescription = this.ability.getStackDescription(); - this.activatingPlayer = sa.getActivatingPlayer(); // Payment info this.paidHash = this.ability.getPaidHash(); @@ -140,7 +136,6 @@ public class SpellAbilityStackInstance { this.ability.getTarget().resetTargets(); this.ability.getTarget().setTargetChoices(this.tc); } - this.ability.setActivatingPlayer(this.activatingPlayer); // Saved sub-SA needs to be reset on the way out if (this.subInstace != null) { @@ -191,17 +186,6 @@ public class SpellAbilityStackInstance { return this.ability.getSourceCard(); } - /** - *

- * Getter for the field activatingPlayer. - *

- * - * @return a {@link forge.game.player.Player} object. - */ - public final Player getActivatingPlayer() { - return this.activatingPlayer; - } - /** *

* isSpell. diff --git a/src/main/java/forge/card/spellability/TargetSelection.java b/src/main/java/forge/card/spellability/TargetSelection.java index f976b17c0fb..871398f9bea 100644 --- a/src/main/java/forge/card/spellability/TargetSelection.java +++ b/src/main/java/forge/card/spellability/TargetSelection.java @@ -310,14 +310,14 @@ public class TargetSelection { final List selectOptions = new ArrayList(); final GameState game = ability.getActivatingPlayer().getGame(); - for (int i = 0; i < game.getStack().size(); i++) { - SpellAbility stackItem = game.getStack().peekAbility(i); - if (ability.equals(stackItem)) { + for (SpellAbilityStackInstance si : game.getStack()) { + SpellAbility abilityOnStack = si.getSpellAbility(); + if (ability.equals(abilityOnStack)) { // By peeking at stack item, target is set to its SI state. So set it back before adding targets tgt.resetTargets(); } - else if (ability.canTargetSpellAbility(stackItem)) { - selectOptions.add(stackItem); + else if (ability.canTargetSpellAbility(abilityOnStack)) { + selectOptions.add(abilityOnStack); } } diff --git a/src/main/java/forge/card/trigger/TriggerSpellAbilityCast.java b/src/main/java/forge/card/trigger/TriggerSpellAbilityCast.java index d92dff65d78..926a4203983 100644 --- a/src/main/java/forge/card/trigger/TriggerSpellAbilityCast.java +++ b/src/main/java/forge/card/trigger/TriggerSpellAbilityCast.java @@ -91,7 +91,7 @@ public class TriggerSpellAbilityCast extends Trigger { } 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())) { return false; } diff --git a/src/main/java/forge/control/input/InputPassPriority.java b/src/main/java/forge/control/input/InputPassPriority.java index 87ea9ce8299..73a48751751 100644 --- a/src/main/java/forge/control/input/InputPassPriority.java +++ b/src/main/java/forge/control/input/InputPassPriority.java @@ -61,7 +61,7 @@ public class InputPassPriority extends InputBase { sb.append("Turn : ").append(ph.getPlayerTurn()).append("\n"); sb.append("Phase: ").append(ph.getPhase().Name).append("\n"); sb.append("Stack: "); - if (player.getGame().getStack().size() != 0) { + if (!player.getGame().getStack().isEmpty()) { sb.append(player.getGame().getStack().size()).append(" to Resolve."); } else { sb.append("Empty"); diff --git a/src/main/java/forge/game/GameAction.java b/src/main/java/forge/game/GameAction.java index e15103c5bc7..d498adbc4dc 100644 --- a/src/main/java/forge/game/GameAction.java +++ b/src/main/java/forge/game/GameAction.java @@ -522,7 +522,7 @@ public class GameAction { Player p = recoverable.getController(); if (p.isHuman()) { - if ( GameActionUtil.payCostDuringAbilityResolve(abRecover, abRecover.getPayCosts(), null, game) ) + if ( HumanPlay.payCostDuringAbilityResolve(abRecover, abRecover.getPayCosts(), null, game) ) moveToHand(recoverable); else exile(recoverable); diff --git a/src/main/java/forge/game/GameActionUtil.java b/src/main/java/forge/game/GameActionUtil.java index 5e9701fc807..ba225b86531 100644 --- a/src/main/java/forge/game/GameActionUtil.java +++ b/src/main/java/forge/game/GameActionUtil.java @@ -22,7 +22,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import org.apache.commons.lang3.StringUtils; import com.google.common.base.Function; import com.google.common.collect.Iterables; @@ -36,27 +35,12 @@ import forge.CardUtil; import forge.Command; import forge.Constant; import forge.CounterType; -import forge.FThreads; import forge.card.ability.AbilityFactory; import forge.card.ability.AbilityFactory.AbilityRecordType; import forge.card.ability.AbilityUtils; import forge.card.ability.ApiType; import forge.card.cardfactory.CardFactoryUtil; 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.ManaCostBeingPaid; import forge.card.spellability.Ability; @@ -66,13 +50,7 @@ import forge.card.spellability.AbilitySub; import forge.card.spellability.OptionalCost; import forge.card.spellability.SpellAbility; 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.event.CardDamagedEvent; -import forge.game.event.LifeLossEvent; import forge.game.player.HumanPlay; import forge.game.player.Player; import forge.game.player.PlayerControllerAi; @@ -92,32 +70,7 @@ import forge.util.TextUtil; * @version $Id$ */ 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 final Player controller; private final Card cascCard; @@ -368,7 +321,7 @@ public final class GameActionUtil { * a {@link forge.card.spellability.SpellAbility} object. */ public static void executePlayCardEffects(final SpellAbility sa) { - // (called in MagicStack.java) + // (called from MagicStack.java) final GameState game = sa.getActivatingPlayer().getGame(); final Command cascade = new CascadeExecutor(sa.getActivatingPlayer(), sa.getSourceCard(), game); @@ -377,307 +330,6 @@ public final class GameActionUtil { 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)); - } - - /** - *

- * payCostDuringAbilityResolve. - *

- * - * @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 parts = cost.getCostParts(); - ArrayList remainingParts = new ArrayList(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 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 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 cards = new ArrayList(p.getCardsIn(ZoneType.Graveyard)); - for (final Card card : cards) { - p.getGame().getAction().exile(card); - } - } else { - CostExile costExile = (CostExile) part; - ZoneType from = costExile.getFrom(); - List 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 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 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 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 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 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 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 - /** - *

- * executeDamageDealingEffects. - *

- * - * @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 - /** - *

- * executeDamageToCreatureEffects. - *

- * - * @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 /** *

@@ -723,30 +375,6 @@ public final class GameActionUtil { } } - // not restricted to just combat damage, restricted to players - /** - *

- * executeDamageToPlayerEffects. - *

- * - * @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 /** *

@@ -771,14 +399,11 @@ public final class GameActionUtil { final String parse = c.getKeyword().get(keywordPosition).toString(); final String[] k = parse.split(" "); final int poison = Integer.parseInt(k[1]); - final Card crd = c; final Ability ability = new Ability(c, ManaCost.ZERO) { @Override public void resolve() { - final Player player = crd.getController(); - final Player opponent = player.getOpponent(); - opponent.addPoisonCounters(poison, c); + player.addPoisonCounters(poison, c); } }; @@ -786,9 +411,7 @@ public final class GameActionUtil { sb.append(c); sb.append(" - Poisonous: "); sb.append(c.getController().getOpponent()); - sb.append(" gets "); - sb.append(poison); - sb.append(" poison counter"); + sb.append(" gets ").append(poison).append(" poison counter"); if (poison != 1) { sb.append("s"); } diff --git a/src/main/java/forge/game/MatchController.java b/src/main/java/forge/game/MatchController.java index 8b8861c8dd5..f18a96b6021 100644 --- a/src/main/java/forge/game/MatchController.java +++ b/src/main/java/forge/game/MatchController.java @@ -180,7 +180,7 @@ public class MatchController { // The UI controls should use these game data as models CMatchUI.SINGLETON_INSTANCE.initMatch(currentGame.getRegisteredPlayers(), 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()); CCombat.SINGLETON_INSTANCE.setModel(currentGame); CMessage.SINGLETON_INSTANCE.setModel(match); diff --git a/src/main/java/forge/game/ai/AiController.java b/src/main/java/forge/game/ai/AiController.java index 090c88f0ae1..a470e0cb53a 100644 --- a/src/main/java/forge/game/ai/AiController.java +++ b/src/main/java/forge/game/ai/AiController.java @@ -86,7 +86,7 @@ public class AiController { public final SpellAbility getSpellAbilityToPlay() { // 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 return null; } @@ -792,7 +792,7 @@ public class AiController { public void onPriorityRecieved() { final PhaseType phase = game.getPhaseHandler().getPhase(); - if (game.getStack().size() > 0) { + if (!game.getStack().isEmpty()) { playSpellAbilities(game); } else { switch(phase) { diff --git a/src/main/java/forge/game/ai/ComputerUtil.java b/src/main/java/forge/game/ai/ComputerUtil.java index 2b67d568379..bf96bf8531c 100644 --- a/src/main/java/forge/game/ai/ComputerUtil.java +++ b/src/main/java/forge/game/ai/ComputerUtil.java @@ -42,6 +42,7 @@ import forge.card.cost.CostPayment; import forge.card.spellability.AbilityManaPart; import forge.card.spellability.AbilityStatic; import forge.card.spellability.SpellAbility; +import forge.card.spellability.SpellAbilityStackInstance; import forge.card.spellability.Target; import forge.error.BugReporter; import forge.game.GameState; @@ -271,7 +272,7 @@ public class ComputerUtil { final SpellAbility newSA = sa.copyWithNoManaCost(); newSA.setActivatingPlayer(ai); - if (!ComputerUtilCost.canPayAdditionalCosts(newSA, ai)) { + if (!CostPayment.canPayAdditionalCosts(newSA.getPayCosts(), newSA)) { return; } @@ -462,13 +463,14 @@ public class ComputerUtil { final GameState game = ai.getGame(); List typeList = new ArrayList(); if (zone.equals(ZoneType.Stack)) { - for (int i = 0; i < game.getStack().size(); i++) { - typeList.add(game.getStack().peekAbility(i).getSourceCard()); - typeList = CardLists.getValidCards(typeList, type.split(","), activate.getController(), activate); + for (SpellAbilityStackInstance si : game.getStack()) { + typeList.add(si.getSourceCard()); } } 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)) { typeList.remove(target); // don't exile the card we're pumping } diff --git a/src/main/java/forge/game/ai/ComputerUtilCost.java b/src/main/java/forge/game/ai/ComputerUtilCost.java index 6fcaf3dcbfc..dd8cb56b9d3 100644 --- a/src/main/java/forge/game/ai/ComputerUtilCost.java +++ b/src/main/java/forge/game/ai/ComputerUtilCost.java @@ -364,30 +364,8 @@ public class ComputerUtilCost { return false; } - return ComputerUtilCost.canPayAdditionalCosts(sa, player); - } // canPayCost() - - /** - *

- * canPayAdditionalCosts. - *

- * - * @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); - } + } // canPayCost() public static boolean willPayUnlessCost(SpellAbility sa, Player payer, SpellAbility ability, boolean alreadyPaid, List payers) { final Card source = sa.getSourceCard(); @@ -426,20 +404,17 @@ public class ComputerUtilCost { if (alreadyPaid || (payers.size() > 1 && (isMine && !payForOwnOnly))) { return false; } - if (canPayCost(ability, payer) - && checkLifeCost(payer, ability.getPayCosts(), source, 4, sa) - && checkDamageCost(payer, ability.getPayCosts(), source, 4) - && (isMine || checkDiscardCost(payer, ability.getPayCosts(), source)) - && (!source.getName().equals("Tyrannize") || payer.getCardsIn(ZoneType.Hand).size() > 2) - && (!source.getName().equals("Perplex") || payer.getCardsIn(ZoneType.Hand).size() < 2) - && (!source.getName().equals("Breaking Point") || payer.getCreaturesInPlay().size() > 1) - && (!source.getName().equals("Chain of Vapor") - || (payer.getOpponent().getCreaturesInPlay().size() > 0 && payer.getLandsInPlay().size() > 3))) { - // AI was crashing because the blank ability used to pay costs - // Didn't have any of the data on the original SA to pay dependant costs - return true; - } - return false; + + // AI was crashing because the blank ability used to pay costs + // Didn't have any of the data on the original SA to pay dependant costs + + return checkLifeCost(payer, ability.getPayCosts(), source, 4, sa) + && checkDamageCost(payer, ability.getPayCosts(), source, 4) + && (isMine || checkDiscardCost(payer, ability.getPayCosts(), source)) + && (!source.getName().equals("Tyrannize") || payer.getCardsIn(ZoneType.Hand).size() > 2) + && (!source.getName().equals("Perplex") || payer.getCardsIn(ZoneType.Hand).size() < 2) + && (!source.getName().equals("Breaking Point") || payer.getCreaturesInPlay().size() > 1) + && (!source.getName().equals("Chain of Vapor") || (payer.getOpponent().getCreaturesInPlay().size() > 0 && payer.getLandsInPlay().size() > 3)); } } diff --git a/src/main/java/forge/game/ai/ComputerUtilMana.java b/src/main/java/forge/game/ai/ComputerUtilMana.java index 1fbe8ce7d16..f492da0cbcd 100644 --- a/src/main/java/forge/game/ai/ComputerUtilMana.java +++ b/src/main/java/forge/game/ai/ComputerUtilMana.java @@ -131,7 +131,7 @@ public class ComputerUtilMana { ma.setActivatingPlayer(ai); // if the AI can't pay the additional costs skip the mana ability if (ma.getPayCosts() != null && checkPlayable) { - if (!ComputerUtilCost.canPayAdditionalCosts(ma, ai)) { + if (!CostPayment.canPayAdditionalCosts(ma.getPayCosts(), ma)) { continue; } } else if (sourceCard.isTapped() && checkPlayable) { @@ -535,7 +535,7 @@ public class ComputerUtilMana { // ability m.setActivatingPlayer(ai); if (cost != null) { - if (!ComputerUtilCost.canPayAdditionalCosts(m, ai)) { + if (!CostPayment.canPayAdditionalCosts(m.getPayCosts(), m)) { continue; } } diff --git a/src/main/java/forge/game/phase/CombatUtil.java b/src/main/java/forge/game/phase/CombatUtil.java index e297a7f933a..4e7941ae796 100644 --- a/src/main/java/forge/game/phase/CombatUtil.java +++ b/src/main/java/forge/game/phase/CombatUtil.java @@ -46,12 +46,12 @@ import forge.card.spellability.Ability; import forge.card.spellability.AbilityStatic; import forge.card.staticability.StaticAbility; import forge.card.trigger.TriggerType; -import forge.game.GameActionUtil; import forge.game.GameState; import forge.game.GlobalRuleChange; import forge.game.ai.ComputerUtil; import forge.game.ai.ComputerUtilCard; import forge.game.ai.ComputerUtilCost; +import forge.game.player.HumanPlay; import forge.game.player.Player; import forge.game.zone.PlayerZone; import forge.game.zone.ZoneType; @@ -1078,7 +1078,7 @@ public class CombatUtil { ability.setActivatingPlayer(c.getController()); if (c.getController().isHuman()) { - hasPaid = GameActionUtil.payCostDuringAbilityResolve(ability, attackCost, null, game); + hasPaid = HumanPlay.payCostDuringAbilityResolve(ability, attackCost, null, game); } else { // computer if (ComputerUtilCost.canPayCost(ability, c.getController())) { ComputerUtil.playNoStack(c.getController(), ability, game); diff --git a/src/main/java/forge/game/phase/PhaseHandler.java b/src/main/java/forge/game/phase/PhaseHandler.java index 0ab37e4eabb..f4798683a47 100644 --- a/src/main/java/forge/game/phase/PhaseHandler.java +++ b/src/main/java/forge/game/phase/PhaseHandler.java @@ -429,7 +429,7 @@ public class PhaseHandler extends MyObservable implements java.io.Serializable { this.setPlayersPriorityPermission(true); // PlayerPriorityAllowed = false; // 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."); return; } diff --git a/src/main/java/forge/game/phase/PhaseUtil.java b/src/main/java/forge/game/phase/PhaseUtil.java index 94f5e85270d..1b4328bd196 100644 --- a/src/main/java/forge/game/phase/PhaseUtil.java +++ b/src/main/java/forge/game/phase/PhaseUtil.java @@ -32,10 +32,10 @@ import forge.card.spellability.Ability; import forge.card.spellability.AbilityStatic; import forge.card.staticability.StaticAbility; import forge.card.trigger.TriggerType; -import forge.game.GameActionUtil; import forge.game.GameState; import forge.game.ai.ComputerUtil; import forge.game.ai.ComputerUtilCost; +import forge.game.player.HumanPlay; import forge.game.player.Player; import forge.game.zone.ZoneType; import forge.gui.match.CMatchUI; @@ -225,7 +225,7 @@ public class PhaseUtil { ability.setActivatingPlayer(blocker.getController()); if (blocker.getController().isHuman()) { - hasPaid = GameActionUtil.payCostDuringAbilityResolve(ability, blockCost, null, game); + hasPaid = HumanPlay.payCostDuringAbilityResolve(ability, blockCost, null, game); } else { // computer if (ComputerUtilCost.canPayCost(ability, blocker.getController())) { ComputerUtil.playNoStack(blocker.getController(), ability, game); diff --git a/src/main/java/forge/game/phase/Upkeep.java b/src/main/java/forge/game/phase/Upkeep.java index cbaece3a778..7902c246163 100644 --- a/src/main/java/forge/game/phase/Upkeep.java +++ b/src/main/java/forge/game/phase/Upkeep.java @@ -41,13 +41,13 @@ import forge.control.input.InputPayManaExecuteCommands; import forge.control.input.InputPayment; import forge.control.input.InputSelectCards; import forge.control.input.InputSelectCardsFromList; -import forge.game.GameActionUtil; import forge.game.GameState; import forge.game.ai.ComputerUtil; import forge.game.ai.ComputerUtilCard; import forge.game.ai.ComputerUtilCombat; import forge.game.ai.ComputerUtilCost; import forge.game.ai.ComputerUtilMana; +import forge.game.player.HumanPlay; import forge.game.player.Player; import forge.game.zone.PlayerZone; import forge.game.zone.ZoneType; @@ -182,7 +182,7 @@ public class Upkeep extends Phase { Player controller = c.getController(); if (controller.isHuman()) { 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);; } else { // computer @@ -324,7 +324,7 @@ public class Upkeep extends Phase { @Override public void resolve() { if (controller.isHuman()) { - if ( !GameActionUtil.payCostDuringAbilityResolve(blankAbility, blankAbility.getPayCosts(), this, game)) + if ( !HumanPlay.payCostDuringAbilityResolve(blankAbility, blankAbility.getPayCosts(), this, game)) game.getAction().sacrifice(c, null); } else { // computer if (ComputerUtilCost.shouldPayCost(controller, c, upkeepCost) && ComputerUtilCost.canPayCost(blankAbility, controller)) { diff --git a/src/main/java/forge/game/player/HumanPlay.java b/src/main/java/forge/game/player/HumanPlay.java index b93830f3c02..5cbd6581c47 100644 --- a/src/main/java/forge/game/player/HumanPlay.java +++ b/src/main/java/forge/game/player/HumanPlay.java @@ -1,14 +1,35 @@ package forge.game.player; +import java.util.ArrayList; import java.util.List; +import org.apache.commons.lang3.StringUtils; + import forge.Card; +import forge.CardLists; +import forge.CardPredicates.Presets; +import forge.CounterType; import forge.FThreads; import forge.card.ability.AbilityUtils; import forge.card.ability.ApiType; import forge.card.ability.effects.CharmEffect; +import forge.card.cardfactory.CardFactoryUtil; 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.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.ManaCostBeingPaid; import forge.card.mana.ManaCostShard; @@ -17,8 +38,16 @@ import forge.card.spellability.HumanPlaySpellAbility; import forge.card.spellability.SpellAbility; import forge.card.spellability.Target; import forge.control.input.InputPayManaBase; +import forge.control.input.InputPayManaExecuteCommands; 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.GameState; +import forge.game.zone.ZoneType; +import forge.gui.GuiChoose; +import forge.gui.GuiDialog; /** * 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)); + } + + /** + *

+ * payCostDuringAbilityResolve. + *

+ * + * @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 parts = cost.getCostParts(); + ArrayList remainingParts = new ArrayList(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 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 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 cards = new ArrayList(p.getCardsIn(ZoneType.Graveyard)); + for (final Card card : cards) { + p.getGame().getAction().exile(card); + } + } else { + CostExile costExile = (CostExile) part; + ZoneType from = costExile.getFrom(); + List 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 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 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 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 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 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 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; + } + } diff --git a/src/main/java/forge/game/player/Player.java b/src/main/java/forge/game/player/Player.java index 44f3f0ba7aa..2fba2637423 100644 --- a/src/main/java/forge/game/player/Player.java +++ b/src/main/java/forge/game/player/Player.java @@ -612,7 +612,7 @@ public class Player extends GameEntity implements Comparable { public final boolean addDamageAfterPrevention(final int damage, final Card source, final boolean isCombat) { final int damageToDo = damage; - if (damageToDo == 0) { + if (damageToDo <= 0) { return false; } String additionalLog = ""; @@ -639,8 +639,11 @@ public class Player extends GameEntity implements Comparable { } this.assignedDamage.put(source, damageToDo); - GameActionUtil.executeDamageDealingEffects(source, damageToDo); - GameActionUtil.executeDamageToPlayerEffects(this, source, damageToDo); + if (source.hasKeyword("Lifelink")) { + source.getController().gainLife(damageToDo, source); + } + source.getDamageHistory().registerDamage(this); + this.getGame().getEvents().post(new LifeLossEvent()); if (isCombat) { final ArrayList types = source.getType(); @@ -2822,7 +2825,7 @@ public class Player extends GameEntity implements Comparable { public boolean canCastSorcery() { 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(); } diff --git a/src/main/java/forge/game/zone/MagicStack.java b/src/main/java/forge/game/zone/MagicStack.java index e2b8672b9dc..53a958201dc 100644 --- a/src/main/java/forge/game/zone/MagicStack.java +++ b/src/main/java/forge/game/zone/MagicStack.java @@ -20,6 +20,7 @@ package forge.game.zone; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; +import java.util.Iterator; import java.util.List; import java.util.Stack; import com.esotericsoftware.minlog.Log; @@ -66,7 +67,7 @@ import forge.util.MyObservable; * @author Forge * @version $Id$ */ -public class MagicStack extends MyObservable { +public class MagicStack extends MyObservable implements Iterable { private final List simultaneousStackEntryList = new ArrayList(); private final Stack stack = new Stack(); @@ -539,7 +540,7 @@ public class MagicStack extends MyObservable { * @return a boolean. */ public final boolean isEmpty() { - return this.getStack().size() == 0; + return this.getStack().isEmpty(); } // 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 SpellAbility sa = si.getSpellAbility(); 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... - /** - *

- * peekInstance. - *

- * - * @param index - * a int. - * @return a {@link forge.card.spellability.SpellAbilityStackInstance} - * object. - */ - public final SpellAbilityStackInstance peekInstance(final int index) { - return this.getStack().get(index); - } - - /** - *

- * peekAbility. - *

- * - * @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(); - } - - /** - *

- * peekInstance. - *

- * - * @return a {@link forge.card.spellability.SpellAbilityStackInstance} - * object. - */ - public final SpellAbilityStackInstance peekInstance() { - return this.getStack().peek(); - } - /** *

* peekAbility. @@ -1168,4 +1128,13 @@ public class MagicStack extends MyObservable { return c.equals(this.curResolvingCard); } + + /* (non-Javadoc) + * @see java.lang.Iterable#iterator() + */ + @Override + public Iterator iterator() { + // TODO Auto-generated method stub + return stack.iterator(); + } } diff --git a/src/main/java/forge/gui/match/controllers/CStack.java b/src/main/java/forge/gui/match/controllers/CStack.java index 73fa71b0395..a4d7c20ef6f 100644 --- a/src/main/java/forge/gui/match/controllers/CStack.java +++ b/src/main/java/forge/gui/match/controllers/CStack.java @@ -5,6 +5,7 @@ import java.util.Observer; import forge.Command; import forge.FThreads; +import forge.game.player.Player; import forge.game.zone.MagicStack; import forge.gui.framework.EDocID; import forge.gui.framework.ICDoc; @@ -22,6 +23,7 @@ public enum CStack implements ICDoc, Observer { SINGLETON_INSTANCE; private MagicStack model; + private Player viewer; /* (non-Javadoc) * @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() { SDisplayUtil.showTab(EDocID.REPORT_STACK.getDoc()); - VStack.SINGLETON_INSTANCE.updateStack(model); + VStack.SINGLETON_INSTANCE.updateStack(model, viewer); } }; /* (non-Javadoc) @@ -59,6 +61,9 @@ public enum CStack implements ICDoc, Observer { FThreads.invokeInEdtNowOrLater(upd); } - public void setModel(MagicStack model) { this.model = model; } + public void setModel(MagicStack model, Player guiPlayer) { + this.model = model; + this.viewer = guiPlayer; + } } diff --git a/src/main/java/forge/gui/match/views/VStack.java b/src/main/java/forge/gui/match/views/VStack.java index 77f4b71c24e..991be775a3c 100644 --- a/src/main/java/forge/gui/match/views/VStack.java +++ b/src/main/java/forge/gui/match/views/VStack.java @@ -23,6 +23,7 @@ import java.awt.event.ActionListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import javax.swing.JCheckBoxMenuItem; @@ -35,6 +36,7 @@ import net.miginfocom.swing.MigLayout; import forge.CardUtil; import forge.card.spellability.SpellAbilityStackInstance; import forge.control.FControl; +import forge.game.player.Player; import forge.game.player.PlayerController; import forge.game.zone.MagicStack; import forge.gui.framework.DragCell; @@ -115,14 +117,15 @@ public enum VStack implements IVDoc { //========== Observer update methods /** - * @param stack */ - public void updateStack(final MagicStack stack) { + * @param stack + * @param viewer */ + public void updateStack(final MagicStack stack, Player viewer) { // No need to update this unless it's showing if (!parentCell.getSelected().equals(this)) { return; } int count = 1; - JTextArea tar; - String txt, isOptional; + + List list = new ArrayList(); parentCell.getBody().removeAll(); parentCell.getBody().setLayout(new MigLayout("insets 1%, gap 1%, wrap")); @@ -133,15 +136,13 @@ public enum VStack implements IVDoc { Color[] scheme; stackTARs.clear(); - for (int i = stack.size() - 1; 0 <= i; i--) { - final SpellAbilityStackInstance spell = stack.peekInstance(i); - + boolean isFirst = true; + for (final SpellAbilityStackInstance spell : stack) { scheme = getSpellColor(spell); - isOptional = stack.peekAbility(i).isOptionalTrigger() - && stack.peekAbility(i).getSourceCard().getController().isHuman() ? "(OPTIONAL) " : ""; - txt = (count++) + ". " + isOptional + spell.getStackDescription(); - tar = new JTextArea(txt); + String isOptional = spell.getSpellAbility().isOptionalTrigger() && spell.getSourceCard().getController().equals(viewer) ? "(OPTIONAL) " : ""; + String txt = (count++) + ". " + isOptional + spell.getStackDescription(); + JTextArea tar = new JTextArea(txt); tar.setToolTipText(txt); tar.setOpaque(true); tar.setBorder(border); @@ -183,17 +184,23 @@ public enum VStack implements IVDoc { } }); } - + list.add(tar); + /* * This updates the Card Picture/Detail when the spell is added to * the stack. This functionality was not present in v 1.1.8. * * 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()); } - + isFirst = false; + } + + Collections.reverse(list); + + for(JTextArea tar : list) { parentCell.getBody().add(tar, "w 98%!"); stackTARs.add(tar); }