From 15ef415cb3808804c5b1880d15b4ab218279a78c Mon Sep 17 00:00:00 2001 From: Northmoc Date: Mon, 7 Nov 2022 12:23:30 -0500 Subject: [PATCH 1/5] combat_thresher.txt --- forge-gui/res/cardsfolder/upcoming/combat_thresher.txt | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 forge-gui/res/cardsfolder/upcoming/combat_thresher.txt diff --git a/forge-gui/res/cardsfolder/upcoming/combat_thresher.txt b/forge-gui/res/cardsfolder/upcoming/combat_thresher.txt new file mode 100644 index 00000000000..1544b419089 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/combat_thresher.txt @@ -0,0 +1,10 @@ +Name:Combat Thresher +ManaCost:7 +Types:Artifact Creature Construct +PT:3/3 +K:Prototype:2 W:1:1 +K:Double Strike +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigDraw | TriggerDescription$ When CARDNAME enters the battlefield, draw a card. +SVar:TrigDraw:DB$ Draw +DeckHints:Color$White +Oracle:Prototype {2}{W} — 1/1 (You may cast this spell with different mana cost, color, and size. It keeps its abilities and types.)\nDouble strike\nWhen Combat Thresher enters the battlefield, draw a card. From 0d08f7991616b7932f505c367a54d38a7426a175 Mon Sep 17 00:00:00 2001 From: Northmoc Date: Mon, 7 Nov 2022 12:25:05 -0500 Subject: [PATCH 2/5] Prototype keyword v1 --- .../src/main/java/forge/game/GameAction.java | 3 +++ .../src/main/java/forge/game/card/Card.java | 2 +- .../java/forge/game/card/CardFactory.java | 18 +++++++++++-- .../java/forge/game/card/CardFactoryUtil.java | 27 +++++++++++++++++++ .../main/java/forge/game/keyword/Keyword.java | 1 + .../game/spellability/AlternativeCost.java | 1 + 6 files changed, 49 insertions(+), 3 deletions(-) diff --git a/forge-game/src/main/java/forge/game/GameAction.java b/forge-game/src/main/java/forge/game/GameAction.java index 6f4961a9c77..fa885dcccc2 100644 --- a/forge-game/src/main/java/forge/game/GameAction.java +++ b/forge-game/src/main/java/forge/game/GameAction.java @@ -851,6 +851,9 @@ public class GameAction { } public final Card moveToStack(final Card c, SpellAbility cause, Map params) { Card result = moveTo(game.getStackZone(), c, cause, params); + if (cause.hasParam("Prototype")) { + result.addCloneState(CardFactory.getCloneStates(c, c, cause), game.getNextTimestamp()); + } if (cause != null && cause.isSpell() && result.equals(cause.getHostCard())) { result.setSplitStateToPlayAbility(cause); 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 b56437f1a58..cd9a0e03cbe 100644 --- a/forge-game/src/main/java/forge/game/card/Card.java +++ b/forge-game/src/main/java/forge/game/card/Card.java @@ -2284,7 +2284,7 @@ public class Card extends GameEntity implements Comparable, IHasSVars { || keyword.startsWith("Transfigure") || keyword.startsWith("Aura swap") || keyword.startsWith("Cycling") || keyword.startsWith("TypeCycling") || keyword.startsWith("Encore") || keyword.startsWith("Mutate") || keyword.startsWith("Dungeon") - || keyword.startsWith("Class") || keyword.startsWith("Blitz") + || keyword.startsWith("Class") || keyword.startsWith("Blitz") || keyword.startsWith("Prototype") || keyword.startsWith("Specialize") || keyword.equals("Ravenous")) { // keyword parsing takes care of adding a proper description } else if(keyword.startsWith("Read ahead")) { diff --git a/forge-game/src/main/java/forge/game/card/CardFactory.java b/forge-game/src/main/java/forge/game/card/CardFactory.java index 0dd9ba86c26..e6eec57b756 100644 --- a/forge-game/src/main/java/forge/game/card/CardFactory.java +++ b/forge-game/src/main/java/forge/game/card/CardFactory.java @@ -24,6 +24,7 @@ import forge.ImageKeys; import forge.StaticData; import forge.card.*; import forge.card.mana.ManaCost; +import forge.card.mana.ManaCostParser; import forge.game.CardTraitBase; import forge.game.Game; import forge.game.ability.AbilityFactory; @@ -688,6 +689,14 @@ public class CardFactory { colors = ColorSet.fromNames(sa.getParam("SetColor").split(",")); } + if (sa.hasParam("SetColorByManaCost")) { + if (sa.hasParam("SetManaCost")) { + colors = ColorSet.fromManaCost(new ManaCost(new ManaCostParser(sa.getParam("SetManaCost")))); + } else { + colors = ColorSet.fromManaCost(host.getManaCost()); + } + } + // TODO handle Volrath's Shapeshifter if (in.isFaceDown()) { @@ -736,7 +745,7 @@ public class CardFactory { state.addColor(colors.getColor()); } - if (sa.hasParam("SetColor")) { + if (sa.hasParam("SetColor") || sa.hasParam("SetColorByManaCost")) { state.setColor(colors.getColor()); } @@ -777,6 +786,10 @@ public class CardFactory { state.setManaCost(ManaCost.NO_COST); } + if (sa.hasParam("SetManaCost")) { + state.setManaCost(new ManaCost(new ManaCostParser(sa.getParam("SetManaCost")))); + } + // SVars to add to clone if (sa.hasParam("AddSVars") || sa.hasParam("GainTextSVars")) { final String str = sa.getParamOrDefault("GainTextSVars", sa.getParam("AddSVars")); @@ -900,7 +913,8 @@ public class CardFactory { if (sa.hasParam("SetCreatureTypes")) { state.removeIntrinsicKeyword("Changeling"); } - if (sa.hasParam("SetColor") || sa.hasParam("Embalm") || sa.hasParam("Eternalize")) { + if (sa.hasParam("SetColor") || sa.hasParam("Embalm") || sa.hasParam("Eternalize") + || sa.hasParam("SetColorByManaCost")) { state.removeIntrinsicKeyword("Devoid"); } } 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 6f8bf19d7f4..b283ffd7c69 100644 --- a/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java +++ b/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java @@ -3189,6 +3189,33 @@ public class CardFactoryUtil { sa.setIntrinsic(intrinsic); sa.setAlternativeCost(AlternativeCost.Outlast); inst.addSpellAbility(sa); + } else if (keyword.startsWith("Prototype")) { + final String[] k = keyword.split(":"); + if (k.length < 4) { + System.err.println("Malformed Prototype entry! - Card: " + card.toString()); + return; + } + + final Cost protoCost = new Cost(k[1], false); + final SpellAbility newSA = card.getFirstSpellAbility().copyWithDefinedCost(protoCost); + newSA.putParam("SetManaCost", k[1]); + newSA.putParam("SetColorByManaCost", "True"); + newSA.putParam("SetPower", k[2]); + newSA.putParam("SetToughness", k[3]); + newSA.putParam("PrecostDesc", "Prototype"); + newSA.putParam("Prototype", "True"); + newSA.putParam("CostDesc", ManaCostParser.parse(k[1])); + + // makes new SpellDescription + final StringBuilder sb = new StringBuilder(); + sb.append(newSA.getCostDescription()).append("[").append(k[2]).append("/").append(k[3]).append("] "); + sb.append("(").append(inst.getReminderText()).append(")"); + newSA.setDescription(sb.toString()); + + newSA.setAlternativeCost(AlternativeCost.Prototype); + + newSA.setIntrinsic(intrinsic); + inst.addSpellAbility(newSA); } else if (keyword.startsWith("Prowl")) { final String[] k = keyword.split(":"); final Cost prowlCost = new Cost(k[1], false); 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 570cb5ee228..4ced94b48b0 100644 --- a/forge-game/src/main/java/forge/game/keyword/Keyword.java +++ b/forge-game/src/main/java/forge/game/keyword/Keyword.java @@ -135,6 +135,7 @@ public enum Keyword { POISONOUS("Poisonous", KeywordWithAmount.class, false, "Whenever this creature deals combat damage to a player, that player gets {%d:poison counter}."), PRESENCE("Presence", KeywordWithType.class, false, "As an additional cost to cast this spell, you may reveal a %s card from your hand."), PROTECTION("Protection", Protection.class, false, "This creature can't be blocked, targeted, dealt damage, or equipped/enchanted by %s."), + PROTOTYPE("Prototype", KeywordWithCost.class, false, "You may cast this spell with different mana cost, color, and size. It keeps its abilities and types."), PROVOKE("Provoke", SimpleKeyword.class, false, "Whenever this creature attacks, you may have target creature defending player controls untap and block it if able."), PROWESS("Prowess", SimpleKeyword.class, false, "Whenever you cast a noncreature spell, this creature gets +1/+1 until end of turn."), PROWL("Prowl", KeywordWithCost.class, false, "You may pay %s rather than pay this spell's mana cost if a player was dealt combat damage this turn by a source that, at the time it dealt that damage, was under your control and had any of this spell's creature types."), diff --git a/forge-game/src/main/java/forge/game/spellability/AlternativeCost.java b/forge-game/src/main/java/forge/game/spellability/AlternativeCost.java index b33858ff990..4e2d2d5f36d 100644 --- a/forge-game/src/main/java/forge/game/spellability/AlternativeCost.java +++ b/forge-game/src/main/java/forge/game/spellability/AlternativeCost.java @@ -17,6 +17,7 @@ public enum AlternativeCost { Mutate, Offering, Outlast, // ActivatedAbility + Prototype, Prowl, Spectacle, Surge; From 937e2e648a7ccc2f45c6b5f4f21c0b581392aa02 Mon Sep 17 00:00:00 2001 From: Northmoc Date: Mon, 7 Nov 2022 15:17:43 -0500 Subject: [PATCH 3/5] cradle_clearcutter.txt --- .../res/cardsfolder/upcoming/cradle_clearcutter.txt | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 forge-gui/res/cardsfolder/upcoming/cradle_clearcutter.txt diff --git a/forge-gui/res/cardsfolder/upcoming/cradle_clearcutter.txt b/forge-gui/res/cardsfolder/upcoming/cradle_clearcutter.txt new file mode 100644 index 00000000000..1a4dd6bef21 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/cradle_clearcutter.txt @@ -0,0 +1,9 @@ +Name:Cradle Clearcutter +ManaCost:6 +Types:Artifact Creature Golem +PT:3/6 +K:Prototype:2 G:1:3 +A:AB$ Mana | Cost$ T | Produced$ G | Amount$ X | SpellDescription$ Add an amount of {G} equal to CARDNAME's power. +SVar:X:Count$CardPower +DeckHints:Color$Green +Oracle:Prototype {2}{G} - 1/3 (You may cast this spell with different mana cost, color, and size. It keeps its abilities and types.)\n{T}: Add an amount of {G} equal to Cradle Clearcutter's power. From 88e2d1de6520a8f1b549eab6e553bce141e7c2d0 Mon Sep 17 00:00:00 2001 From: Northmoc Date: Mon, 7 Nov 2022 15:18:16 -0500 Subject: [PATCH 4/5] goring_warplow.txt --- forge-gui/res/cardsfolder/upcoming/goring_warplow.txt | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 forge-gui/res/cardsfolder/upcoming/goring_warplow.txt diff --git a/forge-gui/res/cardsfolder/upcoming/goring_warplow.txt b/forge-gui/res/cardsfolder/upcoming/goring_warplow.txt new file mode 100644 index 00000000000..01521347827 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/goring_warplow.txt @@ -0,0 +1,8 @@ +Name:Goring Warplow +ManaCost:6 +Types:Artifact Creature Construct +PT:5/4 +K:Prototype:1 B:1:1 +K:Deathtouch +DeckHints:Color$Black +Oracle:Prototype {1}{B} — 1/1 (You may cast this spell with different mana cost, color, and size. It keeps its abilities and types.)\nDeathtouch From adde6a942edc1029ad20b48f33a45848d7a5ff99 Mon Sep 17 00:00:00 2001 From: Northmoc Date: Mon, 7 Nov 2022 15:26:17 -0500 Subject: [PATCH 5/5] prototype v2 --- forge-game/src/main/java/forge/game/GameAction.java | 3 --- forge-game/src/main/java/forge/game/card/Card.java | 9 ++++++++- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/forge-game/src/main/java/forge/game/GameAction.java b/forge-game/src/main/java/forge/game/GameAction.java index fa885dcccc2..6f4961a9c77 100644 --- a/forge-game/src/main/java/forge/game/GameAction.java +++ b/forge-game/src/main/java/forge/game/GameAction.java @@ -851,9 +851,6 @@ public class GameAction { } public final Card moveToStack(final Card c, SpellAbility cause, Map params) { Card result = moveTo(game.getStackZone(), c, cause, params); - if (cause.hasParam("Prototype")) { - result.addCloneState(CardFactory.getCloneStates(c, c, cause), game.getNextTimestamp()); - } if (cause != null && cause.isSpell() && result.equals(cause.getHostCard())) { result.setSplitStateToPlayAbility(cause); 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 cd9a0e03cbe..67e917658cc 100644 --- a/forge-game/src/main/java/forge/game/card/Card.java +++ b/forge-game/src/main/java/forge/game/card/Card.java @@ -222,6 +222,7 @@ public class Card extends GameEntity implements Comparable, IHasSVars { private long transformedTimestamp = 0; private long convertedTimestamp = 0; private long mutatedTimestamp = -1; + private long prototypeTimestamp = -1; private int timesMutated = 0; private boolean tributed = false; private boolean embalmed = false; @@ -988,7 +989,8 @@ public class Card extends GameEntity implements Comparable, IHasSVars { } public boolean isCloned() { - return !clonedStates.isEmpty() && clonedStates.lastEntry().getKey() != mutatedTimestamp; + return !clonedStates.isEmpty() && clonedStates.lastEntry().getKey() != mutatedTimestamp + && clonedStates.lastEntry().getKey() != prototypeTimestamp; } public final CardCollectionView getDevouredCards() { @@ -6494,6 +6496,11 @@ public class Card extends GameEntity implements Comparable, IHasSVars { if (sa.isBestow()) { animateBestow(); } + if (sa.hasParam("Prototype")) { + Long next = game.getNextTimestamp(); + addCloneState(CardFactory.getCloneStates(this, this, sa), next); + prototypeTimestamp = next; + } CardStateName stateName = sa.getCardStateName(); if (stateName != null && hasState(stateName) && this.getCurrentStateName() != stateName) { setState(stateName, true);