From ee418ded60ef31a217a7b3d83354f80e3fe038d0 Mon Sep 17 00:00:00 2001 From: Northmoc <103371817+Northmoc@users.noreply.github.com> Date: Thu, 6 Apr 2023 22:32:41 -0400 Subject: [PATCH] MOM: Backup keyword and cards (#2799) * Backup keyword setup * Backup cards * KeywordWithAmount spacing * mirror_shield_hoplite.txt and support * fix for KeywordWithAmount * doomskar_warrior.txt fearless_skald.txt * add PlayMain1 * typo fix * add logic to ComputerUtil.castPermanentInMain1 instead of using PlayMain1 on cardscripts --- .../src/main/java/forge/ai/ComputerUtil.java | 9 ++++++ .../src/main/java/forge/game/ForgeScript.java | 2 ++ .../src/main/java/forge/game/card/Card.java | 12 ++++++-- .../java/forge/game/card/CardFactoryUtil.java | 29 +++++++++++++++++-- .../main/java/forge/game/keyword/Keyword.java | 1 + .../forge/game/keyword/KeywordWithAmount.java | 4 +-- .../forge/game/spellability/SpellAbility.java | 4 +++ .../upcoming/archpriest_of_shadows.txt | 13 +++++++++ .../upcoming/boon_bringer_valkyrie.txt | 11 +++++++ .../cardsfolder/upcoming/cragsmasher_yeti.txt | 10 +++++++ .../cardsfolder/upcoming/doomskar_warrior.txt | 13 +++++++++ .../cardsfolder/upcoming/fearless_skald.txt | 9 ++++++ .../upcoming/mirror_shield_hoplite.txt | 9 ++++++ .../upcoming/streetwise_negotiatior.txt | 10 +++++++ .../upcoming/voldaren_thrilseeker.txt | 11 +++++++ 15 files changed, 141 insertions(+), 6 deletions(-) create mode 100644 forge-gui/res/cardsfolder/upcoming/archpriest_of_shadows.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/boon_bringer_valkyrie.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/cragsmasher_yeti.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/doomskar_warrior.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/fearless_skald.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/mirror_shield_hoplite.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/streetwise_negotiatior.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/voldaren_thrilseeker.txt diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtil.java b/forge-ai/src/main/java/forge/ai/ComputerUtil.java index 91bf2bb9334..64307ba3754 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtil.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtil.java @@ -1074,6 +1074,15 @@ public class ComputerUtil { } } + // cast Backup creatures in main 1 to pump attackers + if (cardState.hasKeyword(Keyword.BACKUP)) { + for (Card potentialAtkr: ai.getCreaturesInPlay()) { + if (ComputerUtilCard.doesCreatureAttackAI(ai, potentialAtkr)) { + return true; + } + } + } + // try not to cast Raid creatures in main 1 if an attack is likely if ("Count$AttackersDeclared".equals(card.getSVar("RaidTest")) && !cardState.hasKeyword(Keyword.HASTE)) { for (Card potentialAtkr: ai.getCreaturesInPlay()) { diff --git a/forge-game/src/main/java/forge/game/ForgeScript.java b/forge-game/src/main/java/forge/game/ForgeScript.java index c80b659dc4f..2ecb44a56e8 100644 --- a/forge-game/src/main/java/forge/game/ForgeScript.java +++ b/forge-game/src/main/java/forge/game/ForgeScript.java @@ -193,6 +193,8 @@ public class ForgeScript { } else if (property.equals("hasTapCost")) { Cost cost = sa.getPayCosts(); return cost != null && cost.hasTapCost(); + } else if (property.equals("Backup")) { + return sa.isBackup(); } else if (property.equals("Blitz")) { return sa.isBlitz(); } else if (property.equals("Buyback")) { 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 d31a52ff2f6..8088c700d5b 100644 --- a/forge-game/src/main/java/forge/game/card/Card.java +++ b/forge-game/src/main/java/forge/game/card/Card.java @@ -2303,10 +2303,18 @@ public class Card extends GameEntity implements Comparable, IHasSVars { || keyword.startsWith("Specialize") || keyword.equals("Ravenous") || keyword.equals("For Mirrodin")) { // keyword parsing takes care of adding a proper description - } else if(keyword.startsWith("Read ahead")) { + } else if (keyword.startsWith("Read ahead")) { sb.append(Localizer.getInstance().getMessage("lblReadAhead")).append(" (").append(Localizer.getInstance().getMessage("lblReadAheadDesc")); sb.append(" ").append(Localizer.getInstance().getMessage("lblSagaFooter")).append(" ").append(TextUtil.toRoman(getFinalChapterNr())).append("."); sb.append(")").append("\r\n\r\n"); + } else if (keyword.startsWith("Backup")) { + final String[] k = keyword.split(":"); + sb.append(k[0]).append(" ").append(k[1]).append(" ("); + String remStr = inst.getReminderText(); + if (k[2].endsWith("s")) { + remStr = remStr.replace("ability", "abilities"); + } + sb.append(remStr).append(")"); } else if (keyword.startsWith("MayEffectFromOpening")) { final String[] k = keyword.split(":"); // need to get SpellDescription from Svar @@ -2325,7 +2333,7 @@ public class Card extends GameEntity implements Comparable, IHasSVars { sbLong.append("\r\n"); } - if (keyword.equals("Flash")) { + if (keyword.equals("Flash") || keyword.startsWith("Backup")) { sb.append("\r\n\r\n"); i = 0; } else { 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 de058f5f264..605577a85ab 100644 --- a/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java +++ b/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java @@ -839,6 +839,31 @@ public class CardFactoryUtil { sa.setBlessing(true); } } + } else if (keyword.startsWith("Backup")) { + final String[] k = keyword.split(":"); + final String magnitude = k[1]; + final String backupVar = card.getSVar(k[2]); + + String descStr = "Backup " + magnitude; + + final String trigStr = "Mode$ ChangesZone | Destination$ Battlefield | ValidCard$ Card.Self | " + + "Secondary$ True | TriggerDescription$ " + descStr; + + final String putCounter = "DB$ PutCounter | ValidTgts$ Creature | CounterNum$ " + magnitude + + " | CounterType$ P1P1 | Backup$ True"; + + final String addAbility = backupVar + " | ConditionDefined$ Targeted | " + + "ConditionPresent$ Card.Other | Defined$ Targeted"; + + SpellAbility sa = AbilityFactory.getAbility(putCounter, card); + AbilitySub backupSub = (AbilitySub) AbilityFactory.getAbility(addAbility, card); + sa.setSubAbility(backupSub); + + final Trigger trigger = TriggerHandler.parseTrigger(trigStr, card, intrinsic); + sa.setIntrinsic(intrinsic); + trigger.setOverridingAbility(sa); + + inst.addTrigger(trigger); } else if (keyword.equals("Battle cry")) { final String trig = "Mode$ Attacks | ValidCard$ Card.Self | TriggerZones$ Battlefield | Secondary$ True " + " | TriggerDescription$ " + keyword + " (" + inst.getReminderText() + ")"; @@ -1151,7 +1176,7 @@ public class CardFactoryUtil { } else if (keyword.equals("Extort")) { final String extortTrigger = "Mode$ SpellCast | ValidCard$ Card | ValidActivatingPlayer$ You | " + "TriggerZones$ Battlefield | Secondary$ True" - + " | TriggerDescription$ Extort ("+ inst.getReminderText() +")"; + + " | TriggerDescription$ Extort (" + inst.getReminderText() + ")"; final String loseLifeStr = "AB$ LoseLife | Cost$ WB | Defined$ Player.Opponent | LifeAmount$ 1"; final String gainLifeStr = "DB$ GainLife | Defined$ You | LifeAmount$ AFLifeLost"; @@ -2687,7 +2712,7 @@ public class CardFactoryUtil { if (keyword.startsWith("Alternative Cost") && !host.isLand()) { final String[] kw = keyword.split(":"); String costStr = kw[1]; - for (SpellAbility sa: host.getBasicSpells()) { + for (SpellAbility sa : host.getBasicSpells()) { if (costStr.equals("ConvertedManaCost")) { costStr = Integer.toString(host.getCMC()); } 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 22073210873..ca735fef027 100644 --- a/forge-game/src/main/java/forge/game/keyword/Keyword.java +++ b/forge-game/src/main/java/forge/game/keyword/Keyword.java @@ -28,6 +28,7 @@ public enum Keyword { ASSIST("Assist", SimpleKeyword.class, true, "Another player can pay up to %s of this spell's cost."), AURA_SWAP("Aura swap", KeywordWithCost.class, false, "%s: You may exchange this Aura with an Aura card in your hand."), AWAKEN("Awaken", KeywordWithCostAndAmount.class, false, "If you cast this spell for %s, also put {%d:+1/+1 counter} on target land you control and it becomes a 0/0 Elemental creature with haste. It's still a land."), + BACKUP("Backup", KeywordWithAmount.class, false, "When this creature enters the battlefield, put {%1$d:+1/+1 counter} on target creature. If that's another creature, it gains the following ability until end of turn."), BANDING("Banding", SimpleKeyword.class, true, "Any creatures with banding, and up to one without, can attack in a band. Bands are blocked as a group. If any creatures with banding you control are blocking or being blocked by a creature, you divide that creature's combat damage, not its controller, among any of the creatures it's being blocked by or is blocking."), BATTLE_CRY("Battle cry", SimpleKeyword.class, false, "Whenever this creature attacks, each other attacking creature gets +1/+0 until end of turn."), BESTOW("Bestow", KeywordWithCost.class, false, "If you cast this card for its bestow cost, it's an Aura spell with enchant creature. It becomes a creature again if it's not attached to a creature."), diff --git a/forge-game/src/main/java/forge/game/keyword/KeywordWithAmount.java b/forge-game/src/main/java/forge/game/keyword/KeywordWithAmount.java index dea434cbd2e..d39f3f93e7f 100644 --- a/forge-game/src/main/java/forge/game/keyword/KeywordWithAmount.java +++ b/forge-game/src/main/java/forge/game/keyword/KeywordWithAmount.java @@ -17,8 +17,8 @@ public class KeywordWithAmount extends KeywordInstance { if (details.contains(":")) { extra = details.split(":")[1]; } - } else { - amount = Integer.parseInt(details); + } else { + amount = details.contains(":") ? Integer.parseInt(details.split(":")[0]) : Integer.parseInt(details); } } 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 1a4563b0a76..34649e4207c 100644 --- a/forge-game/src/main/java/forge/game/spellability/SpellAbility.java +++ b/forge-game/src/main/java/forge/game/spellability/SpellAbility.java @@ -508,6 +508,10 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit return isAlternativeCost(AlternativeCost.Cycling); } + public boolean isBackup() { + return this.hasParam("Backup"); + } + public boolean isBoast() { return this.hasParam("Boast"); } diff --git a/forge-gui/res/cardsfolder/upcoming/archpriest_of_shadows.txt b/forge-gui/res/cardsfolder/upcoming/archpriest_of_shadows.txt new file mode 100644 index 00000000000..ccb063ad085 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/archpriest_of_shadows.txt @@ -0,0 +1,13 @@ +Name:Archpriest of Shadows +ManaCost:3 B B +Types:Creature Human Warlock +PT:4/4 +K:Backup:1:BackupAbilities +SVar:BackupAbilities:DB$ Animate | Keywords$ Deathtouch | Triggers$ DamageTrig +SVar:DamageTrig:Mode$ DamageDone | ValidSource$ Card.Self | ValidTarget$ Player,Battle | Execute$ TrigReturn | CombatDamage$ True | TriggerDescription$ Whenever this creature deals combat damage to a player or battle, return target creature card from your graveyard to the battlefield. +K:Deathtouch +T:Mode$ DamageDone | ValidSource$ Card.Self | ValidTarget$ Player,Battle | Execute$ TrigReturn | CombatDamage$ True | TriggerDescription$ Whenever this creature deals combat damage to a player or battle, return target creature card from your graveyard to the battlefield. +SVar:TrigReturn:DB$ ChangeZone | ValidTgts$ Creature.YouOwn | TgtPrompt$ Select target creature from your graveyard | Origin$ Graveyard | Destination$ Battlefield +DeckHas:Ability$Counters|Graveyard +DeckHints:Ability$Discard|Sacrifice +Oracle:Backup 1 (When this creature enters the battlefield, put a +1/+1 counter on target creature. If that's another creature, it gains the following abilities until end of turn.)\nDeathtouch\nWhenever this creature deals combat damage to a player or battle, return target creature card from your graveyard to the battlefield. diff --git a/forge-gui/res/cardsfolder/upcoming/boon_bringer_valkyrie.txt b/forge-gui/res/cardsfolder/upcoming/boon_bringer_valkyrie.txt new file mode 100644 index 00000000000..ed4e7889ca5 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/boon_bringer_valkyrie.txt @@ -0,0 +1,11 @@ +Name:Boon-Bringer Valkyrie +ManaCost:3 W W +Types:Creature Angel Warrior +PT:4/4 +K:Backup:1:BackupAbilities +SVar:BackupAbilities:DB$ Pump | KW$ Flying & First Strike & Lifelink +K:Flying +K:First Strike +K:Lifelink +DeckHas:Ability$Counters|LifeGain +Oracle:Backup 1 (When this creature enters the battlefield, put a +1/+1 counter on target creature. If that’s another creature, it gains the following abilities until end of turn.)\nFlying, first strike, lifelink diff --git a/forge-gui/res/cardsfolder/upcoming/cragsmasher_yeti.txt b/forge-gui/res/cardsfolder/upcoming/cragsmasher_yeti.txt new file mode 100644 index 00000000000..cb531538fb9 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/cragsmasher_yeti.txt @@ -0,0 +1,10 @@ +Name:Cragsmasher Yeti +ManaCost:4 R R +Types:Creature Yeti +PT:4/2 +K:TypeCycling:Mountain:2 +K:Backup:2:BackupAbility +SVar:BackupAbility:DB$ Pump | KW$ Trample +K:Trample +DeckHas:Ability$Counters +Oracle:Mountaincycling {2} ({2}, Discard this card: Search your library for a Mountain card, reveal it, put it into your hand, then shuffle.)\nBackup 2 (When this creature enters the battlefield, put two +1/+1 counters on target creature. If that's another creature, it gains the following ability until end of turn.)\nTrample diff --git a/forge-gui/res/cardsfolder/upcoming/doomskar_warrior.txt b/forge-gui/res/cardsfolder/upcoming/doomskar_warrior.txt new file mode 100644 index 00000000000..3f58448fcc8 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/doomskar_warrior.txt @@ -0,0 +1,13 @@ +Name:Doomskar Warrior +ManaCost:2 G G +Types:Creature Human Warrior +PT:4/3 +K:Backup:1:BackupAbilities +SVar:BackupAbilities:DB$ Animate | Keywords$ Trample | Triggers$ DamageTrig +SVar:DamageTrig:Mode$ DamageDone | ValidSource$ Card.Self | ValidTarget$ Player,Battle | Execute$ TrigDig | CombatDamage$ True | TriggerDescription$ Whenever this creature deals combat damage to a player or battle, look at that many cards from the top of your library. You may reveal a creature or land card from among them and put it into your hand. Put the rest on the bottom of your library in a random order. +SVar:TrigDig:DB$ Dig | DigNum$ X | ChangeNum$ 1 | ChangeValid$ Creature,Land | Optional$ True | DestinationZone$ Hand | RestRandomOrder$ True +K:Trample +T:Mode$ DamageDone | ValidSource$ Card.Self | ValidTarget$ Player,Planeswalker | Execute$ TrigDig | CombatDamage$ True | TriggerDescription$ Whenever this creature deals combat damage to a player or battle, look at that many cards from the top of your library. You may reveal a creature or land card from among them and put it into your hand. Put the rest on the bottom of your library in a random order. +SVar:X:TriggerCount$DamageAmount +DeckHas:Ability$Counters +Oracle:Backup 1\nTrample\nWhenever this creature deals combat damage to a player or battle, look at that many cards from the top of your library. You may reveal a creature or land card from among them and put it into your hand. Put the rest on the bottom of your library in a random order. diff --git a/forge-gui/res/cardsfolder/upcoming/fearless_skald.txt b/forge-gui/res/cardsfolder/upcoming/fearless_skald.txt new file mode 100644 index 00000000000..07431895a95 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/fearless_skald.txt @@ -0,0 +1,9 @@ +Name:Fearless Skald +ManaCost:4 R +Types:Creature Dwarf Berserker +PT:3/2 +K:Backup:1:BackupAbility +SVar:BackupAbility:DB$ Pump | KW$ Double Strike +K:Double Strike +DeckHas:Ability$Counters +Oracle:Backup 1 (When this creature enters the battlefield, put a + 1/+ 1 counter on target creature. If that's another creature, it gains the following ability until end of turn.)\nDouble strike diff --git a/forge-gui/res/cardsfolder/upcoming/mirror_shield_hoplite.txt b/forge-gui/res/cardsfolder/upcoming/mirror_shield_hoplite.txt new file mode 100644 index 00000000000..d07273f28db --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/mirror_shield_hoplite.txt @@ -0,0 +1,9 @@ +Name:Mirror-Shield Hoplite +ManaCost:R W +Types:Creature Human Soldier +PT:2/2 +K:Vigilance +T:Mode$ BecomesTarget | ValidTarget$ Creature.inZoneBattlefield+YouCtrl | ValidSource$ Ability.Backup | TriggerZones$ Battlefield | Execute$ TrigCopy | ActivationLimit$ 1 | TriggerDescription$ Whenever a creature you control becomes the target of a backup ability, copy that ability. You may choose new targets for the copy. This ability triggers only once each turn. +SVar:TrigCopy:DB$ CopySpellAbility | Defined$ TriggeredStackInstance | MayChooseTarget$ True +DeckNeeds:Keyword$Backup +Oracle:Vigilance\nWhenever a creature you control becomes the target of a backup ability, copy that ability. You may choose new targets for the copy. This ability triggers only once each turn. diff --git a/forge-gui/res/cardsfolder/upcoming/streetwise_negotiatior.txt b/forge-gui/res/cardsfolder/upcoming/streetwise_negotiatior.txt new file mode 100644 index 00000000000..faae830750d --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/streetwise_negotiatior.txt @@ -0,0 +1,10 @@ +Name:Streetwise Negotiator +ManaCost:1 G +Types:Creature Cat Citizen +PT:0/2 +K:Backup:1:BackupAbility +SVar:BackupAbility:DB$ Animate | staticAbilities$ CDT +SVar:CDT:Mode$ CombatDamageToughness | ValidCard$ Card.Self | Description$ This creature assigns combat damage equal to its toughness rather than its power. +S:Mode$ CombatDamageToughness | ValidCard$ Card.Self | Description$ This creature assigns combat damage equal to its toughness rather than its power. +DeckHas:Ability$Counters +Oracle:Backup 1 (When this creature enters the battlefield, put a +1/+1 counter on target creature. If that's another creature, it gains the following ability until end of turn.)\nThis creature assigns combat damage equal to its toughness rather than its power. diff --git a/forge-gui/res/cardsfolder/upcoming/voldaren_thrilseeker.txt b/forge-gui/res/cardsfolder/upcoming/voldaren_thrilseeker.txt new file mode 100644 index 00000000000..534cd599a15 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/voldaren_thrilseeker.txt @@ -0,0 +1,11 @@ +Name:Voldaren Thrillseeker +ManaCost:2 R +Types:Creature Vampire Warrior +PT:1/1 +K:Backup:2:BackupAbility +SVar:BackupAbility:DB$ Animate | Abilities$ ABDealDamage +SVar:ABDealDamage:AB$ DealDamage | Cost$ 1 Sac<1/CARDNAME/this creature> | ValidTgts$ Player,Creature,Planeswalker | TgtPrompt$ Select any target | NumDmg$ X | SpellDescription$ It deals damage equal to its power to any target. +A:AB$ DealDamage | Cost$ 1 Sac<1/CARDNAME/this creature> | ValidTgts$ Player,Creature,Planeswalker | TgtPrompt$ Select any target | NumDmg$ X | SpellDescription$ It deals damage equal to its power to any target. +SVar:X:Sacrificed$CardPower +DeckHas:Ability$Counters|Sacrifice +Oracle:Backup 2 (When this creature enters the battlefield, put two +1/+1 counters on target creature. If that's another creature, it gains the following ability until end of turn.)\n{1}, Sacrifice this creature: It deals damage equal to its power to any target.