diff --git a/.gitattributes b/.gitattributes index 91dc8de20f3..5976aedf564 100644 --- a/.gitattributes +++ b/.gitattributes @@ -13839,6 +13839,7 @@ src/main/java/forge/card/spellability/AbilitySub.java svneol=native#text/plain src/main/java/forge/card/spellability/AbilityTriggered.java svneol=native#text/plain src/main/java/forge/card/spellability/HumanPlaySpellAbility.java svneol=native#text/plain src/main/java/forge/card/spellability/ISpellAbility.java -text +src/main/java/forge/card/spellability/OptionalCost.java -text src/main/java/forge/card/spellability/Spell.java svneol=native#text/plain src/main/java/forge/card/spellability/SpellAbility.java svneol=native#text/plain src/main/java/forge/card/spellability/SpellAbilityCondition.java svneol=native#text/plain diff --git a/src/main/java/forge/Card.java b/src/main/java/forge/Card.java index a27069fbcf2..3d19bc227e9 100644 --- a/src/main/java/forge/Card.java +++ b/src/main/java/forge/Card.java @@ -22,6 +22,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.EnumMap; +import java.util.EnumSet; import java.util.HashMap; import java.util.Iterator; import java.util.List; @@ -51,6 +52,7 @@ import forge.card.replacement.ReplaceMoved; import forge.card.replacement.ReplacementEffect; import forge.card.replacement.ReplacementResult; import forge.card.spellability.AbilityTriggered; +import forge.card.spellability.OptionalCost; import forge.card.spellability.SpellAbility; import forge.card.spellability.SpellPermanent; import forge.card.spellability.Target; @@ -110,7 +112,6 @@ public class Card extends GameEntity implements Comparable { // if this card is an Aura, what Entity is it enchanting? private GameEntity enchanting = null; - private ArrayList optionalAdditionalCostsPaid = null; // changes by AF animate and continuous static effects - timestamp is the key of maps private Map changedCardTypes = new ConcurrentSkipListMap(); @@ -182,7 +183,6 @@ public class Card extends GameEntity implements Comparable { private int xManaCostPaid = 0; - private int multiKickerMagnitude = 0; private int replicateMagnitude = 0; private int sunburstValue = 0; @@ -3892,23 +3892,14 @@ public class Card extends GameEntity implements Comparable { return this.getNetAttack(); } - /** - *

- * addMultiKickerMagnitude. - *

- * - * @param n - * a int. - */ - public final void addMultiKickerMagnitude(final int n) { - this.multiKickerMagnitude += n; - } - public final void setMultiKickerMagnitude(final int n) { - this.multiKickerMagnitude = n; - } - - public final int getMultiKickerMagnitude() { - return this.multiKickerMagnitude; + private int multiKickerMagnitude = 0; + public final void addMultiKickerMagnitude(final int n) { this.multiKickerMagnitude += n; } + public final void setKickerMagnitude(final int n) { this.multiKickerMagnitude = n; } + public final int getKickerMagnitude() { + if ( this.multiKickerMagnitude > 0 ) + return multiKickerMagnitude; + boolean hasK1 = costsPaid.contains(OptionalCost.Kicker1); + return hasK1 == costsPaid.contains(OptionalCost.Kicker2) ? (hasK1 ? 2 : 0) : 1; } /** @@ -4290,8 +4281,8 @@ public class Card extends GameEntity implements Comparable { * * @return a {@link java.util.ArrayList} object. */ - public final ArrayList getIntrinsicAbilities() { - return this.getCharacteristics().getIntrinsicAbility(); + public final List getUnparsedAbilities() { + return this.getCharacteristics().getUnparsedAbilities(); } /** @@ -4328,8 +4319,8 @@ public class Card extends GameEntity implements Comparable { * @param a * a {@link java.util.ArrayList} object. */ - public final void setIntrinsicAbilities(final ArrayList a) { - this.getCharacteristics().setIntrinsicAbility(new ArrayList(a)); + public final void setIntrinsicAbilities(final List a) { + this.getCharacteristics().setUnparsedAbilities(new ArrayList(a)); } /** @@ -4358,7 +4349,7 @@ public class Card extends GameEntity implements Comparable { */ public final void addIntrinsicAbility(final String s) { if (s.trim().length() != 0) { - this.getCharacteristics().getIntrinsicAbility().add(s); + this.getCharacteristics().getUnparsedAbilities().add(s); } } @@ -4796,64 +4787,6 @@ public class Card extends GameEntity implements Comparable { this.suspendCast = b; } - /** - *

- * optionalAdditionalCostsPaid. - *

- * - * @param cost - * a String. - */ - public final void addOptionalAdditionalCostsPaid(final String cost) { - if (optionalAdditionalCostsPaid == null) { - optionalAdditionalCostsPaid = new ArrayList(); - } - this.optionalAdditionalCostsPaid.add(cost); - } - - /** - *

- * optionalAdditionalCostsPaid. - *

- */ - public final void clearAdditionalCostsPaid() { - if (optionalAdditionalCostsPaid != null) { - optionalAdditionalCostsPaid.clear(); - } - } - - /** - *

- * isOptionalAdditionalCostsPaid. - *

- * - * @param cost - * a String. - * @return a boolean. - */ - public final boolean isOptionalAdditionalCostsPaid(final String cost) { - if (optionalAdditionalCostsPaid == null) { - return false; - } - for (String s : optionalAdditionalCostsPaid) { - if (s.startsWith(cost)) { - return true; - } - } - return false; - } - - /** - *

- * isOptionalAdditionalCostsPaid. - *

- * @return an ArrayList. - * - */ - public final ArrayList getOptionalAdditionalCostsPaid() { - return this.optionalAdditionalCostsPaid; - } - /** * Checks if is phased out. * @@ -6369,17 +6302,16 @@ public class Card extends GameEntity implements Comparable { } } else if (property.startsWith("kicked")) { if (property.equals("kicked")) { - if (!this.isOptionalAdditionalCostsPaid("Kicker")) { + if (this.getKickerMagnitude() == 0) { return false; } } else { String s = "Kicker " + property.split("kicked ")[1]; - if (!this.isOptionalAdditionalCostsPaid(s)) { - return false; - } + if ("1".equals(s) && !this.isOptionalCostPaid(OptionalCost.Kicker1)) return false; + if ("2".equals(s) && !this.isOptionalCostPaid(OptionalCost.Kicker2)) return false; } } else if (property.startsWith("notkicked")) { - if (this.isOptionalAdditionalCostsPaid("Kicker")) { + if (this.getKickerMagnitude() > 0) { return false; } } else if (property.startsWith("evoked")) { @@ -8317,15 +8249,13 @@ public class Card extends GameEntity implements Comparable { public void setSplitStateToPlayAbility(SpellAbility sa) { if( !isSplitCard() ) return; // just in case // Split card support - List leftSplitAbilities = getState(CardCharacteristicName.LeftSplit).getSpellAbility(); - List rightSplitAbilities = getState(CardCharacteristicName.RightSplit).getSpellAbility(); - for (SpellAbility a : leftSplitAbilities) { + for (SpellAbility a : getState(CardCharacteristicName.LeftSplit).getSpellAbility()) { if (sa == a || sa.getDescription().equals(String.format("%s (without paying its mana cost)", a.getDescription()))) { setState(CardCharacteristicName.LeftSplit); return; } } - for (SpellAbility a : rightSplitAbilities) { + for (SpellAbility a : getState(CardCharacteristicName.RightSplit).getSpellAbility()) { if (sa == a || sa.getDescription().equals(String.format("%s (without paying its mana cost)", a.getDescription()))) { setState(CardCharacteristicName.RightSplit); return; @@ -8336,5 +8266,12 @@ public class Card extends GameEntity implements Comparable { throw new RuntimeException("Not found which part to choose for ability " + sa + " from card " + this); } - + + // Optional costs paid + private final EnumSet costsPaid = EnumSet.noneOf(OptionalCost.class); + public void clearOptionalCostsPaid() { costsPaid.clear(); } + public void addOptionalCostPaid(OptionalCost cost) { costsPaid.add(cost); } + public Iterable getOptionalCostsPaid() { return costsPaid; } + public boolean isOptionalCostPaid(OptionalCost cost) { return costsPaid.contains(cost); } + } // end Card class diff --git a/src/main/java/forge/card/CardCharacteristics.java b/src/main/java/forge/card/CardCharacteristics.java index 99f65098b97..3d6baf93229 100644 --- a/src/main/java/forge/card/CardCharacteristics.java +++ b/src/main/java/forge/card/CardCharacteristics.java @@ -46,7 +46,7 @@ public class CardCharacteristics { private ArrayList intrinsicKeyword = new ArrayList(); private final List spellAbility = new ArrayList(); private final List manaAbility = new ArrayList(); - private ArrayList intrinsicAbility = new ArrayList(); + private List unparsedAbilities = new ArrayList(); private ArrayList triggers = new ArrayList(); private ArrayList replacementEffects = new ArrayList(); private ArrayList staticAbilities = new ArrayList(); @@ -215,18 +215,18 @@ public class CardCharacteristics { * * @return the intrinsicAbility */ - public final ArrayList getIntrinsicAbility() { - return this.intrinsicAbility; + public final List getUnparsedAbilities() { + return this.unparsedAbilities; } /** * Sets the intrinsic ability. * - * @param intrinsicAbility0 + * @param list * the intrinsicAbility to set */ - public final void setIntrinsicAbility(final ArrayList intrinsicAbility0) { - this.intrinsicAbility = intrinsicAbility0; + public final void setUnparsedAbilities(final List list) { + this.unparsedAbilities = list; } /** @@ -408,7 +408,7 @@ public class CardCharacteristics { // ArrayList intrinsicKeyword : list of String objects so use copy constructor this.intrinsicKeyword = new ArrayList(source.getIntrinsicKeyword()); // ArrayList intrinsicAbility : list of String objects so use copy constructor - this.intrinsicAbility = new ArrayList(source.getIntrinsicAbility()); + this.unparsedAbilities = new ArrayList(source.getUnparsedAbilities()); // ArrayList staticAbilityStrings : list of String objects so use copy constructor this.staticAbilityStrings = new ArrayList(source.getStaticAbilityStrings()); // String imageFilename = copy reference diff --git a/src/main/java/forge/card/ability/AbilityFactory.java b/src/main/java/forge/card/ability/AbilityFactory.java index ac004474d51..68983e2ea1c 100644 --- a/src/main/java/forge/card/ability/AbilityFactory.java +++ b/src/main/java/forge/card/ability/AbilityFactory.java @@ -121,7 +121,7 @@ public final class AbilityFactory { return abCost; } - private static final SpellAbility getAbility(AbilityRecordType type, ApiType api, Map mapParams, Cost abCost, Card hostCard) { + public static final SpellAbility getAbility(AbilityRecordType type, ApiType api, Map mapParams, Cost abCost, Card hostCard) { Target abTgt = mapParams.containsKey("ValidTgts") ? readTarget(hostCard, mapParams) : null; @@ -350,7 +350,7 @@ public final class AbilityFactory { if(!card.isSplitCard()) throw new IllegalStateException("Fuse ability may be built only on split cards"); - final String strLeftAbility = card.getState(CardCharacteristicName.LeftSplit).getIntrinsicAbility().get(0); + final String strLeftAbility = card.getState(CardCharacteristicName.LeftSplit).getUnparsedAbilities().get(0); Map leftMap = getMapParams(strLeftAbility); AbilityRecordType leftType = AbilityRecordType.getRecordType(leftMap); ApiType leftApi = leftType.getApiTypeOf(leftMap); @@ -358,7 +358,7 @@ public final class AbilityFactory { leftMap.put("SpellDescription", "Fuse (you may cast both halves of this card from your hand)."); leftMap.put("ActivationZone", "Hand"); - final String strRightAbility = card.getState(CardCharacteristicName.RightSplit).getIntrinsicAbility().get(0); + final String strRightAbility = card.getState(CardCharacteristicName.RightSplit).getUnparsedAbilities().get(0); Map rightMap = getMapParams(strRightAbility); AbilityRecordType rightType = AbilityRecordType.getRecordType(leftMap); ApiType rightApi = leftType.getApiTypeOf(rightMap); diff --git a/src/main/java/forge/card/ability/effects/CloneEffect.java b/src/main/java/forge/card/ability/effects/CloneEffect.java index 8410a7e74a3..ddb1dc5e621 100644 --- a/src/main/java/forge/card/ability/effects/CloneEffect.java +++ b/src/main/java/forge/card/ability/effects/CloneEffect.java @@ -248,7 +248,7 @@ public class CloneEffect extends SpellAbilityEffect { final String actualAbility = origSVars.get(s); // final SpellAbility grantedAbility = newAF.getAbility(actualAbility, tgtCard); // tgtCard.addSpellAbility(grantedAbility); - tgtCard.getIntrinsicAbilities().add(actualAbility); + tgtCard.getUnparsedAbilities().add(actualAbility); } } } diff --git a/src/main/java/forge/card/ability/effects/TokenEffect.java b/src/main/java/forge/card/ability/effects/TokenEffect.java index b5b1a572407..0e1c03b75fa 100644 --- a/src/main/java/forge/card/ability/effects/TokenEffect.java +++ b/src/main/java/forge/card/ability/effects/TokenEffect.java @@ -227,7 +227,7 @@ public class TokenEffect extends SpellAbilityEffect { final SpellAbility grantedAbility = AbilityFactory.getAbility(actualAbility, c); c.addSpellAbility(grantedAbility); // added ability to intrinsic list so copies and clones work - c.getIntrinsicAbilities().add(actualAbility); + c.getUnparsedAbilities().add(actualAbility); } } } diff --git a/src/main/java/forge/card/cardfactory/CardFactory.java b/src/main/java/forge/card/cardfactory/CardFactory.java index 08a5264685b..a996318dc5a 100644 --- a/src/main/java/forge/card/cardfactory/CardFactory.java +++ b/src/main/java/forge/card/cardfactory/CardFactory.java @@ -40,6 +40,7 @@ import forge.card.mana.ManaCost; import forge.card.replacement.ReplacementHandler; import forge.card.spellability.AbilityActivated; import forge.card.spellability.AbilitySub; +import forge.card.spellability.OptionalCost; import forge.card.spellability.SpellAbility; import forge.card.spellability.SpellPermanent; import forge.card.spellability.Target; @@ -191,11 +192,10 @@ public class CardFactory { if (bCopyDetails) { c.addXManaCostPaid(original.getXManaCostPaid()); - c.addMultiKickerMagnitude(original.getMultiKickerMagnitude()); - if (original.getOptionalAdditionalCostsPaid() != null) { - for (String cost : original.getOptionalAdditionalCostsPaid()) { - c.addOptionalAdditionalCostsPaid(cost); - } + c.setKickerMagnitude(original.getKickerMagnitude()); + + for (OptionalCost cost : original.getOptionalCostsPaid()) { + c.addOptionalCostPaid(cost); } c.addReplicateMagnitude(original.getReplicateMagnitude()); if (sa.isReplicate()) { @@ -488,7 +488,7 @@ public class CardFactory { to.setManaCost(from.getManaCost()); to.setColor(from.getColor()); to.setSVars(from.getSVars()); - to.setIntrinsicAbilities(from.getIntrinsicAbilities()); + to.setIntrinsicAbilities(from.getUnparsedAbilities()); to.setImageKey(from.getImageKey()); to.setTriggers(from.getTriggers()); diff --git a/src/main/java/forge/card/cardfactory/CardFactoryUtil.java b/src/main/java/forge/card/cardfactory/CardFactoryUtil.java index fbcdcd9fd5f..f200aa269b4 100644 --- a/src/main/java/forge/card/cardfactory/CardFactoryUtil.java +++ b/src/main/java/forge/card/cardfactory/CardFactoryUtil.java @@ -55,6 +55,7 @@ import forge.card.spellability.Ability; import forge.card.spellability.AbilityActivated; import forge.card.spellability.AbilityStatic; import forge.card.spellability.AbilitySub; +import forge.card.spellability.OptionalCost; import forge.card.spellability.Spell; import forge.card.spellability.SpellAbility; import forge.card.spellability.SpellAbilityRestriction; @@ -1556,11 +1557,8 @@ public class CardFactoryUtil { } } if (sq[0].startsWith("Kicked")) { - if (c.isOptionalAdditionalCostsPaid("Kicker")) { - return CardFactoryUtil.doXMath(Integer.parseInt(sq[1]), m, c); - } else { - return CardFactoryUtil.doXMath(Integer.parseInt(sq[2]), m, c); - } + int ix = c.getKickerMagnitude() > 0 ? 1 : 2; + return CardFactoryUtil.doXMath(Integer.parseInt(sq[ix]), m, c); } if (sq[0].contains("GraveyardWithGE20Cards")) { @@ -1660,7 +1658,7 @@ public class CardFactoryUtil { // Count$TimesKicked if (sq[0].contains("TimesKicked")) { - return CardFactoryUtil.doXMath(c.getMultiKickerMagnitude(), m, c); + return CardFactoryUtil.doXMath(c.getKickerMagnitude(), m, c); } if (sq[0].contains("NumCounters")) { final int num = c.getCounters(CounterType.getType(sq[1])); @@ -2370,14 +2368,8 @@ public class CardFactoryUtil { public static final void addAbilityFactoryAbilities(final Card card) { // ************************************************** // AbilityFactory cards - final ArrayList ia = card.getIntrinsicAbilities(); - for (int i = 0; i < ia.size(); i++) { - // System.out.println(cardName); - final SpellAbility sa = AbilityFactory.getAbility(ia.get(i), card); - if (sa.hasParam("SetAsKicked")) { - sa.addOptionalAdditionalCosts("Kicker"); - } - card.addSpellAbility(sa); + for (String rawAbility : card.getUnparsedAbilities()) { + card.addSpellAbility(AbilityFactory.getAbility(rawAbility, card)); } } @@ -2601,10 +2593,11 @@ public class CardFactoryUtil { } // AltCost - if (!card.getSVar("AltCost").equals("")) { + String altCost = card.getSVar("AltCost"); + if (StringUtils.isNotBlank(altCost)) { final SpellAbility sa1 = card.getFirstSpellAbility(); if (sa1 != null && sa1.isSpell()) { - card.addSpellAbility(makeAltCost(card, sa1)); + card.addSpellAbility(makeAltCostAbility(card, altCost, sa1)); } } @@ -2779,7 +2772,7 @@ public class CardFactoryUtil { final SpellAbility sa = AbilityFactory.getAbility(abilityStr.toString(), card); card.addSpellAbility(sa); // add ability to instrinic strings so copies/clones create the ability also - card.getIntrinsicAbilities().add(abilityStr.toString()); + card.getUnparsedAbilities().add(abilityStr.toString()); } setupEtbKeywords(card); @@ -3066,63 +3059,26 @@ public class CardFactoryUtil { * @param abilities * @return */ - private static SpellAbility makeAltCost(final Card card, final SpellAbility sa) { - String altCost = card.getSVar("AltCost"); - final HashMap mapParams = new HashMap(); - String altCostDescription = ""; - final String[] altCosts = altCost.split("\\|"); - - for (int aCnt = 0; aCnt < altCosts.length; aCnt++) { - altCosts[aCnt] = altCosts[aCnt].trim(); - } - - for (final String altCost2 : altCosts) { - final String[] aa = altCost2.split("\\$"); - - for (int aaCnt = 0; aaCnt < aa.length; aaCnt++) { - aa[aaCnt] = aa[aaCnt].trim(); - } - - if (aa.length != 2) { - final StringBuilder sb = new StringBuilder(); - sb.append("StaticEffectFactory Parsing Error: Split length of "); - sb.append(altCost2).append(" in ").append(card.getName()).append(" is not 2."); - throw new RuntimeException(sb.toString()); - } - - mapParams.put(aa[0], aa[1]); - } - - altCost = mapParams.get("Cost"); - - if (mapParams.containsKey("Description")) { - altCostDescription = mapParams.get("Description"); - } + private static SpellAbility makeAltCostAbility(final Card card, final String altCost, final SpellAbility sa) { + final Map params = AbilityFactory.getMapParams(altCost); final SpellAbility altCostSA = sa.copy(); - - final Cost abCost = new Cost(altCost, altCostSA.isAbility()); + final Cost abCost = new Cost(params.get("Cost"), altCostSA.isAbility()); altCostSA.setPayCosts(abCost); - - final StringBuilder sb = new StringBuilder(); - - if (!altCostDescription.equals("")) { - sb.append(altCostDescription); - } else { - sb.append("You may ").append(abCost.toStringAlt()); - sb.append(" rather than pay ").append(card.getName()).append("'s mana cost."); - } + altCostSA.setBasicSpell(false); + altCostSA.addOptionalCost(OptionalCost.AltCost); final SpellAbilityRestriction restriction = new SpellAbilityRestriction(); - restriction.setRestrictions(mapParams); - if (!mapParams.containsKey("ActivationZone")) { + restriction.setRestrictions(params); + if (!params.containsKey("ActivationZone")) { restriction.setZone(ZoneType.Hand); } altCostSA.setRestrictions(restriction); - altCostSA.setDescription(sb.toString()); - altCostSA.setBasicSpell(false); - altCostSA.setAltCost(true); + final String costDescription = params.containsKey("Description") ? params.get("Description") + : String.format("You may %s rather than pay %s's mana cost.", abCost.toStringAlt(), card.getName()); + + altCostSA.setDescription(costDescription); return altCostSA; } @@ -3186,14 +3142,7 @@ public class CardFactoryUtil { * @return a int. */ public static final int hasKeyword(final Card c, final String k) { - final List a = c.getKeyword(); - for (int i = 0; i < a.size(); i++) { - if (a.get(i).startsWith(k)) { - return i; - } - } - - return -1; + return hasKeyword(c, k, 0); } /** @@ -3209,7 +3158,7 @@ public class CardFactoryUtil { * a int. * @return a int. */ - static final int hasKeyword(final Card c, final String k, final int startPos) { + private static final int hasKeyword(final Card c, final String k, final int startPos) { final List a = c.getKeyword(); for (int i = startPos; i < a.size(); i++) { if (a.get(i).startsWith(k)) { diff --git a/src/main/java/forge/card/cost/CostPartMana.java b/src/main/java/forge/card/cost/CostPartMana.java index 0e5252ae865..82877cca029 100644 --- a/src/main/java/forge/card/cost/CostPartMana.java +++ b/src/main/java/forge/card/cost/CostPartMana.java @@ -128,7 +128,7 @@ public class CostPartMana extends CostPart { toPay.increaseShard(ManaCostShard.valueOf(xColor), xCost); xWasBilled = true; } - int timesMultikicked = ability.getSourceCard().getMultiKickerMagnitude(); + int timesMultikicked = ability.getSourceCard().getKickerMagnitude(); if ( timesMultikicked > 0 && ability.isAnnouncing("Multikicker")) { ManaCost mkCost = ability.getMultiKickerManaCost(); for(int i = 0; i < timesMultikicked; i++) diff --git a/src/main/java/forge/card/spellability/HumanPlaySpellAbility.java b/src/main/java/forge/card/spellability/HumanPlaySpellAbility.java index 7b5afb659e0..4b6c186643c 100644 --- a/src/main/java/forge/card/spellability/HumanPlaySpellAbility.java +++ b/src/main/java/forge/card/spellability/HumanPlaySpellAbility.java @@ -168,7 +168,7 @@ public class HumanPlaySpellAbility { ability.setSVar(varName, value.toString()); if( "Multikicker".equals(varName) ) { - ability.getSourceCard().setMultiKickerMagnitude(value); + ability.getSourceCard().setKickerMagnitude(value); } else { ability.getSourceCard().setSVar(varName, value.toString()); } diff --git a/src/main/java/forge/card/spellability/OptionalCost.java b/src/main/java/forge/card/spellability/OptionalCost.java new file mode 100644 index 00000000000..726cf7f1ff5 --- /dev/null +++ b/src/main/java/forge/card/spellability/OptionalCost.java @@ -0,0 +1,13 @@ +package forge.card.spellability; + +/** + * TODO: Write javadoc for this type. + * + */ +public enum OptionalCost { + Conspire, + Buyback, + Kicker1, + Kicker2, + AltCost, // used by prowl +} diff --git a/src/main/java/forge/card/spellability/SpellAbility.java b/src/main/java/forge/card/spellability/SpellAbility.java index 534f596d20f..f9994a610b4 100644 --- a/src/main/java/forge/card/spellability/SpellAbility.java +++ b/src/main/java/forge/card/spellability/SpellAbility.java @@ -18,6 +18,7 @@ package forge.card.spellability; import java.util.ArrayList; +import java.util.EnumSet; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -92,7 +93,6 @@ public abstract class SpellAbility implements ISpellAbility { private final ArrayList payingMana = new ArrayList(); private final List paidAbilities = new ArrayList(); - private List optionalAdditionalCosts = new ArrayList(); private HashMap> paidLists = new HashMap>(); @@ -109,7 +109,6 @@ public abstract class SpellAbility implements ISpellAbility { private boolean undoable; private boolean isCopied = false; - private boolean isAltCost = false; public final AbilityManaPart getManaPart() { return manaPart; @@ -302,43 +301,6 @@ public abstract class SpellAbility implements ISpellAbility { public boolean isSpell() { return false; } public boolean isAbility() { return true; } - /** - *

- * isBuyBackAbility. - *

- * - * @return a boolean. - */ - public boolean isBuyBackAbility() { - return this.optionalAdditionalCosts.contains("Buyback"); - } - - /** - *

- * isKicked. - *

- * - * @return a boolean. - */ - public boolean isKicked() { - return isOptionalAdditionalCostPaid("Kicker"); - } - - /** - *

- * isOptionalAdditionalCostPaid. - *

- * - * @return a boolean. - */ - public boolean isOptionalAdditionalCostPaid(String cost) { - for (String s : this.optionalAdditionalCosts) { - if (s.startsWith(cost)) { - return true; - } - } - return false; - } /** *

@@ -668,27 +630,37 @@ public abstract class SpellAbility implements ISpellAbility { this.paidLists = new HashMap>(); } + private EnumSet optionalCosts = EnumSet.noneOf(OptionalCost.class); /** * @return the optionalAdditionalCosts */ - public List getOptionalAdditionalCosts() { - return optionalAdditionalCosts; - } - - /** - * @param costs the optionalAdditionalCosts to set - */ - public final void setOptionalAdditionalCosts(List costs) { - this.optionalAdditionalCosts = costs; + public Iterable getOptionalCosts() { + return optionalCosts; } /** * @param cost the optionalAdditionalCost to add */ - public final void addOptionalAdditionalCosts(String cost) { - this.optionalAdditionalCosts.add(cost); + public final void addOptionalCost(OptionalCost cost) { + // Optional costs are added to swallow copies of original SAs, + // Thus, to protect the original's set from changes, we make a copy right here. + this.optionalCosts = EnumSet.copyOf(optionalCosts); + this.optionalCosts.add(cost); } + public boolean isBuyBackAbility() { + return isOptionalCostPaid(OptionalCost.Buyback); + } + + public boolean isKicked() { + return isOptionalCostPaid(OptionalCost.Kicker1) || isOptionalCostPaid(OptionalCost.Kicker2); + } + + public boolean isOptionalCostPaid(OptionalCost cost) { + SpellAbility saRoot = this.getRootAbility(); + return saRoot.optionalCosts.contains(cost); + } + /** *

* Getter for the field triggeringObjects. @@ -1724,22 +1696,4 @@ public abstract class SpellAbility implements ISpellAbility { CostPartMana cm = payCosts != null ? getPayCosts().getCostMana() : null; return cm != null && cm.getAmountOfX() > 0; } - - /** - * @param isAltCost the isAltCost to set - */ - public void setAltCost(boolean isAltCost) { - this.isAltCost = isAltCost; - } - - /** - * @return the isAltCost - */ - public boolean isAltCost() { - // only used in prowl, cannot distinguish the alt cost type currently - // TODO : support the altcost type - return isAltCost; - } - - } diff --git a/src/main/java/forge/card/spellability/SpellAbilityCondition.java b/src/main/java/forge/card/spellability/SpellAbilityCondition.java index 608f3f5f02b..c71e35f6f94 100644 --- a/src/main/java/forge/card/spellability/SpellAbilityCondition.java +++ b/src/main/java/forge/card/spellability/SpellAbilityCondition.java @@ -78,11 +78,19 @@ public class SpellAbilityCondition extends SpellAbilityVariables { this.setHellbent(true); } if (value.equals("Kicked")) { - this.setKicked(true); + this.kicked = true; } + if (value.equals("Kicked 1")) { + this.kicked1 = true; + } + if (value.equals("Kicked 2")) { + this.kicked2 = true; + } if (value.equals("AllTargetsLegal")) { this.setAllTargetsLegal(true); } + if (value.equals("AltCost")) + this.altCostPaid = true; } if (params.containsKey("ConditionZone")) { @@ -177,27 +185,15 @@ public class SpellAbilityCondition extends SpellAbilityVariables { + " Did not have activator set in SpellAbility_Condition.checkConditions()"); } - if (this.isHellbent()) { - if (!activator.hasHellbent()) { - return false; - } - } - if (this.isThreshold()) { - if (!activator.hasThreshold()) { - return false; - } - } - if (this.isMetalcraft()) { - if (!activator.hasMetalcraft()) { - return false; - } - } - if (this.isKicked()) { - SpellAbility root = sa.getRootAbility(); - if (!root.isKicked()) { - return false; - } - } + if (this.isHellbent() && !activator.hasHellbent()) return false; + if (this.isThreshold() && !activator.hasThreshold()) return false; + if (this.isMetalcraft() && !activator.hasMetalcraft()) return false; + + if (this.kicked && !sa.isKicked()) return false; + if (this.kicked1 && !sa.isOptionalCostPaid(OptionalCost.Kicker1)) return false; + if (this.kicked2 && !sa.isOptionalCostPaid(OptionalCost.Kicker2)) return false; + if( this.altCostPaid && !sa.isOptionalCostPaid(OptionalCost.AltCost)) return false; + if (this.isAllTargetsLegal()) { for (Card c : sa.getTarget().getTargetCards()) { if (!CardFactoryUtil.isTargetStillValid(sa, c)) { @@ -210,11 +206,11 @@ public class SpellAbilityCondition extends SpellAbilityVariables { return false; } - if (this.isPlayerTurn() && !Singletons.getModel().getGame().getPhaseHandler().isPlayerTurn(activator)) { + if (this.isPlayerTurn() && !activator.getGame().getPhaseHandler().isPlayerTurn(activator)) { return false; } - if (this.isOpponentTurn() && !Singletons.getModel().getGame().getPhaseHandler().getPlayerTurn().isOpponentOf(activator)) { + if (this.isOpponentTurn() && !activator.getGame().getPhaseHandler().getPlayerTurn().isOpponentOf(activator)) { return false; } diff --git a/src/main/java/forge/card/spellability/SpellAbilityRestriction.java b/src/main/java/forge/card/spellability/SpellAbilityRestriction.java index de8c6ec247d..393df24856e 100644 --- a/src/main/java/forge/card/spellability/SpellAbilityRestriction.java +++ b/src/main/java/forge/card/spellability/SpellAbilityRestriction.java @@ -85,7 +85,7 @@ public class SpellAbilityRestriction extends SpellAbilityVariables { if (value.split("Prowl").length > 1) { prowlTypes.add(value.split("Prowl")[1]); } - this.setProwl(prowlTypes); + this.setProwlTypes(prowlTypes); } } @@ -352,11 +352,11 @@ public class SpellAbilityRestriction extends SpellAbilityVariables { return false; } } - if (this.getProwl() != null && !this.getProwl().isEmpty()) { + if (this.getProwlTypes() != null && !this.getProwlTypes().isEmpty()) { // only true if the activating player has damaged the opponent with // one of the specified types boolean prowlFlag = false; - for (final String type : this.getProwl()) { + for (final String type : this.getProwlTypes()) { if (activator.hasProwl(type)) { prowlFlag = true; } diff --git a/src/main/java/forge/card/spellability/SpellAbilityVariables.java b/src/main/java/forge/card/spellability/SpellAbilityVariables.java index 023d0a1e3f0..1ad64556f2c 100644 --- a/src/main/java/forge/card/spellability/SpellAbilityVariables.java +++ b/src/main/java/forge/card/spellability/SpellAbilityVariables.java @@ -70,7 +70,7 @@ public class SpellAbilityVariables { this.metalcraft = sav.isThreshold(); this.hellbent = sav.isHellbent(); this.allTargetsLegal = sav.isAllTargetsLegal(); - this.prowl = new ArrayList(sav.getProwl()); + this.prowlTypes = new ArrayList(sav.getProwlTypes()); this.isPresent = sav.getIsPresent(); this.presentCompare = sav.getPresentCompare(); this.presentDefined = sav.getPresentDefined(); @@ -135,13 +135,10 @@ public class SpellAbilityVariables { /** The hellbent. */ private boolean hellbent = false; - /** The Kicked. */ - private boolean kicked = false; - private boolean allTargetsLegal = false; /** The prowl. */ - private ArrayList prowl = new ArrayList(); + private ArrayList prowlTypes = new ArrayList(); /** The s is present. */ private String isPresent = null; @@ -186,6 +183,8 @@ public class SpellAbilityVariables { /** The chosen colors string. */ private String chosenColors = null; + + /** *

* Setter for the field notAllM12Empires. @@ -507,20 +506,11 @@ public class SpellAbilityVariables { this.metalcraft = bMetalcraft; } - /** - * @return the kicked - */ - public boolean isKicked() { - return kicked; - } - - /** - * @param kicked the kicked to set - */ - public void setKicked(boolean kicked) { - this.kicked = kicked; - } - + /** The Kicked. */ + protected boolean kicked = false; + protected boolean kicked1 = false; // http://magiccards.info/query?q=o%3A%22kicker%22+not+o%3A%22multikicker%22+o%3A%22and%2For+{%22 + protected boolean kicked2 = false; // Some spells have 2 kickers with different effects + protected boolean altCostPaid = false; /** * @return the allTargetsLegal @@ -535,7 +525,7 @@ public class SpellAbilityVariables { public void setAllTargetsLegal(boolean allTargets) { this.allTargetsLegal = allTargets; } - + /** *

@@ -545,8 +535,8 @@ public class SpellAbilityVariables { * @param types * the new prowl */ - public final void setProwl(final ArrayList types) { - this.prowl = types; + public final void setProwlTypes(final ArrayList types) { + this.prowlTypes = types; } // IsPresent for Valid battlefield stuff @@ -760,8 +750,8 @@ public class SpellAbilityVariables { * * @return the prowl */ - public final ArrayList getProwl() { - return this.prowl; + public final ArrayList getProwlTypes() { + return this.prowlTypes; } /** diff --git a/src/main/java/forge/card/spellability/SpellPermanent.java b/src/main/java/forge/card/spellability/SpellPermanent.java index 05320ce675d..e3262574993 100644 --- a/src/main/java/forge/card/spellability/SpellPermanent.java +++ b/src/main/java/forge/card/spellability/SpellPermanent.java @@ -238,25 +238,13 @@ public class SpellPermanent extends Spell { continue; } } else if (params.get("ValidCard").contains("kicked")) { - if (!params.get("ValidCard").contains("kicked ")) { - if (!sa.isKicked()) { - continue; - } - } else { - String s = "Kicker " + params.get("ValidCard").split("kicked ")[1]; - boolean rightKicker = false; - if (sa.getOptionalAdditionalCosts() != null) { - for (String string : sa.getOptionalAdditionalCosts()) { - if (string.startsWith(s)) { - rightKicker = true; - } - } - } - if (!rightKicker) { - continue; - } + if (params.get("ValidCard").contains("kicked ")) { // want a specific kicker + String s = params.get("ValidCard").split("kicked ")[1]; + if ( "1".equals(s) && !sa.isOptionalCostPaid(OptionalCost.Kicker1)) continue; + if ( "2".equals(s) && !sa.isOptionalCostPaid(OptionalCost.Kicker2)) continue; + } else if (!sa.isKicked()) { + continue; } - } } @@ -335,23 +323,12 @@ public class SpellPermanent extends Spell { continue; } } else if (params.get("ValidCard").contains("kicked")) { - if (!params.get("ValidCard").contains("kicked ")) { - if (!sa.isKicked()) { - continue; - } - } else { - String s = "Kicker " + params.get("ValidCard").split("kicked ")[1]; - boolean rightKicker = false; - if (sa.getOptionalAdditionalCosts() != null) { - for (String string : sa.getOptionalAdditionalCosts()) { - if (string.startsWith(s)) { - rightKicker = true; - } - } - } - if (!rightKicker) { - continue; - } + if (params.get("ValidCard").contains("kicked ")) { // want a specific kicker + String s = params.get("ValidCard").split("kicked ")[1]; + if ( "1".equals(s) && !sa.isOptionalCostPaid(OptionalCost.Kicker1)) continue; + if ( "2".equals(s) && !sa.isOptionalCostPaid(OptionalCost.Kicker2)) continue; + } else if (!sa.isKicked()) { // otherwise just any must be present + continue; } } } diff --git a/src/main/java/forge/card/staticability/StaticAbility.java b/src/main/java/forge/card/staticability/StaticAbility.java index b2d4cfd20e4..630862a0028 100644 --- a/src/main/java/forge/card/staticability/StaticAbility.java +++ b/src/main/java/forge/card/staticability/StaticAbility.java @@ -497,28 +497,21 @@ public class StaticAbility { } } - if (params.containsKey("Condition")) { - if (params.get("Condition").equals("Threshold")) { - if (!controller.hasThreshold()) { + String condition = params.get("Condition"); + if (null != condition) { + if (condition.equals("Threshold") && !controller.hasThreshold()) return false; + if (condition.equals("Hellbent") && !controller.hasHellbent()) return false; + if (condition.equals("Metalcraft") && !controller.hasMetalcraft()) return false; + + if (condition.equals("PlayerTurn")) { + if (!controller.getGame().getPhaseHandler().isPlayerTurn(controller)) { return false; } - } else if (params.get("Condition").equals("Hellbent")) { - if (!controller.hasHellbent()) { + } else if (condition.equals("NotPlayerTurn")) { + if (controller.getGame().getPhaseHandler().isPlayerTurn(controller)) { return false; } - } else if (params.get("Condition").equals("Metalcraft")) { - if (!controller.hasMetalcraft()) { - return false; - } - } else if (params.get("Condition").equals("PlayerTurn")) { - if (!Singletons.getModel().getGame().getPhaseHandler().isPlayerTurn(controller)) { - return false; - } - } else if (params.get("Condition").equals("NotPlayerTurn")) { - if (Singletons.getModel().getGame().getPhaseHandler().isPlayerTurn(controller)) { - return false; - } - } else if (params.get("Condition").equals("PermanentOfEachColor")) { + } else if (condition.equals("PermanentOfEachColor")) { if ((controller.getColoredCardsInPlay(Constant.Color.BLACK).isEmpty() || controller.getColoredCardsInPlay(Constant.Color.BLUE).isEmpty() || controller.getColoredCardsInPlay(Constant.Color.GREEN).isEmpty() @@ -526,7 +519,7 @@ public class StaticAbility { || controller.getColoredCardsInPlay(Constant.Color.WHITE).isEmpty())) { return false; } - } else if (params.get("Condition").equals("FatefulHour")) { + } else if (condition.equals("FatefulHour")) { if (controller.getLife() > 5) { return false; } diff --git a/src/main/java/forge/card/trigger/Trigger.java b/src/main/java/forge/card/trigger/Trigger.java index e616afb4f7e..c4f5b50d8aa 100644 --- a/src/main/java/forge/card/trigger/Trigger.java +++ b/src/main/java/forge/card/trigger/Trigger.java @@ -26,6 +26,7 @@ import forge.Card; import forge.Singletons; import forge.card.TriggerReplacementBase; import forge.card.spellability.Ability; +import forge.card.spellability.OptionalCost; import forge.card.spellability.SpellAbility; import forge.game.phase.PhaseHandler; import forge.game.phase.PhaseType; @@ -315,22 +316,28 @@ public abstract class Trigger extends TriggerReplacementBase { if ( !meetsCommonRequirements(getMapParams())) return false; - if (this.getMapParams().containsKey("EvolveCondition")) { - if (this.getMapParams().get("EvolveCondition").equals("True")) { - final Card moved = (Card) runParams2.get("Card"); - if (moved == null) { - return false; - // final StringBuilder sb = new StringBuilder(); - // sb.append("Trigger::requirementsCheck() - EvolveCondition condition being checked without a moved card. "); - // sb.append(this.getHostCard().getName()); - // throw new RuntimeException(sb.toString()); - } - if (moved.getNetAttack() <= this.getHostCard().getNetAttack() - && moved.getNetDefense() <= this.getHostCard().getNetDefense()) { - return false; - } + if ("True".equals(getMapParams().get("EvolveCondition"))) { + final Card moved = (Card) runParams2.get("Card"); + if (moved == null) { + return false; + // final StringBuilder sb = new StringBuilder(); + // sb.append("Trigger::requirementsCheck() - EvolveCondition condition being checked without a moved card. "); + // sb.append(this.getHostCard().getName()); + // throw new RuntimeException(sb.toString()); + } + if (moved.getNetAttack() <= this.getHostCard().getNetAttack() + && moved.getNetDefense() <= this.getHostCard().getNetDefense()) { + return false; } } + + String condition = getMapParams().get("Condition"); + if( "AltCost".equals(condition) ) { + final Card moved = (Card) runParams2.get("Card"); + if( null != moved && !moved.isOptionalCostPaid(OptionalCost.AltCost)) + return false; + } + return true; } diff --git a/src/main/java/forge/card/trigger/TriggerSpellAbilityCast.java b/src/main/java/forge/card/trigger/TriggerSpellAbilityCast.java index 7e19873426f..71634308dd1 100644 --- a/src/main/java/forge/card/trigger/TriggerSpellAbilityCast.java +++ b/src/main/java/forge/card/trigger/TriggerSpellAbilityCast.java @@ -20,6 +20,7 @@ package forge.card.trigger; import forge.Card; import forge.Singletons; import forge.card.cost.Cost; +import forge.card.spellability.OptionalCost; import forge.card.spellability.SpellAbility; import forge.card.spellability.SpellAbilityStackInstance; import forge.game.player.Player; @@ -76,7 +77,7 @@ public class TriggerSpellAbilityCast extends Trigger { } if (this.getMapParams().containsKey("AltCostSpellAbility")) { - if (!spellAbility.isAltCost()) { + if (!spellAbility.isOptionalCostPaid(OptionalCost.AltCost)) { return false; } } @@ -157,7 +158,7 @@ public class TriggerSpellAbilityCast extends Trigger { } if (this.getMapParams().containsKey("Conspire")) { - if (!spellAbility.isOptionalAdditionalCostPaid("Conspire")) { + if (!spellAbility.isOptionalCostPaid(OptionalCost.Conspire)) { return false; } } diff --git a/src/main/java/forge/game/GameAction.java b/src/main/java/forge/game/GameAction.java index 533ae67842e..73b75408dfa 100644 --- a/src/main/java/forge/game/GameAction.java +++ b/src/main/java/forge/game/GameAction.java @@ -291,7 +291,7 @@ public class GameAction { } } else if (zoneFrom.is(ZoneType.Exile) && !zoneTo.is(ZoneType.Battlefield)) { // Pull from Eternity used on a suspended card - copied.clearAdditionalCostsPaid(); + copied.clearOptionalCostsPaid(); if (copied.isFaceDown()) { copied.turnFaceUp(); } @@ -315,7 +315,7 @@ public class GameAction { copied.removeHiddenExtrinsicKeyword(s); } } - copied.clearAdditionalCostsPaid(); + copied.clearOptionalCostsPaid(); if (copied.isFaceDown()) { copied.turnFaceUp(); } diff --git a/src/main/java/forge/game/GameActionUtil.java b/src/main/java/forge/game/GameActionUtil.java index 65616b9d0a6..16ecb26a2c5 100644 --- a/src/main/java/forge/game/GameActionUtil.java +++ b/src/main/java/forge/game/GameActionUtil.java @@ -20,6 +20,8 @@ package forge.game; import java.util.ArrayList; import java.util.HashMap; import java.util.List; +import java.util.Map; + import org.apache.commons.lang3.StringUtils; import com.google.common.collect.Iterables; @@ -36,6 +38,7 @@ import forge.CounterType; import forge.FThreads; import forge.Singletons; 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; @@ -57,6 +60,7 @@ import forge.card.mana.ManaCost; import forge.card.spellability.Ability; import forge.card.spellability.AbilityManaPart; 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; @@ -72,6 +76,7 @@ import forge.game.zone.ZoneType; import forge.gui.GuiChoose; import forge.gui.GuiDialog; import forge.sound.SoundEffectType; +import forge.util.TextUtil; /** @@ -1209,6 +1214,115 @@ public final class GameActionUtil { return alternatives; } + /** + * get optional additional costs. + * + * @param original + * the original sa + * @return an ArrayList. + */ + public static List getOptionalCosts(final SpellAbility original) { + final List abilities = new ArrayList(); + + final Card source = original.getSourceCard(); + abilities.add(original); + if (!original.isSpell()) { + return abilities; + } + + // Buyback, Kicker + for (String keyword : source.getKeyword()) { + if (keyword.startsWith("AlternateAdditionalCost")) { + final List newAbilities = new ArrayList(); + String[] costs = TextUtil.split(keyword, ':'); + for (SpellAbility sa : abilities) { + final SpellAbility newSA = sa.copy(); + newSA.setBasicSpell(false); + + final Cost cost1 = new Cost(costs[1], false); + newSA.setDescription(sa.getDescription() + " (Additional cost " + cost1.toSimpleString() + ")"); + newSA.setPayCosts(cost1.add(sa.getPayCosts())); + if (newSA.canPlay()) { + newAbilities.add(newSA); + } + + //second option + final SpellAbility newSA2 = sa.copy(); + newSA2.setBasicSpell(false); + + final Cost cost2 = new Cost(costs[2], false); + newSA2.setDescription(sa.getDescription() + " (Additional cost " + cost2.toSimpleString() + ")"); + newSA2.setPayCosts(cost2.add(sa.getPayCosts())); + if (newSA2.canPlay()) { + newAbilities.add(newAbilities.size(), newSA2); + } + } + abilities.clear(); + abilities.addAll(newAbilities); + } else if (keyword.startsWith("Buyback")) { + for (int i = 0; i < abilities.size(); i++) { + final SpellAbility newSA = abilities.get(i).copy(); + newSA.setBasicSpell(false); + newSA.setPayCosts(new Cost(keyword.substring(8), false).add(newSA.getPayCosts())); + newSA.setDescription(newSA.getDescription() + " (with Buyback)"); + newSA.addOptionalCost(OptionalCost.Buyback); + if ( newSA.canPlay() ) + abilities.add(++i, newSA); + } + } else if (keyword.startsWith("Kicker")) { + for (int i = 0; i < abilities.size(); i++) { + String[] sCosts = TextUtil.split(keyword.substring(7), ':'); + int iUnKicked = i; + for(int j = 0; j < sCosts.length; j++) { + final SpellAbility newSA = abilities.get(iUnKicked).copy(); + newSA.setBasicSpell(false); + final Cost cost = new Cost(sCosts[j], false); + newSA.setDescription(newSA.getDescription() + " (Kicker " + cost.toSimpleString() + ")"); + newSA.setPayCosts(cost.add(newSA.getPayCosts())); + newSA.addOptionalCost(j == 0 ? OptionalCost.Kicker1 : OptionalCost.Kicker2); + if ( newSA.canPlay() ) + abilities.add(++i, newSA); + } + if(sCosts.length == 2) { // case for both kickers - it's hardcoded since they never have more that 2 kickers + final SpellAbility newSA = abilities.get(iUnKicked).copy(); + newSA.setBasicSpell(false); + final Cost cost1 = new Cost(sCosts[0], false); + final Cost cost2 = new Cost(sCosts[1], false); + newSA.setDescription(newSA.getDescription() + String.format(" (Both kickers: %s and %s)", cost1.toSimpleString(), cost2.toSimpleString())); + newSA.setPayCosts(cost2.add(cost1.add(newSA.getPayCosts()))); + newSA.addOptionalCost(OptionalCost.Kicker1); + newSA.addOptionalCost(OptionalCost.Kicker2); + if ( newSA.canPlay() ) + abilities.add(++i, newSA); + } + } + } else if (keyword.startsWith("Conspire")) { + for (int i = 0; i < abilities.size(); i++) { + final SpellAbility newSA = abilities.get(i).copy(); + newSA.setBasicSpell(false); + final String conspireCost = "tapXType<2/Creature.SharesColorWith/untapped creature you control that shares a color with " + source.getName() + ">"; + newSA.setPayCosts(new Cost(conspireCost, false).add(newSA.getPayCosts())); + newSA.setDescription(newSA.getDescription() + " (Conspire)"); + newSA.addOptionalCost(OptionalCost.Conspire); + if ( newSA.canPlay() ) + abilities.add(++i, newSA); + } + } + } + + // Splice + final List newAbilities = new ArrayList(); + for (SpellAbility sa : abilities) { + if( sa.isSpell() && sa.getSourceCard().isType("Arcane") && sa.getApi() != null ) { + newAbilities.addAll(GameActionUtil.getSpliceAbilities(sa)); + } + } + abilities.addAll(newAbilities); + + + return abilities; + } + /** *

* getSpliceAbilities. @@ -1219,179 +1333,70 @@ public final class GameActionUtil { * @return an ArrayList. * get abilities with all Splice options */ - public static final ArrayList getSpliceAbilities(SpellAbility sa) { + private static final ArrayList getSpliceAbilities(SpellAbility sa) { ArrayList newSAs = new ArrayList(); - ArrayList allSAs = new ArrayList(); - allSAs.add(sa); + ArrayList allSaCombinations = new ArrayList(); + allSaCombinations.add(sa); Card source = sa.getSourceCard(); - if (!sa.isSpell() || !source.isType("Arcane") || sa.getApi() == null) { - return newSAs; - } - + for (Card c : sa.getActivatingPlayer().getCardsIn(ZoneType.Hand)) { if (c.equals(source)) { continue; } + + String spliceKwCost = null; for (String keyword : c.getKeyword()) { - if (!keyword.startsWith("Splice")) { - continue; + if (keyword.startsWith("Splice")) { + spliceKwCost = keyword.substring(19); + break; } - String newSubSAString = c.getCharacteristics().getIntrinsicAbility().get(0); - newSubSAString = newSubSAString.replace("SP", "DB"); - final AbilitySub newSubSA = (AbilitySub) AbilityFactory.getAbility(newSubSAString, c); - ArrayList addSAs = new ArrayList(); - // Add the subability to all existing variants - for (SpellAbility s : allSAs) { - //create a new spell copy - final SpellAbility newSA = s.copy(); - newSA.setBasicSpell(false); - newSA.setPayCosts(new Cost(keyword.substring(19), false).add(newSA.getPayCosts())); - newSA.setDescription(s.getDescription() + " (Splicing " + c + " onto it)"); - newSA.addSplicedCards(c); + } - // copy all subAbilities - SpellAbility child = newSA; - while (child.getSubAbility() != null) { - AbilitySub newChild = child.getSubAbility().getCopy(); - child.setSubAbility(newChild); - child.setActivatingPlayer(s.getActivatingPlayer()); - child = newChild; - } + if( spliceKwCost == null ) + continue; - //add the spliced ability to the end of the chain - child.setSubAbility(newSubSA); + Map params = AbilityFactory.getMapParams(c.getCharacteristics().getUnparsedAbilities().get(0)); + AbilityRecordType rc = AbilityRecordType.getRecordType(params); + ApiType api = rc.getApiTypeOf(params); + AbilitySub subAbility = (AbilitySub) AbilityFactory.getAbility(AbilityRecordType.SubAbility, api, params, null, c); - //set correct source and activating player to all the spliced abilities - child = newSubSA; - while (child != null) { - child.setSourceCard(source); - child.setActivatingPlayer(s.getActivatingPlayer()); - child = child.getSubAbility(); - } - newSAs.add(0, newSA); - addSAs.add(newSA); + // Add the subability to all existing variants + for (int i = 0; i < allSaCombinations.size(); ++i) { + //create a new spell copy + final SpellAbility newSA = allSaCombinations.get(i).copy(); + newSA.setBasicSpell(false); + newSA.setPayCosts(new Cost(spliceKwCost, false).add(newSA.getPayCosts())); + newSA.setDescription(newSA.getDescription() + " (Splicing " + c + " onto it)"); + newSA.addSplicedCards(c); + + // copy all subAbilities + SpellAbility child = newSA; + while (child.getSubAbility() != null) { + AbilitySub newChild = child.getSubAbility().getCopy(); + child.setSubAbility(newChild); + child.setActivatingPlayer(newSA.getActivatingPlayer()); + child = newChild; } - allSAs.addAll(addSAs); - break; + + //add the spliced ability to the end of the chain + child.setSubAbility(subAbility); + + //set correct source and activating player to all the spliced abilities + child = subAbility; + while (child != null) { + child.setSourceCard(source); + child.setActivatingPlayer(newSA.getActivatingPlayer()); + child = child.getSubAbility(); + } + newSAs.add(newSA); + allSaCombinations.add(++i, newSA); } } - + return newSAs; } - /** - * get optional additional costs. - * - * @param original - * the original sa - * @return an ArrayList. - */ - public static List getOptionalAdditionalCosts(final SpellAbility original) { - final List abilities = new ArrayList(); - final List newAbilities = new ArrayList(); - final Card source = original.getSourceCard(); - abilities.add(original); - if (!original.isSpell()) { - return abilities; - } - - // Buyback, Kicker - for (String keyword : source.getKeyword()) { - if (keyword.startsWith("Buyback")) { - for (SpellAbility sa : abilities) { - final SpellAbility newSA = sa.copy(); - newSA.setBasicSpell(false); - newSA.setPayCosts(new Cost(keyword.substring(8), false).add(newSA.getPayCosts())); - newSA.setDescription(sa.getDescription() + " (with Buyback)"); - - ArrayList newoacs = new ArrayList(sa.getOptionalAdditionalCosts()); - newoacs.add(keyword); - newSA.setOptionalAdditionalCosts(newoacs); - if (newSA.canPlay()) { - newAbilities.add(newSA); - } - } - abilities.addAll(0, newAbilities); - newAbilities.clear(); - } else if (keyword.startsWith("Kicker")) { - for (SpellAbility sa : abilities) { - final SpellAbility newSA = sa.copy(); - newSA.setBasicSpell(false); - final Cost cost = new Cost(keyword.substring(7), false); - newSA.setDescription(sa.getDescription() + " (Kicker " + cost.toSimpleString() + ")"); - newSA.setPayCosts(cost.add(newSA.getPayCosts())); - - ArrayList newoacs = new ArrayList(sa.getOptionalAdditionalCosts()); - newoacs.add(keyword); - newSA.setOptionalAdditionalCosts(newoacs); - if (newSA.canPlay()) { - newAbilities.add(newSA); - } - } - abilities.addAll(0, newAbilities); - newAbilities.clear(); - } else if (keyword.startsWith("AlternateAdditionalCost")) { - String costString1 = keyword.split(":")[1]; - String costString2 = keyword.split(":")[2]; - for (SpellAbility sa : abilities) { - final SpellAbility newSA = sa.copy(); - newSA.setBasicSpell(false); - - final Cost cost1 = new Cost(costString1, false); - newSA.setDescription(sa.getDescription() + " (Additional cost " + cost1.toSimpleString() + ")"); - newSA.setPayCosts(cost1.add(sa.getPayCosts())); - newSA.setOptionalAdditionalCosts(new ArrayList(sa.getOptionalAdditionalCosts())); - if (newSA.canPlay()) { - newAbilities.add(newSA); - } - - //second option - final SpellAbility newSA2 = sa.copy(); - newSA2.setBasicSpell(false); - - final Cost cost2 = new Cost(costString2, false); - newSA2.setDescription(sa.getDescription() + " (Additional cost " + cost2.toSimpleString() + ")"); - newSA2.setPayCosts(cost2.add(sa.getPayCosts())); - newSA2.setOptionalAdditionalCosts(new ArrayList(sa.getOptionalAdditionalCosts())); - if (newSA2.canPlay()) { - newAbilities.add(newAbilities.size(), newSA2); - } - } - abilities.clear(); - abilities.addAll(0, newAbilities); - newAbilities.clear(); - } else if (keyword.startsWith("Conspire")) { - for (SpellAbility sa : abilities) { - final SpellAbility newSA = sa.copy(); - newSA.setBasicSpell(false); - final String conspireCost = "tapXType<2/Creature.SharesColorWith/untapped creature you control" - + " that shares a color with " + source.getName() + ">"; - newSA.setPayCosts(new Cost(conspireCost, false).add(newSA.getPayCosts())); - newSA.setDescription(sa.getDescription() + " (Conspire)"); - - ArrayList newoacs = new ArrayList(sa.getOptionalAdditionalCosts()); - newoacs.add(keyword); - newSA.setOptionalAdditionalCosts(newoacs); - if (newSA.canPlay()) { - newAbilities.add(newAbilities.size(), newSA); - } - } - abilities.addAll(0, newAbilities); - newAbilities.clear(); - } - } - - // Splice - for (SpellAbility sa : abilities) { - newAbilities.addAll(GameActionUtil.getSpliceAbilities(sa)); - } - abilities.addAll(newAbilities); - newAbilities.clear(); - - return abilities; - } - /** *

* hasUrzaLands. diff --git a/src/main/java/forge/game/ai/AiController.java b/src/main/java/forge/game/ai/AiController.java index 76a2d5d77c5..1d0ef70269b 100644 --- a/src/main/java/forge/game/ai/AiController.java +++ b/src/main/java/forge/game/ai/AiController.java @@ -173,7 +173,7 @@ public class AiController { final List result = new ArrayList(); for (SpellAbility sa : newAbilities) { sa.setActivatingPlayer(player); - result.addAll(GameActionUtil.getOptionalAdditionalCosts(sa)); + result.addAll(GameActionUtil.getOptionalCosts(sa)); } result.addAll(newAbilities); return result; diff --git a/src/main/java/forge/game/player/HumanPlayer.java b/src/main/java/forge/game/player/HumanPlayer.java index 764b73cd12d..adc06e4e0d0 100644 --- a/src/main/java/forge/game/player/HumanPlayer.java +++ b/src/main/java/forge/game/player/HumanPlayer.java @@ -217,7 +217,7 @@ public class HumanPlayer extends Player { */ public SpellAbility chooseOptionalAdditionalCosts(final SpellAbility original) { //final HashMap map = new HashMap(); - final List abilities = GameActionUtil.getOptionalAdditionalCosts(original); + final List abilities = GameActionUtil.getOptionalCosts(original); if (!original.isSpell()) { return original; diff --git a/src/main/java/forge/game/player/PlayerControllerHuman.java b/src/main/java/forge/game/player/PlayerControllerHuman.java index 0dfe4d99585..379790b7316 100644 --- a/src/main/java/forge/game/player/PlayerControllerHuman.java +++ b/src/main/java/forge/game/player/PlayerControllerHuman.java @@ -74,7 +74,7 @@ public class PlayerControllerHuman extends PlayerController { * Uses GUI to learn which spell the player (human in our case) would like to play */ public SpellAbility getAbilityToPlay(List abilities) { - if (abilities.size() == 0) { + if (abilities.isEmpty()) { return null; } else if (abilities.size() == 1) { return abilities.get(0); diff --git a/src/main/java/forge/game/zone/MagicStack.java b/src/main/java/forge/game/zone/MagicStack.java index 434e2ddd5c5..8c9c24323ef 100644 --- a/src/main/java/forge/game/zone/MagicStack.java +++ b/src/main/java/forge/game/zone/MagicStack.java @@ -36,6 +36,7 @@ import forge.card.mana.ManaCost; import forge.card.spellability.Ability; import forge.card.spellability.AbilityStatic; import forge.card.spellability.AbilityTriggered; +import forge.card.spellability.OptionalCost; import forge.card.spellability.Spell; import forge.card.spellability.SpellAbility; import forge.card.spellability.SpellAbilityStackInstance; @@ -369,10 +370,9 @@ public class MagicStack extends MyObservable { game.getAction().checkStateEffects(); //GuiDisplayUtil.updateGUI(); } else { - if (sp.getOptionalAdditionalCosts() != null) { - for (String s : sp.getOptionalAdditionalCosts()) { - sp.getSourceCard().addOptionalAdditionalCostsPaid(s); - } + for (OptionalCost s : sp.getOptionalCosts()) { + + sp.getSourceCard().addOptionalCostPaid(s); } if (sp.getSourceCard().isCopiedSpell()) { this.push(sp); @@ -384,7 +384,7 @@ public class MagicStack extends MyObservable { if (activating.isHuman()) { while(true) { - int mkMagnitude = sa.getSourceCard().getMultiKickerMagnitude(); + int mkMagnitude = sa.getSourceCard().getKickerMagnitude(); String prompt = String.format("Multikicker for %s\r\nTimes Kicked: %d\r\n", sa.getSourceCard(), mkMagnitude ); InputPayManaExecuteCommands toSet = new InputPayManaExecuteCommands(activating, prompt, sp.getMultiKickerManaCost()); FThreads.setInputAndWait(toSet);