From 25a620c6cd9ef131f9479acc4b1a2ffa8efa40b8 Mon Sep 17 00:00:00 2001 From: Hans Mackowiak Date: Wed, 13 Mar 2024 09:39:14 +0100 Subject: [PATCH] Keyword: Spree (#4815) * Keyword: Spree --------- Co-authored-by: tool4EvEr --- .../game/ability/effects/CharmEffect.java | 36 +++++++++++-------- .../src/main/java/forge/game/card/Card.java | 2 +- .../java/forge/game/card/CardFactoryUtil.java | 4 +++ .../java/forge/game/cost/CostAdjustment.java | 9 +++++ .../main/java/forge/game/keyword/Keyword.java | 1 + .../cardsfolder/upcoming/shifting_grift.txt | 9 +++++ 6 files changed, 45 insertions(+), 16 deletions(-) create mode 100644 forge-gui/res/cardsfolder/upcoming/shifting_grift.txt diff --git a/forge-game/src/main/java/forge/game/ability/effects/CharmEffect.java b/forge-game/src/main/java/forge/game/ability/effects/CharmEffect.java index 8293884e698..03d10f9c024 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/CharmEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/CharmEffect.java @@ -8,6 +8,7 @@ import com.google.common.collect.Lists; import forge.game.ability.AbilityUtils; import forge.game.ability.SpellAbilityEffect; import forge.game.card.Card; +import forge.game.cost.Cost; import forge.game.player.Player; import forge.game.spellability.AbilitySub; import forge.game.spellability.SpellAbility; @@ -83,21 +84,24 @@ public class CharmEffect extends SpellAbilityEffect { boolean limit = sa.hasParam("ActivationLimit"); boolean gameLimit = sa.hasParam("GameActivationLimit"); boolean oppChooses = "Opponent".equals(sa.getParam("Chooser")); + boolean spree = sa.hasParam("Spree"); StringBuilder sb = new StringBuilder(); sb.append(sa.getCostDescription()); - sb.append(oppChooses ? "An opponent chooses " : "Choose "); - if (isX) { - sb.append("X"); - } else if (num == min || num == Integer.MAX_VALUE) { - sb.append(num == 0 ? "up to that many" : Lang.getNumeral(min)); - } else if (min == 0 && num == sa.getParam("Choices").split(",").length) { - sb.append("any number "); - } else if (min == 0) { - sb.append("up to ").append(Lang.getNumeral(num)); - } else { - sb.append(Lang.getNumeral(min)).append(" or ").append(list.size() == 2 ? "both" : "more"); + if (!spree) { + sb.append(oppChooses ? "An opponent chooses " : "Choose "); + if (isX) { + sb.append("X"); + } else if (num == min || num == Integer.MAX_VALUE) { + sb.append(num == 0 ? "up to that many" : Lang.getNumeral(min)); + } else if (min == 0 && num == sa.getParam("Choices").split(",").length) { + sb.append("any number "); + } else if (min == 0) { + sb.append("up to ").append(Lang.getNumeral(num)); + } else { + sb.append(Lang.getNumeral(min)).append(" or ").append(list.size() == 2 ? "both" : "more"); + } } if (sa.hasParam("ChoiceRestriction")) { @@ -148,12 +152,14 @@ public class CharmEffect extends SpellAbilityEffect { if (!includeChosen) { sb.append(num == 1 ? " mode." : " modes."); } else if (!list.isEmpty()) { - if (!repeat && !additionalDesc && !limit && !gameLimit) { - sb.append(" \u2014"); + if (!spree) { + if (!repeat && !additionalDesc && !limit && !gameLimit) { + sb.append(" \u2014"); + } + sb.append("\r\n"); } - sb.append("\r\n"); for (AbilitySub sub : list) { - sb.append("\u2022 ").append(sub.getParam("SpellDescription")); + sb.append(spree ? "+" + new Cost(sub.getParam("SpreeCost"), false).toSimpleString() + " \u2014" : "\u2022 ").append(sub.getParam("SpellDescription")); sb.append("\r\n"); } sb.append("\r\n"); 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 c21c5e1a554..41ba8809b9e 100644 --- a/forge-game/src/main/java/forge/game/card/Card.java +++ b/forge-game/src/main/java/forge/game/card/Card.java @@ -2929,7 +2929,7 @@ public class Card extends GameEntity implements Comparable, IHasSVars { || keyword.equals("Improvise") || keyword.equals("Retrace") || keyword.equals("Undaunted") || keyword.equals("Cascade") || keyword.equals("Devoid") || keyword.equals("Lifelink") - || keyword.equals("Bargain") + || keyword.equals("Bargain")|| keyword.equals("Spree") || keyword.equals("Split second")) { sbBefore.append(keyword).append(" (").append(inst.getReminderText()).append(")"); sbBefore.append("\r\n\r\n"); 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 1b8cb1d75d3..d56595a4690 100644 --- a/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java +++ b/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java @@ -3893,6 +3893,10 @@ public class CardFactoryUtil { StaticAbility st = StaticAbility.create(effect, state.getCard(), state, intrinsic); st.setSVar("X", "Count$CardPower"); inst.addStaticAbility(st); + } else if (keyword.equals("Spree")) { + String effect = "Mode$ RaiseCost | ValidCard$ Card.Self | Type$ Spell | Secondary$ True | Amount$ Spree | EffectZone$ All" + + " | Description$ Spree (" + inst.getReminderText() + ")"; + inst.addStaticAbility(StaticAbility.create(effect, state.getCard(), state, intrinsic)); } else if (keyword.startsWith("Strive")) { final String[] k = keyword.split(":"); final String manacost = k[1]; diff --git a/forge-game/src/main/java/forge/game/cost/CostAdjustment.java b/forge-game/src/main/java/forge/game/cost/CostAdjustment.java index 1db2c0ae98c..37ac8f05a55 100644 --- a/forge-game/src/main/java/forge/game/cost/CostAdjustment.java +++ b/forge-game/src/main/java/forge/game/cost/CostAdjustment.java @@ -147,6 +147,15 @@ public class CostAdjustment { count += tc.size(); } --count; + } else if ("Spree".equals(amount)) { + SpellAbility sub = sa; + while (sub != null) { + if (sub.hasParam("SpreeCost")) { + Cost part = new Cost(sub.getParam("SpreeCost"), sa.isAbility(), sa.getHostCard().equals(hostCard)); + cost.mergeTo(part, count, sa); + } + sub = sub.getSubAbility(); + } } else { if (StringUtils.isNumeric(amount)) { count = Integer.parseInt(amount); 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 db5c132ed0f..c00a0e3cf35 100644 --- a/forge-game/src/main/java/forge/game/keyword/Keyword.java +++ b/forge-game/src/main/java/forge/game/keyword/Keyword.java @@ -170,6 +170,7 @@ public enum Keyword { SPECTACLE("Spectacle", KeywordWithCost.class, false, "You may cast this spell for its spectacle cost rather than its mana cost if an opponent lost life this turn."), SPLICE("Splice", KeywordWithCostAndType.class, false, "As you cast an %2$s spell, you may reveal this card from your hand and pay its splice cost. If you do, add this card's effects to that spell."), SPLIT_SECOND("Split second", SimpleKeyword.class, true, "As long as this spell is on the stack, players can't cast other spells or activate abilities that aren't mana abilities."), + SPREE("Spree", SimpleKeyword.class, true, "Choose one or more additional costs."), SQUAD("Squad", KeywordWithCost.class, false, "As an additional cost to cast this spell, you may pay %s any number of times. When this creature enters the battlefield, create that many tokens that are copies of it."), STARTING_INTENSITY("Starting intensity", KeywordWithAmount.class, true, null), STORM("Storm", SimpleKeyword.class, false, "When you cast this spell, copy it for each other spell that was cast before it this turn. You may choose new targets for the copies."), diff --git a/forge-gui/res/cardsfolder/upcoming/shifting_grift.txt b/forge-gui/res/cardsfolder/upcoming/shifting_grift.txt new file mode 100644 index 00000000000..6539074e995 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/shifting_grift.txt @@ -0,0 +1,9 @@ +Name:Shifting Grift +ManaCost:U U +Types:Sorcery +K:Spree +A:SP$ Charm | Choices$ DBCreature,DBArtifact,DBEnchantment | MinCharmNum$ 1 | CharmNum$ 3 | Spree$ True +SVar:DBCreature:DB$ ExchangeControl | SpreeCost$ 2 | ValidTgts$ Creature | TgtPrompt$ Select target creature | TargetMin$ 2 | TargetMax$ 2 | SpellDescription$ Exchange control of two target creatures +SVar:DBArtifact:DB$ ExchangeControl | SpreeCost$ 1 | ValidTgts$ Artifact | TgtPrompt$ Select target artifact | TargetMin$ 2 | TargetMax$ 2 | SpellDescription$ Exchange control of two target artifacts +SVar:DBEnchantment:DB$ ExchangeControl | SpreeCost$ 1 | ValidTgts$ Enchantment | TgtPrompt$ Select target enchantment | TargetMin$ 2 | TargetMax$ 2 | SpellDescription$ Exchange control of two target enchantments +Oracle:Spree (Choose one or more additional costs.)\n+{2} —Exchange control of two target creatures\n+{1} —Exchange control of two target artifacts\n+{1} —Exchange control of two target enchantments