diff --git a/forge-game/src/main/java/forge/game/ability/effects/CopySpellAbilityEffect.java b/forge-game/src/main/java/forge/game/ability/effects/CopySpellAbilityEffect.java index bfc2d185f93..093ac9c33c1 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/CopySpellAbilityEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/CopySpellAbilityEffect.java @@ -163,14 +163,7 @@ public void resolve(SpellAbility sa) { // extra case for Epic to remove the keyword and the last part of the SpellAbility if (sa.hasParam("Epic")) { - copy.getHostCard().getCurrentState().removeIntrinsicKeyword(Keyword.EPIC); - SpellAbility sub = copy; - while (sub.getSubAbility() != null && !sub.hasParam("Epic")) { - sub = sub.getSubAbility(); - } - if (sub != null) { - sub.getParent().setSubAbility(sub.getSubAbility()); - } + copy.getHostCard().removeIntrinsicKeyword(Keyword.EPIC); } copies.add(copy); diff --git a/forge-game/src/main/java/forge/game/ability/effects/PlayEffect.java b/forge-game/src/main/java/forge/game/ability/effects/PlayEffect.java index fd4ecaa4a3a..b575bb9da97 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/PlayEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/PlayEffect.java @@ -264,6 +264,10 @@ public void resolve(final SpellAbility sa) { final Zone zone = tgtCard.getZone(); tgtCard = Card.fromPaperCard(tgtCard.getPaperCard(), controller); + if (sa.hasParam("Paradigm")) { + tgtCard.removeIntrinsicKeyword(Keyword.PARADIGM); + } + tgtCard.setGamePieceType(GamePieceType.TOKEN); tgtCard.setZone(zone); // to fix the CMC 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 1f78851e2ff..552bdc34971 100644 --- a/forge-game/src/main/java/forge/game/card/Card.java +++ b/forge-game/src/main/java/forge/game/card/Card.java @@ -3206,7 +3206,7 @@ private StringBuilder abilityTextInstantSorcery(CardState state) { sbBefore.append("\r\n\r\n"); } else if (keyword.equals("Conspire") || keyword.equals("Epic") || keyword.equals("Suspend") || keyword.equals("Jump-start") - || keyword.equals("Fuse")) { + || keyword.equals("Fuse") || keyword.equals("Paradigm")) { sbAfter.append(keyword).append(" (").append(inst.getReminderText()).append(")"); sbAfter.append("\r\n"); } else if (keyword.startsWith("Casualty")) { @@ -5275,14 +5275,8 @@ public final void addIntrinsicKeywords(final Iterable s, boolean initTra } } - public final void removeIntrinsicKeyword(final String s) { - if (currentState.removeIntrinsicKeyword(s)) { - updateKeywords(); - } - } - - public final void removeIntrinsicKeyword(final KeywordInterface s) { - if (currentState.removeIntrinsicKeyword(s)) { + public final void removeIntrinsicKeyword(final Keyword k) { + if (currentState.removeIntrinsicKeyword(k)) { updateKeywords(); } } 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 1401bf02858..c94b5eed1bb 100644 --- a/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java +++ b/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java @@ -2846,7 +2846,7 @@ else if (part instanceof CostCollectEvidence) { // Epic does modify existing SA, and does not add new one // Add the Epic effect as a subAbility - String dbStr = "DB$ Effect | Triggers$ EpicTrigger | StaticAbilities$ EpicCantBeCast | Duration$ Permanent | Epic$ True"; + String dbStr = "DB$ Effect | Triggers$ EpicTrigger | StaticAbilities$ EpicCantBeCast | Duration$ Permanent | ConditionDefined$ Self | ConditionPresent$ Card.hasKeywordEpic"; final AbilitySub newSA = (AbilitySub) AbilityFactory.getAbility(dbStr, card); @@ -3271,6 +3271,25 @@ public void resolve() { newSA.setIntrinsic(intrinsic); newSA.setAlternativeCost(AlternativeCost.Overload); inst.addSpellAbility(newSA); + } else if (keyword.equals("Paradigm")) { + // Paradigm does modify existing SA, and does not add new one + + // Add the Paradigm effect as a subAbility + String abExile = "DB$ ChangeZone | Defined$ Self | Origin$ Stack | Destination$ Exile"; + final AbilitySub saExile = (AbilitySub) AbilityFactory.getAbility(abExile, card); + + String dbStr = "DB$ Effect | Triggers$ ParadigmTrigger | Duration$ Permanent | ConditionDefined$ Self | ConditionPresent$ Card.hasKeywordParadigm"; + final AbilitySub newSA = (AbilitySub) AbilityFactory.getAbility(dbStr, card); + + newSA.setSVar("ParadigmTrigger", "Mode$ Phase | Phase$ Main1 | ValidPlayer$ You | OptionalDecider$ You | Execute$ ParadigmCopy | TriggerDescription$ Paradigm (" + inst.getReminderText() + ")"); + newSA.setSVar("ParadigmCopy", "DB$ Play | Defined$ EffectSource | ValidSA$ Spell | ZoneRegardless$ True | WithoutManaCost$ True | Optional$ True | CopyCard$ True | Paradigm$ True"); + + saExile.setSubAbility(newSA); + + final SpellAbility origSA = card.getFirstSpellAbility(); + + // append to original SA + origSA.appendSubAbility(saExile); } else if (keyword.startsWith("Plot")) { final String[] k = keyword.split(":"); final String manacost = k[1]; diff --git a/forge-game/src/main/java/forge/game/keyword/Keyword.java b/forge-game/src/main/java/forge/game/keyword/Keyword.java index 33a2edd23ac..c98ac17ecd6 100644 --- a/forge-game/src/main/java/forge/game/keyword/Keyword.java +++ b/forge-game/src/main/java/forge/game/keyword/Keyword.java @@ -137,6 +137,7 @@ public enum Keyword { OFFERING("Offering", KeywordWithType.class, false, "You may cast this card any time you could cast an instant by sacrificing a %1$s and paying the difference in mana costs between this and the sacrificed %1$s. Mana cost includes color."), OFFSPRING("Offspring", KeywordWithCost.class, false, "You may pay an additional %s as you cast this spell. If you do, when this creature enters, create a 1/1 token copy of it."), OVERLOAD("Overload", KeywordWithCost.class, false, "You may cast this spell for its overload cost. If you do, change its text by replacing all instances of \"target\" with \"each.\""), + PARADIGM("Paradigm", SimpleKeyword.class, false, "Then exile this spell. After you first resolve a spell with this name, you may cast a copy of it from exile without paying its mana cost at the beginning of each of your first main phases."), PARTNER("Partner", Partner.class, true, "You can have two commanders if both have partner."), PARTNER_WITH("Partner with", KeywordWithType.class, false, "When this creature enters, target player may put %s into their hand from their library, then shuffle."), PERSIST("Persist", SimpleKeyword.class, false, "When this creature dies, if it had no -1/-1 counters on it, return it to the battlefield under its owner's control with a -1/-1 counter on it."), 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 6f3418552de..a782f8c1ae0 100644 --- a/forge-game/src/main/java/forge/game/spellability/SpellAbility.java +++ b/forge-game/src/main/java/forge/game/spellability/SpellAbility.java @@ -609,14 +609,6 @@ public boolean isCumulativeUpkeep() { return hasParam("CumulativeUpkeep"); } - public boolean isEpic() { - AbilitySub sub = this.getSubAbility(); - while (sub != null && !sub.hasParam("Epic")) { - sub = sub.getSubAbility(); - } - return sub != null && sub.hasParam("Epic"); - } - public boolean isBargained() { return isOptionalCostPaid(OptionalCost.Bargain); } @@ -992,7 +984,7 @@ public void resetOnceResolved() { //resetPaidHash(); // FIXME: if uncommented, breaks Dragon Presence, e.g. Orator of Ojutai + revealing a Dragon from hand. // Is it truly necessary at this point? The paid hash seems to be reset on all SA instance operations. // Epic spell keeps original targets - if (!isEpic()) { + if (!this.getHostCard().hasKeyword(Keyword.EPIC)) { resetTargets(); } resetTriggeringObjects(); diff --git a/forge-gui/res/cardsfolder/upcoming/decorum_dissertation.txt b/forge-gui/res/cardsfolder/upcoming/decorum_dissertation.txt new file mode 100644 index 00000000000..53a450c0904 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/decorum_dissertation.txt @@ -0,0 +1,7 @@ +Name:Decorum Dissertation +ManaCost:3 B B +Types:Sorcery Lesson +A:SP$ Draw | NumCards$ 2 | ValidTgts$ Player | SubAbility$ DBLoseLife | SpellDescription$ Target player draws two cards and loses 2 life. +SVar:DBLoseLife:DB$ LoseLife | LifeAmount$ 2 | Defined$ Targeted +K:Paradigm +Oracle:Target player draws two cards and loses 2 life.\nParadigm (Then exile this spell. After you first resolve a spell with this name, you may cast a copy of it from exile without paying its mana cost at the beginning of each of your first main phases.)