OptionalCosts (Buyback, Kicker, AltCost) moved to EnumSet (there was a string list)

intrinsic abilities renamed to unparsed ones,
getMultiKickerMagnitude is getKickerMagnitude, it returns 1 for simple Kickers now
Kicker syntax changed,
AltCost avaiable to check on triggers
This commit is contained in:
Maxmtg
2013-04-16 17:53:51 +00:00
parent 33ade082ab
commit 44554441ea
25 changed files with 363 additions and 540 deletions

1
.gitattributes vendored
View File

@@ -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/AbilityTriggered.java svneol=native#text/plain
src/main/java/forge/card/spellability/HumanPlaySpellAbility.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/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/Spell.java svneol=native#text/plain
src/main/java/forge/card/spellability/SpellAbility.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 src/main/java/forge/card/spellability/SpellAbilityCondition.java svneol=native#text/plain

View File

@@ -22,6 +22,7 @@ import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.EnumMap; import java.util.EnumMap;
import java.util.EnumSet;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
@@ -51,6 +52,7 @@ import forge.card.replacement.ReplaceMoved;
import forge.card.replacement.ReplacementEffect; import forge.card.replacement.ReplacementEffect;
import forge.card.replacement.ReplacementResult; import forge.card.replacement.ReplacementResult;
import forge.card.spellability.AbilityTriggered; import forge.card.spellability.AbilityTriggered;
import forge.card.spellability.OptionalCost;
import forge.card.spellability.SpellAbility; import forge.card.spellability.SpellAbility;
import forge.card.spellability.SpellPermanent; import forge.card.spellability.SpellPermanent;
import forge.card.spellability.Target; import forge.card.spellability.Target;
@@ -110,7 +112,6 @@ public class Card extends GameEntity implements Comparable<Card> {
// if this card is an Aura, what Entity is it enchanting? // if this card is an Aura, what Entity is it enchanting?
private GameEntity enchanting = null; private GameEntity enchanting = null;
private ArrayList<String> optionalAdditionalCostsPaid = null;
// changes by AF animate and continuous static effects - timestamp is the key of maps // changes by AF animate and continuous static effects - timestamp is the key of maps
private Map<Long, CardType> changedCardTypes = new ConcurrentSkipListMap<Long, CardType>(); private Map<Long, CardType> changedCardTypes = new ConcurrentSkipListMap<Long, CardType>();
@@ -182,7 +183,6 @@ public class Card extends GameEntity implements Comparable<Card> {
private int xManaCostPaid = 0; private int xManaCostPaid = 0;
private int multiKickerMagnitude = 0;
private int replicateMagnitude = 0; private int replicateMagnitude = 0;
private int sunburstValue = 0; private int sunburstValue = 0;
@@ -3892,23 +3892,14 @@ public class Card extends GameEntity implements Comparable<Card> {
return this.getNetAttack(); return this.getNetAttack();
} }
/** private int multiKickerMagnitude = 0;
* <p> public final void addMultiKickerMagnitude(final int n) { this.multiKickerMagnitude += n; }
* addMultiKickerMagnitude. public final void setKickerMagnitude(final int n) { this.multiKickerMagnitude = n; }
* </p> public final int getKickerMagnitude() {
* if ( this.multiKickerMagnitude > 0 )
* @param n return multiKickerMagnitude;
* a int. boolean hasK1 = costsPaid.contains(OptionalCost.Kicker1);
*/ return hasK1 == costsPaid.contains(OptionalCost.Kicker2) ? (hasK1 ? 2 : 0) : 1;
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;
} }
/** /**
@@ -4290,8 +4281,8 @@ public class Card extends GameEntity implements Comparable<Card> {
* *
* @return a {@link java.util.ArrayList} object. * @return a {@link java.util.ArrayList} object.
*/ */
public final ArrayList<String> getIntrinsicAbilities() { public final List<String> getUnparsedAbilities() {
return this.getCharacteristics().getIntrinsicAbility(); return this.getCharacteristics().getUnparsedAbilities();
} }
/** /**
@@ -4328,8 +4319,8 @@ public class Card extends GameEntity implements Comparable<Card> {
* @param a * @param a
* a {@link java.util.ArrayList} object. * a {@link java.util.ArrayList} object.
*/ */
public final void setIntrinsicAbilities(final ArrayList<String> a) { public final void setIntrinsicAbilities(final List<String> a) {
this.getCharacteristics().setIntrinsicAbility(new ArrayList<String>(a)); this.getCharacteristics().setUnparsedAbilities(new ArrayList<String>(a));
} }
/** /**
@@ -4358,7 +4349,7 @@ public class Card extends GameEntity implements Comparable<Card> {
*/ */
public final void addIntrinsicAbility(final String s) { public final void addIntrinsicAbility(final String s) {
if (s.trim().length() != 0) { 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<Card> {
this.suspendCast = b; this.suspendCast = b;
} }
/**
* <p>
* optionalAdditionalCostsPaid.
* </p>
*
* @param cost
* a String.
*/
public final void addOptionalAdditionalCostsPaid(final String cost) {
if (optionalAdditionalCostsPaid == null) {
optionalAdditionalCostsPaid = new ArrayList<String>();
}
this.optionalAdditionalCostsPaid.add(cost);
}
/**
* <p>
* optionalAdditionalCostsPaid.
* </p>
*/
public final void clearAdditionalCostsPaid() {
if (optionalAdditionalCostsPaid != null) {
optionalAdditionalCostsPaid.clear();
}
}
/**
* <p>
* isOptionalAdditionalCostsPaid.
* </p>
*
* @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;
}
/**
* <p>
* isOptionalAdditionalCostsPaid.
* </p>
* @return an ArrayList<String>.
*
*/
public final ArrayList<String> getOptionalAdditionalCostsPaid() {
return this.optionalAdditionalCostsPaid;
}
/** /**
* Checks if is phased out. * Checks if is phased out.
* *
@@ -6369,17 +6302,16 @@ public class Card extends GameEntity implements Comparable<Card> {
} }
} else if (property.startsWith("kicked")) { } else if (property.startsWith("kicked")) {
if (property.equals("kicked")) { if (property.equals("kicked")) {
if (!this.isOptionalAdditionalCostsPaid("Kicker")) { if (this.getKickerMagnitude() == 0) {
return false; return false;
} }
} else { } else {
String s = "Kicker " + property.split("kicked ")[1]; String s = "Kicker " + property.split("kicked ")[1];
if (!this.isOptionalAdditionalCostsPaid(s)) { if ("1".equals(s) && !this.isOptionalCostPaid(OptionalCost.Kicker1)) return false;
return false; if ("2".equals(s) && !this.isOptionalCostPaid(OptionalCost.Kicker2)) return false;
}
} }
} else if (property.startsWith("notkicked")) { } else if (property.startsWith("notkicked")) {
if (this.isOptionalAdditionalCostsPaid("Kicker")) { if (this.getKickerMagnitude() > 0) {
return false; return false;
} }
} else if (property.startsWith("evoked")) { } else if (property.startsWith("evoked")) {
@@ -8317,15 +8249,13 @@ public class Card extends GameEntity implements Comparable<Card> {
public void setSplitStateToPlayAbility(SpellAbility sa) { public void setSplitStateToPlayAbility(SpellAbility sa) {
if( !isSplitCard() ) return; // just in case if( !isSplitCard() ) return; // just in case
// Split card support // Split card support
List<SpellAbility> leftSplitAbilities = getState(CardCharacteristicName.LeftSplit).getSpellAbility(); for (SpellAbility a : getState(CardCharacteristicName.LeftSplit).getSpellAbility()) {
List<SpellAbility> rightSplitAbilities = getState(CardCharacteristicName.RightSplit).getSpellAbility();
for (SpellAbility a : leftSplitAbilities) {
if (sa == a || sa.getDescription().equals(String.format("%s (without paying its mana cost)", a.getDescription()))) { if (sa == a || sa.getDescription().equals(String.format("%s (without paying its mana cost)", a.getDescription()))) {
setState(CardCharacteristicName.LeftSplit); setState(CardCharacteristicName.LeftSplit);
return; 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()))) { if (sa == a || sa.getDescription().equals(String.format("%s (without paying its mana cost)", a.getDescription()))) {
setState(CardCharacteristicName.RightSplit); setState(CardCharacteristicName.RightSplit);
return; return;
@@ -8337,4 +8267,11 @@ public class Card extends GameEntity implements Comparable<Card> {
throw new RuntimeException("Not found which part to choose for ability " + sa + " from card " + this); throw new RuntimeException("Not found which part to choose for ability " + sa + " from card " + this);
} }
// Optional costs paid
private final EnumSet<OptionalCost> costsPaid = EnumSet.noneOf(OptionalCost.class);
public void clearOptionalCostsPaid() { costsPaid.clear(); }
public void addOptionalCostPaid(OptionalCost cost) { costsPaid.add(cost); }
public Iterable<OptionalCost> getOptionalCostsPaid() { return costsPaid; }
public boolean isOptionalCostPaid(OptionalCost cost) { return costsPaid.contains(cost); }
} // end Card class } // end Card class

View File

@@ -46,7 +46,7 @@ public class CardCharacteristics {
private ArrayList<String> intrinsicKeyword = new ArrayList<String>(); private ArrayList<String> intrinsicKeyword = new ArrayList<String>();
private final List<SpellAbility> spellAbility = new ArrayList<SpellAbility>(); private final List<SpellAbility> spellAbility = new ArrayList<SpellAbility>();
private final List<SpellAbility> manaAbility = new ArrayList<SpellAbility>(); private final List<SpellAbility> manaAbility = new ArrayList<SpellAbility>();
private ArrayList<String> intrinsicAbility = new ArrayList<String>(); private List<String> unparsedAbilities = new ArrayList<String>();
private ArrayList<Trigger> triggers = new ArrayList<Trigger>(); private ArrayList<Trigger> triggers = new ArrayList<Trigger>();
private ArrayList<ReplacementEffect> replacementEffects = new ArrayList<ReplacementEffect>(); private ArrayList<ReplacementEffect> replacementEffects = new ArrayList<ReplacementEffect>();
private ArrayList<StaticAbility> staticAbilities = new ArrayList<StaticAbility>(); private ArrayList<StaticAbility> staticAbilities = new ArrayList<StaticAbility>();
@@ -215,18 +215,18 @@ public class CardCharacteristics {
* *
* @return the intrinsicAbility * @return the intrinsicAbility
*/ */
public final ArrayList<String> getIntrinsicAbility() { public final List<String> getUnparsedAbilities() {
return this.intrinsicAbility; return this.unparsedAbilities;
} }
/** /**
* Sets the intrinsic ability. * Sets the intrinsic ability.
* *
* @param intrinsicAbility0 * @param list
* the intrinsicAbility to set * the intrinsicAbility to set
*/ */
public final void setIntrinsicAbility(final ArrayList<String> intrinsicAbility0) { public final void setUnparsedAbilities(final List<String> list) {
this.intrinsicAbility = intrinsicAbility0; this.unparsedAbilities = list;
} }
/** /**
@@ -408,7 +408,7 @@ public class CardCharacteristics {
// ArrayList<String> intrinsicKeyword : list of String objects so use copy constructor // ArrayList<String> intrinsicKeyword : list of String objects so use copy constructor
this.intrinsicKeyword = new ArrayList<String>(source.getIntrinsicKeyword()); this.intrinsicKeyword = new ArrayList<String>(source.getIntrinsicKeyword());
// ArrayList<String> intrinsicAbility : list of String objects so use copy constructor // ArrayList<String> intrinsicAbility : list of String objects so use copy constructor
this.intrinsicAbility = new ArrayList<String>(source.getIntrinsicAbility()); this.unparsedAbilities = new ArrayList<String>(source.getUnparsedAbilities());
// ArrayList<String> staticAbilityStrings : list of String objects so use copy constructor // ArrayList<String> staticAbilityStrings : list of String objects so use copy constructor
this.staticAbilityStrings = new ArrayList<String>(source.getStaticAbilityStrings()); this.staticAbilityStrings = new ArrayList<String>(source.getStaticAbilityStrings());
// String imageFilename = copy reference // String imageFilename = copy reference

View File

@@ -121,7 +121,7 @@ public final class AbilityFactory {
return abCost; return abCost;
} }
private static final SpellAbility getAbility(AbilityRecordType type, ApiType api, Map<String, String> mapParams, Cost abCost, Card hostCard) { public static final SpellAbility getAbility(AbilityRecordType type, ApiType api, Map<String, String> mapParams, Cost abCost, Card hostCard) {
Target abTgt = mapParams.containsKey("ValidTgts") ? readTarget(hostCard, mapParams) : null; Target abTgt = mapParams.containsKey("ValidTgts") ? readTarget(hostCard, mapParams) : null;
@@ -350,7 +350,7 @@ public final class AbilityFactory {
if(!card.isSplitCard()) if(!card.isSplitCard())
throw new IllegalStateException("Fuse ability may be built only on split cards"); 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<String, String> leftMap = getMapParams(strLeftAbility); Map<String, String> leftMap = getMapParams(strLeftAbility);
AbilityRecordType leftType = AbilityRecordType.getRecordType(leftMap); AbilityRecordType leftType = AbilityRecordType.getRecordType(leftMap);
ApiType leftApi = leftType.getApiTypeOf(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("SpellDescription", "Fuse (you may cast both halves of this card from your hand).");
leftMap.put("ActivationZone", "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<String, String> rightMap = getMapParams(strRightAbility); Map<String, String> rightMap = getMapParams(strRightAbility);
AbilityRecordType rightType = AbilityRecordType.getRecordType(leftMap); AbilityRecordType rightType = AbilityRecordType.getRecordType(leftMap);
ApiType rightApi = leftType.getApiTypeOf(rightMap); ApiType rightApi = leftType.getApiTypeOf(rightMap);

View File

@@ -248,7 +248,7 @@ public class CloneEffect extends SpellAbilityEffect {
final String actualAbility = origSVars.get(s); final String actualAbility = origSVars.get(s);
// final SpellAbility grantedAbility = newAF.getAbility(actualAbility, tgtCard); // final SpellAbility grantedAbility = newAF.getAbility(actualAbility, tgtCard);
// tgtCard.addSpellAbility(grantedAbility); // tgtCard.addSpellAbility(grantedAbility);
tgtCard.getIntrinsicAbilities().add(actualAbility); tgtCard.getUnparsedAbilities().add(actualAbility);
} }
} }
} }

View File

@@ -227,7 +227,7 @@ public class TokenEffect extends SpellAbilityEffect {
final SpellAbility grantedAbility = AbilityFactory.getAbility(actualAbility, c); final SpellAbility grantedAbility = AbilityFactory.getAbility(actualAbility, c);
c.addSpellAbility(grantedAbility); c.addSpellAbility(grantedAbility);
// added ability to intrinsic list so copies and clones work // added ability to intrinsic list so copies and clones work
c.getIntrinsicAbilities().add(actualAbility); c.getUnparsedAbilities().add(actualAbility);
} }
} }
} }

View File

@@ -40,6 +40,7 @@ import forge.card.mana.ManaCost;
import forge.card.replacement.ReplacementHandler; import forge.card.replacement.ReplacementHandler;
import forge.card.spellability.AbilityActivated; import forge.card.spellability.AbilityActivated;
import forge.card.spellability.AbilitySub; import forge.card.spellability.AbilitySub;
import forge.card.spellability.OptionalCost;
import forge.card.spellability.SpellAbility; import forge.card.spellability.SpellAbility;
import forge.card.spellability.SpellPermanent; import forge.card.spellability.SpellPermanent;
import forge.card.spellability.Target; import forge.card.spellability.Target;
@@ -191,11 +192,10 @@ public class CardFactory {
if (bCopyDetails) { if (bCopyDetails) {
c.addXManaCostPaid(original.getXManaCostPaid()); c.addXManaCostPaid(original.getXManaCostPaid());
c.addMultiKickerMagnitude(original.getMultiKickerMagnitude()); c.setKickerMagnitude(original.getKickerMagnitude());
if (original.getOptionalAdditionalCostsPaid() != null) {
for (String cost : original.getOptionalAdditionalCostsPaid()) { for (OptionalCost cost : original.getOptionalCostsPaid()) {
c.addOptionalAdditionalCostsPaid(cost); c.addOptionalCostPaid(cost);
}
} }
c.addReplicateMagnitude(original.getReplicateMagnitude()); c.addReplicateMagnitude(original.getReplicateMagnitude());
if (sa.isReplicate()) { if (sa.isReplicate()) {
@@ -488,7 +488,7 @@ public class CardFactory {
to.setManaCost(from.getManaCost()); to.setManaCost(from.getManaCost());
to.setColor(from.getColor()); to.setColor(from.getColor());
to.setSVars(from.getSVars()); to.setSVars(from.getSVars());
to.setIntrinsicAbilities(from.getIntrinsicAbilities()); to.setIntrinsicAbilities(from.getUnparsedAbilities());
to.setImageKey(from.getImageKey()); to.setImageKey(from.getImageKey());
to.setTriggers(from.getTriggers()); to.setTriggers(from.getTriggers());

View File

@@ -55,6 +55,7 @@ import forge.card.spellability.Ability;
import forge.card.spellability.AbilityActivated; import forge.card.spellability.AbilityActivated;
import forge.card.spellability.AbilityStatic; import forge.card.spellability.AbilityStatic;
import forge.card.spellability.AbilitySub; import forge.card.spellability.AbilitySub;
import forge.card.spellability.OptionalCost;
import forge.card.spellability.Spell; import forge.card.spellability.Spell;
import forge.card.spellability.SpellAbility; import forge.card.spellability.SpellAbility;
import forge.card.spellability.SpellAbilityRestriction; import forge.card.spellability.SpellAbilityRestriction;
@@ -1556,11 +1557,8 @@ public class CardFactoryUtil {
} }
} }
if (sq[0].startsWith("Kicked")) { if (sq[0].startsWith("Kicked")) {
if (c.isOptionalAdditionalCostsPaid("Kicker")) { int ix = c.getKickerMagnitude() > 0 ? 1 : 2;
return CardFactoryUtil.doXMath(Integer.parseInt(sq[1]), m, c); return CardFactoryUtil.doXMath(Integer.parseInt(sq[ix]), m, c);
} else {
return CardFactoryUtil.doXMath(Integer.parseInt(sq[2]), m, c);
}
} }
if (sq[0].contains("GraveyardWithGE20Cards")) { if (sq[0].contains("GraveyardWithGE20Cards")) {
@@ -1660,7 +1658,7 @@ public class CardFactoryUtil {
// Count$TimesKicked // Count$TimesKicked
if (sq[0].contains("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")) { if (sq[0].contains("NumCounters")) {
final int num = c.getCounters(CounterType.getType(sq[1])); final int num = c.getCounters(CounterType.getType(sq[1]));
@@ -2370,14 +2368,8 @@ public class CardFactoryUtil {
public static final void addAbilityFactoryAbilities(final Card card) { public static final void addAbilityFactoryAbilities(final Card card) {
// ************************************************** // **************************************************
// AbilityFactory cards // AbilityFactory cards
final ArrayList<String> ia = card.getIntrinsicAbilities(); for (String rawAbility : card.getUnparsedAbilities()) {
for (int i = 0; i < ia.size(); i++) { card.addSpellAbility(AbilityFactory.getAbility(rawAbility, card));
// System.out.println(cardName);
final SpellAbility sa = AbilityFactory.getAbility(ia.get(i), card);
if (sa.hasParam("SetAsKicked")) {
sa.addOptionalAdditionalCosts("Kicker");
}
card.addSpellAbility(sa);
} }
} }
@@ -2601,10 +2593,11 @@ public class CardFactoryUtil {
} }
// AltCost // AltCost
if (!card.getSVar("AltCost").equals("")) { String altCost = card.getSVar("AltCost");
if (StringUtils.isNotBlank(altCost)) {
final SpellAbility sa1 = card.getFirstSpellAbility(); final SpellAbility sa1 = card.getFirstSpellAbility();
if (sa1 != null && sa1.isSpell()) { 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); final SpellAbility sa = AbilityFactory.getAbility(abilityStr.toString(), card);
card.addSpellAbility(sa); card.addSpellAbility(sa);
// add ability to instrinic strings so copies/clones create the ability also // add ability to instrinic strings so copies/clones create the ability also
card.getIntrinsicAbilities().add(abilityStr.toString()); card.getUnparsedAbilities().add(abilityStr.toString());
} }
setupEtbKeywords(card); setupEtbKeywords(card);
@@ -3066,63 +3059,26 @@ public class CardFactoryUtil {
* @param abilities * @param abilities
* @return * @return
*/ */
private static SpellAbility makeAltCost(final Card card, final SpellAbility sa) { private static SpellAbility makeAltCostAbility(final Card card, final String altCost, final SpellAbility sa) {
String altCost = card.getSVar("AltCost"); final Map<String, String> params = AbilityFactory.getMapParams(altCost);
final HashMap<String, String> mapParams = new HashMap<String, String>();
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");
}
final SpellAbility altCostSA = sa.copy(); final SpellAbility altCostSA = sa.copy();
final Cost abCost = new Cost(params.get("Cost"), altCostSA.isAbility());
final Cost abCost = new Cost(altCost, altCostSA.isAbility());
altCostSA.setPayCosts(abCost); altCostSA.setPayCosts(abCost);
altCostSA.setBasicSpell(false);
final StringBuilder sb = new StringBuilder(); altCostSA.addOptionalCost(OptionalCost.AltCost);
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.");
}
final SpellAbilityRestriction restriction = new SpellAbilityRestriction(); final SpellAbilityRestriction restriction = new SpellAbilityRestriction();
restriction.setRestrictions(mapParams); restriction.setRestrictions(params);
if (!mapParams.containsKey("ActivationZone")) { if (!params.containsKey("ActivationZone")) {
restriction.setZone(ZoneType.Hand); restriction.setZone(ZoneType.Hand);
} }
altCostSA.setRestrictions(restriction); 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; return altCostSA;
} }
@@ -3186,14 +3142,7 @@ public class CardFactoryUtil {
* @return a int. * @return a int.
*/ */
public static final int hasKeyword(final Card c, final String k) { public static final int hasKeyword(final Card c, final String k) {
final List<String> a = c.getKeyword(); return hasKeyword(c, k, 0);
for (int i = 0; i < a.size(); i++) {
if (a.get(i).startsWith(k)) {
return i;
}
}
return -1;
} }
/** /**
@@ -3209,7 +3158,7 @@ public class CardFactoryUtil {
* a int. * a int.
* @return 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<String> a = c.getKeyword(); final List<String> a = c.getKeyword();
for (int i = startPos; i < a.size(); i++) { for (int i = startPos; i < a.size(); i++) {
if (a.get(i).startsWith(k)) { if (a.get(i).startsWith(k)) {

View File

@@ -128,7 +128,7 @@ public class CostPartMana extends CostPart {
toPay.increaseShard(ManaCostShard.valueOf(xColor), xCost); toPay.increaseShard(ManaCostShard.valueOf(xColor), xCost);
xWasBilled = true; xWasBilled = true;
} }
int timesMultikicked = ability.getSourceCard().getMultiKickerMagnitude(); int timesMultikicked = ability.getSourceCard().getKickerMagnitude();
if ( timesMultikicked > 0 && ability.isAnnouncing("Multikicker")) { if ( timesMultikicked > 0 && ability.isAnnouncing("Multikicker")) {
ManaCost mkCost = ability.getMultiKickerManaCost(); ManaCost mkCost = ability.getMultiKickerManaCost();
for(int i = 0; i < timesMultikicked; i++) for(int i = 0; i < timesMultikicked; i++)

View File

@@ -168,7 +168,7 @@ public class HumanPlaySpellAbility {
ability.setSVar(varName, value.toString()); ability.setSVar(varName, value.toString());
if( "Multikicker".equals(varName) ) { if( "Multikicker".equals(varName) ) {
ability.getSourceCard().setMultiKickerMagnitude(value); ability.getSourceCard().setKickerMagnitude(value);
} else { } else {
ability.getSourceCard().setSVar(varName, value.toString()); ability.getSourceCard().setSVar(varName, value.toString());
} }

View File

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

View File

@@ -18,6 +18,7 @@
package forge.card.spellability; package forge.card.spellability;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.EnumSet;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@@ -92,7 +93,6 @@ public abstract class SpellAbility implements ISpellAbility {
private final ArrayList<Mana> payingMana = new ArrayList<Mana>(); private final ArrayList<Mana> payingMana = new ArrayList<Mana>();
private final List<SpellAbility> paidAbilities = new ArrayList<SpellAbility>(); private final List<SpellAbility> paidAbilities = new ArrayList<SpellAbility>();
private List<String> optionalAdditionalCosts = new ArrayList<String>();
private HashMap<String, List<Card>> paidLists = new HashMap<String, List<Card>>(); private HashMap<String, List<Card>> paidLists = new HashMap<String, List<Card>>();
@@ -109,7 +109,6 @@ public abstract class SpellAbility implements ISpellAbility {
private boolean undoable; private boolean undoable;
private boolean isCopied = false; private boolean isCopied = false;
private boolean isAltCost = false;
public final AbilityManaPart getManaPart() { public final AbilityManaPart getManaPart() {
return manaPart; return manaPart;
@@ -302,43 +301,6 @@ public abstract class SpellAbility implements ISpellAbility {
public boolean isSpell() { return false; } public boolean isSpell() { return false; }
public boolean isAbility() { return true; } public boolean isAbility() { return true; }
/**
* <p>
* isBuyBackAbility.
* </p>
*
* @return a boolean.
*/
public boolean isBuyBackAbility() {
return this.optionalAdditionalCosts.contains("Buyback");
}
/**
* <p>
* isKicked.
* </p>
*
* @return a boolean.
*/
public boolean isKicked() {
return isOptionalAdditionalCostPaid("Kicker");
}
/**
* <p>
* isOptionalAdditionalCostPaid.
* </p>
*
* @return a boolean.
*/
public boolean isOptionalAdditionalCostPaid(String cost) {
for (String s : this.optionalAdditionalCosts) {
if (s.startsWith(cost)) {
return true;
}
}
return false;
}
/** /**
* <p> * <p>
@@ -668,25 +630,35 @@ public abstract class SpellAbility implements ISpellAbility {
this.paidLists = new HashMap<String, List<Card>>(); this.paidLists = new HashMap<String, List<Card>>();
} }
private EnumSet<OptionalCost> optionalCosts = EnumSet.noneOf(OptionalCost.class);
/** /**
* @return the optionalAdditionalCosts * @return the optionalAdditionalCosts
*/ */
public List<String> getOptionalAdditionalCosts() { public Iterable<OptionalCost> getOptionalCosts() {
return optionalAdditionalCosts; return optionalCosts;
}
/**
* @param costs the optionalAdditionalCosts to set
*/
public final void setOptionalAdditionalCosts(List<String> costs) {
this.optionalAdditionalCosts = costs;
} }
/** /**
* @param cost the optionalAdditionalCost to add * @param cost the optionalAdditionalCost to add
*/ */
public final void addOptionalAdditionalCosts(String cost) { public final void addOptionalCost(OptionalCost cost) {
this.optionalAdditionalCosts.add(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);
} }
/** /**
@@ -1724,22 +1696,4 @@ public abstract class SpellAbility implements ISpellAbility {
CostPartMana cm = payCosts != null ? getPayCosts().getCostMana() : null; CostPartMana cm = payCosts != null ? getPayCosts().getCostMana() : null;
return cm != null && cm.getAmountOfX() > 0; 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;
}
} }

View File

@@ -78,11 +78,19 @@ public class SpellAbilityCondition extends SpellAbilityVariables {
this.setHellbent(true); this.setHellbent(true);
} }
if (value.equals("Kicked")) { 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")) { if (value.equals("AllTargetsLegal")) {
this.setAllTargetsLegal(true); this.setAllTargetsLegal(true);
} }
if (value.equals("AltCost"))
this.altCostPaid = true;
} }
if (params.containsKey("ConditionZone")) { if (params.containsKey("ConditionZone")) {
@@ -177,27 +185,15 @@ public class SpellAbilityCondition extends SpellAbilityVariables {
+ " Did not have activator set in SpellAbility_Condition.checkConditions()"); + " Did not have activator set in SpellAbility_Condition.checkConditions()");
} }
if (this.isHellbent()) { if (this.isHellbent() && !activator.hasHellbent()) return false;
if (!activator.hasHellbent()) { if (this.isThreshold() && !activator.hasThreshold()) return false;
return false; if (this.isMetalcraft() && !activator.hasMetalcraft()) return false;
}
} if (this.kicked && !sa.isKicked()) return false;
if (this.isThreshold()) { if (this.kicked1 && !sa.isOptionalCostPaid(OptionalCost.Kicker1)) return false;
if (!activator.hasThreshold()) { if (this.kicked2 && !sa.isOptionalCostPaid(OptionalCost.Kicker2)) return false;
return false; if( this.altCostPaid && !sa.isOptionalCostPaid(OptionalCost.AltCost)) return false;
}
}
if (this.isMetalcraft()) {
if (!activator.hasMetalcraft()) {
return false;
}
}
if (this.isKicked()) {
SpellAbility root = sa.getRootAbility();
if (!root.isKicked()) {
return false;
}
}
if (this.isAllTargetsLegal()) { if (this.isAllTargetsLegal()) {
for (Card c : sa.getTarget().getTargetCards()) { for (Card c : sa.getTarget().getTargetCards()) {
if (!CardFactoryUtil.isTargetStillValid(sa, c)) { if (!CardFactoryUtil.isTargetStillValid(sa, c)) {
@@ -210,11 +206,11 @@ public class SpellAbilityCondition extends SpellAbilityVariables {
return false; return false;
} }
if (this.isPlayerTurn() && !Singletons.getModel().getGame().getPhaseHandler().isPlayerTurn(activator)) { if (this.isPlayerTurn() && !activator.getGame().getPhaseHandler().isPlayerTurn(activator)) {
return false; return false;
} }
if (this.isOpponentTurn() && !Singletons.getModel().getGame().getPhaseHandler().getPlayerTurn().isOpponentOf(activator)) { if (this.isOpponentTurn() && !activator.getGame().getPhaseHandler().getPlayerTurn().isOpponentOf(activator)) {
return false; return false;
} }

View File

@@ -85,7 +85,7 @@ public class SpellAbilityRestriction extends SpellAbilityVariables {
if (value.split("Prowl").length > 1) { if (value.split("Prowl").length > 1) {
prowlTypes.add(value.split("Prowl")[1]); prowlTypes.add(value.split("Prowl")[1]);
} }
this.setProwl(prowlTypes); this.setProwlTypes(prowlTypes);
} }
} }
@@ -352,11 +352,11 @@ public class SpellAbilityRestriction extends SpellAbilityVariables {
return false; 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 // only true if the activating player has damaged the opponent with
// one of the specified types // one of the specified types
boolean prowlFlag = false; boolean prowlFlag = false;
for (final String type : this.getProwl()) { for (final String type : this.getProwlTypes()) {
if (activator.hasProwl(type)) { if (activator.hasProwl(type)) {
prowlFlag = true; prowlFlag = true;
} }

View File

@@ -70,7 +70,7 @@ public class SpellAbilityVariables {
this.metalcraft = sav.isThreshold(); this.metalcraft = sav.isThreshold();
this.hellbent = sav.isHellbent(); this.hellbent = sav.isHellbent();
this.allTargetsLegal = sav.isAllTargetsLegal(); this.allTargetsLegal = sav.isAllTargetsLegal();
this.prowl = new ArrayList<String>(sav.getProwl()); this.prowlTypes = new ArrayList<String>(sav.getProwlTypes());
this.isPresent = sav.getIsPresent(); this.isPresent = sav.getIsPresent();
this.presentCompare = sav.getPresentCompare(); this.presentCompare = sav.getPresentCompare();
this.presentDefined = sav.getPresentDefined(); this.presentDefined = sav.getPresentDefined();
@@ -135,13 +135,10 @@ public class SpellAbilityVariables {
/** The hellbent. */ /** The hellbent. */
private boolean hellbent = false; private boolean hellbent = false;
/** The Kicked. */
private boolean kicked = false;
private boolean allTargetsLegal = false; private boolean allTargetsLegal = false;
/** The prowl. */ /** The prowl. */
private ArrayList<String> prowl = new ArrayList<String>(); private ArrayList<String> prowlTypes = new ArrayList<String>();
/** The s is present. */ /** The s is present. */
private String isPresent = null; private String isPresent = null;
@@ -186,6 +183,8 @@ public class SpellAbilityVariables {
/** The chosen colors string. */ /** The chosen colors string. */
private String chosenColors = null; private String chosenColors = null;
/** /**
* <p> * <p>
* Setter for the field <code>notAllM12Empires</code>. * Setter for the field <code>notAllM12Empires</code>.
@@ -507,20 +506,11 @@ public class SpellAbilityVariables {
this.metalcraft = bMetalcraft; this.metalcraft = bMetalcraft;
} }
/** /** The Kicked. */
* @return 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
public boolean isKicked() { protected boolean kicked2 = false; // Some spells have 2 kickers with different effects
return kicked; protected boolean altCostPaid = false;
}
/**
* @param kicked the kicked to set
*/
public void setKicked(boolean kicked) {
this.kicked = kicked;
}
/** /**
* @return the allTargetsLegal * @return the allTargetsLegal
@@ -545,8 +535,8 @@ public class SpellAbilityVariables {
* @param types * @param types
* the new prowl * the new prowl
*/ */
public final void setProwl(final ArrayList<String> types) { public final void setProwlTypes(final ArrayList<String> types) {
this.prowl = types; this.prowlTypes = types;
} }
// IsPresent for Valid battlefield stuff // IsPresent for Valid battlefield stuff
@@ -760,8 +750,8 @@ public class SpellAbilityVariables {
* *
* @return the prowl * @return the prowl
*/ */
public final ArrayList<String> getProwl() { public final ArrayList<String> getProwlTypes() {
return this.prowl; return this.prowlTypes;
} }
/** /**

View File

@@ -238,25 +238,13 @@ public class SpellPermanent extends Spell {
continue; continue;
} }
} else if (params.get("ValidCard").contains("kicked")) { } else if (params.get("ValidCard").contains("kicked")) {
if (!params.get("ValidCard").contains("kicked ")) { if (params.get("ValidCard").contains("kicked ")) { // want a specific kicker
if (!sa.isKicked()) { String s = params.get("ValidCard").split("kicked ")[1];
continue; if ( "1".equals(s) && !sa.isOptionalCostPaid(OptionalCost.Kicker1)) continue;
} if ( "2".equals(s) && !sa.isOptionalCostPaid(OptionalCost.Kicker2)) continue;
} else { } else if (!sa.isKicked()) {
String s = "Kicker " + params.get("ValidCard").split("kicked ")[1]; continue;
boolean rightKicker = false;
if (sa.getOptionalAdditionalCosts() != null) {
for (String string : sa.getOptionalAdditionalCosts()) {
if (string.startsWith(s)) {
rightKicker = true;
}
}
}
if (!rightKicker) {
continue;
}
} }
} }
} }
@@ -335,23 +323,12 @@ public class SpellPermanent extends Spell {
continue; continue;
} }
} else if (params.get("ValidCard").contains("kicked")) { } else if (params.get("ValidCard").contains("kicked")) {
if (!params.get("ValidCard").contains("kicked ")) { if (params.get("ValidCard").contains("kicked ")) { // want a specific kicker
if (!sa.isKicked()) { String s = params.get("ValidCard").split("kicked ")[1];
continue; if ( "1".equals(s) && !sa.isOptionalCostPaid(OptionalCost.Kicker1)) continue;
} if ( "2".equals(s) && !sa.isOptionalCostPaid(OptionalCost.Kicker2)) continue;
} else { } else if (!sa.isKicked()) { // otherwise just any must be present
String s = "Kicker " + params.get("ValidCard").split("kicked ")[1]; continue;
boolean rightKicker = false;
if (sa.getOptionalAdditionalCosts() != null) {
for (String string : sa.getOptionalAdditionalCosts()) {
if (string.startsWith(s)) {
rightKicker = true;
}
}
}
if (!rightKicker) {
continue;
}
} }
} }
} }

View File

@@ -497,28 +497,21 @@ public class StaticAbility {
} }
} }
if (params.containsKey("Condition")) { String condition = params.get("Condition");
if (params.get("Condition").equals("Threshold")) { if (null != condition) {
if (!controller.hasThreshold()) { 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; return false;
} }
} else if (params.get("Condition").equals("Hellbent")) { } else if (condition.equals("NotPlayerTurn")) {
if (!controller.hasHellbent()) { if (controller.getGame().getPhaseHandler().isPlayerTurn(controller)) {
return false; return false;
} }
} else if (params.get("Condition").equals("Metalcraft")) { } else if (condition.equals("PermanentOfEachColor")) {
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")) {
if ((controller.getColoredCardsInPlay(Constant.Color.BLACK).isEmpty() if ((controller.getColoredCardsInPlay(Constant.Color.BLACK).isEmpty()
|| controller.getColoredCardsInPlay(Constant.Color.BLUE).isEmpty() || controller.getColoredCardsInPlay(Constant.Color.BLUE).isEmpty()
|| controller.getColoredCardsInPlay(Constant.Color.GREEN).isEmpty() || controller.getColoredCardsInPlay(Constant.Color.GREEN).isEmpty()
@@ -526,7 +519,7 @@ public class StaticAbility {
|| controller.getColoredCardsInPlay(Constant.Color.WHITE).isEmpty())) { || controller.getColoredCardsInPlay(Constant.Color.WHITE).isEmpty())) {
return false; return false;
} }
} else if (params.get("Condition").equals("FatefulHour")) { } else if (condition.equals("FatefulHour")) {
if (controller.getLife() > 5) { if (controller.getLife() > 5) {
return false; return false;
} }

View File

@@ -26,6 +26,7 @@ import forge.Card;
import forge.Singletons; import forge.Singletons;
import forge.card.TriggerReplacementBase; import forge.card.TriggerReplacementBase;
import forge.card.spellability.Ability; import forge.card.spellability.Ability;
import forge.card.spellability.OptionalCost;
import forge.card.spellability.SpellAbility; import forge.card.spellability.SpellAbility;
import forge.game.phase.PhaseHandler; import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType; import forge.game.phase.PhaseType;
@@ -315,23 +316,29 @@ public abstract class Trigger extends TriggerReplacementBase {
if ( !meetsCommonRequirements(getMapParams())) if ( !meetsCommonRequirements(getMapParams()))
return false; return false;
if (this.getMapParams().containsKey("EvolveCondition")) { if ("True".equals(getMapParams().get("EvolveCondition"))) {
if (this.getMapParams().get("EvolveCondition").equals("True")) { final Card moved = (Card) runParams2.get("Card");
final Card moved = (Card) runParams2.get("Card"); if (moved == null) {
if (moved == null) { return false;
return false; // final StringBuilder sb = new StringBuilder();
// final StringBuilder sb = new StringBuilder(); // sb.append("Trigger::requirementsCheck() - EvolveCondition condition being checked without a moved card. ");
// sb.append("Trigger::requirementsCheck() - EvolveCondition condition being checked without a moved card. "); // sb.append(this.getHostCard().getName());
// sb.append(this.getHostCard().getName()); // throw new RuntimeException(sb.toString());
// throw new RuntimeException(sb.toString()); }
} if (moved.getNetAttack() <= this.getHostCard().getNetAttack()
if (moved.getNetAttack() <= this.getHostCard().getNetAttack() && moved.getNetDefense() <= this.getHostCard().getNetDefense()) {
&& moved.getNetDefense() <= this.getHostCard().getNetDefense()) { return false;
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; return true;
} }

View File

@@ -20,6 +20,7 @@ package forge.card.trigger;
import forge.Card; import forge.Card;
import forge.Singletons; import forge.Singletons;
import forge.card.cost.Cost; import forge.card.cost.Cost;
import forge.card.spellability.OptionalCost;
import forge.card.spellability.SpellAbility; import forge.card.spellability.SpellAbility;
import forge.card.spellability.SpellAbilityStackInstance; import forge.card.spellability.SpellAbilityStackInstance;
import forge.game.player.Player; import forge.game.player.Player;
@@ -76,7 +77,7 @@ public class TriggerSpellAbilityCast extends Trigger {
} }
if (this.getMapParams().containsKey("AltCostSpellAbility")) { if (this.getMapParams().containsKey("AltCostSpellAbility")) {
if (!spellAbility.isAltCost()) { if (!spellAbility.isOptionalCostPaid(OptionalCost.AltCost)) {
return false; return false;
} }
} }
@@ -157,7 +158,7 @@ public class TriggerSpellAbilityCast extends Trigger {
} }
if (this.getMapParams().containsKey("Conspire")) { if (this.getMapParams().containsKey("Conspire")) {
if (!spellAbility.isOptionalAdditionalCostPaid("Conspire")) { if (!spellAbility.isOptionalCostPaid(OptionalCost.Conspire)) {
return false; return false;
} }
} }

View File

@@ -291,7 +291,7 @@ public class GameAction {
} }
} else if (zoneFrom.is(ZoneType.Exile) && !zoneTo.is(ZoneType.Battlefield)) { } else if (zoneFrom.is(ZoneType.Exile) && !zoneTo.is(ZoneType.Battlefield)) {
// Pull from Eternity used on a suspended card // Pull from Eternity used on a suspended card
copied.clearAdditionalCostsPaid(); copied.clearOptionalCostsPaid();
if (copied.isFaceDown()) { if (copied.isFaceDown()) {
copied.turnFaceUp(); copied.turnFaceUp();
} }
@@ -315,7 +315,7 @@ public class GameAction {
copied.removeHiddenExtrinsicKeyword(s); copied.removeHiddenExtrinsicKeyword(s);
} }
} }
copied.clearAdditionalCostsPaid(); copied.clearOptionalCostsPaid();
if (copied.isFaceDown()) { if (copied.isFaceDown()) {
copied.turnFaceUp(); copied.turnFaceUp();
} }

View File

@@ -20,6 +20,8 @@ package forge.game;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
@@ -36,6 +38,7 @@ import forge.CounterType;
import forge.FThreads; import forge.FThreads;
import forge.Singletons; import forge.Singletons;
import forge.card.ability.AbilityFactory; import forge.card.ability.AbilityFactory;
import forge.card.ability.AbilityFactory.AbilityRecordType;
import forge.card.ability.AbilityUtils; import forge.card.ability.AbilityUtils;
import forge.card.ability.ApiType; import forge.card.ability.ApiType;
import forge.card.cardfactory.CardFactoryUtil; import forge.card.cardfactory.CardFactoryUtil;
@@ -57,6 +60,7 @@ import forge.card.mana.ManaCost;
import forge.card.spellability.Ability; import forge.card.spellability.Ability;
import forge.card.spellability.AbilityManaPart; import forge.card.spellability.AbilityManaPart;
import forge.card.spellability.AbilitySub; import forge.card.spellability.AbilitySub;
import forge.card.spellability.OptionalCost;
import forge.card.spellability.SpellAbility; import forge.card.spellability.SpellAbility;
import forge.card.spellability.SpellAbilityRestriction; import forge.card.spellability.SpellAbilityRestriction;
import forge.control.input.InputPayManaExecuteCommands; import forge.control.input.InputPayManaExecuteCommands;
@@ -72,6 +76,7 @@ import forge.game.zone.ZoneType;
import forge.gui.GuiChoose; import forge.gui.GuiChoose;
import forge.gui.GuiDialog; import forge.gui.GuiDialog;
import forge.sound.SoundEffectType; import forge.sound.SoundEffectType;
import forge.util.TextUtil;
/** /**
@@ -1209,6 +1214,115 @@ public final class GameActionUtil {
return alternatives; return alternatives;
} }
/**
* get optional additional costs.
*
* @param original
* the original sa
* @return an ArrayList<SpellAbility>.
*/
public static List<SpellAbility> getOptionalCosts(final SpellAbility original) {
final List<SpellAbility> abilities = new ArrayList<SpellAbility>();
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<SpellAbility> newAbilities = new ArrayList<SpellAbility>();
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<SpellAbility> newAbilities = new ArrayList<SpellAbility>();
for (SpellAbility sa : abilities) {
if( sa.isSpell() && sa.getSourceCard().isType("Arcane") && sa.getApi() != null ) {
newAbilities.addAll(GameActionUtil.getSpliceAbilities(sa));
}
}
abilities.addAll(newAbilities);
return abilities;
}
/** /**
* <p> * <p>
* getSpliceAbilities. * getSpliceAbilities.
@@ -1219,179 +1333,70 @@ public final class GameActionUtil {
* @return an ArrayList<SpellAbility>. * @return an ArrayList<SpellAbility>.
* get abilities with all Splice options * get abilities with all Splice options
*/ */
public static final ArrayList<SpellAbility> getSpliceAbilities(SpellAbility sa) { private static final ArrayList<SpellAbility> getSpliceAbilities(SpellAbility sa) {
ArrayList<SpellAbility> newSAs = new ArrayList<SpellAbility>(); ArrayList<SpellAbility> newSAs = new ArrayList<SpellAbility>();
ArrayList<SpellAbility> allSAs = new ArrayList<SpellAbility>(); ArrayList<SpellAbility> allSaCombinations = new ArrayList<SpellAbility>();
allSAs.add(sa); allSaCombinations.add(sa);
Card source = sa.getSourceCard(); Card source = sa.getSourceCard();
if (!sa.isSpell() || !source.isType("Arcane") || sa.getApi() == null) {
return newSAs;
}
for (Card c : sa.getActivatingPlayer().getCardsIn(ZoneType.Hand)) { for (Card c : sa.getActivatingPlayer().getCardsIn(ZoneType.Hand)) {
if (c.equals(source)) { if (c.equals(source)) {
continue; continue;
} }
String spliceKwCost = null;
for (String keyword : c.getKeyword()) { for (String keyword : c.getKeyword()) {
if (!keyword.startsWith("Splice")) { if (keyword.startsWith("Splice")) {
continue; 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<SpellAbility> addSAs = new ArrayList<SpellAbility>();
// 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 if( spliceKwCost == null )
SpellAbility child = newSA; continue;
while (child.getSubAbility() != null) {
AbilitySub newChild = child.getSubAbility().getCopy();
child.setSubAbility(newChild);
child.setActivatingPlayer(s.getActivatingPlayer());
child = newChild;
}
//add the spliced ability to the end of the chain Map<String, String> params = AbilityFactory.getMapParams(c.getCharacteristics().getUnparsedAbilities().get(0));
child.setSubAbility(newSubSA); 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 // Add the subability to all existing variants
child = newSubSA; for (int i = 0; i < allSaCombinations.size(); ++i) {
while (child != null) { //create a new spell copy
child.setSourceCard(source); final SpellAbility newSA = allSaCombinations.get(i).copy();
child.setActivatingPlayer(s.getActivatingPlayer()); newSA.setBasicSpell(false);
child = child.getSubAbility(); newSA.setPayCosts(new Cost(spliceKwCost, false).add(newSA.getPayCosts()));
} newSA.setDescription(newSA.getDescription() + " (Splicing " + c + " onto it)");
newSAs.add(0, newSA); newSA.addSplicedCards(c);
addSAs.add(newSA);
// 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; return newSAs;
} }
/**
* get optional additional costs.
*
* @param original
* the original sa
* @return an ArrayList<SpellAbility>.
*/
public static List<SpellAbility> getOptionalAdditionalCosts(final SpellAbility original) {
final List<SpellAbility> abilities = new ArrayList<SpellAbility>();
final List<SpellAbility> newAbilities = new ArrayList<SpellAbility>();
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<String> newoacs = new ArrayList<String>(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<String> newoacs = new ArrayList<String>(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<String>(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<String>(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<String> newoacs = new ArrayList<String>(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;
}
/** /**
* <p> * <p>
* hasUrzaLands. * hasUrzaLands.

View File

@@ -173,7 +173,7 @@ public class AiController {
final List<SpellAbility> result = new ArrayList<SpellAbility>(); final List<SpellAbility> result = new ArrayList<SpellAbility>();
for (SpellAbility sa : newAbilities) { for (SpellAbility sa : newAbilities) {
sa.setActivatingPlayer(player); sa.setActivatingPlayer(player);
result.addAll(GameActionUtil.getOptionalAdditionalCosts(sa)); result.addAll(GameActionUtil.getOptionalCosts(sa));
} }
result.addAll(newAbilities); result.addAll(newAbilities);
return result; return result;

View File

@@ -217,7 +217,7 @@ public class HumanPlayer extends Player {
*/ */
public SpellAbility chooseOptionalAdditionalCosts(final SpellAbility original) { public SpellAbility chooseOptionalAdditionalCosts(final SpellAbility original) {
//final HashMap<String, SpellAbility> map = new HashMap<String, SpellAbility>(); //final HashMap<String, SpellAbility> map = new HashMap<String, SpellAbility>();
final List<SpellAbility> abilities = GameActionUtil.getOptionalAdditionalCosts(original); final List<SpellAbility> abilities = GameActionUtil.getOptionalCosts(original);
if (!original.isSpell()) { if (!original.isSpell()) {
return original; return original;

View File

@@ -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 * Uses GUI to learn which spell the player (human in our case) would like to play
*/ */
public SpellAbility getAbilityToPlay(List<SpellAbility> abilities) { public SpellAbility getAbilityToPlay(List<SpellAbility> abilities) {
if (abilities.size() == 0) { if (abilities.isEmpty()) {
return null; return null;
} else if (abilities.size() == 1) { } else if (abilities.size() == 1) {
return abilities.get(0); return abilities.get(0);

View File

@@ -36,6 +36,7 @@ import forge.card.mana.ManaCost;
import forge.card.spellability.Ability; import forge.card.spellability.Ability;
import forge.card.spellability.AbilityStatic; import forge.card.spellability.AbilityStatic;
import forge.card.spellability.AbilityTriggered; import forge.card.spellability.AbilityTriggered;
import forge.card.spellability.OptionalCost;
import forge.card.spellability.Spell; import forge.card.spellability.Spell;
import forge.card.spellability.SpellAbility; import forge.card.spellability.SpellAbility;
import forge.card.spellability.SpellAbilityStackInstance; import forge.card.spellability.SpellAbilityStackInstance;
@@ -369,10 +370,9 @@ public class MagicStack extends MyObservable {
game.getAction().checkStateEffects(); game.getAction().checkStateEffects();
//GuiDisplayUtil.updateGUI(); //GuiDisplayUtil.updateGUI();
} else { } else {
if (sp.getOptionalAdditionalCosts() != null) { for (OptionalCost s : sp.getOptionalCosts()) {
for (String s : sp.getOptionalAdditionalCosts()) {
sp.getSourceCard().addOptionalAdditionalCostsPaid(s); sp.getSourceCard().addOptionalCostPaid(s);
}
} }
if (sp.getSourceCard().isCopiedSpell()) { if (sp.getSourceCard().isCopiedSpell()) {
this.push(sp); this.push(sp);
@@ -384,7 +384,7 @@ public class MagicStack extends MyObservable {
if (activating.isHuman()) { if (activating.isHuman()) {
while(true) { 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 ); String prompt = String.format("Multikicker for %s\r\nTimes Kicked: %d\r\n", sa.getSourceCard(), mkMagnitude );
InputPayManaExecuteCommands toSet = new InputPayManaExecuteCommands(activating, prompt, sp.getMultiKickerManaCost()); InputPayManaExecuteCommands toSet = new InputPayManaExecuteCommands(activating, prompt, sp.getMultiKickerManaCost());
FThreads.setInputAndWait(toSet); FThreads.setInputAndWait(toSet);