Merge branch 'escapeRefactorFlashback' into 'master'

Refactor Escape and AlternativeCost

See merge request core-developers/forge!2361
This commit is contained in:
Michael Kamensky
2020-01-03 04:40:10 +00:00
21 changed files with 191 additions and 184 deletions

View File

@@ -109,7 +109,7 @@ public class ComputerUtil {
if (chooseTargets != null) { if (chooseTargets != null) {
chooseTargets.run(); chooseTargets.run();
} }
if (sa.hasParam("Bestow")) { if (sa.isBestow()) {
sa.getHostCard().animateBestow(); sa.getHostCard().animateBestow();
} }

View File

@@ -1031,7 +1031,7 @@ public class AttachAi extends SpellAbilityAi {
Card c = null; Card c = null;
List<Card> magnetList = null; List<Card> magnetList = null;
String stCheck = null; String stCheck = null;
if (attachSource.isAura() || sa.hasParam("Bestow")) { if (attachSource.isAura() || sa.isBestow()) {
stCheck = "EnchantedBy"; stCheck = "EnchantedBy";
magnetList = CardLists.filter(list, new Predicate<Card>() { magnetList = CardLists.filter(list, new Predicate<Card>() {
@Override @Override

View File

@@ -75,7 +75,7 @@ public final class GameActionUtil {
Card source = sa.getHostCard(); Card source = sa.getHostCard();
final Game game = source.getGame(); final Game game = source.getGame();
if (sa.isSpell()) { if (sa.isSpell() && !source.isInZone(ZoneType.Battlefield)) {
boolean lkicheck = false; boolean lkicheck = false;
// need to be done before so it works with Vivien and Zoetic Cavern // need to be done before so it works with Vivien and Zoetic Cavern
@@ -88,7 +88,7 @@ public final class GameActionUtil {
lkicheck = true; lkicheck = true;
} }
if (sa.hasParam("Bestow") && !source.isBestowed() && !source.isInZone(ZoneType.Battlefield)) { if (sa.isBestow() && !source.isBestowed() && !source.isInZone(ZoneType.Battlefield)) {
if (!source.isLKI()) { if (!source.isLKI()) {
source = CardUtil.getLKICopy(source); source = CardUtil.getLKICopy(source);
} }
@@ -102,7 +102,7 @@ public final class GameActionUtil {
} }
source.turnFaceDownNoUpdate(); source.turnFaceDownNoUpdate();
lkicheck = true; lkicheck = true;
} else if (sa.isAdventure() && !source.isInZone(ZoneType.Battlefield)) { } else if (sa.isAdventure()) {
if (!source.isLKI()) { if (!source.isLKI()) {
source = CardUtil.getLKICopy(source); source = CardUtil.getLKICopy(source);
} }
@@ -146,6 +146,7 @@ public final class GameActionUtil {
if (lkicheck) { if (lkicheck) {
// double freeze tracker, so it doesn't update view // double freeze tracker, so it doesn't update view
game.getTracker().freeze(); game.getTracker().freeze();
source.clearChangedCardKeywords(false);
CardCollection preList = new CardCollection(source); CardCollection preList = new CardCollection(source);
game.getAction().checkStaticAbilities(false, Sets.newHashSet(source), preList); game.getAction().checkStaticAbilities(false, Sets.newHashSet(source), preList);
} }
@@ -207,6 +208,57 @@ public final class GameActionUtil {
alternatives.add(newSA); alternatives.add(newSA);
} }
// need to be done there before static abilities does reset the card
if (sa.isBasicSpell()) {
for (final KeywordInterface inst : source.getKeywords()) {
final String keyword = inst.getOriginal();
if (keyword.startsWith("Escape")) {
final String[] k = keyword.split(":");
final Cost escapeCost = new Cost(k[1], true);
final SpellAbility newSA = sa.copyWithDefinedCost(escapeCost);
newSA.getMapParams().put("PrecostDesc", "Escape—");
newSA.getMapParams().put("CostDesc", escapeCost.toString());
// makes new SpellDescription
final StringBuilder desc = new StringBuilder();
desc.append(newSA.getCostDescription());
desc.append("(").append(inst.getReminderText()).append(")");
newSA.setDescription(desc.toString());
// Stack Description only for Permanent or it might crash
if (source.isPermanent()) {
final StringBuilder sbStack = new StringBuilder();
sbStack.append(sa.getStackDescription()).append(" (Escaped)");
newSA.setStackDescription(sbStack.toString());
}
newSA.setAlternativeCost(AlternativeCost.Escape);
newSA.getRestrictions().setZone(ZoneType.Graveyard);
alternatives.add(newSA);
} else if (keyword.startsWith("Flashback")) {
// if source has No Mana cost, and flashback doesn't have own one,
// flashback can't work
if (keyword.equals("Flashback") && source.getManaCost().isNoCost()) {
continue;
}
final SpellAbility flashback = sa.copy(activator);
flashback.setAlternativeCost(AlternativeCost.Flashback);
flashback.getRestrictions().setZone(ZoneType.Graveyard);
// there is a flashback cost (and not the cards cost)
if (keyword.contains(":")) {
final String[] k = keyword.split(":");
flashback.setPayCosts(new Cost(k[1], false));
}
alternatives.add(flashback);
}
}
}
// reset static abilities // reset static abilities
if (lkicheck) { if (lkicheck) {
game.getAction().checkStaticAbilities(false); game.getAction().checkStaticAbilities(false);
@@ -244,28 +296,6 @@ public final class GameActionUtil {
alternatives.add(newSA); alternatives.add(newSA);
} }
for (final KeywordInterface inst : source.getKeywords()) {
final String keyword = inst.getOriginal();
if (sa.isSpell() && keyword.startsWith("Flashback")) {
// if source has No Mana cost, and flashback doesn't have own one,
// flashback can't work
if (keyword.equals("Flashback") && source.getManaCost().isNoCost()) {
continue;
}
final SpellAbility flashback = sa.copy(activator);
flashback.setFlashBackAbility(true);
flashback.getRestrictions().setZone(ZoneType.Graveyard);
// there is a flashback cost (and not the cards cost)
if (keyword.contains(":")) {
final String[] k = keyword.split(":");
flashback.setPayCosts(new Cost(k[1], false));
}
alternatives.add(flashback);
}
}
return alternatives; return alternatives;
} }

View File

@@ -382,21 +382,10 @@ public final class AbilityFactory {
* @param mapParams * @param mapParams
*/ */
private static final void initializeParams(final SpellAbility sa, Map<String, String> mapParams) { private static final void initializeParams(final SpellAbility sa, Map<String, String> mapParams) {
if (mapParams.containsKey("Flashback")) {
sa.setFlashBackAbility(true);
}
if (mapParams.containsKey("NonBasicSpell")) { if (mapParams.containsKey("NonBasicSpell")) {
sa.setBasicSpell(false); sa.setBasicSpell(false);
} }
if (mapParams.containsKey("Dash")) {
sa.setDash(true);
}
if (mapParams.containsKey("Outlast")) {
sa.setOutlast(true);
}
} }
/** /**

View File

@@ -1649,7 +1649,7 @@ public class Card extends GameEntity implements Comparable<Card> {
} else { } else {
sbLong.append(parts[0]).append(" ").append(ManaCostParser.parse(parts[1])).append("\r\n"); sbLong.append(parts[0]).append(" ").append(ManaCostParser.parse(parts[1])).append("\r\n");
} }
} else if (keyword.startsWith("Morph") || keyword.startsWith("Megamorph")) { } else if (keyword.startsWith("Morph") || keyword.startsWith("Megamorph") || keyword.startsWith("Escape")) {
String[] k = keyword.split(":"); String[] k = keyword.split(":");
sbLong.append(k[0]); sbLong.append(k[0]);
if (k.length > 1) { if (k.length > 1) {
@@ -1794,7 +1794,7 @@ public class Card extends GameEntity implements Comparable<Card> {
|| keyword.startsWith("Level up") || keyword.equals("Prowess") || keyword.startsWith("Eternalize") || keyword.startsWith("Level up") || keyword.equals("Prowess") || keyword.startsWith("Eternalize")
|| keyword.startsWith("Reinforce") || keyword.startsWith("Champion") || keyword.startsWith("Prowl") || keyword.startsWith("Reinforce") || keyword.startsWith("Champion") || keyword.startsWith("Prowl")
|| keyword.startsWith("Amplify") || keyword.startsWith("Ninjutsu") || keyword.startsWith("Adapt") || keyword.startsWith("Amplify") || keyword.startsWith("Ninjutsu") || keyword.startsWith("Adapt")
|| keyword.startsWith("Transfigure") || keyword.startsWith("Aura swap") || keyword.startsWith("Escape") || keyword.startsWith("Transfigure") || keyword.startsWith("Aura swap")
|| keyword.startsWith("Cycling") || keyword.startsWith("TypeCycling")) { || keyword.startsWith("Cycling") || keyword.startsWith("TypeCycling")) {
// keyword parsing takes care of adding a proper description // keyword parsing takes care of adding a proper description
} else if (keyword.startsWith("CantBeBlockedByAmount")) { } else if (keyword.startsWith("CantBeBlockedByAmount")) {
@@ -2189,7 +2189,8 @@ public class Card extends GameEntity implements Comparable<Card> {
sbBefore.append(inst.getReminderText()); sbBefore.append(inst.getReminderText());
sbBefore.append("\r\n"); sbBefore.append("\r\n");
} else if (keyword.startsWith("Entwine") || keyword.startsWith("Madness") } else if (keyword.startsWith("Entwine") || keyword.startsWith("Madness")
|| keyword.startsWith("Miracle") || keyword.startsWith("Recover")) { || keyword.startsWith("Miracle") || keyword.startsWith("Recover")
|| keyword.startsWith("Escape")) {
final String[] k = keyword.split(":"); final String[] k = keyword.split(":");
final Cost cost = new Cost(k[1], false); final Cost cost = new Cost(k[1], false);
@@ -3787,6 +3788,17 @@ public class Card extends GameEntity implements Comparable<Card> {
updateKeywordsCache(currentState); updateKeywordsCache(currentState);
} }
public final boolean clearChangedCardKeywords(final boolean updateView) {
if (changedCardKeywords.isEmpty()) {
return false;
}
changedCardKeywords.clear();
if (updateView) {
updateKeywords();
}
return true;
}
// Hidden keywords will be left out // Hidden keywords will be left out
public final Collection<KeywordInterface> getUnhiddenKeywords() { public final Collection<KeywordInterface> getUnhiddenKeywords() {
return getUnhiddenKeywords(currentState); return getUnhiddenKeywords(currentState);

View File

@@ -3812,7 +3812,7 @@ public class CardFactoryUtil {
final String counters = k[1]; final String counters = k[1];
final Cost awakenCost = new Cost(k[2], false); final Cost awakenCost = new Cost(k[2], false);
final SpellAbility awakenSpell = card.getFirstSpellAbility().copy(); final SpellAbility awakenSpell = card.getFirstSpellAbility().copyWithDefinedCost(awakenCost);
final String awaken = "DB$ PutCounter | CounterType$ P1P1 | CounterNum$ "+ counters + " | " final String awaken = "DB$ PutCounter | CounterType$ P1P1 | CounterNum$ "+ counters + " | "
+ "ValidTgts$ Land.YouCtrl | TgtPrompt$ Select target land you control | Awaken$ True"; + "ValidTgts$ Land.YouCtrl | TgtPrompt$ Select target land you control | Awaken$ True";
@@ -3827,8 +3827,7 @@ public class CardFactoryUtil {
String desc = "Awaken " + counters + "" + awakenCost.toSimpleString() + String desc = "Awaken " + counters + "" + awakenCost.toSimpleString() +
" (" + inst.getReminderText() + ")"; " (" + inst.getReminderText() + ")";
awakenSpell.setDescription(desc); awakenSpell.setDescription(desc);
awakenSpell.setBasicSpell(false); awakenSpell.setAlternativeCost(AlternativeCost.Awaken);
awakenSpell.setPayCosts(awakenCost);
awakenSpell.setIntrinsic(intrinsic); awakenSpell.setIntrinsic(intrinsic);
inst.addSpellAbility(awakenSpell); inst.addSpellAbility(awakenSpell);
} else if (keyword.startsWith("Bestow")) { } else if (keyword.startsWith("Bestow")) {
@@ -3845,16 +3844,16 @@ public class CardFactoryUtil {
sa.setDescription("Bestow " + ManaCostParser.parse(cost) + sa.setDescription("Bestow " + ManaCostParser.parse(cost) +
" (" + inst.getReminderText() + ")"); " (" + inst.getReminderText() + ")");
sa.setStackDescription("Bestow - " + card.getName()); sa.setStackDescription("Bestow - " + card.getName());
sa.setBasicSpell(false); sa.setAlternativeCost(AlternativeCost.Bestow);
sa.setIntrinsic(intrinsic); sa.setIntrinsic(intrinsic);
inst.addSpellAbility(sa); inst.addSpellAbility(sa);
} else if (keyword.startsWith("Dash")) { } else if (keyword.startsWith("Dash")) {
final String[] k = keyword.split(":"); final String[] k = keyword.split(":");
final String dashString = "SP$ PermanentCreature | Cost$ " + k[1] + " | StackDescription$ CARDNAME (Dash)" final String dashString = "SP$ PermanentCreature | Cost$ " + k[1] + " | StackDescription$ CARDNAME (Dash)"
+ " | Dash$ True | NonBasicSpell$ True" + " | Dash$ True | SpellDescription$ Dash " + ManaCostParser.parse(k[1]) + " (" + inst.getReminderText() + ")";
+ " | SpellDescription$ Dash " + ManaCostParser.parse(k[1]) + " (" + inst.getReminderText() + ")";
final SpellAbility newSA = AbilityFactory.getAbility(dashString, card); final SpellAbility newSA = AbilityFactory.getAbility(dashString, card);
newSA.setAlternativeCost(AlternativeCost.Dash);
newSA.setIntrinsic(intrinsic); newSA.setIntrinsic(intrinsic);
inst.addSpellAbility(newSA); inst.addSpellAbility(newSA);
} else if (keyword.startsWith("Emerge")) { } else if (keyword.startsWith("Emerge")) {
@@ -3862,13 +3861,12 @@ public class CardFactoryUtil {
String costStr = kw[1]; String costStr = kw[1];
final SpellAbility sa = card.getFirstSpellAbility(); final SpellAbility sa = card.getFirstSpellAbility();
final SpellAbility newSA = sa.copy(); final SpellAbility newSA = sa.copyWithDefinedCost(new Cost(costStr, false));
newSA.getRestrictions().setIsPresent("Creature.YouCtrl+CanBeSacrificedBy"); newSA.getRestrictions().setIsPresent("Creature.YouCtrl+CanBeSacrificedBy");
newSA.getMapParams().put("Secondary", "True"); newSA.getMapParams().put("Secondary", "True");
newSA.setBasicSpell(false); newSA.setAlternativeCost(AlternativeCost.Emerge);
newSA.setIsEmerge(true);
newSA.setPayCosts(new Cost(costStr, false));
newSA.setDescription(sa.getDescription() + " (Emerge)"); newSA.setDescription(sa.getDescription() + " (Emerge)");
newSA.setIntrinsic(intrinsic); newSA.setIntrinsic(intrinsic);
inst.addSpellAbility(newSA); inst.addSpellAbility(newSA);
@@ -3938,33 +3936,6 @@ public class CardFactoryUtil {
final SpellAbility newSA = AbilityFactory.getAbility(abilityStr.toString(), card); final SpellAbility newSA = AbilityFactory.getAbility(abilityStr.toString(), card);
newSA.setIntrinsic(intrinsic); newSA.setIntrinsic(intrinsic);
inst.addSpellAbility(newSA); inst.addSpellAbility(newSA);
} else if (keyword.startsWith("Escape")) {
final String[] k = keyword.split(":");
final Cost escapeCost = new Cost(k[1], false);
final SpellAbility sa = card.getFirstSpellAbility();
final SpellAbility newSA = sa.copyWithDefinedCost(escapeCost);
newSA.getMapParams().put("PrecostDesc", "Escape—");
newSA.getMapParams().put("CostDesc", ManaCostParser.parse(k[1]));
// makes new SpellDescription
final StringBuilder desc = new StringBuilder();
desc.append(newSA.getCostDescription());
desc.append("(").append(inst.getReminderText()).append(")");
newSA.setDescription(desc.toString());
// Stack Description only for Permanent or it might crash
if (card.isPermanent()) {
final StringBuilder sbStack = new StringBuilder();
sbStack.append(sa.getStackDescription()).append(" (Escaped)");
newSA.setStackDescription(sbStack.toString());
}
newSA.setBasicSpell(false);
newSA.setEscape(true);
newSA.setIntrinsic(intrinsic);
newSA.getRestrictions().setZone(ZoneType.Graveyard);
inst.addSpellAbility(newSA);
} else if (keyword.startsWith("Eternalize")) { } else if (keyword.startsWith("Eternalize")) {
final String[] kw = keyword.split(":"); final String[] kw = keyword.split(":");
String costStr = kw[1]; String costStr = kw[1];
@@ -4011,8 +3982,7 @@ public class CardFactoryUtil {
final StringBuilder sb = new StringBuilder(); final StringBuilder sb = new StringBuilder();
sb.append(card.getName()).append(" (Evoked)"); sb.append(card.getName()).append(" (Evoked)");
newSA.setStackDescription(sb.toString()); newSA.setStackDescription(sb.toString());
newSA.setBasicSpell(false); newSA.setAlternativeCost(AlternativeCost.Evoke);
newSA.setEvoke(true);
newSA.setIntrinsic(intrinsic); newSA.setIntrinsic(intrinsic);
inst.addSpellAbility(newSA); inst.addSpellAbility(newSA);
} else if (keyword.startsWith("Fortify")) { } else if (keyword.startsWith("Fortify")) {
@@ -4169,8 +4139,7 @@ public class CardFactoryUtil {
abilityStr.append("AB$ PutCounter | Cost$ "); abilityStr.append("AB$ PutCounter | Cost$ ");
abilityStr.append(manacost); abilityStr.append(manacost);
abilityStr.append(" T | Defined$ Self | CounterType$ P1P1 | CounterNum$ 1 "); abilityStr.append(" T | Defined$ Self | CounterType$ P1P1 | CounterNum$ 1 ");
abilityStr.append("| SorcerySpeed$ True | Outlast$ True "); abilityStr.append("| SorcerySpeed$ True | PrecostDesc$ Outlast");
abilityStr.append("| PrecostDesc$ Outlast");
Cost cost = new Cost(manacost, true); Cost cost = new Cost(manacost, true);
if (!cost.isOnlyManaCost()) { //Something other than a mana cost if (!cost.isOnlyManaCost()) { //Something other than a mana cost
abilityStr.append(""); abilityStr.append("");
@@ -4182,6 +4151,7 @@ public class CardFactoryUtil {
final SpellAbility sa = AbilityFactory.getAbility(abilityStr.toString(), card); final SpellAbility sa = AbilityFactory.getAbility(abilityStr.toString(), card);
sa.setIntrinsic(intrinsic); sa.setIntrinsic(intrinsic);
sa.setAlternativeCost(AlternativeCost.Outlast);
inst.addSpellAbility(sa); inst.addSpellAbility(sa);
} else if (keyword.startsWith("Prowl")) { } else if (keyword.startsWith("Prowl")) {
@@ -4201,8 +4171,7 @@ public class CardFactoryUtil {
sb.append("(").append(inst.getReminderText()).append(")"); sb.append("(").append(inst.getReminderText()).append(")");
newSA.setDescription(sb.toString()); newSA.setDescription(sb.toString());
newSA.setBasicSpell(false); newSA.setAlternativeCost(AlternativeCost.Prowl);
newSA.setProwl(true);
newSA.setIntrinsic(intrinsic); newSA.setIntrinsic(intrinsic);
inst.addSpellAbility(newSA); inst.addSpellAbility(newSA);
@@ -4247,8 +4216,7 @@ public class CardFactoryUtil {
final Cost cost = new Cost(k[1], false); final Cost cost = new Cost(k[1], false);
final SpellAbility newSA = card.getFirstSpellAbility().copyWithDefinedCost(cost); final SpellAbility newSA = card.getFirstSpellAbility().copyWithDefinedCost(cost);
newSA.setBasicSpell(false); newSA.setAlternativeCost(AlternativeCost.Spectacle);
newSA.setSpectacle(true);
String desc = "Spectacle " + cost.toSimpleString() + " (" + inst.getReminderText() String desc = "Spectacle " + cost.toSimpleString() + " (" + inst.getReminderText()
+ ")"; + ")";
@@ -4262,8 +4230,7 @@ public class CardFactoryUtil {
final Cost surgeCost = new Cost(k[1], false); final Cost surgeCost = new Cost(k[1], false);
final SpellAbility newSA = card.getFirstSpellAbility().copyWithDefinedCost(surgeCost); final SpellAbility newSA = card.getFirstSpellAbility().copyWithDefinedCost(surgeCost);
newSA.setBasicSpell(false); newSA.setAlternativeCost(AlternativeCost.Surge);
newSA.setSurged(true);
String desc = "Surge " + surgeCost.toSimpleString() + " (" + inst.getReminderText() String desc = "Surge " + surgeCost.toSimpleString() + " (" + inst.getReminderText()
+ ")"; + ")";
@@ -4374,8 +4341,7 @@ public class CardFactoryUtil {
sar.setInstantSpeed(true); sar.setInstantSpeed(true);
newSA.getMapParams().put("Secondary", "True"); newSA.getMapParams().put("Secondary", "True");
newSA.setBasicSpell(false); newSA.setAlternativeCost(AlternativeCost.Offering);
newSA.setIsOffering(true);
newSA.setPayCosts(sa.getPayCosts()); newSA.setPayCosts(sa.getPayCosts());
newSA.setDescription(sa.getDescription() + " (" + offeringType + " offering)"); newSA.setDescription(sa.getDescription() + " (" + offeringType + " offering)");
newSA.setIntrinsic(intrinsic); newSA.setIntrinsic(intrinsic);
@@ -4410,7 +4376,7 @@ public class CardFactoryUtil {
sb.append("| SpellDescription$ (").append(inst.getReminderText()).append(")"); sb.append("| SpellDescription$ (").append(inst.getReminderText()).append(")");
SpellAbility sa = AbilityFactory.getAbility(sb.toString(), card); SpellAbility sa = AbilityFactory.getAbility(sb.toString(), card);
sa.setIsCycling(true); sa.setAlternativeCost(AlternativeCost.Cycling);
sa.setIntrinsic(intrinsic); sa.setIntrinsic(intrinsic);
inst.addSpellAbility(sa); inst.addSpellAbility(sa);
@@ -4434,7 +4400,7 @@ public class CardFactoryUtil {
sb.append(" | SpellDescription$ (").append(inst.getReminderText()).append(")"); sb.append(" | SpellDescription$ (").append(inst.getReminderText()).append(")");
SpellAbility sa = AbilityFactory.getAbility(sb.toString(), card); SpellAbility sa = AbilityFactory.getAbility(sb.toString(), card);
sa.setIsCycling(true); sa.setAlternativeCost(AlternativeCost.Cycling);
sa.setIntrinsic(intrinsic); sa.setIntrinsic(intrinsic);
inst.addSpellAbility(sa); inst.addSpellAbility(sa);

View File

@@ -0,0 +1,18 @@
package forge.game.spellability;
public enum AlternativeCost {
Awaken,
Bestow,
Cycling, // ActivatedAbility
Dash,
Emerge,
Escape,
Evoke,
Flashback,
Offering,
Outlast, // ActivatedAbility
Prowl,
Spectacle,
Surge;
}

View File

@@ -118,7 +118,7 @@ public abstract class Spell extends SpellAbility implements java.io.Serializable
lkicheck = true; lkicheck = true;
} }
if (hasParam("Bestow") && !card.isBestowed() && !card.isInZone(ZoneType.Battlefield)) { if (isBestow() && !card.isBestowed() && !card.isInZone(ZoneType.Battlefield)) {
// Rule 601.3: cast Bestow with Flash // Rule 601.3: cast Bestow with Flash
// for the check the card does need to be animated // for the check the card does need to be animated
// otherwise the StaticAbility will not found them // otherwise the StaticAbility will not found them

View File

@@ -101,20 +101,11 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
private int sourceTrigger = -1; private int sourceTrigger = -1;
private List<Object> triggerRemembered = Lists.newArrayList(); private List<Object> triggerRemembered = Lists.newArrayList();
// TODO use enum for the flags private AlternativeCost altCost = null;
private boolean flashBackAbility = false;
private boolean aftermath = false; private boolean aftermath = false;
private boolean cycling = false;
private boolean dash = false;
private boolean escape = false;
private boolean evoke = false;
private boolean prowl = false;
private boolean surge = false;
private boolean spectacle = false;
private boolean offering = false;
private boolean emerge = false;
private boolean cumulativeupkeep = false; private boolean cumulativeupkeep = false;
private boolean outlast = false;
private boolean blessing = false; private boolean blessing = false;
private Integer chapter = null; private Integer chapter = null;
@@ -388,10 +379,7 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
} }
public boolean isCycling() { public boolean isCycling() {
return cycling; return this.isAlternativeCost(AlternativeCost.Cycling);
}
public final void setIsCycling(final boolean b) {
cycling = b;
} }
public Card getOriginalHost() { public Card getOriginalHost() {
@@ -780,17 +768,14 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
} }
public boolean isBasicSpell() { public boolean isBasicSpell() {
return basicSpell && !isFlashBackAbility() && !isBuyBackAbility(); return basicSpell && this.altCost == null && getRootAbility().optionalCosts.isEmpty();
} }
public void setBasicSpell(final boolean basicSpell0) { public void setBasicSpell(final boolean basicSpell0) {
basicSpell = basicSpell0; basicSpell = basicSpell0;
} }
public void setFlashBackAbility(final boolean flashBackAbility0) {
flashBackAbility = flashBackAbility0;
}
public boolean isFlashBackAbility() { public boolean isFlashBackAbility() {
return flashBackAbility; return this.isAlternativeCost(AlternativeCost.Flashback);
} }
public void setBasicLandAbility(final boolean basicLandAbility0) { public void setBasicLandAbility(final boolean basicLandAbility0) {
@@ -815,10 +800,7 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
} }
public boolean isOutlast() { public boolean isOutlast() {
return outlast; return isAlternativeCost(AlternativeCost.Outlast);
}
public void setOutlast(boolean outlast0) {
outlast = outlast0;
} }
public boolean isBlessing() { public boolean isBlessing() {
@@ -1165,57 +1147,32 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
return false; return false;
} }
public final boolean isDash() { public final boolean isBestow() {
return dash; return isAlternativeCost(AlternativeCost.Bestow);
} }
public final void setDash(final boolean isDash) {
dash = isDash; public final boolean isDash() {
return isAlternativeCost(AlternativeCost.Dash);
} }
public final boolean isEscape() { public final boolean isEscape() {
return escape; return isAlternativeCost(AlternativeCost.Escape);
}
public final void setEscape(final boolean isEscape) {
escape = isEscape;
} }
public final boolean isEvoke() { public final boolean isEvoke() {
return evoke; return isAlternativeCost(AlternativeCost.Evoke);
}
public final void setEvoke(final boolean isEvoke) {
evoke = isEvoke;
} }
public final boolean isProwl() { public final boolean isProwl() {
return prowl; return isAlternativeCost(AlternativeCost.Prowl);
}
public final void setProwl(final boolean isProwl) {
prowl = isProwl;
} }
public final boolean isSurged() { public final boolean isSurged() {
if (surge) return isAlternativeCost(AlternativeCost.Surge);
return true;
SpellAbility parent = getParent();
if (parent != null) {
return parent.isSurged();
}
return false;
}
public final void setSurged(final boolean isSurge) {
surge = isSurge;
} }
public final boolean isSpectacle() { public final boolean isSpectacle() {
return spectacle; return isAlternativeCost(AlternativeCost.Spectacle);
}
public final void setSpectacle(final boolean isSpectacle) {
spectacle = isSpectacle;
} }
public CardCollection getTappedForConvoke() { public CardCollection getTappedForConvoke() {
@@ -1234,10 +1191,7 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
} }
public boolean isEmerge() { public boolean isEmerge() {
return emerge; return isAlternativeCost(AlternativeCost.Emerge);
}
public void setIsEmerge(final boolean bEmerge) {
emerge = bEmerge;
} }
public Card getSacrificedAsEmerge() { public Card getSacrificedAsEmerge() {
@@ -1251,10 +1205,7 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
} }
public boolean isOffering() { public boolean isOffering() {
return offering; return isAlternativeCost(AlternativeCost.Offering);
}
public void setIsOffering(final boolean bOffering) {
offering = bOffering;
} }
public Card getSacrificedAsOffering() { //for Patron offering public Card getSacrificedAsOffering() { //for Patron offering
@@ -1983,4 +1934,32 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
public void setGrantorStatic(final StaticAbility st) { public void setGrantorStatic(final StaticAbility st) {
grantorStatic = st; grantorStatic = st;
} }
public boolean isAlternativeCost(AlternativeCost ac) {
if (ac.equals(altCost)) {
return true;
}
SpellAbility parent = getParent();
if (parent != null) {
return parent.isAlternativeCost(ac);
}
return false;
}
public AlternativeCost getAlternativeCost() {
if (altCost != null) {
return altCost;
}
SpellAbility parent = getParent();
if (parent != null) {
return parent.getAlternativeCost();
}
return null;
}
public void setAlternativeCost(AlternativeCost ac) {
altCost = ac;
}
} }

View File

@@ -97,10 +97,6 @@ public class SpellAbilityRestriction extends SpellAbilityVariables {
this.setZone(ZoneType.smartValueOf(params.get("ActivationZone"))); this.setZone(ZoneType.smartValueOf(params.get("ActivationZone")));
} }
if (params.containsKey("Flashback")) {
this.setZone(ZoneType.Graveyard);
}
if (params.containsKey("SorcerySpeed")) { if (params.containsKey("SorcerySpeed")) {
this.setSorcerySpeed(true); this.setSorcerySpeed(true);
} }
@@ -202,7 +198,7 @@ public class SpellAbilityRestriction extends SpellAbilityVariables {
Card cp = c; Card cp = c;
// for Bestow need to check the animated State // for Bestow need to check the animated State
if (sa.isSpell() && sa.hasParam("Bestow")) { if (sa.isSpell() && sa.isBestow()) {
// already bestowed or in battlefield, no need to check for spell // already bestowed or in battlefield, no need to check for spell
if (c.isInZone(ZoneType.Battlefield)) { if (c.isInZone(ZoneType.Battlefield)) {
return false; return false;

View File

@@ -341,11 +341,6 @@ public class WrappedAbility extends Ability {
sa.setDescription(s); sa.setDescription(s);
} }
@Override
public void setFlashBackAbility(final boolean flashBackAbility) {
sa.setFlashBackAbility(flashBackAbility);
}
@Override @Override
public void setMultiKickerManaCost(final ManaCost cost) { public void setMultiKickerManaCost(final ManaCost cost) {
sa.setMultiKickerManaCost(cost); sa.setMultiKickerManaCost(cost);
@@ -546,4 +541,16 @@ public class WrappedAbility extends Ability {
} }
// TODO: CardCollection // TODO: CardCollection
} }
public boolean isAlternativeCost(AlternativeCost ac) {
return sa.isAlternativeCost(ac);
}
public AlternativeCost getAlternativeCost() {
return sa.getAlternativeCost();
}
public void setAlternativeCost(AlternativeCost ac) {
sa.setAlternativeCost(ac);
}
} }

View File

@@ -460,7 +460,7 @@ public class MagicStack /* extends MyObservable */ implements Iterable<SpellAbil
} }
if (thisHasFizzled) { // Fizzle if (thisHasFizzled) { // Fizzle
if (sa.hasParam("Bestow")) { if (sa.isBestow()) {
// 702.102d: if its target is illegal, // 702.102d: if its target is illegal,
// the effect making it an Aura spell ends. // the effect making it an Aura spell ends.
// It continues resolving as a creature spell. // It continues resolving as a creature spell.

View File

@@ -6,6 +6,6 @@ K:Reach
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigDamage | TriggerDescription$ When CARDNAME enters the battlefield, it deals damage equal to its power to target creature with flying an opponent controls. T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigDamage | TriggerDescription$ When CARDNAME enters the battlefield, it deals damage equal to its power to target creature with flying an opponent controls.
SVar:TrigDamage:DB$ DealDamage | ValidTgts$ Creature.OppCtrl+withFlying | TgtPrompt$ Select target creature with flying an opponent controls | NumDmg$ X | References$ X SVar:TrigDamage:DB$ DealDamage | ValidTgts$ Creature.OppCtrl+withFlying | TgtPrompt$ Select target creature with flying an opponent controls | NumDmg$ X | References$ X
SVar:X:Count$CardPower SVar:X:Count$CardPower
K:Escape:3 G G ExileFromGrave<4/Card.Other> K:Escape:3 G G ExileFromGrave<4/Card.Other/other>
K:etbCounter:P1P1:3:ValidCard$ Card.Self+escaped:CARDNAME escapes with three +1/+1 counters on it. K:etbCounter:P1P1:3:ValidCard$ Card.Self+escaped:CARDNAME escapes with three +1/+1 counters on it.
Oracle:Reach\nWhen Chainweb Aracnir enters the battlefield, it deals damage equal to its power to target creature with flying an opponent controls.\nEscape — {2}{G}{G}, Exile four other cards from your graveyard. (You may cast this card from your graveyard for its escape cost).\nChainweb Aracnir escapes with three +1/+1 counters on it. Oracle:Reach\nWhen Chainweb Aracnir enters the battlefield, it deals damage equal to its power to target creature with flying an opponent controls.\nEscape — {2}{G}{G}, Exile four other cards from your graveyard. (You may cast this card from your graveyard for its escape cost).\nChainweb Aracnir escapes with three +1/+1 counters on it.

View File

@@ -5,5 +5,5 @@ Loyalty:5
A:AB$ Pump | Cost$ SubCounter<1/LOYALTY> | Planeswalker$ True | TargetMin$ 0 | TargetMax$ 2 | ValidTgts$ Creature.YouCtrl | TgtPrompt$ Select target creature you control | NumAtt$ +2 | NumDef$ +1 | SpellDescription$ Up to two target creatures you control each get +2/+1 until end of turn. A:AB$ Pump | Cost$ SubCounter<1/LOYALTY> | Planeswalker$ True | TargetMin$ 0 | TargetMax$ 2 | ValidTgts$ Creature.YouCtrl | TgtPrompt$ Select target creature you control | NumAtt$ +2 | NumDef$ +1 | SpellDescription$ Up to two target creatures you control each get +2/+1 until end of turn.
A:AB$ Token | Cost$ SubCounter<2/LOYALTY> | Planeswalker$ True | TokenAmount$ 2 | TokenScript$ w_1_1_human_soldier | TokenOwner$ You | LegacyImage$ w 1 1 human soldier the | SpellDescription$ Create two 1/1 white Human Soldier creature tokens. A:AB$ Token | Cost$ SubCounter<2/LOYALTY> | Planeswalker$ True | TokenAmount$ 2 | TokenScript$ w_1_1_human_soldier | TokenOwner$ You | LegacyImage$ w 1 1 human soldier the | SpellDescription$ Create two 1/1 white Human Soldier creature tokens.
A:AB$ GainLife | Cost$ SubCounter<3/LOYALTY> | Planeswalker$ True | Ultimate$ True | LifeAmount$ 5 | SpellDescription$ You gain 5 life. A:AB$ GainLife | Cost$ SubCounter<3/LOYALTY> | Planeswalker$ True | Ultimate$ True | LifeAmount$ 5 | SpellDescription$ You gain 5 life.
K:Escape:4 W W ExileFromGrave<4/Card.Other> K:Escape:4 W W ExileFromGrave<4/Card.Other/other>
Oracle:1: Up to two target creatures you control each get +2/+1 until end of turn.\n2: Create two 1/1 white Human Soldier creature tokens.\n3: You gain 5 life.\nEscape—{4}{W}{W}, Exile four other cards from your graveyard. (You may cast this card from your graveyard for its escape cost.) Oracle:1: Up to two target creatures you control each get +2/+1 until end of turn.\n2: Create two 1/1 white Human Soldier creature tokens.\n3: You gain 5 life.\nEscape—{4}{W}{W}, Exile four other cards from your graveyard. (You may cast this card from your graveyard for its escape cost.)

View File

@@ -2,5 +2,5 @@ Name:Fruit of Tizerus
ManaCost:B ManaCost:B
Types:Sorcery Types:Sorcery
A:SP$ LoseLife | Cost$ B | ValidTgts$ Player | TgtPrompt$ Select a player | LifeAmount$ 2 | SpellDescription$ Target player loses 2 life. A:SP$ LoseLife | Cost$ B | ValidTgts$ Player | TgtPrompt$ Select a player | LifeAmount$ 2 | SpellDescription$ Target player loses 2 life.
K:Escape:3 B ExileFromGrave<3/Card.Other> K:Escape:3 B ExileFromGrave<3/Card.Other/other>
Oracle:Target player loses 2 life.\nEscape — {3}{B}, Exile three other cards from your graveyard. (You may cast this card from your graveyard for its escape cost). Oracle:Target player loses 2 life.\nEscape — {3}{B}, Exile three other cards from your graveyard. (You may cast this card from your graveyard for its escape cost).

View File

@@ -2,5 +2,5 @@ Name:Satyr's Cunning
ManaCost:R ManaCost:R
Types:Sorcery Types:Sorcery
A:SP$ Token | Cost$ R | TokenAmount$ 1 | TokenScript$ r_1_1_satyr_noblock | TokenOwner$ You | LegacyImage$ r 1 1 satyr noblock thb | SpellDescription$ Create a 1/1 red Satyr creature token with "This creature can't block." A:SP$ Token | Cost$ R | TokenAmount$ 1 | TokenScript$ r_1_1_satyr_noblock | TokenOwner$ You | LegacyImage$ r 1 1 satyr noblock thb | SpellDescription$ Create a 1/1 red Satyr creature token with "This creature can't block."
K:Escape:2 R ExileFromGrave<2/Card.Other> K:Escape:2 R ExileFromGrave<2/Card.Other/other>
Oracle:Create a 1/1 red Satyr creature token with "This creature can't block."\nEscape — {2}{R}, Exile two other cards from your graveyard. (You may cast this card from your graveyard for its escape cost). Oracle:Create a 1/1 red Satyr creature token with "This creature can't block."\nEscape — {2}{R}, Exile two other cards from your graveyard. (You may cast this card from your graveyard for its escape cost).

View File

@@ -0,0 +1,11 @@
Name:Underworld Breach
ManaCost:1 R
Types:Enchantment
S:Mode$ Continuous | Affected$ Card.YouOwn+nonLand | AffectedZone$ Graveyard | AddKeyword$ Escape:CardManaCost ExileFromGrave<3/Card.Other/other> | Description$ Each nonland card in your graveyard has escape. The escape cost is equal to the card's mana cost plus exile three other cards from your graveyard.
T:Mode$ Phase | Phase$ End of Turn | TriggerZones$ Battlefield | Execute$ TrigSac | TriggerDescription$ At the beginning of the end step, sacrifice CARDNAME.
SVar:TrigSac:DB$ Sacrifice | SacValid$ Self
SVar:EndOfTurnLeavePlay:True
SVar:PlayMain1:TRUE
Oracle:Each nonland card in your graveyard has escape. The escape cost is equal to the card's mana cost plus exile three other cards from your graveyard.\nAt the beginning of the end step, sacrifice Underworld Breach.

View File

@@ -3,6 +3,6 @@ ManaCost:1 R
Types:Creature Elemental Hound Types:Creature Elemental Hound
PT:3/1 PT:3/1
K:CARDNAME attacks each combat if able. K:CARDNAME attacks each combat if able.
K:Escape:3 R ExileFromGrave<3/Card.Other> K:Escape:3 R ExileFromGrave<3/Card.Other/other>
K:etbCounter:P1P1:1:ValidCard$ Card.Self+escaped:CARDNAME escapes with a +1/+1 counter on it. K:etbCounter:P1P1:1:ValidCard$ Card.Self+escaped:CARDNAME escapes with a +1/+1 counter on it.
Oracle:Underworld Rage-Hound attacks each combat if able.\nEscape — {3}{R}, Exile three other cards from your graveyard. (You may cast this card from your graveyard for its escape cost).\nUnderworld Rage-Hound escapes with a +1/+1 counter on it. Oracle:Underworld Rage-Hound attacks each combat if able.\nEscape — {3}{R}, Exile three other cards from your graveyard. (You may cast this card from your graveyard for its escape cost).\nUnderworld Rage-Hound escapes with a +1/+1 counter on it.

View File

@@ -2,6 +2,6 @@ Name:Voracious Typhon
ManaCost:2 G G ManaCost:2 G G
Types:Creature Snake Beast Types:Creature Snake Beast
PT:4/4 PT:4/4
K:Escape:5 G G ExileFromGrave<4/Card.Other> K:Escape:5 G G ExileFromGrave<4/Card.Other/other>
K:etbCounter:P1P1:3:ValidCard$ Card.Self+escaped:CARDNAME escapes with three +1/+1 counters on it. K:etbCounter:P1P1:3:ValidCard$ Card.Self+escaped:CARDNAME escapes with three +1/+1 counters on it.
Oracle:Escape — {5}{G}{G}, Exile four other cards from your graveyard. (You may cast this card from your graveyard for its escape cost).\nVoracious Typhon escapes with three +1/+1 counters on it. Oracle:Escape — {5}{G}{G}, Exile four other cards from your graveyard. (You may cast this card from your graveyard for its escape cost).\nVoracious Typhon escapes with three +1/+1 counters on it.

View File

@@ -87,7 +87,7 @@ public class HumanPlay {
sa = AbilityUtils.addSpliceEffects(sa); sa = AbilityUtils.addSpliceEffects(sa);
if (sa.hasParam("Bestow")) { if (sa.isBestow()) {
source.animateBestow(); source.animateBestow();
} }

View File

@@ -159,7 +159,7 @@ public class HumanPlaySpellAbility {
if (!prerequisitesMet) { if (!prerequisitesMet) {
if (!ability.isTrigger()) { if (!ability.isTrigger()) {
rollbackAbility(fromZone, fromState, zonePosition, payment); rollbackAbility(fromZone, zonePosition, payment);
if (ability.getHostCard().isMadness()) { if (ability.getHostCard().isMadness()) {
// if a player failed to play madness cost, move the card to graveyard // if a player failed to play madness cost, move the card to graveyard
Card newCard = game.getAction().moveToGraveyard(c, null); Card newCard = game.getAction().moveToGraveyard(c, null);
@@ -244,14 +244,13 @@ public class HumanPlaySpellAbility {
} }
} }
private void rollbackAbility(final Zone fromZone, final CardStateName fromState, final int zonePosition, CostPayment payment) { private void rollbackAbility(final Zone fromZone, final int zonePosition, CostPayment payment) {
// cancel ability during target choosing // cancel ability during target choosing
final Game game = ability.getActivatingPlayer().getGame(); final Game game = ability.getActivatingPlayer().getGame();
if (fromZone != null) { // and not a copy if (fromZone != null) { // and not a copy
// add back to where it came from // add back to where it came from
game.getAction().moveTo(fromZone, ability.getHostCard(), zonePosition >= 0 ? Integer.valueOf(zonePosition) : null, null); game.getAction().moveTo(fromZone, ability.getHostCard(), zonePosition >= 0 ? Integer.valueOf(zonePosition) : null, null);
ability.getHostCard().setState(fromState, true);
} }
clearTargets(ability); clearTargets(ability);