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.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<Card> {
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<String, Object> runParams = new TreeMap<String, Object>();
@@ -7421,25 +7424,57 @@ public class Card extends GameEntity implements Comparable<Card> {
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;
}

View File

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

View File

@@ -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<Object> objects = ComputerUtil.predictThreatenedObjects(ai, sa);
final List<Card> threatenedTargets = new ArrayList<Card>();

View File

@@ -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<Object> 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

View File

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

View File

@@ -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()) {

View File

@@ -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;

View File

@@ -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()) {

View File

@@ -83,7 +83,7 @@ public class RegenerateAi extends SpellAbilityAi {
// them
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);
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<Object> objects = ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), sa);

View File

@@ -56,7 +56,7 @@ public class RegenerateAllAi extends SpellAbilityAi {
}
int numSaved = 0;
if (game.getStack().size() > 0) {
if (!game.getStack().isEmpty()) {
final ArrayList<Object> objects = ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), sa);
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) {
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<Object> objects = new ArrayList<Object>();
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;
}

View File

@@ -24,8 +24,8 @@ public class CounterEffect extends SpellAbilityEffect {
if (sa.hasParam("AllType")) {
sas = new ArrayList<SpellAbility>();
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<SpellAbility>();
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;
}

View File

@@ -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<SpellAbility> saList = new ArrayList<SpellAbility>();
ArrayList<String> descList = new ArrayList<String>();
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;
}

View File

@@ -51,9 +51,6 @@ public class SpellAbilityStackInstance {
private TargetChoices tc = null;
private List<Card> 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();
}
/**
* <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>
* isSpell.

View File

@@ -310,14 +310,14 @@ public class TargetSelection {
final List<Object> selectOptions = new ArrayList<Object>();
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);
}
}

View File

@@ -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;
}

View File

@@ -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");

View File

@@ -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);

View File

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

View File

@@ -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);

View File

@@ -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) {

View File

@@ -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<Card> typeList = new ArrayList<Card>();
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
}

View File

@@ -364,30 +364,8 @@ public class ComputerUtilCost {
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);
}
} // canPayCost()
public static boolean willPayUnlessCost(SpellAbility sa, Player payer, SpellAbility ability, boolean alreadyPaid, List<Player> 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));
}
}

View File

@@ -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;
}
}

View File

@@ -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);

View File

@@ -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;
}

View File

@@ -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);

View File

@@ -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)) {

View File

@@ -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));
}
/**
* <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) {
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<Player> {
}
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<String> types = source.getType();
@@ -2822,7 +2825,7 @@ public class Player extends GameEntity implements Comparable<Player> {
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();
}

View File

@@ -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<SpellAbilityStackInstance> {
private final List<SpellAbility> simultaneousStackEntryList = new ArrayList<SpellAbility>();
private final Stack<SpellAbilityStackInstance> stack = new Stack<SpellAbilityStackInstance>();
@@ -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...
/**
* <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>
* 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<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.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;
}
}

View File

@@ -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<CStack> {
//========== 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<JTextArea> list = new ArrayList<JTextArea>();
parentCell.getBody().removeAll();
parentCell.getBody().setLayout(new MigLayout("insets 1%, gap 1%, wrap"));
@@ -133,15 +136,13 @@ public enum VStack implements IVDoc<CStack> {
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<CStack> {
}
});
}
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);
}