diff --git a/.gitattributes b/.gitattributes index 68fbdd34622..16e38c7fbe0 100644 --- a/.gitattributes +++ b/.gitattributes @@ -490,6 +490,7 @@ forge-game/src/main/java/forge/game/combat/CombatView.java -text forge-game/src/main/java/forge/game/combat/GlobalAttackRestrictions.java -text forge-game/src/main/java/forge/game/cost/Cost.java svneol=native#text/plain forge-game/src/main/java/forge/game/cost/CostAddMana.java -text +forge-game/src/main/java/forge/game/cost/CostAdjustment.java -text forge-game/src/main/java/forge/game/cost/CostChooseCreatureType.java -text forge-game/src/main/java/forge/game/cost/CostDamage.java -text forge-game/src/main/java/forge/game/cost/CostDecisionMakerBase.java -text diff --git a/forge-game/src/main/java/forge/game/cost/CostAdjustment.java b/forge-game/src/main/java/forge/game/cost/CostAdjustment.java new file mode 100644 index 00000000000..bead6ad75ca --- /dev/null +++ b/forge-game/src/main/java/forge/game/cost/CostAdjustment.java @@ -0,0 +1,255 @@ +package forge.game.cost; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import org.apache.commons.lang3.StringUtils; + +import forge.card.CardStateName; +import forge.card.mana.ManaAtom; +import forge.card.mana.ManaCostShard; +import forge.game.Game; +import forge.game.GameObject; +import forge.game.ability.AbilityUtils; +import forge.game.card.Card; +import forge.game.card.CardCollection; +import forge.game.card.CardFactoryUtil; +import forge.game.card.CardLists; +import forge.game.player.Player; +import forge.game.spellability.AbilityActivated; +import forge.game.spellability.Spell; +import forge.game.spellability.SpellAbility; +import forge.game.spellability.TargetChoices; +import forge.game.spellability.TargetRestrictions; +import forge.game.staticability.StaticAbility; +import forge.game.zone.ZoneType; + +public class CostAdjustment { + + public static Cost adjust(final Cost cost, final SpellAbility sa) { + final Player player = sa.getActivatingPlayer(); + final Card host = sa.getHostCard(); + final Game game = player.getGame(); + + if (sa.isTrigger()) { + return cost; + } + + boolean isStateChangeToFaceDown = false; + if (sa.isSpell() && ((Spell) sa).isCastFaceDown()) { + // Turn face down to apply cost modifiers correctly + host.setState(CardStateName.FaceDown, false); + isStateChangeToFaceDown = true; + } // isSpell + + CardCollection cardsOnBattlefield = new CardCollection(game.getCardsIn(ZoneType.Battlefield)); + cardsOnBattlefield.addAll(game.getCardsIn(ZoneType.Stack)); + cardsOnBattlefield.addAll(game.getCardsIn(ZoneType.Command)); + if (!cardsOnBattlefield.contains(host)) { + cardsOnBattlefield.add(host); + } + final List raiseAbilities = new ArrayList(); + + // Sort abilities to apply them in proper order + for (Card c : cardsOnBattlefield) { + for (final StaticAbility stAb : c.getStaticAbilities()) { + if (stAb.getMapParams().get("Mode").equals("RaiseCost2")) { + raiseAbilities.add(stAb); + } + } + } + // Raise cost + for (final StaticAbility stAb : raiseAbilities) { + applyRaise(cost, sa, stAb); + } + + // Reset card state (if changed) + if (isStateChangeToFaceDown) { + host.setState(CardStateName.Original, false); + } + return cost; + } + + private static void applyRaise(final Cost cost, final SpellAbility sa, final StaticAbility st) { + final Map params = st.getMapParams(); + final Card hostCard = st.getHostCard(); + final Card card = sa.getHostCard(); + + if (!checkRequirement(sa, st)) { + return; + } + + Cost part = new Cost(params.get("Cost"), sa.isAbility()); + int count = 0; + + if (params.containsKey("ForEachShard")) { + CostPartMana mc = cost.getCostMana(); + if (mc != null) { + byte atom = ManaAtom.fromName(params.get("ForEachShard").toLowerCase()); + for (ManaCostShard shard : mc.getManaCostFor(sa)) { + if ((shard.getColorMask() & atom) != 0) { + ++count; + } + } + } + } else if (params.containsKey("Amount")) { + String amount = params.get("Amount"); + if ("Escalate".equals(amount)) { + SpellAbility sub = sa; + while(sub != null) { + if (!sub.getSVar("CharmOrder").equals("")) { + count++; + } + sub = sub.getSubAbility(); + } + } else if ("Strive".equals(amount)) { + for (TargetChoices tc : sa.getAllTargetChoices()) { + count += tc.getNumTargeted(); + } + --count; + } else { + if (StringUtils.isNumeric(amount)) { + count = Integer.parseInt(amount); + } else { + if (params.containsKey("AffectedAmount")) { + count = CardFactoryUtil.xCount(card, hostCard.getSVar(amount)); + } else { + count = AbilityUtils.calculateAmount(hostCard, amount, sa); + } + } + } + } else { + // Amount 1 as default + count = 1; + } + for(int i = 0; i < count; ++i) { + cost.add(part); + } + } + + private static boolean checkRequirement(final SpellAbility sa, final StaticAbility st) { + if (st.isSuppressed() || !st.checkConditions()) { + return false; + } + + final Map params = st.getMapParams(); + final Card hostCard = st.getHostCard(); + final Player activator = sa.getActivatingPlayer(); + final Card card = sa.getHostCard(); + + if (params.containsKey("Type")) { + final String type = params.get("Type"); + if (type.equals("Spell")) { + if (!sa.isSpell()) { + return false; + } + if (params.containsKey("OnlyFirstSpell")) { + if (activator == null ) { + return false; + } + CardCollection list = CardLists.filterControlledBy(activator.getGame().getStack().getSpellsCastThisTurn(), activator); + if (params.containsKey("ValidCard")) { + list = CardLists.getValidCards(list, params.get("ValidCard"), hostCard.getController(), hostCard); + } + if (list.size() > 0) { + return false; + } + } + } else if (type.equals("Ability")) { + if (!(sa instanceof AbilityActivated) || sa.isReplacementAbility()) { + return false; + } + } else if (type.equals("NonManaAbility")) { + if (!(sa instanceof AbilityActivated) || sa.isManaAbility() || sa.isReplacementAbility()) { + return false; + } + } else if (type.equals("Buyback")) { + if (!sa.isBuyBackAbility()) { + return false; + } + } else if (type.equals("Cycling")) { + if (!sa.isCycling()) { + return false; + } + } else if (type.equals("Dash")) { + if (!sa.isDash()) { + return false; + } + } else if (type.equals("Equip")) { + if (!(sa instanceof AbilityActivated) || !sa.hasParam("Equip")) { + return false; + } + } else if (type.equals("Flashback")) { + if (!sa.isFlashBackAbility()) { + return false; + } + } else if (type.equals("MorphUp")) { + if (!sa.isMorphUp()) { + return false; + } + } else if (type.equals("MorphDown")) { + if (!sa.isSpell() || !((Spell) sa).isCastFaceDown()) { + return false; + } + } else if (type.equals("SelfMonstrosity")) { + if (!(sa instanceof AbilityActivated) || !sa.hasParam("Monstrosity") || sa.isTemporary()) { + // Nemesis of Mortals + return false; + } + } else if (type.equals("SelfIntrinsicAbility")) { + if (!(sa instanceof AbilityActivated) || sa.isReplacementAbility() || sa.isTemporary()) { + return false; + } + } + } + if (params.containsKey("AffectedZone")) { + List zones = ZoneType.listValueOf(params.get("AffectedZone")); + boolean found = false; + for(ZoneType zt : zones) { + if(card.isInZone(zt)) + { + found = true; + break; + } + } + if(!found) { + return false; + } + } + if (params.containsKey("ValidTarget")) { + TargetRestrictions tgt = sa.getTargetRestrictions(); + if (tgt == null) { + return false; + } + boolean targetValid = false; + for (GameObject target : sa.getTargets().getTargets()) { + if (target.isValid(params.get("ValidTarget").split(","), hostCard.getController(), hostCard, sa)) { + targetValid = true; + } + } + if (!targetValid) { + return false; + } + } + if (params.containsKey("ValidSpellTarget")) { + TargetRestrictions tgt = sa.getTargetRestrictions(); + if (tgt == null) { + return false; + } + boolean targetValid = false; + for (SpellAbility target : sa.getTargets().getTargetSpells()) { + Card targetCard = target.getHostCard(); + if (targetCard.isValid(params.get("ValidSpellTarget").split(","), hostCard.getController(), hostCard, sa)) { + targetValid = true; + break; + } + } + if (!targetValid) { + return false; + } + } + + return true; + } +} diff --git a/forge-game/src/main/java/forge/game/cost/CostPayment.java b/forge-game/src/main/java/forge/game/cost/CostPayment.java index 6541f00bd2f..1945cca2900 100644 --- a/forge-game/src/main/java/forge/game/cost/CostPayment.java +++ b/forge-game/src/main/java/forge/game/cost/CostPayment.java @@ -36,6 +36,7 @@ import java.util.Map; */ public class CostPayment { private final Cost cost; + private Cost adjustedCost; private final SpellAbility ability; private final List paidCostParts = new ArrayList(); @@ -66,6 +67,7 @@ public class CostPayment { */ public CostPayment(final Cost cost, final SpellAbility abil) { this.cost = cost; + this.adjustedCost = cost; this.ability = abil; } @@ -109,7 +111,7 @@ public class CostPayment { * @return a boolean. */ public final boolean isFullyPaid() { - for (final CostPart part : this.cost.getCostParts()) { + for (final CostPart part : adjustedCost.getCostParts()) { if (!this.paidCostParts.contains(part)) { return false; } @@ -136,7 +138,8 @@ public class CostPayment { } public boolean payCost(final CostDecisionMakerBase decisionMaker) { - final List costParts = this.getCost().getCostPartsWithZeroMana(); + adjustedCost = CostAdjustment.adjust(cost, ability); + final List costParts = adjustedCost.getCostPartsWithZeroMana(); for (final CostPart part : costParts) { // Wrap the cost and push onto the cost stack decisionMaker.getPlayer().getGame().costPaymentStack.push(new IndividualCostPaymentInstance(part, this)); @@ -171,8 +174,9 @@ public class CostPayment { Map decisions = new HashMap(); + List parts = CostAdjustment.adjust(cost, ability).getCostParts(); // Set all of the decisions before attempting to pay anything - for (final CostPart part : this.cost.getCostParts()) { + for (final CostPart part : parts) { PaymentDecision decision = part.accept(decisionMaker); if (null == decision) return false; @@ -187,7 +191,7 @@ public class CostPayment { decisions.put(part, decision); } - for (final CostPart part : this.cost.getCostParts()) { + for (final CostPart part : parts) { // wrap the payment and push onto the cost stack decisionMaker.getPlayer().getGame().costPaymentStack.push(new IndividualCostPaymentInstance(part, this)); diff --git a/forge-gui/src/main/java/forge/player/HumanPlay.java b/forge-gui/src/main/java/forge/player/HumanPlay.java index 613f4129377..c70af235e07 100644 --- a/forge-gui/src/main/java/forge/player/HumanPlay.java +++ b/forge-gui/src/main/java/forge/player/HumanPlay.java @@ -30,6 +30,7 @@ import forge.game.card.CardView; import forge.game.card.CounterType; import forge.game.cost.Cost; import forge.game.cost.CostAddMana; +import forge.game.cost.CostAdjustment; import forge.game.cost.CostDamage; import forge.game.cost.CostDiscard; import forge.game.cost.CostDraw; @@ -281,7 +282,7 @@ public class HumanPlay { current = Iterables.getFirst(AbilityUtils.getDefinedCards(source, sourceAbility.getParam("ShowCurrentCard"), sourceAbility), null); } - final List parts = cost.getCostParts(); + final List parts = CostAdjustment.adjust(cost, sourceAbility).getCostParts(); final List remainingParts = new ArrayList(parts); CostPart costPart = null; if (!parts.isEmpty()) {