diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtil.java b/forge-ai/src/main/java/forge/ai/ComputerUtil.java index f874561aaa1..ff73d875785 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtil.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtil.java @@ -109,7 +109,7 @@ public class ComputerUtil { if (chooseTargets != null) { chooseTargets.run(); } - if (sa.hasParam("Bestow")) { + if (sa.isBestow()) { sa.getHostCard().animateBestow(); } diff --git a/forge-ai/src/main/java/forge/ai/ability/AttachAi.java b/forge-ai/src/main/java/forge/ai/ability/AttachAi.java index bbf17fa1069..23648c94401 100644 --- a/forge-ai/src/main/java/forge/ai/ability/AttachAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/AttachAi.java @@ -1031,7 +1031,7 @@ public class AttachAi extends SpellAbilityAi { Card c = null; List magnetList = null; String stCheck = null; - if (attachSource.isAura() || sa.hasParam("Bestow")) { + if (attachSource.isAura() || sa.isBestow()) { stCheck = "EnchantedBy"; magnetList = CardLists.filter(list, new Predicate() { @Override diff --git a/forge-game/src/main/java/forge/game/GameActionUtil.java b/forge-game/src/main/java/forge/game/GameActionUtil.java index e1f237ecf0d..353111565fb 100644 --- a/forge-game/src/main/java/forge/game/GameActionUtil.java +++ b/forge-game/src/main/java/forge/game/GameActionUtil.java @@ -75,7 +75,7 @@ public final class GameActionUtil { Card source = sa.getHostCard(); final Game game = source.getGame(); - if (sa.isSpell()) { + if (sa.isSpell() && !source.isInZone(ZoneType.Battlefield)) { boolean lkicheck = false; // need to be done before so it works with Vivien and Zoetic Cavern @@ -88,7 +88,7 @@ public final class GameActionUtil { lkicheck = true; } - if (sa.hasParam("Bestow") && !source.isBestowed() && !source.isInZone(ZoneType.Battlefield)) { + if (sa.isBestow() && !source.isBestowed() && !source.isInZone(ZoneType.Battlefield)) { if (!source.isLKI()) { source = CardUtil.getLKICopy(source); } @@ -102,7 +102,7 @@ public final class GameActionUtil { } source.turnFaceDownNoUpdate(); lkicheck = true; - } else if (sa.isAdventure() && !source.isInZone(ZoneType.Battlefield)) { + } else if (sa.isAdventure()) { if (!source.isLKI()) { source = CardUtil.getLKICopy(source); } @@ -146,6 +146,7 @@ public final class GameActionUtil { if (lkicheck) { // double freeze tracker, so it doesn't update view game.getTracker().freeze(); + source.clearChangedCardKeywords(false); CardCollection preList = new CardCollection(source); game.getAction().checkStaticAbilities(false, Sets.newHashSet(source), preList); } @@ -207,6 +208,57 @@ public final class GameActionUtil { 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 if (lkicheck) { game.getAction().checkStaticAbilities(false); @@ -244,28 +296,6 @@ public final class GameActionUtil { 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; } diff --git a/forge-game/src/main/java/forge/game/ability/AbilityFactory.java b/forge-game/src/main/java/forge/game/ability/AbilityFactory.java index 9ff6c8d5dc8..a09664512c8 100644 --- a/forge-game/src/main/java/forge/game/ability/AbilityFactory.java +++ b/forge-game/src/main/java/forge/game/ability/AbilityFactory.java @@ -382,21 +382,10 @@ public final class AbilityFactory { * @param mapParams */ private static final void initializeParams(final SpellAbility sa, Map mapParams) { - if (mapParams.containsKey("Flashback")) { - sa.setFlashBackAbility(true); - } if (mapParams.containsKey("NonBasicSpell")) { sa.setBasicSpell(false); } - - if (mapParams.containsKey("Dash")) { - sa.setDash(true); - } - - if (mapParams.containsKey("Outlast")) { - sa.setOutlast(true); - } } /** diff --git a/forge-game/src/main/java/forge/game/card/Card.java b/forge-game/src/main/java/forge/game/card/Card.java index 05da5512d1f..31ab1a80a60 100644 --- a/forge-game/src/main/java/forge/game/card/Card.java +++ b/forge-game/src/main/java/forge/game/card/Card.java @@ -1649,7 +1649,7 @@ public class Card extends GameEntity implements Comparable { } else { 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(":"); sbLong.append(k[0]); if (k.length > 1) { @@ -1794,7 +1794,7 @@ public class Card extends GameEntity implements Comparable { || keyword.startsWith("Level up") || keyword.equals("Prowess") || keyword.startsWith("Eternalize") || keyword.startsWith("Reinforce") || keyword.startsWith("Champion") || keyword.startsWith("Prowl") || 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 parsing takes care of adding a proper description } else if (keyword.startsWith("CantBeBlockedByAmount")) { @@ -2189,7 +2189,8 @@ public class Card extends GameEntity implements Comparable { sbBefore.append(inst.getReminderText()); sbBefore.append("\r\n"); } 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 Cost cost = new Cost(k[1], false); @@ -3787,6 +3788,17 @@ public class Card extends GameEntity implements Comparable { 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 public final Collection getUnhiddenKeywords() { return getUnhiddenKeywords(currentState); diff --git a/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java b/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java index 636812c67cc..8c166d9addd 100644 --- a/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java +++ b/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java @@ -3812,7 +3812,7 @@ public class CardFactoryUtil { final String counters = k[1]; 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 + " | " + "ValidTgts$ Land.YouCtrl | TgtPrompt$ Select target land you control | Awaken$ True"; @@ -3827,8 +3827,7 @@ public class CardFactoryUtil { String desc = "Awaken " + counters + "—" + awakenCost.toSimpleString() + " (" + inst.getReminderText() + ")"; awakenSpell.setDescription(desc); - awakenSpell.setBasicSpell(false); - awakenSpell.setPayCosts(awakenCost); + awakenSpell.setAlternativeCost(AlternativeCost.Awaken); awakenSpell.setIntrinsic(intrinsic); inst.addSpellAbility(awakenSpell); } else if (keyword.startsWith("Bestow")) { @@ -3845,16 +3844,16 @@ public class CardFactoryUtil { sa.setDescription("Bestow " + ManaCostParser.parse(cost) + " (" + inst.getReminderText() + ")"); sa.setStackDescription("Bestow - " + card.getName()); - sa.setBasicSpell(false); + sa.setAlternativeCost(AlternativeCost.Bestow); sa.setIntrinsic(intrinsic); inst.addSpellAbility(sa); } else if (keyword.startsWith("Dash")) { final String[] k = keyword.split(":"); final String dashString = "SP$ PermanentCreature | Cost$ " + k[1] + " | StackDescription$ CARDNAME (Dash)" - + " | Dash$ True | NonBasicSpell$ True" - + " | SpellDescription$ Dash " + ManaCostParser.parse(k[1]) + " (" + inst.getReminderText() + ")"; + + " | Dash$ True | SpellDescription$ Dash " + ManaCostParser.parse(k[1]) + " (" + inst.getReminderText() + ")"; final SpellAbility newSA = AbilityFactory.getAbility(dashString, card); + newSA.setAlternativeCost(AlternativeCost.Dash); newSA.setIntrinsic(intrinsic); inst.addSpellAbility(newSA); } else if (keyword.startsWith("Emerge")) { @@ -3862,13 +3861,12 @@ public class CardFactoryUtil { String costStr = kw[1]; 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.getMapParams().put("Secondary", "True"); - newSA.setBasicSpell(false); - newSA.setIsEmerge(true); - newSA.setPayCosts(new Cost(costStr, false)); + newSA.setAlternativeCost(AlternativeCost.Emerge); + newSA.setDescription(sa.getDescription() + " (Emerge)"); newSA.setIntrinsic(intrinsic); inst.addSpellAbility(newSA); @@ -3938,33 +3936,6 @@ public class CardFactoryUtil { final SpellAbility newSA = AbilityFactory.getAbility(abilityStr.toString(), card); newSA.setIntrinsic(intrinsic); 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")) { final String[] kw = keyword.split(":"); String costStr = kw[1]; @@ -4011,8 +3982,7 @@ public class CardFactoryUtil { final StringBuilder sb = new StringBuilder(); sb.append(card.getName()).append(" (Evoked)"); newSA.setStackDescription(sb.toString()); - newSA.setBasicSpell(false); - newSA.setEvoke(true); + newSA.setAlternativeCost(AlternativeCost.Evoke); newSA.setIntrinsic(intrinsic); inst.addSpellAbility(newSA); } else if (keyword.startsWith("Fortify")) { @@ -4169,8 +4139,7 @@ public class CardFactoryUtil { abilityStr.append("AB$ PutCounter | Cost$ "); abilityStr.append(manacost); abilityStr.append(" T | Defined$ Self | CounterType$ P1P1 | CounterNum$ 1 "); - abilityStr.append("| SorcerySpeed$ True | Outlast$ True "); - abilityStr.append("| PrecostDesc$ Outlast"); + abilityStr.append("| SorcerySpeed$ True | PrecostDesc$ Outlast"); Cost cost = new Cost(manacost, true); if (!cost.isOnlyManaCost()) { //Something other than a mana cost abilityStr.append("—"); @@ -4182,6 +4151,7 @@ public class CardFactoryUtil { final SpellAbility sa = AbilityFactory.getAbility(abilityStr.toString(), card); sa.setIntrinsic(intrinsic); + sa.setAlternativeCost(AlternativeCost.Outlast); inst.addSpellAbility(sa); } else if (keyword.startsWith("Prowl")) { @@ -4200,9 +4170,8 @@ public class CardFactoryUtil { sb.append(newSA.getCostDescription()); sb.append("(").append(inst.getReminderText()).append(")"); newSA.setDescription(sb.toString()); - - newSA.setBasicSpell(false); - newSA.setProwl(true); + + newSA.setAlternativeCost(AlternativeCost.Prowl); newSA.setIntrinsic(intrinsic); inst.addSpellAbility(newSA); @@ -4247,8 +4216,7 @@ public class CardFactoryUtil { final Cost cost = new Cost(k[1], false); final SpellAbility newSA = card.getFirstSpellAbility().copyWithDefinedCost(cost); - newSA.setBasicSpell(false); - newSA.setSpectacle(true); + newSA.setAlternativeCost(AlternativeCost.Spectacle); String desc = "Spectacle " + cost.toSimpleString() + " (" + inst.getReminderText() + ")"; @@ -4262,8 +4230,7 @@ public class CardFactoryUtil { final Cost surgeCost = new Cost(k[1], false); final SpellAbility newSA = card.getFirstSpellAbility().copyWithDefinedCost(surgeCost); - newSA.setBasicSpell(false); - newSA.setSurged(true); + newSA.setAlternativeCost(AlternativeCost.Surge); String desc = "Surge " + surgeCost.toSimpleString() + " (" + inst.getReminderText() + ")"; @@ -4374,8 +4341,7 @@ public class CardFactoryUtil { sar.setInstantSpeed(true); newSA.getMapParams().put("Secondary", "True"); - newSA.setBasicSpell(false); - newSA.setIsOffering(true); + newSA.setAlternativeCost(AlternativeCost.Offering); newSA.setPayCosts(sa.getPayCosts()); newSA.setDescription(sa.getDescription() + " (" + offeringType + " offering)"); newSA.setIntrinsic(intrinsic); @@ -4410,7 +4376,7 @@ public class CardFactoryUtil { sb.append("| SpellDescription$ (").append(inst.getReminderText()).append(")"); SpellAbility sa = AbilityFactory.getAbility(sb.toString(), card); - sa.setIsCycling(true); + sa.setAlternativeCost(AlternativeCost.Cycling); sa.setIntrinsic(intrinsic); inst.addSpellAbility(sa); @@ -4434,7 +4400,7 @@ public class CardFactoryUtil { sb.append(" | SpellDescription$ (").append(inst.getReminderText()).append(")"); SpellAbility sa = AbilityFactory.getAbility(sb.toString(), card); - sa.setIsCycling(true); + sa.setAlternativeCost(AlternativeCost.Cycling); sa.setIntrinsic(intrinsic); inst.addSpellAbility(sa); diff --git a/forge-game/src/main/java/forge/game/spellability/AlternativeCost.java b/forge-game/src/main/java/forge/game/spellability/AlternativeCost.java new file mode 100644 index 00000000000..41f75125da0 --- /dev/null +++ b/forge-game/src/main/java/forge/game/spellability/AlternativeCost.java @@ -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; + +} diff --git a/forge-game/src/main/java/forge/game/spellability/Spell.java b/forge-game/src/main/java/forge/game/spellability/Spell.java index ab91ef02963..504316423ad 100644 --- a/forge-game/src/main/java/forge/game/spellability/Spell.java +++ b/forge-game/src/main/java/forge/game/spellability/Spell.java @@ -118,7 +118,7 @@ public abstract class Spell extends SpellAbility implements java.io.Serializable 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 // for the check the card does need to be animated // otherwise the StaticAbility will not found them diff --git a/forge-game/src/main/java/forge/game/spellability/SpellAbility.java b/forge-game/src/main/java/forge/game/spellability/SpellAbility.java index f92b5538483..66d83c5a483 100644 --- a/forge-game/src/main/java/forge/game/spellability/SpellAbility.java +++ b/forge-game/src/main/java/forge/game/spellability/SpellAbility.java @@ -101,20 +101,11 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit private int sourceTrigger = -1; private List triggerRemembered = Lists.newArrayList(); - // TODO use enum for the flags - private boolean flashBackAbility = false; + private AlternativeCost altCost = null; + 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 outlast = false; private boolean blessing = false; private Integer chapter = null; @@ -388,10 +379,7 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit } public boolean isCycling() { - return cycling; - } - public final void setIsCycling(final boolean b) { - cycling = b; + return this.isAlternativeCost(AlternativeCost.Cycling); } public Card getOriginalHost() { @@ -780,17 +768,14 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit } public boolean isBasicSpell() { - return basicSpell && !isFlashBackAbility() && !isBuyBackAbility(); + return basicSpell && this.altCost == null && getRootAbility().optionalCosts.isEmpty(); } public void setBasicSpell(final boolean basicSpell0) { basicSpell = basicSpell0; } - public void setFlashBackAbility(final boolean flashBackAbility0) { - flashBackAbility = flashBackAbility0; - } public boolean isFlashBackAbility() { - return flashBackAbility; + return this.isAlternativeCost(AlternativeCost.Flashback); } public void setBasicLandAbility(final boolean basicLandAbility0) { @@ -815,10 +800,7 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit } public boolean isOutlast() { - return outlast; - } - public void setOutlast(boolean outlast0) { - outlast = outlast0; + return isAlternativeCost(AlternativeCost.Outlast); } public boolean isBlessing() { @@ -1165,57 +1147,32 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit return false; } - public final boolean isDash() { - return dash; + public final boolean isBestow() { + 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() { - return escape; - } - - public final void setEscape(final boolean isEscape) { - escape = isEscape; + return isAlternativeCost(AlternativeCost.Escape); } public final boolean isEvoke() { - return evoke; - } - - public final void setEvoke(final boolean isEvoke) { - evoke = isEvoke; + return isAlternativeCost(AlternativeCost.Evoke); } public final boolean isProwl() { - return prowl; - } - - public final void setProwl(final boolean isProwl) { - prowl = isProwl; + return isAlternativeCost(AlternativeCost.Prowl); } public final boolean isSurged() { - if (surge) - return true; - SpellAbility parent = getParent(); - if (parent != null) { - return parent.isSurged(); - } - return false; - } - - public final void setSurged(final boolean isSurge) { - surge = isSurge; + return isAlternativeCost(AlternativeCost.Surge); } public final boolean isSpectacle() { - return spectacle; - } - - public final void setSpectacle(final boolean isSpectacle) { - spectacle = isSpectacle; + return isAlternativeCost(AlternativeCost.Spectacle); } public CardCollection getTappedForConvoke() { @@ -1234,10 +1191,7 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit } public boolean isEmerge() { - return emerge; - } - public void setIsEmerge(final boolean bEmerge) { - emerge = bEmerge; + return isAlternativeCost(AlternativeCost.Emerge); } public Card getSacrificedAsEmerge() { @@ -1251,10 +1205,7 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit } public boolean isOffering() { - return offering; - } - public void setIsOffering(final boolean bOffering) { - offering = bOffering; + return isAlternativeCost(AlternativeCost.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) { 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; + } } diff --git a/forge-game/src/main/java/forge/game/spellability/SpellAbilityRestriction.java b/forge-game/src/main/java/forge/game/spellability/SpellAbilityRestriction.java index bc3e6649da4..35c63ba7d0a 100644 --- a/forge-game/src/main/java/forge/game/spellability/SpellAbilityRestriction.java +++ b/forge-game/src/main/java/forge/game/spellability/SpellAbilityRestriction.java @@ -97,10 +97,6 @@ public class SpellAbilityRestriction extends SpellAbilityVariables { this.setZone(ZoneType.smartValueOf(params.get("ActivationZone"))); } - if (params.containsKey("Flashback")) { - this.setZone(ZoneType.Graveyard); - } - if (params.containsKey("SorcerySpeed")) { this.setSorcerySpeed(true); } @@ -202,7 +198,7 @@ public class SpellAbilityRestriction extends SpellAbilityVariables { Card cp = c; // 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 if (c.isInZone(ZoneType.Battlefield)) { return false; diff --git a/forge-game/src/main/java/forge/game/trigger/WrappedAbility.java b/forge-game/src/main/java/forge/game/trigger/WrappedAbility.java index 30b15229817..d28f2800191 100644 --- a/forge-game/src/main/java/forge/game/trigger/WrappedAbility.java +++ b/forge-game/src/main/java/forge/game/trigger/WrappedAbility.java @@ -341,11 +341,6 @@ public class WrappedAbility extends Ability { sa.setDescription(s); } - @Override - public void setFlashBackAbility(final boolean flashBackAbility) { - sa.setFlashBackAbility(flashBackAbility); - } - @Override public void setMultiKickerManaCost(final ManaCost cost) { sa.setMultiKickerManaCost(cost); @@ -546,4 +541,16 @@ public class WrappedAbility extends Ability { } // 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); + } } \ No newline at end of file diff --git a/forge-game/src/main/java/forge/game/zone/MagicStack.java b/forge-game/src/main/java/forge/game/zone/MagicStack.java index 56ceee1a273..0d20d1239fa 100644 --- a/forge-game/src/main/java/forge/game/zone/MagicStack.java +++ b/forge-game/src/main/java/forge/game/zone/MagicStack.java @@ -460,7 +460,7 @@ public class MagicStack /* extends MyObservable */ implements Iterable +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. 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. diff --git a/forge-gui/res/cardsfolder/upcoming/elspeth_suns_nemesis.txt b/forge-gui/res/cardsfolder/upcoming/elspeth_suns_nemesis.txt index 6a0a65454c8..994b6045f16 100755 --- a/forge-gui/res/cardsfolder/upcoming/elspeth_suns_nemesis.txt +++ b/forge-gui/res/cardsfolder/upcoming/elspeth_suns_nemesis.txt @@ -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$ 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. -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.\n−2: Create two 1/1 white Human Soldier creature tokens.\n−3: 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.) diff --git a/forge-gui/res/cardsfolder/upcoming/fruit_of_tizerus.txt b/forge-gui/res/cardsfolder/upcoming/fruit_of_tizerus.txt index c8a9a620b7e..9e8b2669363 100644 --- a/forge-gui/res/cardsfolder/upcoming/fruit_of_tizerus.txt +++ b/forge-gui/res/cardsfolder/upcoming/fruit_of_tizerus.txt @@ -2,5 +2,5 @@ Name:Fruit of Tizerus ManaCost:B Types:Sorcery 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). diff --git a/forge-gui/res/cardsfolder/upcoming/satyrs_cunning.txt b/forge-gui/res/cardsfolder/upcoming/satyrs_cunning.txt index 2b0ae2d7494..01f1fa91f57 100644 --- a/forge-gui/res/cardsfolder/upcoming/satyrs_cunning.txt +++ b/forge-gui/res/cardsfolder/upcoming/satyrs_cunning.txt @@ -2,5 +2,5 @@ Name:Satyr's Cunning ManaCost:R 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." -K:Escape:2 R ExileFromGrave<2/Card.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). \ No newline at end of file +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). diff --git a/forge-gui/res/cardsfolder/upcoming/underworld_breach.txt b/forge-gui/res/cardsfolder/upcoming/underworld_breach.txt new file mode 100644 index 00000000000..250d8e08620 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/underworld_breach.txt @@ -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. + + diff --git a/forge-gui/res/cardsfolder/upcoming/underworld_rage_hound.txt b/forge-gui/res/cardsfolder/upcoming/underworld_rage_hound.txt index 0a6acaae5a8..1218084c0d4 100644 --- a/forge-gui/res/cardsfolder/upcoming/underworld_rage_hound.txt +++ b/forge-gui/res/cardsfolder/upcoming/underworld_rage_hound.txt @@ -3,6 +3,6 @@ ManaCost:1 R Types:Creature Elemental Hound PT:3/1 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. 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. diff --git a/forge-gui/res/cardsfolder/upcoming/voracious_typhon.txt b/forge-gui/res/cardsfolder/upcoming/voracious_typhon.txt index 252dea34905..b552f64c618 100644 --- a/forge-gui/res/cardsfolder/upcoming/voracious_typhon.txt +++ b/forge-gui/res/cardsfolder/upcoming/voracious_typhon.txt @@ -2,6 +2,6 @@ Name:Voracious Typhon ManaCost:2 G G Types:Creature Snake Beast 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. 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. diff --git a/forge-gui/src/main/java/forge/player/HumanPlay.java b/forge-gui/src/main/java/forge/player/HumanPlay.java index 9fda7312c54..2e0a22155b4 100644 --- a/forge-gui/src/main/java/forge/player/HumanPlay.java +++ b/forge-gui/src/main/java/forge/player/HumanPlay.java @@ -87,7 +87,7 @@ public class HumanPlay { sa = AbilityUtils.addSpliceEffects(sa); - if (sa.hasParam("Bestow")) { + if (sa.isBestow()) { source.animateBestow(); } diff --git a/forge-gui/src/main/java/forge/player/HumanPlaySpellAbility.java b/forge-gui/src/main/java/forge/player/HumanPlaySpellAbility.java index 84c5bd0555d..e3663f41ad3 100644 --- a/forge-gui/src/main/java/forge/player/HumanPlaySpellAbility.java +++ b/forge-gui/src/main/java/forge/player/HumanPlaySpellAbility.java @@ -159,7 +159,7 @@ public class HumanPlaySpellAbility { if (!prerequisitesMet) { if (!ability.isTrigger()) { - rollbackAbility(fromZone, fromState, zonePosition, payment); + rollbackAbility(fromZone, zonePosition, payment); if (ability.getHostCard().isMadness()) { // if a player failed to play madness cost, move the card to graveyard 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 final Game game = ability.getActivatingPlayer().getGame(); if (fromZone != null) { // and not a copy // add back to where it came from game.getAction().moveTo(fromZone, ability.getHostCard(), zonePosition >= 0 ? Integer.valueOf(zonePosition) : null, null); - ability.getHostCard().setState(fromState, true); } clearTargets(ability);