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 d5a5727b2e0..c2df8c3f5c5 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() { @@ -2284,7 +2286,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")) { @@ -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); 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 7100cfb2740..724e469c1df 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; 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. 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. 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