From 6078dbe091fdd927e0df4e0c17743c160910fd25 Mon Sep 17 00:00:00 2001 From: John Date: Mon, 1 Feb 2021 12:48:49 +0000 Subject: [PATCH 01/74] shackles of treachery --- .../res/cardsfolder/upcoming/shackles_of_treachery.txt | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 forge-gui/res/cardsfolder/upcoming/shackles_of_treachery.txt diff --git a/forge-gui/res/cardsfolder/upcoming/shackles_of_treachery.txt b/forge-gui/res/cardsfolder/upcoming/shackles_of_treachery.txt new file mode 100644 index 00000000000..5a2bf5fcea6 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/shackles_of_treachery.txt @@ -0,0 +1,8 @@ +Name:Shackles of Treachery +ManaCost:2 R +Types:Sorcery +A:SP$ GainControl | Cost$ 2 R | ValidTgts$ Creature | TgtPrompt$ Select target creature | LoseControl$ EOT | Untap$ True | AddKWs$ Haste | SubAbility$ DBAnimate | SpellDescription$ Gain control of target creature until end of turn. Untap that creature. Until end of turn, it gains haste and "Whenever this creature deals damage, destroy target Equipment attached to it." +SVar:DBAnimate:DB$ Animate | Defined$ ParentTarget | Triggers$ Shackles | sVars$ TrigDestroy +SVar:Shackles:Mode$ DamageDone | ValidSource$ Card.Self | Execute$ TrigDestroy | TriggerDescription$ Whenever this creature deals damage, destroy target Equipment attached to it. +SVar:TrigDestroy:DB$ Destroy | ValidTgts$ Equipment.Attached | TgtPrompt$ Select target Equipment attached to the creature +Oracle:Gain control of target creature until end of turn. Untap that creature. Until end of turn, it gains haste and "Whenever this creature deals damage, destroy target Equipment attached to it." From 824fe2908a33e37bab6a069fcfc936c469ff6168 Mon Sep 17 00:00:00 2001 From: John Date: Mon, 1 Feb 2021 12:50:46 +0000 Subject: [PATCH 02/74] withercrown --- forge-gui/res/cardsfolder/upcoming/withercrown.txt | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 forge-gui/res/cardsfolder/upcoming/withercrown.txt diff --git a/forge-gui/res/cardsfolder/upcoming/withercrown.txt b/forge-gui/res/cardsfolder/upcoming/withercrown.txt new file mode 100644 index 00000000000..f5cf548cfec --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/withercrown.txt @@ -0,0 +1,9 @@ +Name:Withercrown +ManaCost:1 B +Types:Enchantment Aura +K:Enchant creature +A:SP$ Attach | Cost$ 1 B | ValidTgts$ Creature | AILogic$ Curse +S:Mode$ Continuous | Affected$ Card.EnchantedBy | SetPower$ 0 | AddTrigger$ WitherTrig | AddSVar$ TrigLoseLife | Description$ Enchanted creature has base power 0 and has "At the beginning of your upkeep, you lose 1 life unless you sacrifice this creature." +SVar:WitherTrig:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | Execute$ TrigLoseLife | TriggerDescription$ At the beginning of your upkeep, you lose 1 life unless you sacrifice this creature. +SVar:TrigLoseLife:DB$ LoseLife | LifeAmount$ 1 | UnlessCost$ Sac<1/CARDNAME> | UnlessPayer$ You | UnlessAI$ LifeLE10 +Oracle:Enchant creature\nEnchanted creature has base power 0 and has "At the beginning of your upkeep, you lose 1 life unless you sacrifice this creature." From 6fc3386fe13b6948f3d923b7548845e05d31fbfb Mon Sep 17 00:00:00 2001 From: John Date: Mon, 1 Feb 2021 12:53:58 +0000 Subject: [PATCH 03/74] old-growth troll --- .../res/cardsfolder/upcoming/old_growth_troll.txt | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 forge-gui/res/cardsfolder/upcoming/old_growth_troll.txt diff --git a/forge-gui/res/cardsfolder/upcoming/old_growth_troll.txt b/forge-gui/res/cardsfolder/upcoming/old_growth_troll.txt new file mode 100644 index 00000000000..f411bbd10fd --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/old_growth_troll.txt @@ -0,0 +1,14 @@ +Name:Old-Growth Troll +ManaCost:G G G +Types:Creature Troll Warrior +PT:4/4 +K:Trample +T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Self+Creature | TriggerController$ TriggeredCardController | Execute$ DBReturn | TriggerDescription$ When CARDNAME dies, if it was a creature. return it to the battlefield. It's an Aura enchantment with enchant Forest you control and “Enchanted Forest has ‘{T}: Add {G}{G}’ and ‘{1}, {T}, Sacrifice this land: Create a tapped 4/4 green Troll Warrior creature token with trample.’” +SVar:DBReturn:DB$ ChangeZone | Defined$ TriggeredNewCardLKICopy | Origin$ Graveyard | Destination$ Battlefield | AnimateSubAbility$ DBAnimate +SVar:DBAnimate:DB$ Animate | Defined$ Remembered | Types$ Enchantment,Aura | RemoveCardTypes$ True | RemoveAllAbilities$ True | Keywords$ Enchant Forest you control | Abilities$ SPAttach | staticAbilities$ STAura | Permanent$ True +SVar:STAura:Mode$ Continuous | Affected$ Land.EnchantedBy | AddAbility$ ABMana & ABToken | Description$ Enchanted Forest has ‘{T}: Add {G}{G}’ and ‘{1}, {T}, Sacrifice this land: Create a tapped 4/4 green Troll Warrior creature token with trample.’ +SVar:SPAttach:SP$ Attach | Cost$ 0 | ValidTgts$ Forest.YouCtrl | AILogic$ Pump +SVar:ABMana:AB$ Mana | Cost$ T | Produced$ G | Amount$ 2 | SpellDescription$ Add {G}{G}. +SVar:ABToken:AB$ Token | Cost$ 1 T Sac<1/CARDNAME> | TokenAmount$1 | TokenScript$ g_4_4_troll_warrior_trample | TokenOwner$ You | LegacyImage$ g 4 4 troll warrior trample khm | TokenTapped$ True | SpellDescription$ Create a tapped 4/4 green Troll Warrior creature token with trample. +DeckHas:Ability$Token +Oracle:Trample\nWhen Old-Growth Troll dies, if it was a creature, return it to the battlefield. It’s an Aura enchantment with enchant Forest you control and “Enchanted Forest has ‘{T}: Add {G}{G}’ and ‘{1}, {T}, Sacrifice this land: Create a tapped 4/4 green Troll Warrior creature token with trample.’” From 62d04ef855bd34c029dc6cecaab279b6db91db4b Mon Sep 17 00:00:00 2001 From: John Date: Mon, 1 Feb 2021 12:56:20 +0000 Subject: [PATCH 04/74] Add new file --- .../res/cardsfolder/upcoming/roots_of_wisdom.txt | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 forge-gui/res/cardsfolder/upcoming/roots_of_wisdom.txt diff --git a/forge-gui/res/cardsfolder/upcoming/roots_of_wisdom.txt b/forge-gui/res/cardsfolder/upcoming/roots_of_wisdom.txt new file mode 100644 index 00000000000..294a485d28a --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/roots_of_wisdom.txt @@ -0,0 +1,11 @@ +Name:Roots of Wisdom +ManaCost:1 G +Types:Sorcery +A:SP$ Mill | Cost$ 1 G | NumCards$ 3 | Defined$ You | SubAbility$ DBChangeZone | SpellDescription$ Mill three cards, then return a land card or Elf card from your graveyard to your hand. If you can't draw a card. +SVar:DBChangeZone:DB$ ChangeZone | Hidden$ True | Mandatory$ True | ChangeType$ Elf.YouOwn,Land.YouOwn | ChangeNum$ 1 | Origin$ Graveyard | Destination$ Hand | RememberChanged$ True | SubAbility$ DBDraw +SVar:DBDraw:DB$ Draw | Defined$ You | NumCards$ 1 | ConditionCheckSVar$ X | ConditionSVarCompare$ LT1 | SubAbility$ DBCleanup | References$ X +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True +SVar:X:Remembered$Amount +DeckHints:Type$Elf +DeckHas:Ability$Mill +Oracle:Mill three cards, then return a land card or Elf card from your graveyard to your hand. If you can’t, draw a card. (To mill a card, put the top card of your library into your graveyard.) From 1e78dc3313934fad9a738a80ff902179a0442017 Mon Sep 17 00:00:00 2001 From: John Date: Mon, 1 Feb 2021 12:57:23 +0000 Subject: [PATCH 05/74] ascent of the worthy --- .../res/cardsfolder/upcoming/ascent_of_the_worthy.txt | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 forge-gui/res/cardsfolder/upcoming/ascent_of_the_worthy.txt diff --git a/forge-gui/res/cardsfolder/upcoming/ascent_of_the_worthy.txt b/forge-gui/res/cardsfolder/upcoming/ascent_of_the_worthy.txt new file mode 100644 index 00000000000..cb80198ee3a --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/ascent_of_the_worthy.txt @@ -0,0 +1,11 @@ +Name:Ascent of the Worthy +ManaCost:1 W B +Types:Enchantment Saga +K:Saga:3:DBChoose,DBChoose,DBChangeZone +SVar:DBChoose:DB$ ChooseCard | Choices$ Creature.YouCtrl | SubAbility$ DBEffect | SpellDescription$ Choose a creature you control. Until your next turn, all damage that would be dealt to creatures you control is dealt to that creature instead. +SVar:DBEffect:DB$ Effect | ReplacementEffects$ DamageEvent | SVars$ GideonSac | References$ DamageEvent,GideonSac | ExileOnMoved$ True | RememberObjects$ ChosenCard +SVar:DamageEvent:Event$ DamageDone | ActiveZones$ Command | ValidTarget$ Creature.YouCtrl | ReplaceWith$ GideonSac | DamageTarget$ Remembered | Description$ All damage that would be dealt this turn to creatures you control is dealt to the chosen creature instead (if it's still on the battlefield). +SVar:DBChangeZone:DB$ ChangeZone | Origin$ Graveyard | Destination$ Battlefield | TgtPrompt$ Choose target creature card in your graveyard | ValidTgts$ Creature.YouOwn | WithCounters$ Flying_1 | AnimateSubAbility$ Animate | SpellDescription$ Return target creature card from your graveyard to the battlefield with a flying counter on it. +SVar:GideonSac:DB$ ReplaceEffect | VarName$ Affected | VarValue$ Remembered | VarType$ Card +SVar:Animate:DB$Animate | Defined$ Remembered | Types$ Angel,Warrior | Permanent$ True +Oracle:(As this Saga enters and after your draw step, add a lore counter. Sacrifice after III.)\nI, II — Choose a creature you control. Until your next turn, all damage that would be dealt to creatures you control is dealt to that creature instead.\nIII — Return target creature card from your graveyard to the battlefield with a flying counter on it. That creature is an Angel Warrior in addition to its other types. From 198d922c14fc529da170e6f18a17097d98b6e69e Mon Sep 17 00:00:00 2001 From: John Date: Mon, 1 Feb 2021 12:58:30 +0000 Subject: [PATCH 06/74] battle of frost and fire --- .../upcoming/battle_of_frost_and_fire.txt | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 forge-gui/res/cardsfolder/upcoming/battle_of_frost_and_fire.txt diff --git a/forge-gui/res/cardsfolder/upcoming/battle_of_frost_and_fire.txt b/forge-gui/res/cardsfolder/upcoming/battle_of_frost_and_fire.txt new file mode 100644 index 00000000000..1591ec3b7ff --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/battle_of_frost_and_fire.txt @@ -0,0 +1,12 @@ +Name:Battle of Frost and Fire +ManaCost:3 U R +Types:Enchantment Saga +K:Saga:3:DBDamageAll,DBScry,DBEffect +SVar:DBDamageAll:DB$ DamageAll | NumDmg$ 4 | ValidCards$ Creature.nonGiant,Planeswalker | ValidDescription$ each non-Giant creature and each planeswalker. | SpellDescription$ CARDNAME deals 4 damage to each non-Giant creature and each planeswalker. +SVar:DBScry:DB$ Scry | ScryNum$ 3 | SpellDescription$ Scry 3. +SVar:DBEffect:DB$ Effect | Triggers$ CastSpell | SpellDescription$ Whenever you cast a spell with converted mana cost 5 or greater this turn, draw two cards, then discard a card. +SVar:CastSpell:Mode$ SpellCast | ValidCard$ Card.cmcGE5 | ValidActivatingPlayer$ You | TriggerZones$ Command | Execute$ DBDraw | TriggerDescription$ Whenever you cast a spell with converted mana cost 5 or greater this turn, draw two cards, then discard a card. +SVar:DBDraw:DB$ Draw | Defined$ You | NumCards$ 2 | SubAbility$ DBDiscard +SVar:DBDiscard:DB$ Discard | Defined$ You | NumCards$ 1 | Mode$ TgtChoose +DeckHints:Type$Giant +Oracle:(As this Saga enters and after your draw step, add a lore counter. Sacrifice after III.)\nI — Battle of Frost and Fire deals 4 damage to each non-Giant creature and each planeswalker.\nII — Scry 3.\nIII — Whenever you cast a spell with converted mana cost 5 or greater this turn, draw two cards, then discard a card. From d8f802e8ffda0a7729dc4feb962bcf2ce8c6d052 Mon Sep 17 00:00:00 2001 From: John Date: Mon, 1 Feb 2021 12:59:56 +0000 Subject: [PATCH 07/74] bears of littjara --- .../cardsfolder/upcoming/the_bears_of_littjara.txt | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 forge-gui/res/cardsfolder/upcoming/the_bears_of_littjara.txt diff --git a/forge-gui/res/cardsfolder/upcoming/the_bears_of_littjara.txt b/forge-gui/res/cardsfolder/upcoming/the_bears_of_littjara.txt new file mode 100644 index 00000000000..d9b64203a0f --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/the_bears_of_littjara.txt @@ -0,0 +1,13 @@ +Name:The Bears of Littjara +ManaCost:1 G U +Types:Enchantment Saga +K:Saga:3:DBToken,DBAnimate,DBGangUp +SVar:DBToken:DB$ Token | TokenAmount$ 1 | TokenScript$ u_2_2_shapeshifter_changeling | TokenOwner$ You | SpellDescription$ Create a 2/2 blue Shapeshifter creature token with changeling. +SVar:DBAnimate:DB$ Animate | ValidTgts$ Creature.Shapeshifter+YouCtrl | TargetMin$ 0 | TargetMax$ MaxTargets | TgtPrompt$ Select any number of Shapeshifter creatures you control | Power$ 4 | Toughness$ 4 | Permanent$ True | SpellDescription$ Any number of target Shapeshifter creatures you control have base power and toughness 4/4. +SVar:DBGangUp:DB$ Pump | ValidTgts$ Creature,Planeswalker | TargetMin$ 0 | TargetMax$ 1 | TgtPrompt$ Choose up to one target creature or planeswalker | ImprintCards$ Targeted | SubAbility$ DBRepeatEach +SVar:DBRepeatEach:DB$ RepeatEach | RepeatCards$ Creature.YouCtrl+powerGE4 | RepeatSubAbility$ DBDamage | DamageMap$ True | SubAbility$ DBCleanup | SpellDescription$ Choose up to one target creature or planeswalker. Each creature with power 4 or greater you control deals damage equal to its power to that permanent. +SVar:DBDamage:DB$ DealDamage | Defined$ Imprinted | DamageSource$ Remembered | NumDmg$ Z | References$ Z +SVar:MaxTargets:Count$Valid Creature.Shapeshifter+YouCtrl +SVar:Z:Remembered$CardPower +DeckHas:Ability$Token +Oracle:(As this Saga enters and after your draw step, add a lore counter. Sacrifice after III.)\nI — Create a 2/2 blue Shapeshifter creature token with changeling.\nII — Any number of target Shapeshifter creatures you control have base power and toughness 4/4.\nIII — Choose up to one target creature or planeswalker. Each creature with power 4 or greater you control deals damage equal to its power to that permanent. From a783eec9eba6c72e838d10646adc5855a5f34484 Mon Sep 17 00:00:00 2001 From: John Date: Mon, 1 Feb 2021 13:00:52 +0000 Subject: [PATCH 08/74] fall of the impostor --- .../res/cardsfolder/upcoming/fall_of_the_impostor.txt | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 forge-gui/res/cardsfolder/upcoming/fall_of_the_impostor.txt diff --git a/forge-gui/res/cardsfolder/upcoming/fall_of_the_impostor.txt b/forge-gui/res/cardsfolder/upcoming/fall_of_the_impostor.txt new file mode 100644 index 00000000000..6047960e2db --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/fall_of_the_impostor.txt @@ -0,0 +1,11 @@ +Name:Fall of the Impostor +ManaCost:1 G W +Types:Enchantment Saga +K:Saga:3:DBPutCounter,DBPutCounter,DBExileGreatest +SVar:DBPutCounter:DB$ PutCounter | ValidTgts$ Creature | TgtPrompt$ Select target creature | CounterType$ P1P1 | CounterNum$ 1 | TargetMin$ 0 | TargetMax$ 1 | SpellDescription$ Put a +1/+1 counter on up to one target creature. +SVar:DBExileGreatest:DB$ Pump | ValidTgts$ Player.Opponent | RememberTargets$ True | SubAbility$ DBChooseExiled | SpellDescription$ Exile a creature with the greatest power among creatures target opponent controls. +SVar:DBChooseExiled:DB$ ChooseCard | Choices$ Creature.greatestPowerControlledByRemembered | MinAmount$ 1 | Amount$ 1 | Mandatory$ True | ChoiceZone$ Battlefield | SubAbility$ DBChangeZone +SVar:DBChangeZone:DB$ ChangeZone | Origin$ Battlefield | Destination$ Exile | Defined$ ChosenCard | SubAbility$ DBCleanup +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True +DeckHas:Ability$Counters +Oracle:I - Put a +1/+1 counter on up to one target creature.\nII - Put a +1/+1 counter on up to one target creature.\nIII - Exile a creature with the greatest power among creatures target opponent controls. From 2ea967bdb404a149360b972f99590d14fac29a99 Mon Sep 17 00:00:00 2001 From: John Date: Mon, 1 Feb 2021 13:02:06 +0000 Subject: [PATCH 09/74] forging the tyrite sword --- .../res/cardsfolder/upcoming/forging_the_tyrite_sword.txt | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 forge-gui/res/cardsfolder/upcoming/forging_the_tyrite_sword.txt diff --git a/forge-gui/res/cardsfolder/upcoming/forging_the_tyrite_sword.txt b/forge-gui/res/cardsfolder/upcoming/forging_the_tyrite_sword.txt new file mode 100644 index 00000000000..c1cdb81e3cc --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/forging_the_tyrite_sword.txt @@ -0,0 +1,8 @@ +Name:Forging the Tyrite Sword +ManaCost:1 R W +Types:Enchantment Saga +K:Saga:3:DBToken,DBToken,DBChangeZone +SVar:DBToken:DB$ Token | TokenAmount$ 1 | TokenScript$ c_a_treasure_sac | TokenOwner$ You $ SpellDescription +SVar:DBChangeZone:DB$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Card.namedHalvar; God of Battle,Equipment | ChangeNum$ 1 | SpellDescription$ Search your library for a card named Halvar, God of Battle or an Equipment card, reveal it, put it into your hand, then shuffle your library. +DeckHas:Ability$Token +Oracle:(As this Saga enters and after your draw step, add a lore counter. Sacrifice after III.)\nI, II — Create a Treasure token.\nIII — Search your library for a card named Halvar, God of Battle or an Equipment card, reveal it, put it into your hand, then shuffle your library. From 3a2451a8cc9a98d8a4849650b2cabf9a6a4409c4 Mon Sep 17 00:00:00 2001 From: John Date: Mon, 1 Feb 2021 13:03:25 +0000 Subject: [PATCH 10/74] harald king of skemfar --- .../res/cardsfolder/upcoming/harald_king_of_skemfar.txt | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 forge-gui/res/cardsfolder/upcoming/harald_king_of_skemfar.txt diff --git a/forge-gui/res/cardsfolder/upcoming/harald_king_of_skemfar.txt b/forge-gui/res/cardsfolder/upcoming/harald_king_of_skemfar.txt new file mode 100644 index 00000000000..15bface1ab9 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/harald_king_of_skemfar.txt @@ -0,0 +1,9 @@ +Name:Harald, King of Skemfar +ManaCost:1 B G +Types:Legendary Creature Elf Warrior +PT:3/2 +K:Menace +T:Mode$ ChangesZone | ValidCard$ Card.Self | Origin$ Any | Destination$ Battlefield | Execute$ TrigDig | TriggerDescription$ When CARDNAME enters the battlefield, look at the top five cards of your library. You may reveal an Elf, Warrior, or Tyvar 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$ 5 | ChangeNum$ 1 | Optional$ True | ForceRevealToController$ True | ChangeValid$ Elf,Warrior,Tyvar | RestRandomOrder$ True +DeckHints:Type$Elf +Oracle:Menace (This creature can’t be blocked except by two or more creatures.)\nWhen Harald, King of Skemfar enters the battlefield, look at the top five cards of your library. You may reveal an Elf, Warrior, or Tyvar card from among them and put it into your hand. Put the rest on the bottom of your library in a random order. From 5292005c34d55ddd7ad3041d2f584018855e383c Mon Sep 17 00:00:00 2001 From: John Date: Mon, 1 Feb 2021 13:04:29 +0000 Subject: [PATCH 11/74] harald unites the elves --- .../upcoming/harald_unites_the_elves.txt | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 forge-gui/res/cardsfolder/upcoming/harald_unites_the_elves.txt diff --git a/forge-gui/res/cardsfolder/upcoming/harald_unites_the_elves.txt b/forge-gui/res/cardsfolder/upcoming/harald_unites_the_elves.txt new file mode 100644 index 00000000000..ed07a0599d9 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/harald_unites_the_elves.txt @@ -0,0 +1,13 @@ +Name:Harald Unites the Elves +ManaCost:2 B G +Types:Enchantment Saga +K:Saga:3:DBMill,DBPutCounterAll,DBEffect +SVar:DBMill:DB$ Mill | NumCards$ 3 | Defined$ You | SubAbility$ DBChangeZone | SpellDescription$ Mill three cards. You may put an Elf card or Tyvar card from your graveyard onto the battlefield. +SVar:DBChangeZone:DB$ ChangeZone | Hidden$ True | ChangeType$ Elf.YouOwn,Tyvar.YouOwn | ChangeNum$ 1 | Origin$ Graveyard | Destination$ Battlefield +SVar:DBPutCounterAll:DB$ PutCounterAll | ValidCards$ Creature.Elf+YouCtrl | CounterType$ P1P1 | CounterNum$ 1 | SpellDescription$ Put a +1/+1 counter on each Elf you control. +SVar:DBEffect:DB$ Effect | Triggers$ TrigAttack | SpellDescription$ Whenever an Elf you control attacks this turn, target creature an opponent controls gets -1/-1 until end of turn. +SVar:TrigAttack:Mode$ Attacks | ValidCard$ Creature.Elf+YouCtrl | Execute$ TrigPump | TriggerZones$ Battlefield | TriggerDescription$ Whenever an Elf you control attacks this turn, target creature an opponent controls gets -1/-1 until end of turn. +SVar:TrigPump:DB$ Pump | ValidTgts$ Creature.OppCtrl | TgtPrompt$ Select target creature | NumAtt$ -1 | NumDef$ -1 | IsCurse$ True +DeckHints:Type$Elf +DeckHas:Ability$Counters +Oracle:(As this Saga enters and after your draw step, add a lore counter. Sacrifice after III.)\nI - Mill three cards. You may put an Elf card or Tyvar card from your graveyard onto the battlefield.\nII - Put a +1/+1 counter on each Elf you control.\nIII - Whenever an Elf you control attacks this turn, target creature an opponent controls gets -1/-1 until end of turn. From 3dabe1228d3db2e8530dccebb4733bd5a6cc0362 Mon Sep 17 00:00:00 2001 From: John Date: Mon, 1 Feb 2021 13:05:38 +0000 Subject: [PATCH 12/74] kardurs vicious return --- .../cardsfolder/upcoming/kardurs_vicious_return.txt | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 forge-gui/res/cardsfolder/upcoming/kardurs_vicious_return.txt diff --git a/forge-gui/res/cardsfolder/upcoming/kardurs_vicious_return.txt b/forge-gui/res/cardsfolder/upcoming/kardurs_vicious_return.txt new file mode 100644 index 00000000000..d966cc95d8c --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/kardurs_vicious_return.txt @@ -0,0 +1,13 @@ +Name:Kardur's Vicious Return +ManaCost:2 B R +Types:Enchantment Saga +K:Saga:3:DBSacrifice,DBDiscard,DBReturn +SVar:DBSacrifice:DB$ ImmediateTrigger | Execute$ TrigDealDamage | UnlessCost$ Sac<1/Creature> | UnlessPayer$ You | UnlessSwitched$ True | SpellDescription$ You may sacrifice a creature. When you do, CARDNAME deals 3 damage to any target. +SVar:TrigDealDamage:DB$ DealDamage | ValidTgts$ Creature,Player,Planeswalker | TgtPrompt$ Select any target. | NumDmg$ 3 +SVar:DBDiscard:DB$ Discard | Defined$ Player | NumCards$ 1 | Mode$ TgtChoose | SpellDescription$ Each player discards a card. +SVar:DBReturn:DB$ ChangeZone | Origin$ Graveyard | Destination$ Battlefield | TgtPrompt$ Choose target creature card in your graveyard | ValidTgts$ Creature.YouCtrl | RememberChanged$ True | SubAbility$ DBPutCounter | SpellDescription$ Return target creature card from your graveyard to the battlefield. Put a +1/+1 counter on it. It gains haste until your next turn. +SVar:DBPutCounter:DB$PutCounter | CounterType$ P1P1 | CounterNum$ 1 | Defined$ Remembered | SubAbility$ DBPump +SVar:DBPump:DB$ Pump | Defined$ Remembered | KW$ Haste | UntilYourNextTurn$ True | SubAbility$ DBCleanup +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True +DeckHas:Ability$Counters +Oracle:(As this Saga enters and after your draw step, add a lore counter. Sacrifice after III.)\nI - You may sacrifice a creature. When you do, Kardur's Vicious Return deals 3 damage to any target.\nII - Each player discards a card.\nIII - Return target creature card from your graveyard to the battlefield. Put a +1/+1 counter on it. It gains haste until your next turn. From 7def71a50c0562a1701927a3dbb9d12ffb0e9923 Mon Sep 17 00:00:00 2001 From: John Date: Mon, 1 Feb 2021 13:15:04 +0000 Subject: [PATCH 13/74] kolvori --- ...lvori_god_of_kinship_the_ringhart_crest.txt | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 forge-gui/res/cardsfolder/upcoming/kolvori_god_of_kinship_the_ringhart_crest.txt diff --git a/forge-gui/res/cardsfolder/upcoming/kolvori_god_of_kinship_the_ringhart_crest.txt b/forge-gui/res/cardsfolder/upcoming/kolvori_god_of_kinship_the_ringhart_crest.txt new file mode 100644 index 00000000000..d6dbe16169d --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/kolvori_god_of_kinship_the_ringhart_crest.txt @@ -0,0 +1,18 @@ +Name:Kolvori, God of Kinship +ManaCost:2 G G +Types:Legendary Creature God +PT:2/2 +S:Mode$ Continuous | Affected$ Card.Self | AddPower$ 4 | AddToughness$ 2 | AddKeyword$ Vigilance | IsPresent$ Creature.Legendary+YouCtrl | PresentCompare$ GE3 | Description$ As long as you control three or more legendary creatures, CARDNAME gets +4/+2 and has vigilance. +A:AB$ Dig | Cost$ 1 G T | DigNum$ 6 | ChangeNum$ 1 | ChangeValid$ Creature.Legendary | Optional$ True | RestRandomOrder$ True | SpellDescription$ Look at the top six cards of your library. You may reveal a legendary creature card from among them and put it into your hand. Put the rest on the bottom of your library in a random order. +AlternateMode:Modal +Oracle:As long as you control three or more legendary creatures, Kolvori gets +4/+2 and has vigilance.\n{1}{G}, {T}: Look at the top six cards of your library. You may reveal a legendary creature card from among them and put it into your hand. Put the rest on the bottom of your library in a random order. + +ALTERNATE + +Name:The Ringhart Crest +ManaCost:1 G +Types:Legendary Artifact +K:ETBReplacement:Other:ChooseCT +SVar:ChooseCT:DB$ ChooseType | Defined$ You | Type$ Creature | AILogic$ MostProminentInComputerDeck | SpellDescription$ As CARDNAME enters the battlefield, choose a creature type. +A:AB$ Mana | Cost$ T | Produced$ G | RestrictValid$ Creature.ChosenType,Creature.Legendary | SpellDescription$ {T}: Add {G}. Spend this mana only to cast a creature spell of the chosen type or a legendary creature spell. +Oracle:As The Ringhart Crest enters the battlefield, choose a creature type.\n{T}: Add {G}. Spend this mana only to cast a creature spell of the chosen type or a legendary creature spell. From 46a31472ef1a5a81488d33af707b466a0670ec78 Mon Sep 17 00:00:00 2001 From: John Date: Mon, 1 Feb 2021 16:05:37 +0000 Subject: [PATCH 14/74] Update ascent_of_the_worthy.txt --- forge-gui/res/cardsfolder/upcoming/ascent_of_the_worthy.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/forge-gui/res/cardsfolder/upcoming/ascent_of_the_worthy.txt b/forge-gui/res/cardsfolder/upcoming/ascent_of_the_worthy.txt index cb80198ee3a..9e86d9f1505 100644 --- a/forge-gui/res/cardsfolder/upcoming/ascent_of_the_worthy.txt +++ b/forge-gui/res/cardsfolder/upcoming/ascent_of_the_worthy.txt @@ -3,7 +3,7 @@ ManaCost:1 W B Types:Enchantment Saga K:Saga:3:DBChoose,DBChoose,DBChangeZone SVar:DBChoose:DB$ ChooseCard | Choices$ Creature.YouCtrl | SubAbility$ DBEffect | SpellDescription$ Choose a creature you control. Until your next turn, all damage that would be dealt to creatures you control is dealt to that creature instead. -SVar:DBEffect:DB$ Effect | ReplacementEffects$ DamageEvent | SVars$ GideonSac | References$ DamageEvent,GideonSac | ExileOnMoved$ True | RememberObjects$ ChosenCard +SVar:DBEffect:DB$ Effect | ReplacementEffects$ DamageEvent | SVars$ GideonSac | References$ DamageEvent,GideonSac | ExileOnMoved$ True | RememberObjects$ ChosenCard | Duration$ UntilYourNextTurn SVar:DamageEvent:Event$ DamageDone | ActiveZones$ Command | ValidTarget$ Creature.YouCtrl | ReplaceWith$ GideonSac | DamageTarget$ Remembered | Description$ All damage that would be dealt this turn to creatures you control is dealt to the chosen creature instead (if it's still on the battlefield). SVar:DBChangeZone:DB$ ChangeZone | Origin$ Graveyard | Destination$ Battlefield | TgtPrompt$ Choose target creature card in your graveyard | ValidTgts$ Creature.YouOwn | WithCounters$ Flying_1 | AnimateSubAbility$ Animate | SpellDescription$ Return target creature card from your graveyard to the battlefield with a flying counter on it. SVar:GideonSac:DB$ ReplaceEffect | VarName$ Affected | VarValue$ Remembered | VarType$ Card From d8b94fbfedde60c8e243aff775dfa1ea26b5370c Mon Sep 17 00:00:00 2001 From: John Date: Mon, 1 Feb 2021 17:34:41 +0000 Subject: [PATCH 15/74] the ravens warning --- .../res/cardsfolder/upcoming/the_ravens_warning.txt | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 forge-gui/res/cardsfolder/upcoming/the_ravens_warning.txt diff --git a/forge-gui/res/cardsfolder/upcoming/the_ravens_warning.txt b/forge-gui/res/cardsfolder/upcoming/the_ravens_warning.txt new file mode 100644 index 00000000000..3cf56a010a7 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/the_ravens_warning.txt @@ -0,0 +1,13 @@ +Name:The Raven's Warning +ManaCost:1 W U +Types:Enchantment Saga +K:Saga:3:DBToken,DBEffect,DBWish +SVar:DBToken:DB$ Token | TokenAmount$ 1 | TokenScript$ u_1_1_bird_flying | TokenOwner$ You | SubAbility$ DBGainLife | SpellDescription$ Create a 1/1 blue Bird creature token with flying. You gain 2 life. +SVar:DBGainLife:DB$ GainLife | Defined$ You | LifeAmount$ 2 +SVar:DBEffect:DB$ Effect | Triggers$ TrigDamage | SpellDescription$ Whenever one or more creatures you control with flying deal combat damage to a player this turn, look at that player’s hand and draw a card. +SVar:TrigDamage:Mode$ DamageDoneOnce | ValidSource$ Creature.YouCtrl+withFlying | ValidTarget$ Player | CombatDamage$ True | Execute$ TrigPeek | TriggerDescription$ Whenever one or more creatures you control with flying deal combat damage to a player this turn, look at that player’s hand and draw a card. +SVar:TrigPeek:DB$ RevealHand | Defined$ TriggeredTarget | SubAbility$ DBDraw | SpellDescription$ Look at that player's hand. +SVar:DBDraw:DB$ Draw | Defined$ You | NumCards$ 1 | SpellDescription$ Draw a card. +SVar:DBWish:DB$ ChangeZone | Origin$ Sideboard | Destination$ Library | OptionalDecider$ You | ChangeType$ Card.YouOwn | ChangeNum$ 1 | Optional$ True | Hidden$ True | SpellDescription$ You may put a card you own from outside the game on top of your library. +DeckHas:Ability$Token +Oracle:(As this Saga enters and after your draw step, add a lore counter. Sacrifice after III.)\nI — Create a 1/1 blue Bird creature token with flying. You gain 2 life.\nII — Whenever one or more creatures you control with flying deal combat damage to a player this turn, look at that player’s hand and draw a card.\nIII — You may put a card you own from outside the game on top of your library. From fcd653de8bbb39028ba866243eba710a6101557a Mon Sep 17 00:00:00 2001 From: John Date: Mon, 1 Feb 2021 20:34:32 +0000 Subject: [PATCH 16/74] waking the trolls --- .../res/cardsfolder/upcoming/waking_the_trolls.txt | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 forge-gui/res/cardsfolder/upcoming/waking_the_trolls.txt diff --git a/forge-gui/res/cardsfolder/upcoming/waking_the_trolls.txt b/forge-gui/res/cardsfolder/upcoming/waking_the_trolls.txt new file mode 100644 index 00000000000..6c22a9890ca --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/waking_the_trolls.txt @@ -0,0 +1,13 @@ +Name:Waking the Trolls +ManaCost:4 R G +Types:Enchantment Saga +K:Saga:3:DBDestroy,DBChangeZone,DBPump +SVar:DBDestroy:DB$ Destroy | ValidTgts$ Land | TgtPrompt$ Choose target land. | SpellDescription$ Destroy target land. +SVar:DBChangeZone:DB$ ChangeZone | ValidTgts$ Land | TgtPrompt$ Choose target land card. | Origin$ Graveyard | Destination$ Battlefield | GainControl$ True | SpellDescription$ Put target land card from a graveyard onto the battlefield under your control. +SVar:DBPump:DB$ Pump | ValidTgts$ Player.Opponent | RememberTargets$ True | TgtPrompt$ Choose target player. | SubAbility$ DBToken | SpellDescription$ Choose target player. If they control fewer lands than you, create a number of 4/4 green Troll Warrior creature tokens with trample equal to the difference. +SVar:DBToken:DB$ Token | TokenAmount$ X | References$ X,Y | TokenScript$ g_4_4_troll_warrior_trample | TokenOwner$ You | SubAbility$ DBCleanup +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True +SVar:X:Count$Valid Land.YouCtrl/Minus.Y +SVar:Y:Count$Valid Land.RememberedPlayerCtrl +DeckHas:Ability$Token +Oracle:(As this Saga enters and after your draw step, add a lore counter. Sacrifice after III.)\nI - Destroy target land.\nII - Put target land from a graveyard onto the battlefield under your control.\nIII - Choose target player. If they control fewer lands than you, create a number of 4/4 green Troll Warrior creature tokens with trample equal to the difference. From afa4b25de9d7526d63c2b7b593d1e3e746baa1ab Mon Sep 17 00:00:00 2001 From: Hans Mackowiak Date: Tue, 2 Feb 2021 06:51:00 +0000 Subject: [PATCH 17/74] CopyPermanentEffect add WithDifferentNames for Choice --- .../ability/effects/CopyPermanentEffect.java | 209 ------------------ .../upcoming/battle_for_bretagard.txt | 10 + 2 files changed, 10 insertions(+), 209 deletions(-) create mode 100644 forge-gui/res/cardsfolder/upcoming/battle_for_bretagard.txt diff --git a/forge-game/src/main/java/forge/game/ability/effects/CopyPermanentEffect.java b/forge-game/src/main/java/forge/game/ability/effects/CopyPermanentEffect.java index c83bca2d61d..8b137891791 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/CopyPermanentEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/CopyPermanentEffect.java @@ -1,210 +1 @@ -package forge.game.ability.effects; -import com.google.common.base.Predicate; -import com.google.common.base.Predicates; -import com.google.common.collect.Iterables; -import com.google.common.collect.Lists; - -import forge.StaticData; -import forge.card.CardRulesPredicates; -import forge.game.Game; -import forge.game.ability.AbilityUtils; -import forge.game.card.Card; -import forge.game.card.CardCollectionView; -import forge.game.card.CardFactory; -import forge.game.card.CardLists; -import forge.game.card.CardZoneTable; -import forge.game.event.GameEventCombatChanged; -import forge.game.player.Player; -import forge.game.spellability.SpellAbility; -import forge.game.zone.ZoneType; -import forge.item.PaperCard; -import forge.util.Aggregates; -import forge.util.TextUtil; -import forge.util.collect.FCollectionView; -import forge.util.PredicateString.StringOp; -import forge.util.Localizer; - -import org.apache.commons.lang3.StringUtils; -import org.apache.commons.lang3.mutable.MutableBoolean; - -import java.util.Arrays; -import java.util.List; - -public class CopyPermanentEffect extends TokenEffectBase { - - @Override - protected String getStackDescription(SpellAbility sa) { - if (sa.hasParam("Populate")) { - return "Populate. (Create a token that's a copy of a creature token you control.)"; - } - final StringBuilder sb = new StringBuilder(); - - - final List tgtCards = getTargetCards(sa); - - sb.append("Copy "); - sb.append(StringUtils.join(tgtCards, ", ")); - sb.append("."); - return sb.toString(); - } - - /* (non-Javadoc) - * @see forge.card.ability.SpellAbilityEffect#resolve(forge.card.spellability.SpellAbility) - */ - @Override - public void resolve(final SpellAbility sa) { - final Card host = sa.getHostCard(); - final Player activator = sa.getActivatingPlayer(); - final Game game = host.getGame(); - final List pumpKeywords = Lists.newArrayList(); - - if (sa.hasParam("Optional")) { - if (!activator.getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblCopyPermanentConfirm"))) { - return; - } - } - - if (sa.hasParam("PumpKeywords")) { - pumpKeywords.addAll(Arrays.asList(sa.getParam("PumpKeywords").split(" & "))); - } - - final int numCopies = sa.hasParam("NumCopies") ? AbilityUtils.calculateAmount(host, - sa.getParam("NumCopies"), sa) : 1; - - Player controller = null; - if (sa.hasParam("Controller")) { - final FCollectionView defined = AbilityUtils.getDefinedPlayers(host, sa.getParam("Controller"), sa); - if (!defined.isEmpty()) { - controller = defined.getFirst(); - } - } - if (controller == null) { - controller = activator; - } - - List tgtCards = Lists.newArrayList(); - - if (sa.hasParam("ValidSupportedCopy")) { - List cards = Lists.newArrayList(StaticData.instance().getCommonCards().getUniqueCards()); - String valid = sa.getParam("ValidSupportedCopy"); - if (valid.contains("X")) { - valid = TextUtil.fastReplace(valid, - "X", Integer.toString(AbilityUtils.calculateAmount(host, "X", sa))); - } - if (StringUtils.containsIgnoreCase(valid, "creature")) { - Predicate cpp = Predicates.compose(CardRulesPredicates.Presets.IS_CREATURE, PaperCard.FN_GET_RULES); - cards = Lists.newArrayList(Iterables.filter(cards, cpp)); - } - if (StringUtils.containsIgnoreCase(valid, "equipment")) { - Predicate cpp = Predicates.compose(CardRulesPredicates.Presets.IS_EQUIPMENT, PaperCard.FN_GET_RULES); - cards = Lists.newArrayList(Iterables.filter(cards, cpp)); - } - if (sa.hasParam("RandomCopied")) { - List copysource = Lists.newArrayList(cards); - List choice = Lists.newArrayList(); - final String num = sa.hasParam("RandomNum") ? sa.getParam("RandomNum") : "1"; - int ncopied = AbilityUtils.calculateAmount(host, num, sa); - while(ncopied > 0) { - final PaperCard cp = Aggregates.random(copysource); - Card possibleCard = Card.fromPaperCard(cp, activator); // Need to temporarily set the Owner so the Game is set - - if (possibleCard.isValid(valid, host.getController(), host, sa)) { - choice.add(possibleCard); - copysource.remove(cp); - ncopied -= 1; - } - } - tgtCards = choice; - } else if (sa.hasParam("DefinedName")) { - String name = sa.getParam("DefinedName"); - if (name.equals("NamedCard")) { - if (!host.getNamedCard().isEmpty()) { - name = host.getNamedCard(); - } - } - - Predicate cpp = Predicates.compose(CardRulesPredicates.name(StringOp.EQUALS, name), PaperCard.FN_GET_RULES); - cards = Lists.newArrayList(Iterables.filter(cards, cpp)); - - if (!cards.isEmpty()) { - tgtCards.add(Card.fromPaperCard(cards.get(0), controller)); - } - } - } else if (sa.hasParam("Choices")) { - Player chooser = activator; - if (sa.hasParam("Chooser")) { - final String choose = sa.getParam("Chooser"); - chooser = AbilityUtils.getDefinedPlayers(sa.getHostCard(), choose, sa).get(0); - } - - CardCollectionView choices = game.getCardsIn(ZoneType.Battlefield); - choices = CardLists.getValidCards(choices, sa.getParam("Choices"), activator, host); - if (!choices.isEmpty()) { - String title = sa.hasParam("ChoiceTitle") ? sa.getParam("ChoiceTitle") : Localizer.getInstance().getMessage("lblChooseaCard") +" "; - - Card choosen = chooser.getController().chooseSingleEntityForEffect(choices, sa, title, false, null); - - if (choosen != null) { - tgtCards.add(choosen); - } - } - } else { - tgtCards = getDefinedCardsOrTargeted(sa); - } - - boolean useZoneTable = true; - CardZoneTable triggerList = sa.getChangeZoneTable(); - if (triggerList == null) { - triggerList = new CardZoneTable(); - useZoneTable = false; - } - if (sa.hasParam("ChangeZoneTable")) { - sa.setChangeZoneTable(triggerList); - useZoneTable = true; - } - - MutableBoolean combatChanged = new MutableBoolean(false); - for (final Card c : tgtCards) { - // if it only targets player, it already got all needed cards from defined - if (sa.usesTargeting() && !sa.getTargetRestrictions().canTgtPlayer() && !c.canBeTargetedBy(sa)) { - continue; - } - - makeTokens(getProtoType(sa, c), controller, sa, numCopies, true, true, triggerList, combatChanged); - } // end foreach Card - - if (!useZoneTable) { - triggerList.triggerChangesZoneAll(game); - triggerList.clear(); - } - if (combatChanged.isTrue()) { - game.updateCombatForView(); - game.fireEvent(new GameEventCombatChanged()); - } - } // end resolve - - private Card getProtoType(final SpellAbility sa, final Card original) { - final Card host = sa.getHostCard(); - final Player newOwner = sa.getActivatingPlayer(); - int id = newOwner == null ? 0 : newOwner.getGame().nextCardId(); - final Card copy = new Card(id, original.getPaperCard(), host.getGame()); - copy.setOwner(newOwner); - copy.setSetCode(original.getSetCode()); - - if (sa.hasParam("Embalm")) { - copy.setEmbalmed(true); - } - - if (sa.hasParam("Eternalize")) { - copy.setEternalized(true); - } - - copy.setStates(CardFactory.getCloneStates(original, copy, sa)); - // force update the now set State - copy.setState(copy.getCurrentStateName(), true, true); - copy.setToken(true); - - return copy; - } -} diff --git a/forge-gui/res/cardsfolder/upcoming/battle_for_bretagard.txt b/forge-gui/res/cardsfolder/upcoming/battle_for_bretagard.txt new file mode 100644 index 00000000000..b5d31297e24 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/battle_for_bretagard.txt @@ -0,0 +1,10 @@ +Name:Battle for Bretagard +ManaCost:1 G W +Types:Enchantment Saga +K:Saga:3:TrigToken1,TrigToken2,DBCopy +SVar:TrigToken1:DB$ Token | TokenAmount$ 1 | TokenScript$ w_1_1_human_warrior | TokenOwner$ You | SpellDescription$ Create a 1/1 white Human Warrior creature token. +SVar:TrigToken2:DB$ Token | TokenAmount$ 1 | TokenScript$ g_1_1_elf_warrior | TokenOwner$ You | SpellDescription$ Create a 1/1 green Elf Warrior creature token. +SVar:DBCopy:DB$ CopyPermanent | Choices$ Artifact.token+YouCtrl,Creature.token+YouCtrl | WithDifferentNames$ True | SpellDescription$ Choose any number of artifact tokens and/or creature tokens you control with different names. For each of them, create a token that’s a copy of it. +DeckHas:Ability$Token +Oracle:(As this Saga enters and after your draw step, add a lore counter. Sacrifice after III.)\nI — Create a 1/1 white Human Warrior creature token.\nII — Create a 1/1 green Elf Warrior creature token.\nIII — Choose any number of artifact tokens and/or creature tokens you control with different names. For each of them, create a token that’s a copy of it. + From b295acb68104aa42efbd9ae1b15044601a5dc3c8 Mon Sep 17 00:00:00 2001 From: Hans Mackowiak Date: Tue, 2 Feb 2021 08:01:01 +0100 Subject: [PATCH 18/74] fix CopyPermanentEffect --- .../ability/effects/CopyPermanentEffect.java | 226 ++++++++++++++++++ 1 file changed, 226 insertions(+) diff --git a/forge-game/src/main/java/forge/game/ability/effects/CopyPermanentEffect.java b/forge-game/src/main/java/forge/game/ability/effects/CopyPermanentEffect.java index 8b137891791..16979981f74 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/CopyPermanentEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/CopyPermanentEffect.java @@ -1 +1,227 @@ +package forge.game.ability.effects; + +import com.google.common.base.Predicate; +import com.google.common.base.Predicates; +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; + +import forge.StaticData; +import forge.card.CardRulesPredicates; +import forge.game.Game; +import forge.game.ability.AbilityUtils; +import forge.game.card.Card; +import forge.game.card.CardCollectionView; +import forge.game.card.CardFactory; +import forge.game.card.CardLists; +import forge.game.card.CardPredicates; +import forge.game.card.CardZoneTable; +import forge.game.event.GameEventCombatChanged; +import forge.game.player.Player; +import forge.game.player.PlayerActionConfirmMode; +import forge.game.spellability.SpellAbility; +import forge.game.zone.ZoneType; +import forge.item.PaperCard; +import forge.util.Aggregates; +import forge.util.TextUtil; +import forge.util.collect.FCollectionView; +import forge.util.PredicateString.StringOp; +import forge.util.Localizer; + +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.mutable.MutableBoolean; + +import java.util.Arrays; +import java.util.List; + +public class CopyPermanentEffect extends TokenEffectBase { + + @Override + protected String getStackDescription(SpellAbility sa) { + if (sa.hasParam("Populate")) { + return "Populate. (Create a token that's a copy of a creature token you control.)"; + } + final StringBuilder sb = new StringBuilder(); + + + final List tgtCards = getTargetCards(sa); + + sb.append("Copy "); + sb.append(StringUtils.join(tgtCards, ", ")); + sb.append("."); + return sb.toString(); + } + + /* (non-Javadoc) + * @see forge.card.ability.SpellAbilityEffect#resolve(forge.card.spellability.SpellAbility) + */ + @Override + public void resolve(final SpellAbility sa) { + final Card host = sa.getHostCard(); + final Player activator = sa.getActivatingPlayer(); + final Game game = host.getGame(); + final List pumpKeywords = Lists.newArrayList(); + + if (sa.hasParam("Optional")) { + if (!activator.getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblCopyPermanentConfirm"))) { + return; + } + } + + if (sa.hasParam("PumpKeywords")) { + pumpKeywords.addAll(Arrays.asList(sa.getParam("PumpKeywords").split(" & "))); + } + + final int numCopies = sa.hasParam("NumCopies") ? AbilityUtils.calculateAmount(host, + sa.getParam("NumCopies"), sa) : 1; + + Player controller = null; + if (sa.hasParam("Controller")) { + final FCollectionView defined = AbilityUtils.getDefinedPlayers(host, sa.getParam("Controller"), sa); + if (!defined.isEmpty()) { + controller = defined.getFirst(); + } + } + if (controller == null) { + controller = activator; + } + + List tgtCards = Lists.newArrayList(); + + if (sa.hasParam("ValidSupportedCopy")) { + List cards = Lists.newArrayList(StaticData.instance().getCommonCards().getUniqueCards()); + String valid = sa.getParam("ValidSupportedCopy"); + if (valid.contains("X")) { + valid = TextUtil.fastReplace(valid, + "X", Integer.toString(AbilityUtils.calculateAmount(host, "X", sa))); + } + if (StringUtils.containsIgnoreCase(valid, "creature")) { + Predicate cpp = Predicates.compose(CardRulesPredicates.Presets.IS_CREATURE, PaperCard.FN_GET_RULES); + cards = Lists.newArrayList(Iterables.filter(cards, cpp)); + } + if (StringUtils.containsIgnoreCase(valid, "equipment")) { + Predicate cpp = Predicates.compose(CardRulesPredicates.Presets.IS_EQUIPMENT, PaperCard.FN_GET_RULES); + cards = Lists.newArrayList(Iterables.filter(cards, cpp)); + } + if (sa.hasParam("RandomCopied")) { + List copysource = Lists.newArrayList(cards); + List choice = Lists.newArrayList(); + final String num = sa.hasParam("RandomNum") ? sa.getParam("RandomNum") : "1"; + int ncopied = AbilityUtils.calculateAmount(host, num, sa); + while(ncopied > 0) { + final PaperCard cp = Aggregates.random(copysource); + Card possibleCard = Card.fromPaperCard(cp, activator); // Need to temporarily set the Owner so the Game is set + + if (possibleCard.isValid(valid, host.getController(), host, sa)) { + choice.add(possibleCard); + copysource.remove(cp); + ncopied -= 1; + } + } + tgtCards = choice; + } else if (sa.hasParam("DefinedName")) { + String name = sa.getParam("DefinedName"); + if (name.equals("NamedCard")) { + if (!host.getNamedCard().isEmpty()) { + name = host.getNamedCard(); + } + } + + Predicate cpp = Predicates.compose(CardRulesPredicates.name(StringOp.EQUALS, name), PaperCard.FN_GET_RULES); + cards = Lists.newArrayList(Iterables.filter(cards, cpp)); + + if (!cards.isEmpty()) { + tgtCards.add(Card.fromPaperCard(cards.get(0), controller)); + } + } + } else if (sa.hasParam("Choices")) { + Player chooser = activator; + if (sa.hasParam("Chooser")) { + final String choose = sa.getParam("Chooser"); + chooser = AbilityUtils.getDefinedPlayers(sa.getHostCard(), choose, sa).get(0); + } + + CardCollectionView choices = game.getCardsIn(ZoneType.Battlefield); + choices = CardLists.getValidCards(choices, sa.getParam("Choices"), activator, host); + if (!choices.isEmpty()) { + String title = sa.hasParam("ChoiceTitle") ? sa.getParam("ChoiceTitle") : Localizer.getInstance().getMessage("lblChooseaCard"); + + if (sa.hasParam("WithDifferentNames")) { + // any Number of choices with different names + while (!choices.isEmpty()) { + Card choosen = chooser.getController().chooseSingleEntityForEffect(choices, sa, title, true, null); + + if (choosen != null) { + tgtCards.add(choosen); + choices = CardLists.filter(choices, Predicates.not(CardPredicates.sharesNameWith(choosen))); + } else if (chooser.getController().confirmAction(sa, PlayerActionConfirmMode.OptionalChoose, Localizer.getInstance().getMessage("lblCancelChooseConfirm"))) { + break; + } + } + } else { + Card choosen = chooser.getController().chooseSingleEntityForEffect(choices, sa, title, false, null); + if (choosen != null) { + tgtCards.add(choosen); + } + } + } + } else { + tgtCards = getDefinedCardsOrTargeted(sa); + } + + boolean useZoneTable = true; + CardZoneTable triggerList = sa.getChangeZoneTable(); + if (triggerList == null) { + triggerList = new CardZoneTable(); + useZoneTable = false; + } + if (sa.hasParam("ChangeZoneTable")) { + sa.setChangeZoneTable(triggerList); + useZoneTable = true; + } + + MutableBoolean combatChanged = new MutableBoolean(false); + for (final Card c : tgtCards) { + // if it only targets player, it already got all needed cards from defined + if (sa.usesTargeting() && !sa.getTargetRestrictions().canTgtPlayer() && !c.canBeTargetedBy(sa)) { + continue; + } + + makeTokens(getProtoType(sa, c), controller, sa, numCopies, true, true, triggerList, combatChanged); + } // end foreach Card + + if (!useZoneTable) { + triggerList.triggerChangesZoneAll(game); + triggerList.clear(); + } + if (combatChanged.isTrue()) { + game.updateCombatForView(); + game.fireEvent(new GameEventCombatChanged()); + } + } // end resolve + + private Card getProtoType(final SpellAbility sa, final Card original) { + final Card host = sa.getHostCard(); + final Player newOwner = sa.getActivatingPlayer(); + int id = newOwner == null ? 0 : newOwner.getGame().nextCardId(); + final Card copy = new Card(id, original.getPaperCard(), host.getGame()); + copy.setOwner(newOwner); + copy.setSetCode(original.getSetCode()); + + if (sa.hasParam("Embalm")) { + copy.setEmbalmed(true); + } + + if (sa.hasParam("Eternalize")) { + copy.setEternalized(true); + } + + copy.setStates(CardFactory.getCloneStates(original, copy, sa)); + // force update the now set State + copy.setState(copy.getCurrentStateName(), true, true); + copy.setToken(true); + + return copy; + } +} + From d87fd1c3761da14bce0469c723e3978a546951ae Mon Sep 17 00:00:00 2001 From: Hans Mackowiak Date: Tue, 2 Feb 2021 08:16:39 +0100 Subject: [PATCH 19/74] ReplaceAddCounter: add ValidSource and extend ValidObject --- .../forge/game/replacement/ReplaceAddCounter.java | 15 +++++++++++++-- .../upcoming/vorinclex_monstrous_raider.txt | 14 ++++++++++++++ 2 files changed, 27 insertions(+), 2 deletions(-) create mode 100644 forge-gui/res/cardsfolder/upcoming/vorinclex_monstrous_raider.txt diff --git a/forge-game/src/main/java/forge/game/replacement/ReplaceAddCounter.java b/forge-game/src/main/java/forge/game/replacement/ReplaceAddCounter.java index 27a388c2160..55cd031279b 100644 --- a/forge-game/src/main/java/forge/game/replacement/ReplaceAddCounter.java +++ b/forge-game/src/main/java/forge/game/replacement/ReplaceAddCounter.java @@ -45,7 +45,7 @@ public class ReplaceAddCounter extends ReplacementEffect { if (!(o instanceof Card)) { return false; } - if (!matchesValid(o, getParam("ValidCard").split(","), this.getHostCard())) { + if (!matchesValid(o, getParam("ValidCard").split(","), getHostCard())) { return false; } } else if (hasParam("ValidPlayer")) { @@ -53,7 +53,17 @@ public class ReplaceAddCounter extends ReplacementEffect { if (!(o instanceof Player)) { return false; } - if (!matchesValid(o, getParam("ValidPlayer").split(","), this.getHostCard())) { + if (!matchesValid(o, getParam("ValidPlayer").split(","), getHostCard())) { + return false; + } + } else if (hasParam("ValidObject")) { + if (!matchesValid(runParams.get(AbilityKey.Affected), getParam("ValidObject").split(","), getHostCard())) { + return false; + } + } + + if (hasParam("ValidSource")) { + if (!matchesValid(runParams.get(AbilityKey.Source), getParam("ValidSource").split(","), getHostCard())) { return false; } } @@ -81,6 +91,7 @@ public class ReplaceAddCounter extends ReplacementEffect { } else if (o instanceof Player) { sa.setReplacingObject(AbilityKey.Player, o); } + sa.setReplacingObject(AbilityKey.Object, o); } } diff --git a/forge-gui/res/cardsfolder/upcoming/vorinclex_monstrous_raider.txt b/forge-gui/res/cardsfolder/upcoming/vorinclex_monstrous_raider.txt new file mode 100644 index 00000000000..2bdb56cc448 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/vorinclex_monstrous_raider.txt @@ -0,0 +1,14 @@ +Name:Vorinclex, Monstrous Raider +ManaCost:4 G G +Types:Legendary Creature Phyrexian Praetor +PT:6/6 +K:Trample +K:Haste +R:Event$ AddCounter | ActiveZones$ Battlefield | ValidSource$ You | ValidObject$ Permanent.inZoneBattlefield,Player | ReplaceWith$ DoubleCounters | Description$ If you would put one or more counters on a permanent or player, put twice that many of each of those kinds of counters on that permanent or player instead. +SVar:DoubleCounters:DB$ ReplaceEffect | VarName$ CounterNum | VarValue$ X | References$ X +SVar:X:ReplaceCount$CounterNum/Twice +R:Event$ AddCounter | ActiveZones$ Battlefield | ValidSource$ Opponent | ValidObject$ Permanent.inZoneBattlefield,Player | ReplaceWith$ HalfCounters | Description$ If an opponent would put one or more counters on a permanent or player, they put half that many of each of those kinds of counters on that permanent or player instead, rounded down. +SVar:HalfCounters:DB$ ReplaceEffect | VarName$ CounterNum | VarValue$ Y | References$ Y +SVar:Y:ReplaceCount$CounterNum/HalfDown +Oracle:Trample, haste\nIf you would put one or more counters on a permanent or player, put twice that many of each of those kinds of counters on that permanent or player instead.\nIf an opponent would put one or more counters on a permanent or player, they put half that many of each of those kinds of counters on that permanent or player instead, rounded down. + From 8ec662d5e1abbad6f16dd2c3481e60708fe906cf Mon Sep 17 00:00:00 2001 From: CCTV-1 Date: Tue, 2 Feb 2021 16:57:27 +0800 Subject: [PATCH 20/74] fix:tax on incorrect targets --- .../reidane_god_of_the_worthy_valkmira_protectors_shield.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/forge-gui/res/cardsfolder/upcoming/reidane_god_of_the_worthy_valkmira_protectors_shield.txt b/forge-gui/res/cardsfolder/upcoming/reidane_god_of_the_worthy_valkmira_protectors_shield.txt index 689740e13d2..8f06d5959c5 100644 --- a/forge-gui/res/cardsfolder/upcoming/reidane_god_of_the_worthy_valkmira_protectors_shield.txt +++ b/forge-gui/res/cardsfolder/upcoming/reidane_god_of_the_worthy_valkmira_protectors_shield.txt @@ -6,9 +6,9 @@ K:Flying K:Vigilance R:Event$ Moved | ValidCard$ Land.Snow+OppCtrl | Destination$ Battlefield | ReplaceWith$ ETBTapped | ActiveZones$ Battlefield | Description$ Snow lands your opponents control enter the battlefield tapped. SVar:ETBTapped:DB$ ChangeZone | Origin$ All | Destination$ Battlefield | Tapped$ True | Defined$ ReplacedCard -S:Mode$ RaiseCost | ValidCard$ Card.cmcGE4 | Type$ Spell | Activator$ Opponent | Amount$ 2 | Description$ Spells your opponents cast with converted mana cost 4 or greater cost {2} more to cast. +S:Mode$ RaiseCost | ValidCard$ Card.nonCreature+cmcGE4 | Type$ Spell | Activator$ Opponent | Amount$ 2 | Description$ Noncreature spells your opponents cast with converted mana cost 4 or greater cost {2} more to cast. AlternateMode:Modal -Oracle:Flying, vigilance\nSnow lands your opponents control enter the battlefield tapped.\nSpells your opponents cast with converted mana cost 4 or greater cost {2} more to cast. +Oracle:Flying, vigilance\nSnow lands your opponents control enter the battlefield tapped.\nNoncreature spells your opponents cast with converted mana cost 4 or greater cost {2} more to cast. ALTERNATE From 0a5697613a921e4ac62f027d5ab654ef5bba19b7 Mon Sep 17 00:00:00 2001 From: John Date: Tue, 2 Feb 2021 09:24:48 +0000 Subject: [PATCH 21/74] bloodline pretender --- .../res/cardsfolder/upcoming/bloodline_pretender.txt | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 forge-gui/res/cardsfolder/upcoming/bloodline_pretender.txt diff --git a/forge-gui/res/cardsfolder/upcoming/bloodline_pretender.txt b/forge-gui/res/cardsfolder/upcoming/bloodline_pretender.txt new file mode 100644 index 00000000000..740d51cc872 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/bloodline_pretender.txt @@ -0,0 +1,11 @@ +Name:Bloodline Pretender +ManaCost:3 +Types:Artifact Creature Shapeshifter +PT:2/2 +K:Changeling +K:ETBReplacement:Other:ChooseCT +SVar:ChooseCT:DB$ ChooseType | Defined$ You | Type$ Creature | AILogic$ MostProminentInComputerDeck | SpellDescription$ As CARDNAME enters the battlefield, choose a creature type. +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Creature.Other+ChosenType+YouCtrl | TriggerZones$ Battlefield | Execute$ TrigPutCounter | TriggerDescription$ Whenever another creature of the chosen type enters the battlefield under your control, put a +1/+1 counter on CARDNAME. +SVar:TrigPutCounter:DB$PutCounter | CounterType$ P1P1 | CounterNum$ 1 +DeckHas:Ability$Counters +Oracle:Changeling\nAs Bloodline Pretender enters the battlefield, choose a creature type.\nWhenever another creature of the chosen type enters the battlefield under your control, put a +1/+1 counter on Bloodline Pretender. From 1a35016ed0884cf3a347f7f3b1decdb9f8bf35f2 Mon Sep 17 00:00:00 2001 From: John Date: Tue, 2 Feb 2021 11:25:45 +0000 Subject: [PATCH 22/74] invasion of the giants --- .../upcoming/invasion_of_the_giants.txt | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 forge-gui/res/cardsfolder/upcoming/invasion_of_the_giants.txt diff --git a/forge-gui/res/cardsfolder/upcoming/invasion_of_the_giants.txt b/forge-gui/res/cardsfolder/upcoming/invasion_of_the_giants.txt new file mode 100644 index 00000000000..75201703005 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/invasion_of_the_giants.txt @@ -0,0 +1,14 @@ +Name:Invasion of the Giants +ManaCost:U R +Types:Enchantment Saga +K:Saga:3:DBScry,DBDraw,DBEffect +SVar:DBScry:DB$ Scry | ScryNum$ 2 | SpellDescription$ Scry 2. +SVar:DBDraw:DB$ Draw | NumCards$ 1 | SubAbility$ DBReveal | SpellDescription$ Draw a card. Then you may reveal a Giant card from your hand. When you do, CARDNAME deals 2 damage to target opponent or planeswalker. +SVar:DBReveal:DB$ ImmediateTrigger | Execute$ TrigDealDamage | UnlessCost$ Reveal<1/Giant> | UnlessPayer$ You | UnlessSwitched$ True | SpellDescription$ You may reveal a Giant card from your hand. When you do, CARDNAME deals 2 damage to target opponent or planeswalker. +SVar:TrigDealDamage:DB$ DealDamage | ValidTgts$ Player.Opponent,Planeswalker | TgtPrompt$ Select target opponent or planeswalker. | NumDmg$ 2 +SVar:DBEffect:DB$ Effect | StaticAbilities$ ReduceCost | Triggers$ TrigCastSpell | SVars$ RemoveEffect| SpellDescription$ The next Giant spell you cast this turn costs {2} less to cast. +SVar:ReduceCost:Mode$ ReduceCost | EffectZone$ Command | Type$ Spell | ValidCard$ Giant | Activator$ You | Amount$ 2 +SVar:TrigCastSpell:Mode$ SpellCast | ValidCard$ Giant | ValidActivatingPlayer$ You | TriggerZones$ Command | Execute$ RemoveEffect | Static$ True +SVar:RemoveEffect:DB$ ChangeZone | Origin$ Command | Destination$ Exile +DeckHints:Type$Giant +Oracle:(As this Saga enters and after your draw step, add a lore counter. Sacrifice after III.)\nI - Scry 2.\nII - Draw a card. Then you may reveal a Giant card from your hand. When you do, Invasion of the Giants deals 2 damage to target opponent or planeswalker.\nIII - The next Giant spell you cast this turn costs {2} less to cast. From 0b93c82a3e70b14e1f1d3feedc5a6fee8e82d8d4 Mon Sep 17 00:00:00 2001 From: John Date: Tue, 2 Feb 2021 12:10:19 +0000 Subject: [PATCH 23/74] Immersturm Predator --- .../res/cardsfolder/upcoming/immersturm_predator.txt | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 forge-gui/res/cardsfolder/upcoming/immersturm_predator.txt diff --git a/forge-gui/res/cardsfolder/upcoming/immersturm_predator.txt b/forge-gui/res/cardsfolder/upcoming/immersturm_predator.txt new file mode 100644 index 00000000000..77c1a3bbc05 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/immersturm_predator.txt @@ -0,0 +1,12 @@ +Name:Immersturm Predator +ManaCost:2 B R +Types:Creature Vampire Dragon +PT:3/3 +K:Flying +T:Mode$ Taps | ValidCard$ Card.Self | Execute$ TrigExile | TriggerDescription$ Whenever CARDNAME becomes tapped, exile up to one target card from a graveyard and put a +1/+1 counter on CARDNAME. +SVar:TrigExile:DB$ Changezone | ValidTgts$ Card | TargetMin$ 0 | TargetMax$ 1 | Origin$ Graveyard | Destination$ Exile | TgtPrompt$ Choose target card in a graveyard | SubAbility$ DBPutCounter +SVar:DBPutCounter:DB$ PutCounter | Defined$ Self | CounterType$ P1P1 | CounterNum$ 1 +A:AB$ Pump | Cost$ Sac<1/Creature.Other/another creature> | Defined$ Self | KW$ Indestructible | SubAbility$ DBTap | SpellDescription$ CARDNAME gains indestructible until end of turn. Tap it. +SVar:DBTap:DB$ Tap | Defined$ Self +DeckHas:Ability$Counters +Oracle:Flying\nWhenever Immersturm Predator becomes tapped, exile up to one target card from a graveyard and put a +1/+1 counter on Immersturm Predator.\nSacrifice another creature: Immersturm Predator gains indestructible until end of turn. Tap it. From 94d60b8ff15a016ccbd99c9aa2a3ee3c81f37163 Mon Sep 17 00:00:00 2001 From: Anthony Calosa Date: Tue, 2 Feb 2021 12:56:25 +0000 Subject: [PATCH 24/74] Remove Unused Import --- forge-game/src/main/java/forge/game/cost/CostRemoveCounter.java | 1 - 1 file changed, 1 deletion(-) diff --git a/forge-game/src/main/java/forge/game/cost/CostRemoveCounter.java b/forge-game/src/main/java/forge/game/cost/CostRemoveCounter.java index 21ed56b4b61..9b012c916a6 100644 --- a/forge-game/src/main/java/forge/game/cost/CostRemoveCounter.java +++ b/forge-game/src/main/java/forge/game/cost/CostRemoveCounter.java @@ -19,7 +19,6 @@ package forge.game.cost; import com.google.common.collect.Lists; -import forge.game.ability.AbilityUtils; import forge.game.card.Card; import forge.game.card.CardLists; import forge.game.card.CounterEnumType; From f106e7ca589b623f38409bc05d265091d5beb8e8 Mon Sep 17 00:00:00 2001 From: John Date: Tue, 2 Feb 2021 12:57:33 +0000 Subject: [PATCH 25/74] Doomskar --- forge-gui/res/cardsfolder/upcoming/Doomskar.txt | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 forge-gui/res/cardsfolder/upcoming/Doomskar.txt diff --git a/forge-gui/res/cardsfolder/upcoming/Doomskar.txt b/forge-gui/res/cardsfolder/upcoming/Doomskar.txt new file mode 100644 index 00000000000..729389b3e93 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/Doomskar.txt @@ -0,0 +1,6 @@ +Name:Doomskar +ManaCost:3 W W +Types:Sorcery +A:SP$ DestroyAll | Cost$ 3 W W | ValidCards$ Creature | SpellDescription$ Destroy all creatures. +K:Foretell:1 W W +Oracle:Destroy all creatures.\nForetell {1}{W}{W} (During your turn, you may pay {2} and exile this card from your hand face down. Cast it on a later turn for its foretell cost.) From bbbe5228270fccda20a3dc603ca6fa23c29c8f43 Mon Sep 17 00:00:00 2001 From: John Date: Tue, 2 Feb 2021 12:59:13 +0000 Subject: [PATCH 26/74] doomskar oracle --- forge-gui/res/cardsfolder/upcoming/doomskar_oracle.txt | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 forge-gui/res/cardsfolder/upcoming/doomskar_oracle.txt diff --git a/forge-gui/res/cardsfolder/upcoming/doomskar_oracle.txt b/forge-gui/res/cardsfolder/upcoming/doomskar_oracle.txt new file mode 100644 index 00000000000..64363d8fa32 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/doomskar_oracle.txt @@ -0,0 +1,10 @@ +Name:Doomskar Oracle +ManaCost:2 W +Types:Creature Human Cleric +PT:3/2 +T:Mode$ SpellCast | ValidCard$ Card.YouCtrl | TriggerZones$ Battlefield | Execute$ TrigGainLife | CheckSVar$ YouCastThisTurn | SVarCompare$ EQ2 | NoResolvingCheck$ True | TriggerDescription$ Whenever you cast your second spell each turn, you gain 2 life. +SVar:TrigGainLife:DB$ GainLife | Defined$ You | LifeAmount$ 2 +SVar:YouCastThisTurn:Count$ThisTurnCast_Card.YouCtrl +SVar:BuffedBy:Card +K:Foretell:W +Oracle:Whenever you cast your second spell each turn, you gain 2 life.\nForetell {W} (During your turn, you may pay {2} and exile this card from your hand face down. Cast it on a later turn for its foretell cost.) From fab352a211940694694347b4d899f6e9c0e56b83 Mon Sep 17 00:00:00 2001 From: John Date: Tue, 2 Feb 2021 13:00:34 +0000 Subject: [PATCH 27/74] Glorious Protector --- .../upcoming/glorious_protector.txt | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 forge-gui/res/cardsfolder/upcoming/glorious_protector.txt diff --git a/forge-gui/res/cardsfolder/upcoming/glorious_protector.txt b/forge-gui/res/cardsfolder/upcoming/glorious_protector.txt new file mode 100644 index 00000000000..26ecd404faa --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/glorious_protector.txt @@ -0,0 +1,22 @@ +Name:Glorious Protector +ManaCost:2 W W +Types:Creature Angel Cleric +PT:3/4 +K:Flash +K:Flying +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigExile | TriggerDescription$ When CARDNAME enters the battlefield, you may exile any number of other non-Angel creatures you control until CARDNAME leaves the battlefield. +SVar:TrigExile:DB$ ChangeZone | Origin$ Battlefield | Destination$ Exile | RememberChanged$ True | Hidden$ True | ChangeType$ Creature.nonAngel+YouCtrl | ChangeNum$ MaxTgts | SelectPrompt$ Choose any number of non-Angel creatures you control | SubAbility$ DBEffect | References$ MaxTgts +SVar:DBEffect:DB$ Effect | Triggers$ ComeBack | RememberObjects$ RememberedCard | ImprintCards$ Self | SVars$ TrigReturn,ExileSelf | ConditionPresent$ Card.Self | Duration$ Permanent | ForgetOnMoved$ Exile +SVar:ComeBack:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Any | ValidCard$ Card.IsImprinted | Execute$ TrigReturn | TriggerZones$ Command | TriggerController$ TriggeredCardController | Static$ True | TriggerDescription$ That creature is exiled until EFFECTSOURCE leaves the battlefield +SVar:TrigReturn:DB$ ChangeZoneAll | Origin$ Exile | Destination$ Battlefield | ChangeType$ Card.IsRemembered | SubAbility$ ExileSelf +SVar:ExileSelf:DB$ ChangeZone | Origin$ Command | Destination$ Exile | Defined$ Self +#Triggers to forget remembered on this +T:Mode$ ChangesZone | ValidCard$ Card.IsRemembered | Origin$ Exile | Destination$ Any | TriggerZones$ Battlefield | Static$ True | Execute$ TrigForget +SVar:TrigForget:DB$ Pump | ForgetObjects$ TriggeredCard +T:Mode$ ChangesZone | ValidCard$ Card.Self | Origin$ Battlefield | Destination$ Any | Static$ True | Execute$ TrigForgetAll +SVar:TrigForgetAll:DB$ Cleanup | ClearRemembered$ True +SVar:X:Count$ValidExile Card.IsRemembered+ExiledWithSource/Times.2 +SVar:MaxTgts:Count$Valid Creature.nonAngel+YouCtrl +AI:RemoveDeck:Random +K:Foretell:2 W +Oracle:Flash\nFlying\nWhen Glorious Protector enters the battlefield, you may exile any number of non-Angel creatures you control until Glorious Protector leaves the battlefield.\nForetell {2}{W} (During your turn, you may pay {2} and exile this card from your hand face down. Cast it on a later turn for its foretell cost.) From 3d001d33a551d8ac5fa3b7b0755994b821d98057 Mon Sep 17 00:00:00 2001 From: John Date: Tue, 2 Feb 2021 13:01:13 +0000 Subject: [PATCH 28/74] Gods' Hall Guardian --- forge-gui/res/cardsfolder/upcoming/gods_hall_guardian.txt | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 forge-gui/res/cardsfolder/upcoming/gods_hall_guardian.txt diff --git a/forge-gui/res/cardsfolder/upcoming/gods_hall_guardian.txt b/forge-gui/res/cardsfolder/upcoming/gods_hall_guardian.txt new file mode 100644 index 00000000000..52f7fdc1910 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/gods_hall_guardian.txt @@ -0,0 +1,7 @@ +Name:Gods' Hall Guardian +ManaCost:5 W +Types:Creature Cat +PT:3/6 +K:Vigilance +K:Foretell:3 W +Oracle:Vigilance\nForetell {3}{W} (During your turn, you may pay {2} and exile this card from your hand face down. Cast it on a later turn for its foretell cost.) From 68263f1254177aff85b54271349e1da2b09cc284 Mon Sep 17 00:00:00 2001 From: John Date: Tue, 2 Feb 2021 13:01:55 +0000 Subject: [PATCH 29/74] Iron Verdict --- forge-gui/res/cardsfolder/upcoming/iron_verdict.txt | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 forge-gui/res/cardsfolder/upcoming/iron_verdict.txt diff --git a/forge-gui/res/cardsfolder/upcoming/iron_verdict.txt b/forge-gui/res/cardsfolder/upcoming/iron_verdict.txt new file mode 100644 index 00000000000..ec7be989360 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/iron_verdict.txt @@ -0,0 +1,6 @@ +Name:Iron Verdict +ManaCost:2 W +Types:Instant +A:SP$ DealDamage | Cost$ 2 W | ValidTgts$ Creature.tapped | NumDmg$ 5 | TgtPrompt$ Select target tapped creature | SpellDescription$ CARDNAME deals 5 damage to target tapped creature. +K:Foretell:W +Oracle:Iron Verdict deals 5 damage to target tapped creature.\nForetell {W} (During your turn, you may pay {2} and exile this card from your hand face down. Cast it on a later turn for its foretell cost.) From 1cb058e7d9fdc29f94b3086bd559c59ed60b3e65 Mon Sep 17 00:00:00 2001 From: John Date: Tue, 2 Feb 2021 13:02:41 +0000 Subject: [PATCH 30/74] Kaya's Onslaught --- forge-gui/res/cardsfolder/upcoming/kayas_onslaught.txt | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 forge-gui/res/cardsfolder/upcoming/kayas_onslaught.txt diff --git a/forge-gui/res/cardsfolder/upcoming/kayas_onslaught.txt b/forge-gui/res/cardsfolder/upcoming/kayas_onslaught.txt new file mode 100644 index 00000000000..30338dfd80c --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/kayas_onslaught.txt @@ -0,0 +1,6 @@ +Name:Kaya's Onslaught +ManaCost:2 W +Types:Instant +A:SP$ Pump | Cost$ 2 W | ValidTgts$ Creature | TgtPrompt$ Select target creature | NumAtt$ 1 | NumDef$ 1 | KW$ Double Strike | SpellDescription$ Target creature gets +1/+1 and gains double strike until end of turn. | StackDescription$ SpellDescription +K:Foretell:W +Oracle:Target creature gets +1/+1 and gains double strike until end of turn.\nForetell {W} (During your turn, you may pay {2} and exile this card from your hand face down. Cast it on a later turn for its foretell cost.) From 1f5c20fc2f40bda11f21fe170f1c88013760c66e Mon Sep 17 00:00:00 2001 From: John Date: Tue, 2 Feb 2021 13:03:32 +0000 Subject: [PATCH 31/74] Shepherd of the Cosmos --- .../res/cardsfolder/upcoming/shepherd_of_the_cosmos.txt | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 forge-gui/res/cardsfolder/upcoming/shepherd_of_the_cosmos.txt diff --git a/forge-gui/res/cardsfolder/upcoming/shepherd_of_the_cosmos.txt b/forge-gui/res/cardsfolder/upcoming/shepherd_of_the_cosmos.txt new file mode 100644 index 00000000000..afe001ec4dc --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/shepherd_of_the_cosmos.txt @@ -0,0 +1,9 @@ +Name:Shepherd of the Cosmos +ManaCost:4 W W +Types:Creature Angel Warrior +PT:3/3 +K:Flying +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigChange | TriggerDescription$ When CARDNAME enters the battlefield, return target permanent card with converted mana cost 2 or less from your graveyard to the battlefield. +SVar:TrigChange:DB$ ChangeZone | Origin$ Graveyard | Destination$ Battlefield | ValidTgts$ Permanent.YouOwn+cmcLE2 | TgtPrompt$ Select target permanent card with converted mana cost 2 or less in your graveyard +K:Foretell:3 W +Oracle:Flying\nWhen Shepherd of the Cosmos enters the battlefield, return target permanent card with converted mana cost 2 or less from your graveyard to the battlefield.\nForetell {3}{W} (During your turn, you may pay {2} and exile this card from your hand face down. Cast it on a later turn for its foretell cost.) From 4f9d3730207cf132d0c740a24e94a57c83c5bb17 Mon Sep 17 00:00:00 2001 From: John Date: Tue, 2 Feb 2021 13:04:20 +0000 Subject: [PATCH 32/74] Warhorn Blast --- forge-gui/res/cardsfolder/upcoming/warhorn_blast.txt | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 forge-gui/res/cardsfolder/upcoming/warhorn_blast.txt diff --git a/forge-gui/res/cardsfolder/upcoming/warhorn_blast.txt b/forge-gui/res/cardsfolder/upcoming/warhorn_blast.txt new file mode 100644 index 00000000000..64a06f40ff3 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/warhorn_blast.txt @@ -0,0 +1,6 @@ +Name:Warhorn Blast +ManaCost:4 W +Types:Instant +A:SP$ PumpAll | Cost$ 4 W | ValidCards$ Creature.YouCtrl | NumAtt$ +2 | NumDef$ +1 | SpellDescription$ Creatures you control get +2/+1 until end of turn. +K:Foretell:2 W +Oracle:Creatures you control get +2/+1 until end of turn.\nForetell {2}{W} (During your turn, you may pay {2} and exile this card from your hand face down. Cast it on a later turn for its foretell cost.) From 23bb1eea32ba788f971e4b39f667675c4b74f322 Mon Sep 17 00:00:00 2001 From: John Date: Tue, 2 Feb 2021 13:04:57 +0000 Subject: [PATCH 33/74] Alrund's Epiphany --- forge-gui/res/cardsfolder/upcoming/alrunds_epiphany.txt | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 forge-gui/res/cardsfolder/upcoming/alrunds_epiphany.txt diff --git a/forge-gui/res/cardsfolder/upcoming/alrunds_epiphany.txt b/forge-gui/res/cardsfolder/upcoming/alrunds_epiphany.txt new file mode 100644 index 00000000000..410be902c9e --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/alrunds_epiphany.txt @@ -0,0 +1,8 @@ +Name:Alrund's Epiphany +ManaCost:5 U U +Types:Sorcery +A:SP$ Token | Cost$ 5 U U | LegacyImage$ u 1 1 bird flying khm | TokenAmount$ 2 | TokenScript$ u_1_1_bird_flying | TokenOwner$ You | SubAbility$ DBAddTurn | SpellDescription$ Create two 1/1 blue Bird creature tokens with flying. Take an extra turn after this one. Exile CARDNAME. +SVar:DBAddTurn: DB$ AddTurn | Defined$ You | NumTurns$ 1 | SubAbility$ DBChange | StackDescription$ None +SVar:DBChange:DB$ ChangeZone | Origin$ Stack | Destination$ Exile | StackDescription$ None +K:Foretell:4 U U +Oracle:Create two 1/1 blue Bird creature tokens with flying. Take an extra turn after this one. Exile Alrund's Epiphany.\nForetell {4}{U}{U} (During your turn, you may pay {2} and exile this card from your hand face down. Cast it on a later turn for its foretell cost.) From 5994824d16cb7180dd2b6177505ad81078d04630 Mon Sep 17 00:00:00 2001 From: John Date: Tue, 2 Feb 2021 13:05:36 +0000 Subject: [PATCH 34/74] Augury Raven --- forge-gui/res/cardsfolder/upcoming/augury_raven.txt | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 forge-gui/res/cardsfolder/upcoming/augury_raven.txt diff --git a/forge-gui/res/cardsfolder/upcoming/augury_raven.txt b/forge-gui/res/cardsfolder/upcoming/augury_raven.txt new file mode 100644 index 00000000000..6ce07238d4a --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/augury_raven.txt @@ -0,0 +1,7 @@ +Name:Augury Raven +ManaCost:3 U +Types:Creature Bird +PT:3/3 +K:Flying +K:Foretell:1 U +Oracle:Flying\nForetell {1}{U} (During your turn, you may pay {2} and exile this card from your hand face down. Cast it on a later turn for its foretell cost.) From 6f3e3e53f7e4b3b6a751c841dc247b56dcf83b5c Mon Sep 17 00:00:00 2001 From: John Date: Tue, 2 Feb 2021 13:06:30 +0000 Subject: [PATCH 35/74] Behold the Multiverse --- .../res/cardsfolder/upcoming/behold_the_multiverse.txt | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 forge-gui/res/cardsfolder/upcoming/behold_the_multiverse.txt diff --git a/forge-gui/res/cardsfolder/upcoming/behold_the_multiverse.txt b/forge-gui/res/cardsfolder/upcoming/behold_the_multiverse.txt new file mode 100644 index 00000000000..943374b6a18 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/behold_the_multiverse.txt @@ -0,0 +1,7 @@ +Name:Behold the Multiverse +ManaCost:3 U +Types:Instant +A:SP$ Scry | Cost$ 3 U | ScryNum$ 2 | SubAbility$ DBDraw | AILogic$ BestOpportunity | SpellDescription$ Scry 2, then draw two cards. +SVar:DBDraw:DB$Draw | NumCards$ 2 +K:Foretell:1 U +Oracle:Scry 2, then draw two cards.\nForetell {1}{U} (During your turn, you may pay {2} and exile this card from your hand face down. Cast it on a later turn for its foretell cost.) From 65df42783159ee6e421c55d08e20ef7b6d6b7881 Mon Sep 17 00:00:00 2001 From: John Date: Tue, 2 Feb 2021 13:07:08 +0000 Subject: [PATCH 36/74] Depart the Realm --- forge-gui/res/cardsfolder/upcoming/depart_the_realm.txt | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 forge-gui/res/cardsfolder/upcoming/depart_the_realm.txt diff --git a/forge-gui/res/cardsfolder/upcoming/depart_the_realm.txt b/forge-gui/res/cardsfolder/upcoming/depart_the_realm.txt new file mode 100644 index 00000000000..e66721623b0 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/depart_the_realm.txt @@ -0,0 +1,6 @@ +Name:Depart the Realm +ManaCost:1 U +Types:Instant +A:SP$ ChangeZone | Cost$ 1 U | ValidTgts$ Permanent.nonLand | TgtPrompt$ Select target nonland permanent | Origin$ Battlefield | Destination$ Hand | SpellDescription$ Return target nonland permanent to its owner's hand. +K:Foretell:U +Oracle:Return target nonland permanent to its owner's hand.\nForetell {U} (During your turn, you may pay {2} and exile this card from your hand face down. Cast it on a later turn for its foretell cost.) From 3ecf9fd854e0befc2c6a1084b238c71e2f2905c8 Mon Sep 17 00:00:00 2001 From: John Date: Tue, 2 Feb 2021 13:07:50 +0000 Subject: [PATCH 37/74] Ravenform --- forge-gui/res/cardsfolder/upcoming/ravenform.txt | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 forge-gui/res/cardsfolder/upcoming/ravenform.txt diff --git a/forge-gui/res/cardsfolder/upcoming/ravenform.txt b/forge-gui/res/cardsfolder/upcoming/ravenform.txt new file mode 100644 index 00000000000..2fbcb54b19c --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/ravenform.txt @@ -0,0 +1,8 @@ +Name:Ravenform +ManaCost:2 U +Types:Sorcery +A:SP$ ChangeZone | Cost$ 2 U | ValidTgts$ Creature,Artifact | TgtPrompt$ Select target artifact or creature | Origin$ Battlefield | Destination$ Exile | SubAbility$ DBToken | AILogic$ Pongify | SpellDescription$ Exile target artifact or creature. Its controller creates a 1/1 Blue bird creature token with flying. +SVar:DBToken:DB$ Token | TokenAmount$ 1 | TokenScript$ u_1_1_bird_flying | TokenOwner$ TargetedController +DeckHas:Ability$Token +K:Foretell:U +Oracle:Exile target artifact or creature. Its controller creates a 1/1 Blue bird creature token with flying.\nForetell {U} (During your turn, you may pay {2} and exile this card from your hand face down. Cast it on a later turn for its foretell cost.) From c4ac6cb0af98c0ae86ef84dda8eb77cc7fe51c06 Mon Sep 17 00:00:00 2001 From: John Date: Tue, 2 Feb 2021 13:08:54 +0000 Subject: [PATCH 38/74] Saw it Coming --- forge-gui/res/cardsfolder/upcoming/saw_it_coming.txt | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 forge-gui/res/cardsfolder/upcoming/saw_it_coming.txt diff --git a/forge-gui/res/cardsfolder/upcoming/saw_it_coming.txt b/forge-gui/res/cardsfolder/upcoming/saw_it_coming.txt new file mode 100644 index 00000000000..c92c3bb8352 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/saw_it_coming.txt @@ -0,0 +1,6 @@ +Name:Saw it Coming +ManaCost:1 U U +Types:Instant +A:SP$ Counter | Cost$ 1 U U | TargetType$ Spell | ValidTgts$ Card | SpellDescription$ Counter target spell. +K:Foretell:1 U +Oracle:Counter target spell.\nForetell {1}{U} (During your turn, you may pay {2} and exile this card from your hand face down. Cast it on a later turn for its foretell cost.) From 3156b0503d37755b19290c6e28c1d8c4847587bf Mon Sep 17 00:00:00 2001 From: John Date: Tue, 2 Feb 2021 13:09:40 +0000 Subject: [PATCH 39/74] Jarl of the Forsaken --- .../res/cardsfolder/upcoming/jarl_of_the_forsaken.txt | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 forge-gui/res/cardsfolder/upcoming/jarl_of_the_forsaken.txt diff --git a/forge-gui/res/cardsfolder/upcoming/jarl_of_the_forsaken.txt b/forge-gui/res/cardsfolder/upcoming/jarl_of_the_forsaken.txt new file mode 100644 index 00000000000..fa9d430dcea --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/jarl_of_the_forsaken.txt @@ -0,0 +1,9 @@ +Name:Jarl of the Forsaken +ManaCost:3 B +Types:Creature Zombie Cleric +PT:3/2 +K:Flash +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigDestroy | TriggerController$ TriggeredCardController | TriggerDescription$ When CARDNAME enters the battlefield, destroy target creature or planeswalker an opponent controls that was dealt damage this turn. +SVar:TrigDestroy:DB$ Destroy | ValidTgts$ Creature.OppCtrl+wasDealtDamageThisTurn,Planeswalker.OppCtrl+wasDealtDamageThisTurn | TgtPrompt$ Select target creature or planeswalker an opponent controls that was dealt damage this turn. +K:Foretell:1 B +Oracle:Flash\nWhen Jarl of the Forsaken enters the battlefield, destroy target creature or planeswalker an opponent controls that was dealt damage this turn.\nForetell {1}{B} (During your turn, you may pay {2} and exile this card from your hand face down. Cast it on a later turn for its foretell cost.) From 65b4c2c1f652796c969c4be9c94c4a294757db6b Mon Sep 17 00:00:00 2001 From: John Date: Tue, 2 Feb 2021 13:10:25 +0000 Subject: [PATCH 40/74] Return Upon the Tide --- .../res/cardsfolder/upcoming/return_upon_the_tide.txt | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 forge-gui/res/cardsfolder/upcoming/return_upon_the_tide.txt diff --git a/forge-gui/res/cardsfolder/upcoming/return_upon_the_tide.txt b/forge-gui/res/cardsfolder/upcoming/return_upon_the_tide.txt new file mode 100644 index 00000000000..31e6c75a363 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/return_upon_the_tide.txt @@ -0,0 +1,8 @@ +Name:Return Upon the Tide +ManaCost:4 B +Types:Sorcery +A:SP$ ChangeZone | Cost$ 4 B | Origin$ Graveyard | Destination$ Battlefield | TgtPrompt$ Choose target creature card in your graveyard | ValidTgts$ Creature.YouCtrl | SubAbility$ DBToken | SpellDescription$ Return target creature card from your graveyard to the battlefield. If it's an Elf, create two 1/1 green Elf Warrior creature tokens. +SVar:DBToken:DB$ Token | TokenAmount$ 2 | TokenScript$ g_1_1_elf_warrior | LegacyImage$ g 1 1 elf warrior khm | TokenOwner$ You | ConditionCheckSVar$ X | ConditionSVarCompare$ GE1 | References$ X +SVar:X:Targeted$Valid Elf +K:ForeTell:3 B +Oracle:Return target creature card from your graveyard to the battlefield. If it's an Elf, create two 1/1 green Elf Warrior creature tokens.\nForetell {3}{B} (During your turn, you may pay {2} and exile this card from your hand face down. Cast it on a later turn for its foretell cost.) From 6a3fec347e2f193be32e9c803b5e5cc64dcf8fa3 Mon Sep 17 00:00:00 2001 From: John Date: Tue, 2 Feb 2021 13:11:04 +0000 Subject: [PATCH 41/74] Rise of the Dread Marn --- .../res/cardsfolder/upcoming/rise_of_the_dread_marn.txt | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 forge-gui/res/cardsfolder/upcoming/rise_of_the_dread_marn.txt diff --git a/forge-gui/res/cardsfolder/upcoming/rise_of_the_dread_marn.txt b/forge-gui/res/cardsfolder/upcoming/rise_of_the_dread_marn.txt new file mode 100644 index 00000000000..e70a6a13535 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/rise_of_the_dread_marn.txt @@ -0,0 +1,7 @@ +Name:Rise of the Dread Marn +ManaCost:2 B +Types:Instant +A:SP$ Token | Cost$ 2 B | LegacyImage$ b 2 2 zombie berserker khm | TokenAmount$ X | References$ X | TokenScript$ b_2_2_zombie_berserker | TokenOwner$ You | SpellDescription$ Create X 2/2 black Zombie Berserker creature tokens, where X is the number of nontoken creatures that died this turn. +SVar:X:Count$ThisTurnEntered_Graveyard_from_Battlefield_Creature.nonToken +K:Foretell:B +Oracle:Create X 2/2 black Zombie Berserker creature tokens, where X is the number of nontoken creatures that died this turn.\nForetell {B} (During your turn, you may pay {2} and exile this card from your hand face down. Cast it on a later turn for its foretell cost.) From 9f8cc8bb4fdcb4564e52eff2cc4cc66b979a7845 Mon Sep 17 00:00:00 2001 From: John Date: Tue, 2 Feb 2021 13:11:58 +0000 Subject: [PATCH 42/74] Tergrid's Shadow --- forge-gui/res/cardsfolder/upcoming/tergrids_shadow.txt | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 forge-gui/res/cardsfolder/upcoming/tergrids_shadow.txt diff --git a/forge-gui/res/cardsfolder/upcoming/tergrids_shadow.txt b/forge-gui/res/cardsfolder/upcoming/tergrids_shadow.txt new file mode 100644 index 00000000000..047c4c7afa4 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/tergrids_shadow.txt @@ -0,0 +1,7 @@ +Name:Tergrid's Shadow +ManaCost:3 B B +Types:Instant +A:SP$ Sacrifice | Cost$ 3 B B | Amount$ 2 | SacValid$ Creature | Defined$ Player | SpellDescription$ Each player sacrifices two creatures. | StackDescription$ SpellDescription +AI:RemoveDeck:All +K:Foretell:2 B B +Oracle:Each player sacrifices two creatures.\nForetell {2}{B}{B} (During your turn, you may pay {2} and exile this card from your hand face down. Cast it on a later turn for its foretell cost.) From d5dbf818c53ed2c4bda06188482312897a592871 Mon Sep 17 00:00:00 2001 From: John Date: Tue, 2 Feb 2021 13:12:31 +0000 Subject: [PATCH 43/74] Vengeful Reaper --- forge-gui/res/cardsfolder/upcoming/vengeful_reaper.txt | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 forge-gui/res/cardsfolder/upcoming/vengeful_reaper.txt diff --git a/forge-gui/res/cardsfolder/upcoming/vengeful_reaper.txt b/forge-gui/res/cardsfolder/upcoming/vengeful_reaper.txt new file mode 100644 index 00000000000..5ce29fefc40 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/vengeful_reaper.txt @@ -0,0 +1,9 @@ +Name:Vengeful Reaper +ManaCost:3 B +Types:Creature Angel Cleric +PT:2/3 +K:Flying +K:Deathtouch +K:Haste +K:Foretell:1 B +Oracle:Flying, deathtouch, haste\nForetell {1}{B} (During your turn, you may pay {2} and exile this card from your hand face down. Cast it on a later turn for its foretell cost.) From 7b340d04aa89599ac20bbdd9b6f86a07c40e3426 Mon Sep 17 00:00:00 2001 From: John Date: Tue, 2 Feb 2021 13:13:07 +0000 Subject: [PATCH 44/74] Crush the Weak --- forge-gui/res/cardsfolder/upcoming/crush_the_weak.txt | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 forge-gui/res/cardsfolder/upcoming/crush_the_weak.txt diff --git a/forge-gui/res/cardsfolder/upcoming/crush_the_weak.txt b/forge-gui/res/cardsfolder/upcoming/crush_the_weak.txt new file mode 100644 index 00000000000..7675d78fcc2 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/crush_the_weak.txt @@ -0,0 +1,7 @@ +Name:Crush the Weak +ManaCost:2 R +Types:Sorcery +A:SP$ DamageAll | Cost$ 2 R | ValidCards$ Creature | NumDmg$ 2 | RememberDamaged$ True | ReplaceDyingDefined$ Remembered | SubAbility$ DBCleanup | SpellDescription$ CARDNAME deals 2 damage to each creature. If a creature dealt damage this way would die this turn, exile it instead. | StackDescription$ SpellDescription +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True +K:Foretell:R +Oracle:Crush the Weak deals 2 damage to each creature. If a creature dealt damage this way would die this turn, exile it instead.\nForetell {R} (During your turn, you may pay {2} and exile this card from your hand face down. Cast it on a later turn for its foretell cost.) From 9224bbaa7542d9aee564d6d41cd5b40a8921a588 Mon Sep 17 00:00:00 2001 From: John Date: Tue, 2 Feb 2021 13:13:49 +0000 Subject: [PATCH 45/74] Demon Bolt --- forge-gui/res/cardsfolder/upcoming/demon_bolt.txt | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 forge-gui/res/cardsfolder/upcoming/demon_bolt.txt diff --git a/forge-gui/res/cardsfolder/upcoming/demon_bolt.txt b/forge-gui/res/cardsfolder/upcoming/demon_bolt.txt new file mode 100644 index 00000000000..8850c59cca7 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/demon_bolt.txt @@ -0,0 +1,6 @@ +Name:Demon Bolt +ManaCost:2 R +Types:Instant +A:SP$ DealDamage | Cost$ 2 R | ValidTgts$ Creature,Planeswalker | TgtPrompt$ Select target creature or planeswalker. | NumDmg$ 4 | SpellDescription$ CARDNAME deals 4 damage to target creature or planeswalker. +K:Foretell:R +Oracle:Demon Bolt deals 4 damage to target creature or planeswalker.\nForetell {R} (During your turn, you may pay {2} and exile this card from your hand face down. Cast it on a later turn for its foretell cost.) From 8be9ce5e784483f46764dca2006c1a79ebf2fb18 Mon Sep 17 00:00:00 2001 From: John Date: Tue, 2 Feb 2021 13:14:22 +0000 Subject: [PATCH 46/74] Dual Strike --- forge-gui/res/cardsfolder/upcoming/dual_strike.txt | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 forge-gui/res/cardsfolder/upcoming/dual_strike.txt diff --git a/forge-gui/res/cardsfolder/upcoming/dual_strike.txt b/forge-gui/res/cardsfolder/upcoming/dual_strike.txt new file mode 100644 index 00000000000..8cd17e6ebfc --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/dual_strike.txt @@ -0,0 +1,8 @@ +Name:Dual Strike +ManaCost:R R +Types:Instant +A:SP$ DelayedTrigger | Cost$ R R | AILogic$ SpellCopy | Execute$ EffTrigCopy | ThisTurn$ True | Mode$ SpellCast | ValidCard$ Instant.cmcLE4,Sorcery.cmcLE4 | ValidActivatingPlayer$ You | SpellDescription$ When you cast your next instant or sorcery spell with converted mana cost 4 or less this turn, copy that spell. You may choose new targets for the copy. +SVar:EffTrigCopy:DB$ CopySpellAbility | Defined$ TriggeredSpellAbility | MayChooseTarget$ True +SVar:AIPriorityModifier:9 +K:Foretell:R +Oracle:When you cast your next instant or sorcery spell with converted mana cost 4 or less this turn, copy that spell. You may choose new targets for that copy.\nForetell {R} (During your turn, you may pay {2} and exile this card from your hand face down. Cast it on a later turn for its foretell cost.) From de6c13b4f5f4cdd3be4f38b1ac5f07d7117e843c Mon Sep 17 00:00:00 2001 From: John Date: Tue, 2 Feb 2021 13:16:34 +0000 Subject: [PATCH 47/74] Dwarven Reinforcements --- .../res/cardsfolder/upcoming/dwarven_reinforcements.txt | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 forge-gui/res/cardsfolder/upcoming/dwarven_reinforcements.txt diff --git a/forge-gui/res/cardsfolder/upcoming/dwarven_reinforcements.txt b/forge-gui/res/cardsfolder/upcoming/dwarven_reinforcements.txt new file mode 100644 index 00000000000..f8746a8da3a --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/dwarven_reinforcements.txt @@ -0,0 +1,5 @@ +Name:Dwarven Reinforcements +ManaCost:3 R +Types:Sorcery +A:SP$ Token | Cost$ 3 R | LegacyImage$ r 2 1 dwarf berserker khm | TokenAmount$ 2 | TokenScript$ r_2_1_dwarf_berserker | TokenOwner$ You | SpellDescription$ Create 2 2/1 red Dwarf Berserker creature tokens. +Oracle:Create 2 2/1 red Dwarf Berserker creature tokens..\nForetell {1}{R} (During your turn, you may pay {2} and exile this card from your hand face down. Cast it on a later turn for its foretell cost.) From d843ee5b497d596926a203612483544eab1c9fe9 Mon Sep 17 00:00:00 2001 From: John Date: Tue, 2 Feb 2021 13:17:19 +0000 Subject: [PATCH 48/74] Battle Mammoth --- forge-gui/res/cardsfolder/upcoming/battle_mammoth.txt | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 forge-gui/res/cardsfolder/upcoming/battle_mammoth.txt diff --git a/forge-gui/res/cardsfolder/upcoming/battle_mammoth.txt b/forge-gui/res/cardsfolder/upcoming/battle_mammoth.txt new file mode 100644 index 00000000000..11d0cb3d057 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/battle_mammoth.txt @@ -0,0 +1,9 @@ +Name:Battle Mammoth +ManaCost:3 G G +Types:Creature Elephant +PT:6/5 +K:Trample +T:Mode$ BecomesTarget | ValidTarget$ Permanent.YouCtrl+inZoneBattlefield | ValidSource$ Card.OppCtrl | TriggerZones$ Battlefield | Execute$ TrigDraw | OptionalDecider$ You | TriggerDescription$ Whenever a permanent you control becomes the target of a spell or ability an opponent controls, you may draw a card. +SVar:TrigDraw:DB$Draw | NumCards$ 1 | SpellDescription$ Draw a card. +K:Foretell:2 G G +Oracle:Trample\nWhenever a permanent you control becomes the target of a spell or ability an opponent controls, you may draw a card.\nForetell {2}{G}{G} (During your turn, you may pay {2} and exile this card from your hand face down. Cast it on a later turn for its foretell cost.) From 11dda1b25b8beef81d74a6012a70f581a86493d5 Mon Sep 17 00:00:00 2001 From: John Date: Tue, 2 Feb 2021 13:17:58 +0000 Subject: [PATCH 49/74] Mammoth Growth --- forge-gui/res/cardsfolder/upcoming/mammoth_growth.txt | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 forge-gui/res/cardsfolder/upcoming/mammoth_growth.txt diff --git a/forge-gui/res/cardsfolder/upcoming/mammoth_growth.txt b/forge-gui/res/cardsfolder/upcoming/mammoth_growth.txt new file mode 100644 index 00000000000..bf8ebc0d11b --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/mammoth_growth.txt @@ -0,0 +1,6 @@ +Name:Mammoth Growth +ManaCost:2 G +Types:Instant +A:SP$ Pump | Cost$ 2 G | ValidTgts$ Creature | TgtPrompt$ Select target creature | NumAtt$ +4 | NumDef$ +4 | SpellDescription$ Target creature gets +4/+4 until end of turn. +K:Foretell:G +Oracle:Target creature gets +4/+4 until end of turn.\nForetell {G} (During your turn, you may pay {2} and exile this card from your hand face down. Cast it on a later turn for its foretell cost.) From ca51382fb5970a07a69a2b2a27498678e536455e Mon Sep 17 00:00:00 2001 From: John Date: Tue, 2 Feb 2021 13:18:36 +0000 Subject: [PATCH 50/74] Sarulf's Packmate --- forge-gui/res/cardsfolder/upcoming/sarulfs_packmate.txt | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 forge-gui/res/cardsfolder/upcoming/sarulfs_packmate.txt diff --git a/forge-gui/res/cardsfolder/upcoming/sarulfs_packmate.txt b/forge-gui/res/cardsfolder/upcoming/sarulfs_packmate.txt new file mode 100644 index 00000000000..141da1e87a3 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/sarulfs_packmate.txt @@ -0,0 +1,8 @@ +Name:Sarulf's Packmate +ManaCost:3 G +Types:Creature Wolf +PT:3/3 +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 | Defined$ You | NumCards$ 1 +K:Foretell:1 G +Oracle:When Sarulf's Packmate enters the battlefield, draw a card.\nForetell {1}{G} (During your turn, you may pay {2} and exile this card from your hand face down. Cast it on a later turn for its foretell cost.) From f023a522d6ed9e29dddaef070f3c0af179229e96 Mon Sep 17 00:00:00 2001 From: John Date: Tue, 2 Feb 2021 13:19:13 +0000 Subject: [PATCH 51/74] Struggle for Skemfar --- .../res/cardsfolder/upcoming/struggle_for_skemfar.txt | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 forge-gui/res/cardsfolder/upcoming/struggle_for_skemfar.txt diff --git a/forge-gui/res/cardsfolder/upcoming/struggle_for_skemfar.txt b/forge-gui/res/cardsfolder/upcoming/struggle_for_skemfar.txt new file mode 100644 index 00000000000..683169e2094 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/struggle_for_skemfar.txt @@ -0,0 +1,8 @@ +Name:Struggle for Skemfar +ManaCost:3 G +Types:Sorcery +A:SP$ PutCounter | Cost$ 3 G | AILogic$ Fight | ValidTgts$ Creature.YouCtrl | TgtPrompt$ Select target creature you control to put a +1/+1 counter | CounterType$ P1P1 | CounterNum$ 1 | SubAbility$ DBFight | SpellDescription$ Put a +1/+1 counter on target creature you control. Then that creature fights target creature you don't control. +SVar:DBFight:DB$ Fight | Defined$ ParentTarget | ValidTgts$ Creature.YouDontCtrl | AILogic$ Always | TgtPrompt$ Select target creature you don't control +DeckHas:Ability$Counters +K:Foretell:G +Oracle:Put a +1/+1 counter on target creature you control. Then that creature fights target creature you don't control. (Each deals damage equal to its power to the other.)\nForetell {G} (During your turn, you may pay {2} and exile this card from your hand face down. Cast it on a later turn for its foretell cost.) From c742ff9ec2e503eed1f080f21b07ab147a9db523 Mon Sep 17 00:00:00 2001 From: John Date: Tue, 2 Feb 2021 13:21:27 +0000 Subject: [PATCH 52/74] Update Doomskar.txt --- forge-gui/res/cardsfolder/upcoming/{Doomskar.txt => doomskar.txt} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename forge-gui/res/cardsfolder/upcoming/{Doomskar.txt => doomskar.txt} (100%) diff --git a/forge-gui/res/cardsfolder/upcoming/Doomskar.txt b/forge-gui/res/cardsfolder/upcoming/doomskar.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/Doomskar.txt rename to forge-gui/res/cardsfolder/upcoming/doomskar.txt From 9d12dc286d90db20324525ec4f136229cc51f989 Mon Sep 17 00:00:00 2001 From: John Date: Tue, 2 Feb 2021 13:55:24 +0000 Subject: [PATCH 53/74] Update dwarven_reinforcements.txt --- forge-gui/res/cardsfolder/upcoming/dwarven_reinforcements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/forge-gui/res/cardsfolder/upcoming/dwarven_reinforcements.txt b/forge-gui/res/cardsfolder/upcoming/dwarven_reinforcements.txt index f8746a8da3a..5ba628a0741 100644 --- a/forge-gui/res/cardsfolder/upcoming/dwarven_reinforcements.txt +++ b/forge-gui/res/cardsfolder/upcoming/dwarven_reinforcements.txt @@ -2,4 +2,4 @@ Name:Dwarven Reinforcements ManaCost:3 R Types:Sorcery A:SP$ Token | Cost$ 3 R | LegacyImage$ r 2 1 dwarf berserker khm | TokenAmount$ 2 | TokenScript$ r_2_1_dwarf_berserker | TokenOwner$ You | SpellDescription$ Create 2 2/1 red Dwarf Berserker creature tokens. -Oracle:Create 2 2/1 red Dwarf Berserker creature tokens..\nForetell {1}{R} (During your turn, you may pay {2} and exile this card from your hand face down. Cast it on a later turn for its foretell cost.) +Oracle:Create 2 2/1 red Dwarf Berserker creature tokens.\nForetell {1}{R} (During your turn, you may pay {2} and exile this card from your hand face down. Cast it on a later turn for its foretell cost.) From c033e374de6f11a583b87c53582992ab4ef4b5f3 Mon Sep 17 00:00:00 2001 From: John Date: Tue, 2 Feb 2021 14:13:44 +0000 Subject: [PATCH 54/74] doomskar titan --- forge-gui/res/cardsfolder/upcoming/doomskar_titan.txt | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 forge-gui/res/cardsfolder/upcoming/doomskar_titan.txt diff --git a/forge-gui/res/cardsfolder/upcoming/doomskar_titan.txt b/forge-gui/res/cardsfolder/upcoming/doomskar_titan.txt new file mode 100644 index 00000000000..2dd959f2d59 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/doomskar_titan.txt @@ -0,0 +1,9 @@ +Name:Doomskar Titan +ManaCost:4 R R +Types:Creature Giant Berserker +PT:4/4 +T:Mode$ ChangesZone | ValidCard$ Card.Self | Origin$ Any | Destination$ Battlefield | Execute$ TrigPumpAll | TriggerDescription$ When CARDNAME enters the battlefield, creatures you control get +1/0 and gain haste until end of turn. +SVar:TrigPumpAll:DB$ PumpAll | ValidCards$ Creature.YouCtrl | NumAtt$ 1 | KW$ Haste +SVar:PlayMain1:TRUE +K:Foretell:4 R +Oracle:When Doomskar Titan enters the battlefield, creatures you control get +1/+) and gain haste until end of turn.\nForetell {4}{R} (During your turn, you may pay {2} and exile this card from your hand face down. Cast it on a later turn for its foretell cost.) From 105ac4130aa226ece3efb95bec56e0708a3d09f7 Mon Sep 17 00:00:00 2001 From: John Date: Tue, 2 Feb 2021 15:40:28 +0000 Subject: [PATCH 55/74] Skull raid --- forge-gui/res/cardsfolder/upcoming/skull_raid.txt | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 forge-gui/res/cardsfolder/upcoming/skull_raid.txt diff --git a/forge-gui/res/cardsfolder/upcoming/skull_raid.txt b/forge-gui/res/cardsfolder/upcoming/skull_raid.txt new file mode 100644 index 00000000000..254990c2567 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/skull_raid.txt @@ -0,0 +1,10 @@ +Name:Skull Raid +ManaCost:3 B +Types:Sorcery +A:SP$ Discard | Cost$ 3 B | ValidTgts$ Player.Opponent | TgtPrompt$ Select target opponent | Mode$ TgtChoose | NumCards$ 2 | RememberDiscarded$ True | SubAbility$ DBDraw | SpellDescription$ Target opponent discards two cards. If fewer than two cards were discarded this way, you draw cards equal to the difference. +SVar:DBDraw:DB$ Draw | Defined$ You | NumCards$ X | References$ X,Y | SubAbility$ DBCleanup +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True +SVar:X:Number$2/Minus.Y +SVar:Y:Count$RememberedSize +K:Foretell:1 B +Oracle:Target opponent discards two cards. If fewer than two cards were discarded this way, you draw cards equal to the difference. From 4cbeca4cb3fc753886f3ba3f81d04254127509fe Mon Sep 17 00:00:00 2001 From: Hans Mackowiak Date: Tue, 2 Feb 2021 15:59:46 +0000 Subject: [PATCH 56/74] Foretell: add Keyword --- .../src/main/java/forge/game/ForgeScript.java | 4 + .../src/main/java/forge/game/GameAction.java | 8 +- .../main/java/forge/game/GameActionUtil.java | 63 ++++++++++++--- .../ability/effects/ChangeZoneEffect.java | 14 ++++ .../src/main/java/forge/game/card/Card.java | 52 +++++++++++-- .../java/forge/game/card/CardFactory.java | 2 +- .../java/forge/game/card/CardFactoryUtil.java | 65 +++++++++++++++- .../java/forge/game/card/CardProperty.java | 16 ++-- .../main/java/forge/game/card/CardUtil.java | 4 + .../main/java/forge/game/keyword/Keyword.java | 1 + .../java/forge/game/phase/PhaseHandler.java | 4 +- .../main/java/forge/game/player/Player.java | 19 ++++- .../game/spellability/AlternativeCost.java | 1 + .../forge/game/spellability/SpellAbility.java | 23 ++++-- .../spellability/SpellAbilityCondition.java | 5 ++ .../spellability/SpellAbilityVariables.java | 1 + .../forge/game/trigger/TriggerForetell.java | 78 +++++++++++++++++++ .../java/forge/game/trigger/TriggerType.java | 1 + .../main/java/forge/game/zone/MagicStack.java | 4 +- .../ai/simulation/GameSimulatorTest.java | 2 +- .../cardsfolder/upcoming/cosmos_charger.txt | 11 +++ .../cardsfolder/upcoming/dream_devourer.txt | 8 ++ .../upcoming/ethereal_valkyrie.txt | 11 +++ .../upcoming/karfell_harbinger.txt | 8 ++ .../upcoming/niko_defies_destiny.txt | 10 +++ .../cardsfolder/upcoming/poison_the_cup.txt | 8 ++ .../res/cardsfolder/upcoming/scorn_effigy.txt | 6 ++ .../upcoming/starnheim_unleashed.txt | 9 +++ .../w_4_4_angel_warrior_flying_vigilance.txt | 8 ++ .../src/main/java/forge/player/HumanPlay.java | 9 ++- .../forge/player/HumanPlaySpellAbility.java | 22 ++---- 31 files changed, 414 insertions(+), 63 deletions(-) create mode 100644 forge-game/src/main/java/forge/game/trigger/TriggerForetell.java create mode 100644 forge-gui/res/cardsfolder/upcoming/cosmos_charger.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/dream_devourer.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/ethereal_valkyrie.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/karfell_harbinger.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/niko_defies_destiny.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/poison_the_cup.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/scorn_effigy.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/starnheim_unleashed.txt create mode 100644 forge-gui/res/tokenscripts/w_4_4_angel_warrior_flying_vigilance.txt diff --git a/forge-game/src/main/java/forge/game/ForgeScript.java b/forge-game/src/main/java/forge/game/ForgeScript.java index ee2aa04bcb0..bf35e289bad 100644 --- a/forge-game/src/main/java/forge/game/ForgeScript.java +++ b/forge-game/src/main/java/forge/game/ForgeScript.java @@ -153,6 +153,10 @@ public class ForgeScript { return sa.hasParam("Equip"); } else if (property.equals("Boast")) { return sa.isBoast(); + } else if (property.equals("Foretelling")) { + return sa.isForetelling(); + } else if (property.equals("Foretold")) { + return sa.isForetold(); } else if (property.equals("MayPlaySource")) { StaticAbility m = sa.getMayPlay(); if (m == null) { diff --git a/forge-game/src/main/java/forge/game/GameAction.java b/forge-game/src/main/java/forge/game/GameAction.java index 21bf17450b8..7237d41a4af 100644 --- a/forge-game/src/main/java/forge/game/GameAction.java +++ b/forge-game/src/main/java/forge/game/GameAction.java @@ -73,6 +73,8 @@ public class GameAction { // Reset Activations per Turn for (final Card card : game.getCardsInGame()) { card.resetActivationsPerTurn(); + // need to reset this in exile + card.resetForetoldThisTurn(); } } @@ -168,7 +170,7 @@ public class GameAction { // Don't copy Tokens, copy only cards leaving the battlefield // and returning to hand (to recreate their spell ability information) - if (suppress || toBattlefield || zoneTo.is(ZoneType.Stack)) { + if (suppress || toBattlefield) { copied = c; if (lastKnownInfo == null) { @@ -193,10 +195,6 @@ public class GameAction { lastKnownInfo = CardUtil.getLKICopy(c); } - if (wasFacedown) { - c.forceTurnFaceUp(); - } - if (!c.isToken()) { copied = CardFactory.copyCard(c, false); diff --git a/forge-game/src/main/java/forge/game/GameActionUtil.java b/forge-game/src/main/java/forge/game/GameActionUtil.java index 510d52d3d15..45deabfc9c1 100644 --- a/forge-game/src/main/java/forge/game/GameActionUtil.java +++ b/forge-game/src/main/java/forge/game/GameActionUtil.java @@ -6,12 +6,12 @@ * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * + * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ @@ -54,7 +54,7 @@ import java.util.List; *

* GameActionUtil class. *

- * + * * @author Forge * @version $Id$ */ @@ -68,7 +68,7 @@ public final class GameActionUtil { *

* Find the alternative costs to a {@link SpellAbility}. *

- * + * * @param sa * a {@link SpellAbility}. * @param activator @@ -204,8 +204,51 @@ public final class GameActionUtil { flashback.setPayCosts(new Cost(k[1], false)); } alternatives.add(flashback); + } else if (keyword.startsWith("Foretell")) { + // Fortell cast only from Exile + if (!source.isInZone(ZoneType.Exile) || !source.isForetold() || source.isForetoldThisTurn()) { + continue; + } + // skip this part for fortell by external source + if (keyword.equals("Foretell")) { + continue; + } + + final SpellAbility foretold = sa.copy(activator); + foretold.setAlternativeCost(AlternativeCost.Foretold); + foretold.getRestrictions().setZone(ZoneType.Exile); + + // Stack Description only for Permanent or it might crash + if (source.isPermanent()) { + final StringBuilder sbStack = new StringBuilder(); + sbStack.append(sa.getStackDescription()).append(" (Foretold)"); + foretold.setStackDescription(sbStack.toString()); + } + + final String[] k = keyword.split(":"); + foretold.setPayCosts(new Cost(k[1], false)); + + alternatives.add(foretold); } } + + // foretell by external source + if (source.isForetoldByEffect() && source.isInZone(ZoneType.Exile) && source.isForetold() && !source.isForetoldThisTurn() && !source.getManaCost().isNoCost()) { + // Its foretell cost is equal to its mana cost reduced by {2}. + final SpellAbility foretold = sa.copy(activator); + foretold.putParam("ReduceCost", "2"); + foretold.setAlternativeCost(AlternativeCost.Foretold); + foretold.getRestrictions().setZone(ZoneType.Exile); + + // Stack Description only for Permanent or it might crash + if (source.isPermanent()) { + final StringBuilder sbStack = new StringBuilder(); + sbStack.append(sa.getStackDescription()).append(" (Foretold)"); + foretold.setStackDescription(sbStack.toString()); + } + + alternatives.add(foretold); + } } // reset static abilities @@ -295,12 +338,12 @@ public final class GameActionUtil { final Cost cost = new Cost(k[1], false); costs.add(new OptionalCostValue(OptionalCost.Flash, cost)); } - + // Surge while having OptionalCost is none of them } return costs; } - + public static SpellAbility addOptionalCosts(final SpellAbility sa, List list) { if (sa == null || list.isEmpty()) { return sa; @@ -309,7 +352,7 @@ public final class GameActionUtil { for (OptionalCostValue v : list) { result.getPayCosts().add(v.getCost()); result.addOptionalCost(v.getType()); - + // add some extra logic, try to move it to other parts switch (v.getType()) { case Retrace: @@ -325,7 +368,7 @@ public final class GameActionUtil { } return result; } - + public static List getAdditionalCostSpell(final SpellAbility sa) { final List abilities = Lists.newArrayList(sa); if (!sa.isSpell()) { @@ -358,14 +401,14 @@ public final class GameActionUtil { if (newSA2.canPlay()) { newAbilities.add(newSA2); } - + abilities.clear(); abilities.addAll(newAbilities); } } return abilities; } - + public static SpellAbility addExtraKeywordCost(final SpellAbility sa) { if (!sa.isSpell() || sa.isCopied()) { return sa; diff --git a/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneEffect.java index 9173d117ba1..8e5f57b1d92 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneEffect.java @@ -701,6 +701,13 @@ public class ChangeZoneEffect extends SpellAbilityEffect { if (sa.hasParam("ExileFaceDown")) { movedCard.turnFaceDown(true); } + if (sa.hasParam("Foretold")) { + movedCard.setForetold(true); + movedCard.setForetoldThisTurn(true); + movedCard.setForetoldByEffect(true); + // look at the exiled card + movedCard.addMayLookTemp(sa.getActivatingPlayer()); + } if (sa.hasParam("TrackDiscarded")) { movedCard.setMadnessWithoutCast(true); @@ -1240,6 +1247,13 @@ public class ChangeZoneEffect extends SpellAbilityEffect { if (sa.hasParam("ExileFaceDown")) { movedCard.turnFaceDown(true); } + if (sa.hasParam("Foretold")) { + movedCard.setForetold(true); + movedCard.setForetoldThisTurn(true); + movedCard.setForetoldByEffect(true); + // look at the exiled card + movedCard.addMayLookTemp(sa.getActivatingPlayer()); + } } else { movedCard = game.getAction().moveTo(destination, c, 0, cause, moveParams); 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 0eccdec5c6d..e1b2ca62fbd 100644 --- a/forge-game/src/main/java/forge/game/card/Card.java +++ b/forge-game/src/main/java/forge/game/card/Card.java @@ -154,7 +154,7 @@ public class Card extends GameEntity implements Comparable, IHasSVars { private boolean hasdealtDamagetoAny = false; private boolean isCommander = false; - private boolean canMoveToCommandZone = false; + private boolean canMoveToCommandZone = false; private boolean startsGameInPlay = false; private boolean drawnThisTurn = false; @@ -177,6 +177,10 @@ public class Card extends GameEntity implements Comparable, IHasSVars { private boolean manifested = false; + private boolean foretold = false; + private boolean foretoldThisTurn = false; + private boolean foretoldByEffect = false; + private long bestowTimestamp = -1; private long transformedTimestamp = 0; private boolean tributed = false; @@ -1694,7 +1698,7 @@ public class Card extends GameEntity implements Comparable, IHasSVars { } else { sbLong.append(parts[0]).append(" ").append(ManaCostParser.parse(parts[1])).append("\r\n"); } - } else if (keyword.startsWith("Morph") || keyword.startsWith("Megamorph") || keyword.startsWith("Escape")) { + } else if (keyword.startsWith("Morph") || keyword.startsWith("Megamorph") || keyword.startsWith("Escape") || keyword.startsWith("Foretell:")) { String[] k = keyword.split(":"); sbLong.append(k[0]); if (k.length > 1) { @@ -1789,6 +1793,7 @@ public class Card extends GameEntity implements Comparable, IHasSVars { || keyword.equals("Changeling") || keyword.equals("Delve") || keyword.equals("Split second") || keyword.equals("Sunburst") || keyword.equals("Suspend") // for the ones without amounnt + || keyword.equals("Foretell") // for the ones without cost || keyword.equals("Hideaway") || keyword.equals("Ascend") || keyword.equals("Totem armor") || keyword.equals("Battle cry") || keyword.equals("Devoid") || keyword.equals("Riot")){ @@ -2236,7 +2241,7 @@ public class Card extends GameEntity implements Comparable, IHasSVars { sbBefore.append("\r\n"); } else if (keyword.startsWith("Entwine") || keyword.startsWith("Madness") || keyword.startsWith("Miracle") || keyword.startsWith("Recover") - || keyword.startsWith("Escape")) { + || keyword.startsWith("Escape") || keyword.startsWith("Foretell:")) { final String[] k = keyword.split(":"); final Cost cost = new Cost(k[1], false); @@ -5319,6 +5324,42 @@ public class Card extends GameEntity implements Comparable, IHasSVars { } } + public final boolean isForetold() { + // in exile and foretold + if (this.isInZone(ZoneType.Exile)) { + return this.foretold; + } + // cast as foretold, currently only spells + if (this.getCastSA() != null) { + return this.getCastSA().isForetold(); + } + return false; + } + + public final void setForetold(final boolean foretold) { + this.foretold = foretold; + } + + public boolean isForetoldByEffect() { + return foretoldByEffect; + } + + public void setForetoldByEffect(final boolean val) { + this.foretoldByEffect = val; + } + + public boolean isForetoldThisTurn() { + return foretoldThisTurn; + } + + public final void setForetoldThisTurn(final boolean foretoldThisTurn) { + this.foretoldThisTurn = foretoldThisTurn; + } + + public void resetForetoldThisTurn() { + foretoldThisTurn = false; + } + public final void animateBestow() { animateBestow(true); } @@ -6567,11 +6608,6 @@ public class Card extends GameEntity implements Comparable, IHasSVars { return numberGameActivations.containsKey(original) ? numberGameActivations.get(original) : 0; } - public void resetTurnActivations() { - numberTurnActivations.clear(); - numberTurnActivationsStatic.clear(); - } - public List getChosenModesTurn(SpellAbility ability) { SpellAbility original = null; SpellAbility root = ability.getRootAbility(); 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 5dbfabc2740..dce30d6d5b7 100644 --- a/forge-game/src/main/java/forge/game/card/CardFactory.java +++ b/forge-game/src/main/java/forge/game/card/CardFactory.java @@ -88,7 +88,7 @@ public class CardFactory { } out.setZone(in.getZone()); - out.setState(in.getCurrentStateName(), true); + out.setState(in.getFaceupCardStateName(), true); out.setBackSide(in.isBackSide()); // this's necessary for forge.game.GameAction.unattachCardLeavingBattlefield(Card) 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 f6f4655d747..70da4cfd33d 100644 --- a/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java +++ b/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java @@ -66,7 +66,6 @@ import java.util.Map.Entry; import io.sentry.Sentry; import io.sentry.event.BreadcrumbBuilder; - /** *

* CardFactoryUtil class. @@ -1404,6 +1403,13 @@ public class CardFactoryUtil { return doXMath(StringUtils.isNumeric(v) ? Integer.parseInt(v) : xCount(c, c.getSVar(v)), m, c); } + // Count$Foretold.. + if (sq[0].startsWith("Foretold")) { + String v = c.isForetold() ? sq[1] : sq[2]; + // TODO move this to AbilityUtils + return doXMath(StringUtils.isNumeric(v) ? Integer.parseInt(v) : xCount(c, c.getSVar(v)), m, c); + } + // Count$Presence_.. if (sq[0].startsWith("Presence")) { final String type = sq[0].split("_")[1]; @@ -1449,7 +1455,6 @@ public class CardFactoryUtil { return forge.util.MyRandom.getRandom().nextInt(1+max-min) + min; } - // Count$Domain if (sq[0].startsWith("Domain")) { int n = 0; @@ -1997,7 +2002,6 @@ public class CardFactoryUtil { final Set hexproofkw = Sets.newHashSet(); final Set allkw = Sets.newHashSet(); - for (Card c : CardLists.getValidCards(cardlist, restrictions, p, host, null)) { for (KeywordInterface inst : c.getKeywords()) { final String k = inst.getOriginal(); @@ -2155,7 +2159,6 @@ public class CardFactoryUtil { return re; } - public static ReplacementEffect makeEtbCounter(final String kw, final Card card, final boolean intrinsic) { String parse = kw; @@ -4095,6 +4098,60 @@ public class CardFactoryUtil { newSA.setAlternativeCost(AlternativeCost.Evoke); newSA.setIntrinsic(intrinsic); inst.addSpellAbility(newSA); + } else if (keyword.startsWith("Foretell")) { + + final SpellAbility foretell = new AbilityStatic(card, new Cost(ManaCost.TWO, false), null) { + @Override + public boolean canPlay() { + if (!getRestrictions().canPlay(getHostCard(), this)) { + return false; + } + + Player activator = this.getActivatingPlayer(); + final Game game = activator.getGame(); + + if (!activator.hasKeyword("Foretell on any player’s turn") && !game.getPhaseHandler().isPlayerTurn(activator)) { + return false; + } + + return true; + } + + @Override + public boolean isForetelling() { + return true; + } + + @Override + public void resolve() { + final Game game = getHostCard().getGame(); + final Card c = game.getAction().exile(getHostCard(), this); + c.setForetold(true); + c.setForetoldThisTurn(true); + c.turnFaceDown(true); + // look at the exiled card + c.addMayLookTemp(getActivatingPlayer()); + + // only done when the card is foretold by the static ability + getActivatingPlayer().addForetoldThisTurn(); + + if (!isIntrinsic()) { + // because it doesn't work other wise + c.setForetoldByEffect(true); + } + String sb = TextUtil.concatWithSpace(getActivatingPlayer().toString(),"has foretold."); + game.getGameLog().add(GameLogEntryType.STACK_RESOLVE, sb); + } + }; + final StringBuilder sbDesc = new StringBuilder(); + sbDesc.append("Foretell (").append(inst.getReminderText()).append(")"); + foretell.setDescription(sbDesc.toString()); + foretell.putParam("Secondary", "True"); + + foretell.getRestrictions().setZone(ZoneType.Hand); + foretell.setIntrinsic(intrinsic); + inst.addSpellAbility(foretell); + } else if (keyword.startsWith("Fortify")) { String[] k = keyword.split(":"); // Get cost string diff --git a/forge-game/src/main/java/forge/game/card/CardProperty.java b/forge-game/src/main/java/forge/game/card/CardProperty.java index 57cc898e053..2da6fca758d 100644 --- a/forge-game/src/main/java/forge/game/card/CardProperty.java +++ b/forge-game/src/main/java/forge/game/card/CardProperty.java @@ -1670,36 +1670,40 @@ public class CardProperty { if (property.equals("pseudokicked")) { if (!card.isOptionalCostPaid(OptionalCost.Generic)) return false; } - } else if (property.startsWith("surged")) { + } else if (property.equals("surged")) { if (card.getCastSA() == null) { return false; } return card.getCastSA().isSurged(); - } else if (property.startsWith("dashed")) { + } else if (property.equals("dashed")) { if (card.getCastSA() == null) { return false; } return card.getCastSA().isDash(); - } else if (property.startsWith("escaped")) { + } else if (property.equals("escaped")) { if (card.getCastSA() == null) { return false; } return card.getCastSA().isEscape(); - } else if (property.startsWith("evoked")) { + } else if (property.equals("evoked")) { if (card.getCastSA() == null) { return false; } return card.getCastSA().isEvoke(); - } else if (property.startsWith("prowled")) { + } else if (property.equals("prowled")) { if (card.getCastSA() == null) { return false; } return card.getCastSA().isProwl(); - } else if (property.startsWith("spectacle")) { + } else if (property.equals("spectacle")) { if (card.getCastSA() == null) { return false; } return card.getCastSA().isSpectacle(); + } else if (property.equals("foretold")) { + if (!card.isForetold()) { + return false; + } } else if (property.equals("HasDevoured")) { if (card.getDevouredCards().isEmpty()) { return false; diff --git a/forge-game/src/main/java/forge/game/card/CardUtil.java b/forge-game/src/main/java/forge/game/card/CardUtil.java index ce15796ef90..e1726689621 100644 --- a/forge-game/src/main/java/forge/game/card/CardUtil.java +++ b/forge-game/src/main/java/forge/game/card/CardUtil.java @@ -278,6 +278,10 @@ public final class CardUtil { newCopy.copyChangedTextFrom(in); + newCopy.setForetold(in.isForetold()); + newCopy.setForetoldThisTurn(in.isForetoldThisTurn()); + newCopy.setForetoldByEffect(in.isForetoldByEffect()); + newCopy.setMeldedWith(getLKICopy(in.getMeldedWith(), cachedMap)); newCopy.setTimestamp(in.getTimestamp()); 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 b244e3c5ba8..96a11fb2ef4 100644 --- a/forge-game/src/main/java/forge/game/keyword/Keyword.java +++ b/forge-game/src/main/java/forge/game/keyword/Keyword.java @@ -70,6 +70,7 @@ public enum Keyword { FLASH("Flash", SimpleKeyword.class, true, "You may cast this spell any time you could cast an instant."), FLASHBACK("Flashback", KeywordWithCost.class, false, "You may cast this card from your graveyard by paying %s rather than paying its mana cost. If you do, exile it as it resolves."), FLYING("Flying", SimpleKeyword.class, true, "This creature can't be blocked except by creatures with flying or reach."), + FORETELL("Foretell", KeywordWithCost.class, false, "During your turn, you may pay {2} and exile this card from your hand face down. Cast it on a later turn for its foretell cost."), FORTIFY("Fortify", KeywordWithCost.class, false, "%s: Attach to target land you control. Fortify only as a sorcery."), FRENZY("Frenzy", KeywordWithAmount.class, false, "Whenever this creature attacks and isn't blocked, it gets +%d/+0 until end of turn."), GRAFT("Graft", KeywordWithAmount.class, false, "This permanent enters the battlefield with {%d:+1/+1 counter} on it. Whenever another creature enters the battlefield, you may move a +1/+1 counter from this permanent onto it."), diff --git a/forge-game/src/main/java/forge/game/phase/PhaseHandler.java b/forge-game/src/main/java/forge/game/phase/PhaseHandler.java index bc2f165b584..782bce143af 100644 --- a/forge-game/src/main/java/forge/game/phase/PhaseHandler.java +++ b/forge-game/src/main/java/forge/game/phase/PhaseHandler.java @@ -26,7 +26,6 @@ import forge.game.*; import forge.game.ability.AbilityKey; import forge.game.card.Card; import forge.game.card.CardCollection; -import forge.game.card.CardCollectionView; import forge.game.card.CardLists; import forge.game.card.CardPredicates.Presets; import forge.game.card.CardZoneTable; @@ -175,8 +174,7 @@ public class PhaseHandler implements java.io.Serializable { game.fireEvent(new GameEventTurnBegan(playerTurn, turn)); // Tokens starting game in play should suffer from Sum. Sickness - final CardCollectionView list = playerTurn.getCardsIncludePhasingIn(ZoneType.Battlefield); - for (final Card c : list) { + for (final Card c : playerTurn.getCardsIncludePhasingIn(ZoneType.Battlefield)) { if (playerTurn.getTurn() > 0 || !c.isStartsGameInPlay()) { c.setSickness(false); } diff --git a/forge-game/src/main/java/forge/game/player/Player.java b/forge-game/src/main/java/forge/game/player/Player.java index 877158b6a3f..1ee63399db8 100644 --- a/forge-game/src/main/java/forge/game/player/Player.java +++ b/forge-game/src/main/java/forge/game/player/Player.java @@ -111,6 +111,7 @@ public class Player extends GameEntity implements Comparable { private int numDrawnThisDrawStep = 0; private int numDiscardedThisTurn = 0; private int numTokenCreatedThisTurn = 0; + private int numForetoldThisTurn = 0; private int numCardsInHandStartedThisTurnWith = 0; private final Map> notes = Maps.newHashMap(); @@ -1666,6 +1667,22 @@ public class Player extends GameEntity implements Comparable { numTokenCreatedThisTurn = 0; } + public final int getNumForetoldThisTurn() { + return numForetoldThisTurn; + } + + public final void addForetoldThisTurn() { + numForetoldThisTurn++; + final Map runParams = AbilityKey.newMap(); + runParams.put(AbilityKey.Player, this); + runParams.put(AbilityKey.Num, numForetoldThisTurn); + game.getTriggerHandler().runTrigger(TriggerType.Foretell, runParams, false); + } + + public final void resetNumForetoldThisTurn() { + numForetoldThisTurn = 0; + } + public final int getNumDiscardedThisTurn() { return numDiscardedThisTurn; } @@ -2581,7 +2598,7 @@ public class Player extends GameEntity implements Comparable { controlledBy.remove(timestamp); getView().updateMindSlaveMaster(this); - + if (event) { game.fireEvent(new GameEventPlayerControl(this, oldLobbyPlayer, oldController, getLobbyPlayer(), getController())); } 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 bb8029e89aa..7f23a7fdbc6 100644 --- a/forge-game/src/main/java/forge/game/spellability/AlternativeCost.java +++ b/forge-game/src/main/java/forge/game/spellability/AlternativeCost.java @@ -9,6 +9,7 @@ public enum AlternativeCost { Escape, Evoke, Flashback, + Foretold, Madness, Offering, Outlast, // ActivatedAbility 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 344345b9dd5..bc63cb582f7 100644 --- a/forge-game/src/main/java/forge/game/spellability/SpellAbility.java +++ b/forge-game/src/main/java/forge/game/spellability/SpellAbility.java @@ -466,23 +466,23 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit public final void clearManaPaid() { payingMana.clear(); } - + public final void applyPayingManaEffects() { Card host = getHostCard(); - + for (Mana mana : getPayingMana()) { if (mana.triggersWhenSpent()) { mana.getManaAbility().addTriggersWhenSpent(this, host); } - + if (mana.addsCounters(this)) { mana.getManaAbility().createETBCounters(host, getActivatingPlayer()); } - + if (mana.addsNoCounterMagic(this) && host != null) { host.setCanCounter(false); } - + if (isSpell() && host != null) { if (mana.addsKeywords(this) && mana.addsKeywordsType() && host.getType().hasStringType(mana.getManaAbility().getAddsKeywordsType())) { @@ -823,6 +823,14 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit return this.isAlternativeCost(AlternativeCost.Flashback); } + public boolean isForetelling() { + return false; + } + public boolean isForetold() { + return this.isAlternativeCost(AlternativeCost.Foretold); + } + + /** * @return the aftermath */ @@ -1780,6 +1788,11 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit return false; } } + else if (incR[0].equals("Static")) { + if (!(root instanceof AbilityStatic)) { + return false; + } + } else { //not a spell/ability type return false; } diff --git a/forge-game/src/main/java/forge/game/spellability/SpellAbilityCondition.java b/forge-game/src/main/java/forge/game/spellability/SpellAbilityCondition.java index 687d58ffdad..416e3a05f65 100644 --- a/forge-game/src/main/java/forge/game/spellability/SpellAbilityCondition.java +++ b/forge-game/src/main/java/forge/game/spellability/SpellAbilityCondition.java @@ -118,6 +118,10 @@ public class SpellAbilityCondition extends SpellAbilityVariables { this.optionalCostPaid = true; } + if (value.equals("Foretold")) { + this.foretold = true; + } + if (params.containsKey("ConditionOptionalPaid")) { this.optionalBoolean = Boolean.parseBoolean(params.get("ConditionOptionalPaid")); } @@ -250,6 +254,7 @@ public class SpellAbilityCondition extends SpellAbilityVariables { if (this.kicked2 && !sa.isOptionalCostPaid(OptionalCost.Kicker2)) return false; if (this.altCostPaid && !sa.isOptionalCostPaid(OptionalCost.AltCost)) return false; if (this.surgeCostPaid && !sa.isSurged()) return false; + if (this.foretold && !sa.isForetold()) return false; if (this.optionalCostPaid && this.optionalBoolean && !sa.isOptionalCostPaid(OptionalCost.Generic)) return false; if (this.optionalCostPaid && !this.optionalBoolean && sa.isOptionalCostPaid(OptionalCost.Generic)) return false; diff --git a/forge-game/src/main/java/forge/game/spellability/SpellAbilityVariables.java b/forge-game/src/main/java/forge/game/spellability/SpellAbilityVariables.java index 69a4fd24d49..00120c5b815 100644 --- a/forge-game/src/main/java/forge/game/spellability/SpellAbilityVariables.java +++ b/forge-game/src/main/java/forge/game/spellability/SpellAbilityVariables.java @@ -405,6 +405,7 @@ public class SpellAbilityVariables implements Cloneable { protected boolean optionalCostPaid = false; // Undergrowth other Pseudo-kickers protected boolean optionalBoolean = true; // Just in case you need to check if something wasn't kicked, etc protected boolean surgeCostPaid = false; + protected boolean foretold = false; /** * @return the allTargetsLegal diff --git a/forge-game/src/main/java/forge/game/trigger/TriggerForetell.java b/forge-game/src/main/java/forge/game/trigger/TriggerForetell.java new file mode 100644 index 00000000000..b051315ffff --- /dev/null +++ b/forge-game/src/main/java/forge/game/trigger/TriggerForetell.java @@ -0,0 +1,78 @@ +/* + * Forge: Play Magic: the Gathering. + * Copyright (C) 2011 Forge Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package forge.game.trigger; + +import forge.game.ability.AbilityKey; +import forge.game.card.Card; +import forge.game.player.Player; +import forge.game.spellability.SpellAbility; +import forge.util.Localizer; + +import java.util.Map; + +/** + * @author Forge + */ +public class TriggerForetell extends Trigger { + + /** + * + * @param params + * a {@link java.util.HashMap} object. + * @param host + * a {@link forge.game.card.Card} object. + * @param intrinsic + * the intrinsic + */ + public TriggerForetell(final Map params, final Card host, final boolean intrinsic) { + super(params, host, intrinsic); + } + + @Override + public String getImportantStackObjects(SpellAbility sa) { + StringBuilder sb = new StringBuilder(); + sb.append(Localizer.getInstance().getMessage("lblPlayer")).append(": ").append(sa.getTriggeringObject(AbilityKey.Player)); + return sb.toString(); + } + + /** {@inheritDoc} */ + @Override + public final void setTriggeringObjects(final SpellAbility sa, Map runParams) { + sa.setTriggeringObjectsFrom(runParams, AbilityKey.Player); + } + + /** {@inheritDoc} + * @param runParams*/ + @Override + public final boolean performTest(final Map runParams) { + Player p = (Player) runParams.get(AbilityKey.Player); + if (hasParam("ValidPlayer")) { + if (!matchesValid(p, getParam("ValidPlayer").split(","), getHostCard())) { + return false; + } + } + + if (hasParam("OnlyFirst")) { + if ((int) runParams.get(AbilityKey.Num) != 1) { + return false; + } + } + return true; + } + +} diff --git a/forge-game/src/main/java/forge/game/trigger/TriggerType.java b/forge-game/src/main/java/forge/game/trigger/TriggerType.java index d31de582d13..bd3175bf094 100644 --- a/forge-game/src/main/java/forge/game/trigger/TriggerType.java +++ b/forge-game/src/main/java/forge/game/trigger/TriggerType.java @@ -64,6 +64,7 @@ public enum TriggerType { Fight(TriggerFight.class), FightOnce(TriggerFightOnce.class), FlippedCoin(TriggerFlippedCoin.class), + Foretell(TriggerForetell.class), Immediate(TriggerImmediate.class), Investigated(TriggerInvestigated.class), LandPlayed(TriggerLandPlayed.class), diff --git a/forge-game/src/main/java/forge/game/zone/MagicStack.java b/forge-game/src/main/java/forge/game/zone/MagicStack.java index 213755d6d02..ea9308f0895 100644 --- a/forge-game/src/main/java/forge/game/zone/MagicStack.java +++ b/forge-game/src/main/java/forge/game/zone/MagicStack.java @@ -52,7 +52,6 @@ import forge.game.keyword.Keyword; import forge.game.player.Player; import forge.game.spellability.AbilityStatic; import forge.game.spellability.OptionalCost; -import forge.game.spellability.Spell; import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbilityStackInstance; import forge.game.spellability.TargetChoices; @@ -241,8 +240,7 @@ public class MagicStack /* extends MyObservable */ implements Iterable= 0 ? Integer.valueOf(zonePosition) : null, null); + oldCard.setCastSA(null); + oldCard.setCastFrom(null); + // add back to where it came from, hopefully old state + // skip GameAction + oldCard.getZone().remove(oldCard); + fromZone.add(oldCard, zonePosition >= 0 ? Integer.valueOf(zonePosition) : null); } ability.clearTargets(); From 3d8749e5e1b38c9c34ce3a5e03dd278954df8b1d Mon Sep 17 00:00:00 2001 From: Alumi Date: Tue, 2 Feb 2021 16:00:30 +0000 Subject: [PATCH 57/74] Shahrazad --- forge-game/src/main/java/forge/game/Game.java | 37 ++- .../src/main/java/forge/game/GameAction.java | 12 + .../main/java/forge/game/ability/ApiType.java | 1 + .../game/ability/effects/SubgameEffect.java | 254 ++++++++++++++++++ .../forge/game/event/GameEventSubgameEnd.java | 18 ++ .../game/event/GameEventSubgameStart.java | 19 ++ .../forge/game/event/IGameEventVisitor.java | 4 + .../main/java/forge/game/player/Player.java | 11 +- .../main/java/forge/game/zone/ZoneType.java | 1 + forge-gui/res/cardsfolder/s/shahrazad.txt | 10 + forge-gui/res/languages/de-DE.properties | 5 + forge-gui/res/languages/en-US.properties | 5 + forge-gui/res/languages/es-ES.properties | 5 + forge-gui/res/languages/it-IT.properties | 5 + forge-gui/res/languages/zh-CN.properties | 5 + .../main/java/forge/match/HostedMatch.java | 76 +++++- 16 files changed, 462 insertions(+), 6 deletions(-) create mode 100644 forge-game/src/main/java/forge/game/ability/effects/SubgameEffect.java create mode 100644 forge-game/src/main/java/forge/game/event/GameEventSubgameEnd.java create mode 100644 forge-game/src/main/java/forge/game/event/GameEventSubgameStart.java create mode 100644 forge-gui/res/cardsfolder/s/shahrazad.txt diff --git a/forge-game/src/main/java/forge/game/Game.java b/forge-game/src/main/java/forge/game/Game.java index 1a4376377d6..58d844b2911 100644 --- a/forge-game/src/main/java/forge/game/Game.java +++ b/forge-game/src/main/java/forge/game/Game.java @@ -109,6 +109,7 @@ public class Game { private final Match match; private GameStage age = GameStage.BeforeMulligan; private GameOutcome outcome; + private Game maingame = null; private final GameView view; private final Tracker tracker = new Tracker(); @@ -219,7 +220,11 @@ public class Game { changeZoneLKIInfo.clear(); } - public Game(Iterable players0, GameRules rules0, Match match0) { /* no more zones to map here */ + public Game(Iterable players0, GameRules rules0, Match match0) { + this(players0, rules0, match0, -1); + } + + public Game(Iterable players0, GameRules rules0, Match match0, int startingLife) { /* no more zones to map here */ rules = rules0; match = match0; this.id = nextId(); @@ -243,7 +248,11 @@ public class Game { allPlayers.add(pl); ingamePlayers.add(pl); - pl.setStartingLife(psc.getStartingLife()); + if (startingLife != -1) { + pl.setStartingLife(startingLife); + } else { + pl.setStartingLife(psc.getStartingLife()); + } pl.setMaxHandSize(psc.getStartingHand()); pl.setStartingHandSize(psc.getStartingHand()); @@ -430,6 +439,14 @@ public class Game { return outcome; } + public final Game getMaingame() { + return maingame; + } + + public void setMaingame(final Game maingame0) { + maingame = maingame0; + } + public ReplacementHandler getReplacementHandler() { return replacementHandler; } @@ -452,12 +469,16 @@ public class Game { result.setTurnsPlayed(getPhaseHandler().getTurn()); outcome = result; - match.addGamePlayed(this); + if (maingame == null) { + match.addGamePlayed(this); + } view.updateGameOver(this); // The log shall listen to events and generate text internally - fireEvent(new GameEventGameOutcome(result, match.getOutcomes())); + if (maingame == null) { + fireEvent(new GameEventGameOutcome(result, match.getOutcomes())); + } } public Zone getZoneOf(final Card card) { @@ -492,6 +513,14 @@ public class Game { return cards; } + public CardCollectionView getCardsInOwnedBy(final Iterable zones, Player p) { + CardCollection cards = new CardCollection(); + for (final ZoneType z : zones) { + cards.addAll(getCardsIncludePhasingIn(z)); + } + return CardLists.filter(cards, CardPredicates.isOwner(p)); + } + public boolean isCardExiled(final Card c) { return getCardsIn(ZoneType.Exile).contains(c); } diff --git a/forge-game/src/main/java/forge/game/GameAction.java b/forge-game/src/main/java/forge/game/GameAction.java index 21bf17450b8..03272b027aa 100644 --- a/forge-game/src/main/java/forge/game/GameAction.java +++ b/forge-game/src/main/java/forge/game/GameAction.java @@ -518,6 +518,18 @@ public class GameAction { c = changeZone(zoneFrom, zoneTo, c, position, cause, params); + // Move card in maingame if take card from subgame + // 720.4a + if (zoneFrom != null && zoneFrom.is(ZoneType.Sideboard) && game.getMaingame() != null) { + Card maingameCard = c.getOwner().getMappingMaingameCard(c); + if (maingameCard != null) { + if (maingameCard.getZone().is(ZoneType.Stack)) { + game.getMaingame().getStack().remove(maingameCard); + } + game.getMaingame().getAction().moveTo(ZoneType.Subgame, maingameCard, null); + } + } + if (zoneFrom == null) { c.setCastFrom(null); c.setCastSA(null); diff --git a/forge-game/src/main/java/forge/game/ability/ApiType.java b/forge-game/src/main/java/forge/game/ability/ApiType.java index 365afb52c11..f21cb010fc6 100644 --- a/forge-game/src/main/java/forge/game/ability/ApiType.java +++ b/forge-game/src/main/java/forge/game/ability/ApiType.java @@ -153,6 +153,7 @@ public enum ApiType { SkipTurn (SkipTurnEffect.class), StoreSVar (StoreSVarEffect.class), StoreMap (StoreMapEffect.class), + Subgame (SubgameEffect.class), Surveil (SurveilEffect.class), SwitchBlock (SwitchBlockEffect.class), Tap (TapEffect.class), diff --git a/forge-game/src/main/java/forge/game/ability/effects/SubgameEffect.java b/forge-game/src/main/java/forge/game/ability/effects/SubgameEffect.java new file mode 100644 index 00000000000..486c43a2c26 --- /dev/null +++ b/forge-game/src/main/java/forge/game/ability/effects/SubgameEffect.java @@ -0,0 +1,254 @@ +package forge.game.ability.effects; + +import java.util.*; + +import com.google.common.collect.Lists; + +import forge.card.MagicColor; +import forge.game.Game; +import forge.game.GameOutcome; +import forge.game.ability.ApiType; +import forge.game.ability.SpellAbilityEffect; +import forge.game.card.Card; +import forge.game.card.CardCollectionView; +import forge.game.event.GameEventSubgameStart; +import forge.game.event.GameEventSubgameEnd; +import forge.game.player.Player; +import forge.game.player.PlayerController; +import forge.game.player.RegisteredPlayer; +import forge.game.spellability.SpellAbility; +import forge.game.zone.PlayerZone; +import forge.game.zone.ZoneType; +import forge.item.PaperCard; +import forge.util.CardTranslation; +import forge.util.Lang; +import forge.util.Localizer; +import forge.util.collect.FCollectionView; + +public class SubgameEffect extends SpellAbilityEffect { + + private final Game createSubGame(Game maingame, int startingLife) { + List players = Lists.newArrayList(); + + // Add remaining players to subgame + for (Player p : maingame.getPlayers()) { + players.add(p.getRegisteredPlayer()); + } + + return new Game(players, maingame.getRules(), maingame.getMatch(), startingLife); + } + + private final void setCardsInZone(Player player, final ZoneType zoneType, final CardCollectionView oldCards, boolean addMapping) { + PlayerZone zone = player.getZone(zoneType); + List newCards = Lists.newArrayList(); + for (final Card card : oldCards) { + if (card.isToken() || card.isCopiedSpell()) continue; + Card newCard = Card.fromPaperCard(card.getPaperCard(), player); + newCards.add(newCard); + if (addMapping) { + // Build mapping between maingame cards and subgame cards, + // so when subgame pick a card from maingame (like Wish effects), + // The maingame card will also be moved. + // (Will be move to Subgame zone, which will be added back to libary after subgame ends.) + player.addMaingameCardMapping(newCard, card); + } + } + zone.setCards(newCards); + } + + private final void initVariantsZonesSubgame(final Game subgame, final Player maingamePlayer, final Player player) { + PlayerZone com = player.getZone(ZoneType.Command); + RegisteredPlayer registeredPlayer = player.getRegisteredPlayer(); + + // Vanguard + if (registeredPlayer.getVanguardAvatars() != null) { + for(PaperCard avatar:registeredPlayer.getVanguardAvatars()) { + com.add(Card.fromPaperCard(avatar, player)); + } + } + + // Commander + List commanders = Lists.newArrayList(); + final CardCollectionView commandCards = maingamePlayer.getCardsIn(ZoneType.Command); + for (final Card card : commandCards) { + if (card.isCommander()) { + Card cmd = Card.fromPaperCard(card.getPaperCard(), player); + if (cmd.hasKeyword("If CARDNAME is your commander, choose a color before the game begins.")) { + List colorChoices = new ArrayList<>(MagicColor.Constant.ONLY_COLORS); + String prompt = Localizer.getInstance().getMessage("lblChooseAColorFor", cmd.getName()); + List chosenColors; + SpellAbility cmdColorsa = new SpellAbility.EmptySa(ApiType.ChooseColor, cmd, player); + chosenColors = player.getController().chooseColors(prompt,cmdColorsa, 1, 1, colorChoices); + cmd.setChosenColors(chosenColors); + subgame.getAction().nofityOfValue(cmdColorsa, cmd, Localizer.getInstance().getMessage("lblPlayerPickedChosen", player.getName(), Lang.joinHomogenous(chosenColors)), player); + } + cmd.setCommander(true); + com.add(cmd); + commanders.add(cmd); + com.add(Player.createCommanderEffect(subgame, cmd)); + } + } + if (!commanders.isEmpty()) { + player.setCommanders(commanders); + } + + // Conspiracies + // 720.2 doesn't mention Conspiracy cards so I guess they don't move + } + + private void prepareAllZonesSubgame(final Game maingame, final Game subgame) { + final FCollectionView players = subgame.getPlayers(); + final FCollectionView maingamePlayers = maingame.getPlayers(); + final List outsideZones = Arrays.asList(ZoneType.Hand, ZoneType.Battlefield, + ZoneType.Graveyard, ZoneType.Exile, ZoneType.Stack, ZoneType.Sideboard, ZoneType.Ante); + + for (int i = 0; i < players.size(); i++) { + final Player player = players.get(i); + final Player maingamePlayer = maingamePlayers.get(i); + + // Library + setCardsInZone(player, ZoneType.Library, maingamePlayer.getCardsIn(ZoneType.Library), false); + + // Sideboard + // 720.4 + final CardCollectionView outsideCards = maingame.getCardsInOwnedBy(outsideZones, maingamePlayer); + if (!outsideCards.isEmpty()) { + setCardsInZone(player, ZoneType.Sideboard, outsideCards, true); + + // Assign Companion + PlayerController person = player.getController(); + Card companion = player.assignCompanion(subgame, person); + // Create an effect that lets you cast your companion from your sideboard + if (companion != null) { + PlayerZone commandZone = player.getZone(ZoneType.Command); + companion = subgame.getAction().moveTo(ZoneType.Command, companion, null); + commandZone.add(Player.createCompanionEffect(subgame, companion)); + + player.updateZoneForView(commandZone); + } + } + + // Schemes + setCardsInZone(player, ZoneType.SchemeDeck, maingamePlayer.getCardsIn(ZoneType.SchemeDeck), false); + + // Planes + setCardsInZone(player, ZoneType.PlanarDeck, maingamePlayer.getCardsIn(ZoneType.PlanarDeck), false); + + // Vanguard and Commanders + initVariantsZonesSubgame(subgame, maingamePlayer, player); + + player.shuffle(null); + player.getZone(ZoneType.SchemeDeck).shuffle(); + player.getZone(ZoneType.PlanarDeck).shuffle(); + } + } + + @Override + public void resolve(SpellAbility sa) { + final Card hostCard = sa.getHostCard(); + final Game maingame = hostCard.getGame(); + + int startingLife = -1; + if (sa.hasParam("StartingLife")) { + startingLife = Integer.parseInt(sa.getParam("StartingLife")); + } + Game subgame = createSubGame(maingame, startingLife); + subgame.setMaingame(maingame); + + String startMessage = Localizer.getInstance().getMessage("lblSubgameStart", + CardTranslation.getTranslatedName(hostCard.getName())); + maingame.fireEvent(new GameEventSubgameStart(subgame, startMessage)); + + prepareAllZonesSubgame(maingame, subgame); + subgame.getAction().startGame(null, null); + subgame.clearCaches(); + + // Find out winners and losers + final GameOutcome outcome = subgame.getOutcome(); + List winPlayers = Lists.newArrayList(); + List notWinPlayers = Lists.newArrayList(); + StringBuilder sbWinners = new StringBuilder(); + StringBuilder sbLosers = new StringBuilder(); + for (Player p : maingame.getPlayers()) { + if (outcome.isWinner(p.getRegisteredPlayer())) { + if (!winPlayers.isEmpty()) { + sbWinners.append(", "); + } + sbWinners.append(p.getName()); + winPlayers.add(p); + } else { + if (!notWinPlayers.isEmpty()) { + sbLosers.append(", "); + } + sbLosers.append(p.getName()); + notWinPlayers.add(p); + } + } + + if (sa.hasParam("RememberPlayers")) { + final String param = sa.getParam("RememberPlayers"); + if (param.equals("Win")) { + for (Player p : winPlayers) { + hostCard.addRemembered(p); + } + } else if (param.equals("NotWin")) { + for (Player p : notWinPlayers) { + hostCard.addRemembered(p); + } + } + } + + + String endMessage = outcome.isDraw() ? Localizer.getInstance().getMessage("lblSubgameEndDraw") : + Localizer.getInstance().getMessage("lblSubgameEnd", sbWinners.toString(), sbLosers.toString()); + maingame.fireEvent(new GameEventSubgameEnd(maingame, endMessage)); + + // Setup maingame library + final FCollectionView subgamePlayers = subgame.getRegisteredPlayers(); + final FCollectionView players = maingame.getPlayers(); + for (int i = 0; i < players.size(); i++) { + final Player subgamePlayer = subgamePlayers.get(i); + final Player player = players.get(i); + + // All cards moved to Subgame Zone will be put into library when subgame ends. + // 720.5 + final CardCollectionView movedCards = player.getCardsIn(ZoneType.Subgame); + PlayerZone library = player.getZone(ZoneType.Library); + for (final Card card : movedCards) { + library.add(card); + } + player.getZone(ZoneType.Subgame).removeAllCards(true); + + // Move commander if it is no longer in subgame's commander zone + // 720.5c + List subgameCommanders = Lists.newArrayList(); + List movedCommanders = Lists.newArrayList(); + for (final Card card : subgamePlayer.getCardsIn(ZoneType.Command)) { + if (card.isCommander()) { + subgameCommanders.add(card); + } + } + for (final Card card : player.getCardsIn(ZoneType.Command)) { + if (card.isCommander()) { + boolean isInSubgameCommand = false; + for (final Card subCard : subgameCommanders) { + if (card.getName().equals(subCard.getName())) { + isInSubgameCommand = true; + } + } + if (!isInSubgameCommand) { + movedCommanders.add(card); + } + } + } + for (final Card card : movedCommanders) { + maingame.getAction().moveTo(ZoneType.Library, card, null); + } + + player.shuffle(sa); + player.getZone(ZoneType.SchemeDeck).shuffle(); + player.getZone(ZoneType.PlanarDeck).shuffle(); + } + } + +} diff --git a/forge-game/src/main/java/forge/game/event/GameEventSubgameEnd.java b/forge-game/src/main/java/forge/game/event/GameEventSubgameEnd.java new file mode 100644 index 00000000000..5fcaf2a9256 --- /dev/null +++ b/forge-game/src/main/java/forge/game/event/GameEventSubgameEnd.java @@ -0,0 +1,18 @@ +package forge.game.event; + +import forge.game.Game; + +public class GameEventSubgameEnd extends GameEvent { + public final Game maingame; + public final String message; + + public GameEventSubgameEnd(Game game, String message0) { + maingame = game; + message = message0; + } + + @Override + public T visit(IGameEventVisitor visitor) { + return visitor.visit(this); + } +} diff --git a/forge-game/src/main/java/forge/game/event/GameEventSubgameStart.java b/forge-game/src/main/java/forge/game/event/GameEventSubgameStart.java new file mode 100644 index 00000000000..674427a6f91 --- /dev/null +++ b/forge-game/src/main/java/forge/game/event/GameEventSubgameStart.java @@ -0,0 +1,19 @@ +package forge.game.event; + +import forge.game.Game; +import forge.game.card.Card; + +public class GameEventSubgameStart extends GameEvent { + public final Game subgame; + public final String message; + + public GameEventSubgameStart(Game subgame0, String message0) { + subgame = subgame0; + message = message0; + } + + @Override + public T visit(IGameEventVisitor visitor) { + return visitor.visit(this); + } +} diff --git a/forge-game/src/main/java/forge/game/event/IGameEventVisitor.java b/forge-game/src/main/java/forge/game/event/IGameEventVisitor.java index 2ded38b9cc4..0041e7545fc 100644 --- a/forge-game/src/main/java/forge/game/event/IGameEventVisitor.java +++ b/forge-game/src/main/java/forge/game/event/IGameEventVisitor.java @@ -44,6 +44,8 @@ public interface IGameEventVisitor { T visit(GameEventSpellAbilityCast gameEventSpellAbilityCast); T visit(GameEventSpellResolved event); T visit(GameEventSpellRemovedFromStack event); + T visit(GameEventSubgameStart event); + T visit(GameEventSubgameEnd event); T visit(GameEventSurveil event); T visit(GameEventTokenCreated event); T visit(GameEventTurnBegan gameEventTurnBegan); @@ -92,6 +94,8 @@ public interface IGameEventVisitor { public T visit(GameEventSpellResolved event) { return null; } public T visit(GameEventSpellAbilityCast event) { return null; } public T visit(GameEventSpellRemovedFromStack event) { return null; } + public T visit(GameEventSubgameStart event) { return null; } + public T visit(GameEventSubgameEnd event) { return null; } public T visit(GameEventSurveil event) { return null; } public T visit(GameEventTokenCreated event) { return null; } public T visit(GameEventTurnBegan event) { return null; } diff --git a/forge-game/src/main/java/forge/game/player/Player.java b/forge-game/src/main/java/forge/game/player/Player.java index 877158b6a3f..e6c04706673 100644 --- a/forge-game/src/main/java/forge/game/player/Player.java +++ b/forge-game/src/main/java/forge/game/player/Player.java @@ -77,7 +77,7 @@ import java.util.concurrent.ConcurrentSkipListMap; public class Player extends GameEntity implements Comparable { public static final List ALL_ZONES = Collections.unmodifiableList(Arrays.asList(ZoneType.Battlefield, ZoneType.Library, ZoneType.Graveyard, ZoneType.Hand, ZoneType.Exile, ZoneType.Command, ZoneType.Ante, - ZoneType.Sideboard, ZoneType.PlanarDeck, ZoneType.SchemeDeck)); + ZoneType.Sideboard, ZoneType.PlanarDeck, ZoneType.SchemeDeck, ZoneType.Subgame)); private final Map commanderDamage = Maps.newHashMap(); @@ -140,6 +140,7 @@ public class Player extends GameEntity implements Comparable { private final Map zones = Maps.newEnumMap(ZoneType.class); private final Map adjustLandPlays = Maps.newHashMap(); private final Set adjustLandPlaysInfinite = Sets.newHashSet(); + private Map maingameCardsMap = Maps.newHashMap();; private CardCollection currentPlanes = new CardCollection(); private Set prowl = Sets.newHashSet(); @@ -1905,6 +1906,14 @@ public class Player extends GameEntity implements Comparable { return !adjustLandPlaysInfinite.isEmpty(); } + public final void addMaingameCardMapping(Card subgameCard, Card maingameCard) { + maingameCardsMap.put(subgameCard, maingameCard); + } + + public final Card getMappingMaingameCard(Card subgameCard) { + return maingameCardsMap.get(subgameCard); + } + public final ManaPool getManaPool() { return manaPool; } diff --git a/forge-game/src/main/java/forge/game/zone/ZoneType.java b/forge-game/src/main/java/forge/game/zone/ZoneType.java index 79ef9238ab9..d15e6daeb02 100644 --- a/forge-game/src/main/java/forge/game/zone/ZoneType.java +++ b/forge-game/src/main/java/forge/game/zone/ZoneType.java @@ -24,6 +24,7 @@ public enum ZoneType { Ante(false, "lblAnteZone"), SchemeDeck(true, "lblSchemeDeckZone"), PlanarDeck(true, "lblPlanarDeckZone"), + Subgame(true, "lblSubgameZone"), None(true, "lblNoneZone"); public static final List STATIC_ABILITIES_SOURCE_ZONES = Arrays.asList(Battlefield, Graveyard, Exile, Command/*, Hand*/); diff --git a/forge-gui/res/cardsfolder/s/shahrazad.txt b/forge-gui/res/cardsfolder/s/shahrazad.txt new file mode 100644 index 00000000000..f6de4101e23 --- /dev/null +++ b/forge-gui/res/cardsfolder/s/shahrazad.txt @@ -0,0 +1,10 @@ +Name:Shahrazad +ManaCost:W W +Types:Sorcery +A:SP$ Subgame | RememberPlayers$ NotWin | SubAbility$ DBRepeatEachPlayer | SpellDescription$ Players play a Magic subgame, using their libraries as their decks. Each player who doesn't win the subgame loses half their life, rounded up. | StackDescription$ SpellDescription +SVar:DBRepeatEachPlayer:DB$ RepeatEach | RepeatPlayers$ Remembered | ClearRememberedBeforeLoop$ True | RepeatSubAbility$ DBLoseLife | SpellDescription$ Each player who doesn't win the subgame loses half their life, rounded up. | StackDescription$ SpellDescription +SVar:DBLoseLife:DB$ LoseLife | LifeAmount$ X | References$ X | Defined$ Player.IsRemembered +SVar:X:PlayerCountRemembered$LifeTotal/HalfUp +AI:RemoveDeck:All +AI:RemoveDeck:Random +Oracle:Players play a Magic subgame, using their libraries as their decks. Each player who doesn't win the subgame loses half their life, rounded up. diff --git a/forge-gui/res/languages/de-DE.properties b/forge-gui/res/languages/de-DE.properties index afa090b5adb..3534dfe9fea 100644 --- a/forge-gui/res/languages/de-DE.properties +++ b/forge-gui/res/languages/de-DE.properties @@ -1882,6 +1882,10 @@ lblDoYouWantSacrifice=Opfern durchführen? lblFaceDownCardCantTurnFaceUp=Verdeckte Karte kann nicht umgedreht werden #ShuffleEffect.java lblHaveTargetShuffle=Soll {0} mischen? +#SubgameEffect.java +lblSubgameStart=Subgame started by {0}''s effect. +lblSubgameEnd=Subgame ended. {0} wins. {1} loses. +lblSubgameEndDraw=Subgame ended in a draw. #SurveilEffect.java lblDoYouWantSurveil=Möchtest du Überwachen anwenden? #TapOrUntapAllEffect.java @@ -1937,6 +1941,7 @@ lblSideboardZone=Sideboard lblAnteZone=Ante lblSchemeDeckZone=Verschwörungsdeck lblPlanarDeckZone=Weltendeck +lblSubgameZone=subgame lblNoneZone=Keine #BoosterDraft.java lblChooseBlock=Wähle Block diff --git a/forge-gui/res/languages/en-US.properties b/forge-gui/res/languages/en-US.properties index 55547849226..c52e1f54380 100644 --- a/forge-gui/res/languages/en-US.properties +++ b/forge-gui/res/languages/en-US.properties @@ -1882,6 +1882,10 @@ lblDoYouWantSacrifice=Do you want to sacrifice? lblFaceDownCardCantTurnFaceUp=Face-down card can''t turn face up #ShuffleEffect.java lblHaveTargetShuffle=Have {0} shuffle? +#SubgameEffect.java +lblSubgameStart=Subgame started by {0}''s effect. +lblSubgameEnd=Subgame ended. {0} wins. {1} loses. +lblSubgameEndDraw=Subgame ended in a draw. #SurveilEffect.java lblDoYouWantSurveil=Do you want to surveil? #TapOrUntapAllEffect.java @@ -1937,6 +1941,7 @@ lblSideboardZone=sideboard lblAnteZone=ante lblSchemeDeckZone=schemedeck lblPlanarDeckZone=planardeck +lblSubgameZone=subgame lblNoneZone=none #BoosterDraft.java lblChooseBlock=Choose Block diff --git a/forge-gui/res/languages/es-ES.properties b/forge-gui/res/languages/es-ES.properties index 769e28dba5b..2b90de1b841 100644 --- a/forge-gui/res/languages/es-ES.properties +++ b/forge-gui/res/languages/es-ES.properties @@ -1882,6 +1882,10 @@ lblDoYouWantSacrifice=¿Quieres sacrificar? lblFaceDownCardCantTurnFaceUp=La carta boca abajo no se puede girar boca arriba #ShuffleEffect.java lblHaveTargetShuffle=¿Ha barajado {0}? +#SubgameEffect.java +lblSubgameStart=Subgame started by {0}''s effect. +lblSubgameEnd=Subgame ended. {0} wins. {1} loses. +lblSubgameEndDraw=Subgame ended in a draw. #SurveilEffect.java lblDoYouWantSurveil=¿Quieres vigilar? #TapOrUntapAllEffect.java @@ -1937,6 +1941,7 @@ lblSideboardZone=banquillo lblAnteZone=ante lblSchemeDeckZone=mazo scheme lblPlanarDeckZone=mazo planar +lblSubgameZone=subgame lblNoneZone=ninguna #BoosterDraft.java lblChooseBlock=Selecciona Bloque diff --git a/forge-gui/res/languages/it-IT.properties b/forge-gui/res/languages/it-IT.properties index 9dba07178e2..a13e803e8db 100644 --- a/forge-gui/res/languages/it-IT.properties +++ b/forge-gui/res/languages/it-IT.properties @@ -1882,6 +1882,10 @@ lblDoYouWantSacrifice=Do you want to sacrifice? lblFaceDownCardCantTurnFaceUp=Face-down card can''t turn face up #ShuffleEffect.java lblHaveTargetShuffle=Have {0} shuffle? +#SubgameEffect.java +lblSubgameStart=Subgame started by {0}''s effect. +lblSubgameEnd=Subgame ended. {0} wins. {1} loses. +lblSubgameEndDraw=Subgame ended in a draw. #SurveilEffect.java lblDoYouWantSurveil=Do you want to surveil? #TapOrUntapAllEffect.java @@ -1937,6 +1941,7 @@ lblSideboardZone=sideboard lblAnteZone=ante lblSchemeDeckZone=schemedeck lblPlanarDeckZone=planardeck +lblSubgameZone=subgame lblNoneZone=none #BoosterDraft.java lblChooseBlock=Choose Block diff --git a/forge-gui/res/languages/zh-CN.properties b/forge-gui/res/languages/zh-CN.properties index 5bd3630b8b2..164f9732f66 100644 --- a/forge-gui/res/languages/zh-CN.properties +++ b/forge-gui/res/languages/zh-CN.properties @@ -1882,6 +1882,10 @@ lblDoYouWantSacrifice=你想牺牲吗? lblFaceDownCardCantTurnFaceUp=面朝下的牌不能面朝上 #ShuffleEffect.java lblHaveTargetShuffle={0}洗牌了吗? +#SubgameEffect.java +lblSubgameStart=Subgame started by {0}''s effect. +lblSubgameEnd=Subgame ended. {0} wins. {1} loses. +lblSubgameEndDraw=Subgame ended in a draw. #SurveilEffect.java lblDoYouWantSurveil=你想刺探吗? #TapOrUntapAllEffect.java @@ -1937,6 +1941,7 @@ lblSideboardZone=备牌 lblAnteZone=赌注牌区 lblSchemeDeckZone=魔王套牌 lblPlanarDeckZone=时空套牌 +lblSubgameZone=subgame lblNoneZone=空 #BoosterDraft.java lblChooseBlock=选择环境 diff --git a/forge-gui/src/main/java/forge/match/HostedMatch.java b/forge-gui/src/main/java/forge/match/HostedMatch.java index 65b638a3131..7c66b7ea1d0 100644 --- a/forge-gui/src/main/java/forge/match/HostedMatch.java +++ b/forge-gui/src/main/java/forge/match/HostedMatch.java @@ -34,6 +34,7 @@ import forge.game.GameRules; import forge.game.GameType; import forge.game.GameView; import forge.game.Match; +import forge.game.event.*; import forge.game.player.Player; import forge.game.player.PlayerView; import forge.game.player.RegisteredPlayer; @@ -332,7 +333,7 @@ public class HostedMatch { return isMatchOver; } - private final class MatchUiEventVisitor implements IUiEventVisitor { + private final class MatchUiEventVisitor extends IGameEventVisitor.Base implements IUiEventVisitor { @Override public Void visit(final UiEventBlockerAssigned event) { for (final PlayerControllerHuman humanController : humanControllers) { @@ -359,6 +360,69 @@ public class HostedMatch { return null; } + @Override + public Void visit(final GameEventSubgameStart event) { + event.subgame.subscribeToEvents(SoundSystem.instance); + event.subgame.subscribeToEvents(visitor); + + final GameView gameView = event.subgame.getView(); + + Runnable switchGameView = new Runnable() { + @Override + public void run() { + for (final Player p : event.subgame.getPlayers()) { + if (p.getController() instanceof PlayerControllerHuman) { + final PlayerControllerHuman humanController = (PlayerControllerHuman) p.getController(); + final IGuiGame gui = guis.get(p.getRegisteredPlayer()); + humanController.setGui(gui); + gui.setGameView(null); + gui.setGameView(gameView); + gui.setOriginalGameController(p.getView(), humanController); + gui.openView(new TrackableCollection<>(p.getView())); + gui.setGameView(null); + gui.setGameView(gameView); + event.subgame.subscribeToEvents(new FControlGameEventHandler(humanController)); + gui.message(event.message); + } + } + } + }; + GuiBase.getInterface().invokeInEdtAndWait(switchGameView); + + //ensure opponents set properly + for (final Player p : event.subgame.getPlayers()) { + p.updateOpponentsForView(); + } + + return null; + } + + @Override + public Void visit(final GameEventSubgameEnd event) { + final GameView gameView = event.maingame.getView(); + Runnable switchGameView = new Runnable() { + @Override + public void run() { + for (final Player p : event.maingame.getPlayers()) { + if (p.getController() instanceof PlayerControllerHuman) { + final PlayerControllerHuman humanController = (PlayerControllerHuman) p.getController(); + final IGuiGame gui = guis.get(p.getRegisteredPlayer()); + gui.setGameView(null); + gui.setGameView(gameView); + gui.setOriginalGameController(p.getView(), humanController); + gui.openView(new TrackableCollection<>(p.getView())); + gui.setGameView(null); + gui.setGameView(gameView); + gui.updatePhase(); + gui.message(event.message); + } + } + } + }; + GuiBase.getInterface().invokeInEdtAndWait(switchGameView); + return null; + } + @Subscribe public void receiveEvent(final UiEvent evt) { try { @@ -368,6 +432,16 @@ public class HostedMatch { e.printStackTrace(); } } + + @Subscribe + public void receiveGameEvent(final GameEvent evt) { + try { + evt.visit(this); + } catch (Exception e) { + System.out.println(e.getMessage()); + e.printStackTrace(); + } + } } private void addNextGameDecision(final PlayerControllerHuman controller, final NextGameDecision decision) { From e67130574025a648fe6abffefc41108c278cdaac Mon Sep 17 00:00:00 2001 From: Andreas Bendel Date: Tue, 2 Feb 2021 16:27:42 +0000 Subject: [PATCH 58/74] Update de-DE.properties translated subgame strings --- forge-gui/res/languages/de-DE.properties | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/forge-gui/res/languages/de-DE.properties b/forge-gui/res/languages/de-DE.properties index 3534dfe9fea..1cd334c1740 100644 --- a/forge-gui/res/languages/de-DE.properties +++ b/forge-gui/res/languages/de-DE.properties @@ -1883,9 +1883,9 @@ lblFaceDownCardCantTurnFaceUp=Verdeckte Karte kann nicht umgedreht werden #ShuffleEffect.java lblHaveTargetShuffle=Soll {0} mischen? #SubgameEffect.java -lblSubgameStart=Subgame started by {0}''s effect. -lblSubgameEnd=Subgame ended. {0} wins. {1} loses. -lblSubgameEndDraw=Subgame ended in a draw. +lblSubgameStart=Unterspiel {0}s Effeckt gestartet. +lblSubgameEnd=Unterspiel beendet. {0} gewinnt. {1} verliert. +lblSubgameEndDraw=Das Unterspiel endete unentschieden. #SurveilEffect.java lblDoYouWantSurveil=Möchtest du Überwachen anwenden? #TapOrUntapAllEffect.java @@ -1941,7 +1941,7 @@ lblSideboardZone=Sideboard lblAnteZone=Ante lblSchemeDeckZone=Verschwörungsdeck lblPlanarDeckZone=Weltendeck -lblSubgameZone=subgame +lblSubgameZone=Unterspiel lblNoneZone=Keine #BoosterDraft.java lblChooseBlock=Wähle Block From 74648c8e8454bac18877b796b2a13678637edd63 Mon Sep 17 00:00:00 2001 From: John Date: Tue, 2 Feb 2021 16:31:22 +0000 Subject: [PATCH 59/74] Update alrunds_epiphany.txt --- forge-gui/res/cardsfolder/upcoming/alrunds_epiphany.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/forge-gui/res/cardsfolder/upcoming/alrunds_epiphany.txt b/forge-gui/res/cardsfolder/upcoming/alrunds_epiphany.txt index 410be902c9e..73a2d05ab1e 100644 --- a/forge-gui/res/cardsfolder/upcoming/alrunds_epiphany.txt +++ b/forge-gui/res/cardsfolder/upcoming/alrunds_epiphany.txt @@ -5,4 +5,5 @@ A:SP$ Token | Cost$ 5 U U | LegacyImage$ u 1 1 bird flying khm | TokenAmount$ 2 SVar:DBAddTurn: DB$ AddTurn | Defined$ You | NumTurns$ 1 | SubAbility$ DBChange | StackDescription$ None SVar:DBChange:DB$ ChangeZone | Origin$ Stack | Destination$ Exile | StackDescription$ None K:Foretell:4 U U +DeckHas:Ability$Token Oracle:Create two 1/1 blue Bird creature tokens with flying. Take an extra turn after this one. Exile Alrund's Epiphany.\nForetell {4}{U}{U} (During your turn, you may pay {2} and exile this card from your hand face down. Cast it on a later turn for its foretell cost.) From fd887fbc1d281d2ffa256fbc0722e09abb59a50f Mon Sep 17 00:00:00 2001 From: John Date: Tue, 2 Feb 2021 16:31:58 +0000 Subject: [PATCH 60/74] Update doomskar_oracle.txt --- forge-gui/res/cardsfolder/upcoming/doomskar_oracle.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/forge-gui/res/cardsfolder/upcoming/doomskar_oracle.txt b/forge-gui/res/cardsfolder/upcoming/doomskar_oracle.txt index 64363d8fa32..83b086270ad 100644 --- a/forge-gui/res/cardsfolder/upcoming/doomskar_oracle.txt +++ b/forge-gui/res/cardsfolder/upcoming/doomskar_oracle.txt @@ -7,4 +7,5 @@ SVar:TrigGainLife:DB$ GainLife | Defined$ You | LifeAmount$ 2 SVar:YouCastThisTurn:Count$ThisTurnCast_Card.YouCtrl SVar:BuffedBy:Card K:Foretell:W +DeckHas:Ability$LifeGain Oracle:Whenever you cast your second spell each turn, you gain 2 life.\nForetell {W} (During your turn, you may pay {2} and exile this card from your hand face down. Cast it on a later turn for its foretell cost.) From 93f47cf639c90c2ab8ea65815521c56159f52de2 Mon Sep 17 00:00:00 2001 From: John Date: Tue, 2 Feb 2021 16:33:26 +0000 Subject: [PATCH 61/74] Update dwarven_reinforcements.txt --- forge-gui/res/cardsfolder/upcoming/dwarven_reinforcements.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/forge-gui/res/cardsfolder/upcoming/dwarven_reinforcements.txt b/forge-gui/res/cardsfolder/upcoming/dwarven_reinforcements.txt index 5ba628a0741..b9b65047fef 100644 --- a/forge-gui/res/cardsfolder/upcoming/dwarven_reinforcements.txt +++ b/forge-gui/res/cardsfolder/upcoming/dwarven_reinforcements.txt @@ -2,4 +2,6 @@ Name:Dwarven Reinforcements ManaCost:3 R Types:Sorcery A:SP$ Token | Cost$ 3 R | LegacyImage$ r 2 1 dwarf berserker khm | TokenAmount$ 2 | TokenScript$ r_2_1_dwarf_berserker | TokenOwner$ You | SpellDescription$ Create 2 2/1 red Dwarf Berserker creature tokens. +K:Foretell:1 R +DeckHas:Ability$Token Oracle:Create 2 2/1 red Dwarf Berserker creature tokens.\nForetell {1}{R} (During your turn, you may pay {2} and exile this card from your hand face down. Cast it on a later turn for its foretell cost.) From 64ec8e886674a9cf2e24ab73132269fcf38d1844 Mon Sep 17 00:00:00 2001 From: John Date: Tue, 2 Feb 2021 16:33:59 +0000 Subject: [PATCH 62/74] Update ravenform.txt --- forge-gui/res/cardsfolder/upcoming/ravenform.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/forge-gui/res/cardsfolder/upcoming/ravenform.txt b/forge-gui/res/cardsfolder/upcoming/ravenform.txt index 2fbcb54b19c..b07c2402e35 100644 --- a/forge-gui/res/cardsfolder/upcoming/ravenform.txt +++ b/forge-gui/res/cardsfolder/upcoming/ravenform.txt @@ -5,4 +5,5 @@ A:SP$ ChangeZone | Cost$ 2 U | ValidTgts$ Creature,Artifact | TgtPrompt$ Select SVar:DBToken:DB$ Token | TokenAmount$ 1 | TokenScript$ u_1_1_bird_flying | TokenOwner$ TargetedController DeckHas:Ability$Token K:Foretell:U +DeckHas:Ability$Token Oracle:Exile target artifact or creature. Its controller creates a 1/1 Blue bird creature token with flying.\nForetell {U} (During your turn, you may pay {2} and exile this card from your hand face down. Cast it on a later turn for its foretell cost.) From 5ccad6b424d175e7f3228f246d24cffa5694c846 Mon Sep 17 00:00:00 2001 From: John Date: Tue, 2 Feb 2021 16:34:40 +0000 Subject: [PATCH 63/74] Update return_upon_the_tide.txt --- forge-gui/res/cardsfolder/upcoming/return_upon_the_tide.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/forge-gui/res/cardsfolder/upcoming/return_upon_the_tide.txt b/forge-gui/res/cardsfolder/upcoming/return_upon_the_tide.txt index 31e6c75a363..1bbf9714bd6 100644 --- a/forge-gui/res/cardsfolder/upcoming/return_upon_the_tide.txt +++ b/forge-gui/res/cardsfolder/upcoming/return_upon_the_tide.txt @@ -5,4 +5,5 @@ A:SP$ ChangeZone | Cost$ 4 B | Origin$ Graveyard | Destination$ Battlefield | Tg SVar:DBToken:DB$ Token | TokenAmount$ 2 | TokenScript$ g_1_1_elf_warrior | LegacyImage$ g 1 1 elf warrior khm | TokenOwner$ You | ConditionCheckSVar$ X | ConditionSVarCompare$ GE1 | References$ X SVar:X:Targeted$Valid Elf K:ForeTell:3 B +DeckHas:Ability$Token Oracle:Return target creature card from your graveyard to the battlefield. If it's an Elf, create two 1/1 green Elf Warrior creature tokens.\nForetell {3}{B} (During your turn, you may pay {2} and exile this card from your hand face down. Cast it on a later turn for its foretell cost.) From afabc1c9a35c80db1f2a45abf0b3021e166a61aa Mon Sep 17 00:00:00 2001 From: John Date: Tue, 2 Feb 2021 16:35:08 +0000 Subject: [PATCH 64/74] Update rise_of_the_dread_marn.txt --- forge-gui/res/cardsfolder/upcoming/rise_of_the_dread_marn.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/forge-gui/res/cardsfolder/upcoming/rise_of_the_dread_marn.txt b/forge-gui/res/cardsfolder/upcoming/rise_of_the_dread_marn.txt index e70a6a13535..2c531e807a2 100644 --- a/forge-gui/res/cardsfolder/upcoming/rise_of_the_dread_marn.txt +++ b/forge-gui/res/cardsfolder/upcoming/rise_of_the_dread_marn.txt @@ -4,4 +4,5 @@ Types:Instant A:SP$ Token | Cost$ 2 B | LegacyImage$ b 2 2 zombie berserker khm | TokenAmount$ X | References$ X | TokenScript$ b_2_2_zombie_berserker | TokenOwner$ You | SpellDescription$ Create X 2/2 black Zombie Berserker creature tokens, where X is the number of nontoken creatures that died this turn. SVar:X:Count$ThisTurnEntered_Graveyard_from_Battlefield_Creature.nonToken K:Foretell:B +DeckHas:Ability$Token Oracle:Create X 2/2 black Zombie Berserker creature tokens, where X is the number of nontoken creatures that died this turn.\nForetell {B} (During your turn, you may pay {2} and exile this card from your hand face down. Cast it on a later turn for its foretell cost.) From 01333191b5d7219949ff4e4a8028a0118c37c411 Mon Sep 17 00:00:00 2001 From: John Date: Tue, 2 Feb 2021 16:35:42 +0000 Subject: [PATCH 65/74] Update tergrids_shadow.txt --- forge-gui/res/cardsfolder/upcoming/tergrids_shadow.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/forge-gui/res/cardsfolder/upcoming/tergrids_shadow.txt b/forge-gui/res/cardsfolder/upcoming/tergrids_shadow.txt index 047c4c7afa4..21bcc7b46da 100644 --- a/forge-gui/res/cardsfolder/upcoming/tergrids_shadow.txt +++ b/forge-gui/res/cardsfolder/upcoming/tergrids_shadow.txt @@ -4,4 +4,5 @@ Types:Instant A:SP$ Sacrifice | Cost$ 3 B B | Amount$ 2 | SacValid$ Creature | Defined$ Player | SpellDescription$ Each player sacrifices two creatures. | StackDescription$ SpellDescription AI:RemoveDeck:All K:Foretell:2 B B +DeckHas:Ability$Sacrifice Oracle:Each player sacrifices two creatures.\nForetell {2}{B}{B} (During your turn, you may pay {2} and exile this card from your hand face down. Cast it on a later turn for its foretell cost.) From 350e2cf48094c28b3fe7c850e38d626c36db05d1 Mon Sep 17 00:00:00 2001 From: Hans Mackowiak Date: Tue, 2 Feb 2021 17:05:50 +0000 Subject: [PATCH 66/74] ForgetOnMoved: extra logic for cast stuff to not forget when cast is canceled --- .../game/ability/SpellAbilityEffect.java | 46 ++++++++----------- .../game/ability/effects/EffectEffect.java | 1 + .../cardsfolder/g/gonti_lord_of_luxury.txt | 7 +-- .../res/cardsfolder/g/gusthas_scepter.txt | 5 +- .../res/cardsfolder/k/kheru_spellsnatcher.txt | 6 +-- .../res/cardsfolder/k/knacksaw_clique.txt | 4 +- .../res/cardsfolder/o/ornate_kanzashi.txt | 4 +- .../res/cardsfolder/p/praetors_grasp.txt | 5 +- .../res/cardsfolder/r/release_to_the_wind.txt | 5 +- forge-gui/res/cardsfolder/s/shared_fate.txt | 4 +- forge-gui/res/cardsfolder/s/spelljack.txt | 6 +-- .../forge/player/HumanPlaySpellAbility.java | 1 + 12 files changed, 34 insertions(+), 60 deletions(-) diff --git a/forge-game/src/main/java/forge/game/ability/SpellAbilityEffect.java b/forge-game/src/main/java/forge/game/ability/SpellAbilityEffect.java index 26278a445f5..2dfe7b90b0e 100644 --- a/forge-game/src/main/java/forge/game/ability/SpellAbilityEffect.java +++ b/forge-game/src/main/java/forge/game/ability/SpellAbilityEffect.java @@ -332,8 +332,7 @@ public abstract class SpellAbilityEffect { } } - protected static void addForgetOnMovedTrigger(final Card card, final String zone) { - String trig = "Mode$ ChangesZone | ValidCard$ Card.IsRemembered | Origin$ " + zone + " | Destination$ Any | TriggerZones$ Command | Static$ True"; + protected static SpellAbility getForgetSpellAbility(final Card card) { String forgetEffect = "DB$ Pump | ForgetObjects$ TriggeredCard"; String exileEffect = "DB$ ChangeZone | Defined$ Self | Origin$ Command | Destination$ Exile" + " | ConditionDefined$ Remembered | ConditionPresent$ Card | ConditionCompare$ EQ0"; @@ -341,11 +340,23 @@ public abstract class SpellAbilityEffect { SpellAbility saForget = AbilityFactory.getAbility(forgetEffect, card); AbilitySub saExile = (AbilitySub) AbilityFactory.getAbility(exileEffect, card); saForget.setSubAbility(saExile); + return saForget; + } + + protected static void addForgetOnMovedTrigger(final Card card, final String zone) { + String trig = "Mode$ ChangesZone | ValidCard$ Card.IsRemembered | Origin$ " + zone + " | ExcludedDestinations$ Stack | Destination$ Any | TriggerZones$ Command | Static$ True"; final Trigger parsedTrigger = TriggerHandler.parseTrigger(trig, card, true); - parsedTrigger.setOverridingAbility(saForget); - final Trigger addedTrigger = card.addTrigger(parsedTrigger); - addedTrigger.setIntrinsic(true); + parsedTrigger.setOverridingAbility(getForgetSpellAbility(card)); + card.addTrigger(parsedTrigger); + } + + protected static void addForgetOnCastTrigger(final Card card) { + String trig = "Mode$ SpellCast | ValidCard$ Card.IsRemembered | TriggerZones$ Command | Static$ True"; + + final Trigger parsedTrigger = TriggerHandler.parseTrigger(trig, card, true); + parsedTrigger.setOverridingAbility(getForgetSpellAbility(card)); + card.addTrigger(parsedTrigger); } protected static void addExileOnMovedTrigger(final Card card, final String zone) { @@ -368,35 +379,18 @@ public abstract class SpellAbilityEffect { protected static void addForgetOnPhasedInTrigger(final Card card) { String trig = "Mode$ PhaseIn | ValidCard$ Card.IsRemembered | TriggerZones$ Command | Static$ True"; - String forgetEffect = "DB$ Pump | ForgetObjects$ TriggeredCard"; - String exileEffect = "DB$ ChangeZone | Defined$ Self | Origin$ Command | Destination$ Exile" - + " | ConditionDefined$ Remembered | ConditionPresent$ Card | ConditionCompare$ EQ0"; - - SpellAbility saForget = AbilityFactory.getAbility(forgetEffect, card); - AbilitySub saExile = (AbilitySub) AbilityFactory.getAbility(exileEffect, card); - saForget.setSubAbility(saExile); final Trigger parsedTrigger = TriggerHandler.parseTrigger(trig, card, true); - parsedTrigger.setOverridingAbility(saForget); - final Trigger addedTrigger = card.addTrigger(parsedTrigger); - addedTrigger.setIntrinsic(true); + parsedTrigger.setOverridingAbility(getForgetSpellAbility(card)); + card.addTrigger(parsedTrigger); } protected static void addForgetCounterTrigger(final Card card, final String counterType) { String trig = "Mode$ CounterRemoved | TriggerZones$ Command | ValidCard$ Card.IsRemembered | CounterType$ " + counterType + " | NewCounterAmount$ 0 | Static$ True"; - String forgetEffect = "DB$ Pump | ForgetObjects$ TriggeredCard"; - String exileEffect = "DB$ ChangeZone | Defined$ Self | Origin$ Command | Destination$ Exile" - + " | ConditionDefined$ Remembered | ConditionPresent$ Card | ConditionCompare$ EQ0"; - - SpellAbility saForget = AbilityFactory.getAbility(forgetEffect, card); - AbilitySub saExile = (AbilitySub) AbilityFactory.getAbility(exileEffect, card); - saForget.setSubAbility(saExile); - final Trigger parsedTrigger = TriggerHandler.parseTrigger(trig, card, true); - parsedTrigger.setOverridingAbility(saForget); - final Trigger addedTrigger = card.addTrigger(parsedTrigger); - addedTrigger.setIntrinsic(true); + parsedTrigger.setOverridingAbility(getForgetSpellAbility(card)); + card.addTrigger(parsedTrigger); } protected static void addLeaveBattlefieldReplacement(final Card card, final SpellAbility sa, final String zone) { diff --git a/forge-game/src/main/java/forge/game/ability/effects/EffectEffect.java b/forge-game/src/main/java/forge/game/ability/effects/EffectEffect.java index 74b43ea292e..14e56102c42 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/EffectEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/EffectEffect.java @@ -215,6 +215,7 @@ public class EffectEffect extends SpellAbilityEffect { } if (sa.hasParam("ForgetOnMoved")) { addForgetOnMovedTrigger(eff, sa.getParam("ForgetOnMoved")); + addForgetOnCastTrigger(eff); } else if (sa.hasParam("ExileOnMoved")) { addExileOnMovedTrigger(eff, sa.getParam("ExileOnMoved")); } diff --git a/forge-gui/res/cardsfolder/g/gonti_lord_of_luxury.txt b/forge-gui/res/cardsfolder/g/gonti_lord_of_luxury.txt index ea780b4b4f6..59d59eba74e 100644 --- a/forge-gui/res/cardsfolder/g/gonti_lord_of_luxury.txt +++ b/forge-gui/res/cardsfolder/g/gonti_lord_of_luxury.txt @@ -5,12 +5,9 @@ PT:2/3 K:Deathtouch T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigDig | TriggerDescription$ When CARDNAME enters the battlefield, look at the top four cards of target opponent's library, exile one of them face down, then put the rest on the bottom of that library in a random order. For as long as that card remains exiled, you may look at it, you may cast it, and you may spend mana as though it were mana of any type to cast that spell. SVar:TrigDig:DB$Dig | ValidTgts$ Opponent | DigNum$ 4 | ChangeNum$ 1 | DestinationZone$ Exile | DestinationZone2$ Library | LibraryPosition$ -1 | RestRandomOrder$ True | ExileFaceDown$ True | ChangeValid$ Card | RememberChanged$ True | SubAbility$ DBEffect | RememberChanged$ True -SVar:DBEffect:DB$ Effect | RememberObjects$ Remembered | StaticAbilities$ STPlay1,STPlay2 | Duration$ Permanent | Triggers$ TrigCleanup | SVars$ DBExileSelf | SubAbility$ DBCleanup +SVar:DBEffect:DB$ Effect | RememberObjects$ Remembered | StaticAbilities$ STPlay1,STPlay2 | Duration$ Permanent | ForgetOnMoved$ Exile | SubAbility$ DBCleanup SVar:STPlay1:Mode$ Continuous | MayLookAt$ You | EffectZone$ Command | Affected$ Card.IsRemembered | AffectedZone$ Exile | Description$ You may look at the card, you may cast it, and you may spend mana as though it were mana of any type to cast that spell. SVar:STPlay2:Mode$ Continuous | MayPlay$ True | MayPlayIgnoreType$ True | EffectZone$ Command | Affected$ Card.IsRemembered+nonLand | AffectedZone$ Exile | Secondary$ True | Description$ You may look at the card, you may cast it, and you may spend mana as though it were mana of any type to cast that spell. -SVar:TrigCleanup:Mode$ ChangesZone | ValidCard$ Card.IsRemembered | Origin$ Exile | Destination$ Any | TriggerZones$ Command | Execute$ DBExileSelf | Static$ True -SVar:DBExileSelf:DB$ ChangeZone | Defined$ Self | Origin$ Command | Destination$ Exile -SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True SVar:PlayMain1:TRUE -SVar:Picture:http://www.wizards.com/global/images/magic/general/gonti_lord_of_luxury.jpg Oracle:Deathtouch\nWhen Gonti, Lord of Luxury enters the battlefield, look at the top four cards of target opponent's library, exile one of them face down, then put the rest on the bottom of that library in a random order. You may look at and cast that card for as long as it remains exiled, and you may spend mana as though it were mana of any type to cast that spell. diff --git a/forge-gui/res/cardsfolder/g/gusthas_scepter.txt b/forge-gui/res/cardsfolder/g/gusthas_scepter.txt index 77f2badf6a4..d76b9217648 100644 --- a/forge-gui/res/cardsfolder/g/gusthas_scepter.txt +++ b/forge-gui/res/cardsfolder/g/gusthas_scepter.txt @@ -2,10 +2,8 @@ Name:Gustha's Scepter ManaCost:0 Types:Artifact A:AB$ ChangeZone | Cost$ T | ChangeType$ Card | ChangeNum$ 1 | Origin$ Hand | Destination$ Exile | ExileFaceDown$ True | RememberChanged$ True | Mandatory$ True | SubAbility$ DBEffect | SpellDescription$ Exile a card from your hand face down. You may look at it for as long as it remains exiled. -SVar:DBEffect:DB$ Effect | RememberObjects$ Remembered | StaticAbilities$ STLook | Duration$ Permanent | Triggers$ TrigCleanup | SVars$ DBExileSelf | SubAbility$ DBCleanup +SVar:DBEffect:DB$ Effect | RememberObjects$ Remembered | StaticAbilities$ STLook | Duration$ Permanent | ForgetOnMoved$ Exile | SubAbility$ DBCleanup SVar:STLook:Mode$ Continuous | MayLookAt$ You | EffectZone$ Command | Affected$ Card.IsRemembered | AffectedZone$ Exile | Description$ You may look at it for as long as it remains exiled. -SVar:TrigCleanup:Mode$ ChangesZone | ValidCard$ Card.IsRemembered | Origin$ Exile | Destination$ Any | TriggerZones$ Command | Execute$ DBExileSelf | Static$ True -SVar:DBExileSelf:DB$ ChangeZone | Defined$ Self | Origin$ Command | Destination$ Exile A:AB$ ChooseCard | Cost$ T | Defined$ You | Amount$ 1 | Mandatory$ True | AILogic$ AtLeast1 | ChoiceTitle$ Choose a card you own to put into your hand | Choices$ Card.IsRemembered+YouOwn+ExiledWithSource | ChoiceZone$ Exile | SubAbility$ MoveChosen | SpellDescription$ Return a card you own exiled with CARDNAME to your hand. SVar:MoveChosen:DB$ ChangeZone | Origin$ Exile | Destination$ Hand | Defined$ ChosenCard T:Mode$ ChangesZone | Origin$ Exile | Destination$ Any | Static$ True | ValidCard$ Card.IsRemembered+ExiledWithSource | Execute$ DBForget @@ -15,5 +13,4 @@ T:Mode$ ChangesController | ValidCard$ Card.Self | TriggerZones$ Battlefield | E SVar:DBChangeZoneAll:DB$ ChangeZoneAll | ChangeType$ Card.IsRemembered | Origin$ Exile | Destination$ Graveyard | SubAbility$ DBCleanup SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True AI:RemoveDeck:All -SVar:Picture:http://www.wizards.com/global/images/magic/general/gusthas_scepter.jpg Oracle:{T}: Exile a card from your hand face down. You may look at it for as long as it remains exiled.\n{T}: Return a card you own exiled with Gustha's Scepter to your hand.\nWhen you lose control of Gustha's Scepter, put all cards exiled with Gustha's Scepter into their owner's graveyard. diff --git a/forge-gui/res/cardsfolder/k/kheru_spellsnatcher.txt b/forge-gui/res/cardsfolder/k/kheru_spellsnatcher.txt index 8cb0d53762c..e4863d113e8 100644 --- a/forge-gui/res/cardsfolder/k/kheru_spellsnatcher.txt +++ b/forge-gui/res/cardsfolder/k/kheru_spellsnatcher.txt @@ -5,10 +5,8 @@ PT:3/3 K:Morph:4 U U T:Mode$ TurnFaceUp | ValidCard$ Card.Self | Execute$ TrigCounter | TriggerZones$ Battlefield | TriggerDescription$ When CARDNAME is turned face up, counter target spell. If that spell is countered this way, exile it instead of putting it into its owner's graveyard. You may cast that card without paying its mana cost as long as it remains exiled. SVar:TrigCounter:DB$ Counter | TargetType$ Spell | TgtPrompt$ Select target spell | ValidTgts$ Card | RememberCountered$ True | ForgetOtherTargets$ True | Destination$ Exile | SubAbility$ DBEffect -SVar:DBEffect:DB$ Effect | RememberObjects$ Remembered | StaticAbilities$ STPlay | Duration$ Permanent | Triggers$ TrigCleanup | SVars$ DBCleanup | References$ PlayOpp,PlayYou,TrigCleanup,DBCleanup +SVar:DBEffect:DB$ Effect | RememberObjects$ Remembered | StaticAbilities$ STPlay | Duration$ Permanent | ForgetOnMoved$ Exile | SubAbility$ DBCleanup SVar:STPlay:Mode$ Continuous | MayPlay$ True | MayPlayWithoutManaCost$ True | EffectZone$ Command | Affected$ Card.IsRemembered | AffectedZone$ Exile | Description$ You may cast cards without paying their mana cost as long as they remain exiled. -SVar:TrigCleanup:Mode$ ChangesZone | ValidCard$ Card.IsRemembered | Origin$ Exile | Destination$ Any | TriggerZones$ Command | Execute$ DBCleanup | Static$ True -SVar:DBCleanup:DB$ ChangeZone | Defined$ Self | Origin$ Command | Destination$ Exile +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True AI:RemoveDeck:All -SVar:Picture:http://www.wizards.com/global/images/magic/general/kheru_spellsnatcher.jpg Oracle:Morph {4}{U}{U} (You may cast this card face down as a 2/2 creature for {3}. Turn it face up any time for its morph cost.)\nWhen Kheru Spellsnatcher is turned face up, counter target spell. If that spell is countered this way, exile it instead of putting it into its owner's graveyard. You may cast that card without paying its mana cost for as long as it remains exiled. diff --git a/forge-gui/res/cardsfolder/k/knacksaw_clique.txt b/forge-gui/res/cardsfolder/k/knacksaw_clique.txt index bc698d776ba..e8dd1a61820 100644 --- a/forge-gui/res/cardsfolder/k/knacksaw_clique.txt +++ b/forge-gui/res/cardsfolder/k/knacksaw_clique.txt @@ -4,10 +4,8 @@ Types:Creature Faerie Rogue PT:1/4 K:Flying A:AB$ Dig | Cost$ 1 U Q | ValidTgts$ Opponent | TgtPrompt$ Select target opponent | DigNum$ 1 | ChangeNum$ All | DestinationZone$ Exile | RememberChanged$ True | SubAbility$ DBEffect | SpellDescription$ Target opponent exiles the top card of their library. Until end of turn, you may play that card. -SVar:DBEffect:DB$ Effect | Duration$ EndOfTurn | RememberObjects$ Remembered | StaticAbilities$ STPlay | Triggers$ TrigCleanup | SVars$ DBExileSelf | SubAbility$ DBCleanup +SVar:DBEffect:DB$ Effect | Duration$ EndOfTurn | RememberObjects$ Remembered | StaticAbilities$ STPlay | ForgetOnMoved$ Exile | SubAbility$ DBCleanup SVar:STPlay:Mode$ Continuous | MayPlay$ True | EffectZone$ Command | Affected$ Card.IsRemembered | AffectedZone$ Exile | Description$ You may play a card this turn. -SVar:TrigCleanup:Mode$ ChangesZone | ValidCard$ Card.IsRemembered | Origin$ Exile | Destination$ Any | TriggerZones$ Command | Execute$ DBExileSelf | Static$ True -SVar:DBExileSelf:DB$ ChangeZone | Defined$ Self | Origin$ Command | Destination$ Exile SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True AI:RemoveDeck:All Oracle:Flying\n{1}{U}, {Q}: Target opponent exiles the top card of their library. Until end of turn, you may play that card. ({Q} is the untap symbol.) diff --git a/forge-gui/res/cardsfolder/o/ornate_kanzashi.txt b/forge-gui/res/cardsfolder/o/ornate_kanzashi.txt index ef107a78fa2..b98f65a8f97 100644 --- a/forge-gui/res/cardsfolder/o/ornate_kanzashi.txt +++ b/forge-gui/res/cardsfolder/o/ornate_kanzashi.txt @@ -2,10 +2,8 @@ Name:Ornate Kanzashi ManaCost:5 Types:Artifact A:AB$ Dig | Cost$ 2 T | ValidTgts$ Opponent | TgtPrompt$ Select target opponent | DigNum$ 1 | ChangeNum$ All | DestinationZone$ Exile | RememberChanged$ True | SubAbility$ DBEffect | SpellDescription$ Target opponent exiles the top card of their library. You may play that card this turn. -SVar:DBEffect:DB$ Effect | Duration$ EndOfTurn | RememberObjects$ Remembered | StaticAbilities$ STPlay | Triggers$ TrigCleanup | SVars$ DBExileSelf | SubAbility$ DBCleanup +SVar:DBEffect:DB$ Effect | Duration$ EndOfTurn | RememberObjects$ Remembered | StaticAbilities$ STPlay | ForgetOnMoved$ Exile | SubAbility$ DBCleanup SVar:STPlay:Mode$ Continuous | MayPlay$ True | EffectZone$ Command | Affected$ Card.IsRemembered | AffectedZone$ Exile | Description$ You may play a card this turn. -SVar:TrigCleanup:Mode$ ChangesZone | ValidCard$ Card.IsRemembered | Origin$ Exile | Destination$ Any | TriggerZones$ Command | Execute$ DBExileSelf | Static$ True -SVar:DBExileSelf:DB$ ChangeZone | Defined$ Self | Origin$ Command | Destination$ Exile SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True AI:RemoveDeck:All Oracle:{2}, {T}: Target opponent exiles the top card of their library. You may play that card this turn. diff --git a/forge-gui/res/cardsfolder/p/praetors_grasp.txt b/forge-gui/res/cardsfolder/p/praetors_grasp.txt index fafb23cd890..285b44d6739 100644 --- a/forge-gui/res/cardsfolder/p/praetors_grasp.txt +++ b/forge-gui/res/cardsfolder/p/praetors_grasp.txt @@ -2,11 +2,8 @@ Name:Praetor's Grasp ManaCost:1 B B Types:Sorcery A:SP$ ChangeZone | Cost$ 1 B B | Origin$ Library | Destination$ Exile | ExileFaceDown$ True | ValidTgts$ Opponent | ChangeType$ Card | ChangeNum$ 1 | IsCurse$ True | RememberChanged$ True | SubAbility$ DBEffect | StackDescription$ SpellDescription | SpellDescription$ Search target opponent's library for a card and exile it face down. Then that player shuffles their library. You may look at and play that card for as long as it remains exiled. -SVar:DBEffect:DB$ Effect | RememberObjects$ Remembered | StaticAbilities$ STPlay | Duration$ Permanent | Triggers$ TrigCleanup | SVars$ DBExileSelf | SubAbility$ DBCleanup +SVar:DBEffect:DB$ Effect | RememberObjects$ Remembered | StaticAbilities$ STPlay | Duration$ Permanent | ForgetOnMoved$ Exile | SubAbility$ DBCleanup SVar:STPlay:Mode$ Continuous | MayLookAt$ You | MayPlay$ True | EffectZone$ Command | Affected$ Card.IsRemembered | AffectedZone$ Exile | Description$ You may look at and play a card as long as it remains exiled. -SVar:TrigCleanup:Mode$ ChangesZone | ValidCard$ Card.IsRemembered | Origin$ Exile | Destination$ Any | TriggerZones$ Command | Execute$ DBExileSelf | Static$ True -SVar:DBExileSelf:DB$ ChangeZone | Defined$ Self | Origin$ Command | Destination$ Exile SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True AI:RemoveDeck:All -SVar:Picture:http://www.wizards.com/global/images/magic/general/praetors_grasp.jpg Oracle:Search target opponent's library for a card and exile it face down. Then that player shuffles their library. You may look at and play that card for as long as it remains exiled. diff --git a/forge-gui/res/cardsfolder/r/release_to_the_wind.txt b/forge-gui/res/cardsfolder/r/release_to_the_wind.txt index 018c7935c76..612e9fc3f94 100644 --- a/forge-gui/res/cardsfolder/r/release_to_the_wind.txt +++ b/forge-gui/res/cardsfolder/r/release_to_the_wind.txt @@ -2,11 +2,8 @@ Name:Release to the Wind ManaCost:2 U Types:Instant A:SP$ ChangeZone | Cost$ 2 U | ValidTgts$ Permanent.nonLand | TgtPrompt$ Select target nonland permanent | Origin$ Battlefield | SubAbility$ DBEffect | Destination$ Exile | SpellDescription$ Exile target nonland permanent. For as long as that card remains exiled, its owner may cast it without paying its mana cost. -SVar:DBEffect:DB$ Effect | RememberObjects$ ParentTarget | EffectOwner$ TargetedOwner | StaticAbilities$ STPlay1,STPlay2 | Duration$ Permanent | Triggers$ TrigCleanup | SVars$ DBExileSelf | SubAbility$ DBCleanup +SVar:DBEffect:DB$ Effect | RememberObjects$ ParentTarget | EffectOwner$ TargetedOwner | StaticAbilities$ STPlay1,STPlay2 | Duration$ Permanent | ForgetOnMoved$ Exile | SubAbility$ DBCleanup SVar:STPlay1:Mode$ Continuous | MayLookAt$ You | EffectZone$ Command | Affected$ Card.IsRemembered | AffectedZone$ Exile | Description$ For as long as that card remains exiled, its owner may cast it without paying its mana cost. SVar:STPlay2:Mode$ Continuous | MayPlay$ True | MayPlayWithoutManaCost$ True | EffectZone$ Command | Affected$ Card.IsRemembered | AffectedZone$ Exile | Secondary$ True -SVar:TrigCleanup:Mode$ ChangesZone | ValidCard$ Card.IsRemembered | Origin$ Exile | Destination$ Any | TriggerZones$ Command | Execute$ DBExileSelf | Static$ True -SVar:DBExileSelf:DB$ ChangeZone | Defined$ Self | Origin$ Command | Destination$ Exile SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True -SVar:Picture:http://www.wizards.com/global/images/magic/general/release_to_the_wind.jpg Oracle:Exile target nonland permanent. For as long as that card remains exiled, its owner may cast it without paying its mana cost. \ No newline at end of file diff --git a/forge-gui/res/cardsfolder/s/shared_fate.txt b/forge-gui/res/cardsfolder/s/shared_fate.txt index 362805bf251..27dac7eac6d 100644 --- a/forge-gui/res/cardsfolder/s/shared_fate.txt +++ b/forge-gui/res/cardsfolder/s/shared_fate.txt @@ -5,13 +5,11 @@ Text:If a player would draw a card, that player exiles the top card of an oppone T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigEffects | Static$ True #Create an effect for each player. The effect contains both Shared Fate's abilities. SVar:TrigEffects:DB$ RepeatEach | RepeatPlayers$ Player | RepeatSubAbility$ DBEffect -SVar:DBEffect:DB$ Effect | EffectOwner$ Remembered | StaticAbilities$ STPlay | Triggers$ TrigCleanup | ReplacementEffects$ RDraw | SVars$ DBChooseOpp,DBExile,DBCleanup | Duration$ UntilHostLeavesPlay +SVar:DBEffect:DB$ Effect | EffectOwner$ Remembered | StaticAbilities$ STPlay | ReplacementEffects$ RDraw | SVars$ DBChooseOpp,DBExile | Duration$ UntilHostLeavesPlay | ForgetOnMoved$ Exile SVar:RDraw:Event$ Draw | ActiveZones$ Command | ValidPlayer$ You | ReplaceWith$ DBChooseOpp | Description$ If you would draw a card, exile the top card of an opponent's library face down instead. SVar:DBChooseOpp:DB$ ChoosePlayer | ChoiceTitle$ Choose an opponent whose top library card to exile | Choices$ Player.Opponent | AILogic$ Curse | SubAbility$ DBExile SVar:DBExile:DB$ Dig | DigNum$ 1 | ChangeNum$ All | DestinationZone$ Exile | ExileFaceDown$ True | Defined$ Player.Chosen | RememberChanged$ True SVar:STPlay:Mode$ Continuous | MayLookAt$ You | MayPlay$ True | EffectZone$ Command | Affected$ Card.IsRemembered | AffectedZone$ Exile | Description$ You may look at and play cards exiled with Shared Fate. -SVar:TrigCleanup:Mode$ ChangesZone | ValidCard$ Card.IsRemembered | Origin$ Exile | Destination$ Any | TriggerZones$ Command | Execute$ DBCleanup | Static$ True -SVar:DBCleanup:DB$ Cleanup | ForgetDefined$ TriggeredCard AI:RemoveDeck:All AI:RemoveDeck:Random Oracle:If a player would draw a card, that player exiles the top card of an opponent's library face down instead.\nEach player may look at and play cards they exiled with Shared Fate. diff --git a/forge-gui/res/cardsfolder/s/spelljack.txt b/forge-gui/res/cardsfolder/s/spelljack.txt index 36f5789db15..ba205295ca0 100644 --- a/forge-gui/res/cardsfolder/s/spelljack.txt +++ b/forge-gui/res/cardsfolder/s/spelljack.txt @@ -2,9 +2,7 @@ Name:Spelljack ManaCost:3 U U U Types:Instant A:SP$ Counter | Cost$ 3 U U U | TargetType$ Spell | TgtPrompt$ Select target spell | ValidTgts$ Card | RememberCountered$ True | ForgetOtherTargets$ True | Destination$ Exile | SubAbility$ DBEffect | SpellDescription$ Counter target spell. If that spell is countered this way, exile it instead of putting it into its owner's graveyard. You may play it without paying its mana cost for as long as it remains exiled. (If it has X in its mana cost, X is 0.) -SVar:DBEffect:DB$ Effect | Name$ Spelljack Effect | RememberObjects$ Remembered | StaticAbilities$ Play | Duration$ Permanent | Triggers$ TrigCleanup | SVars$ DBCleanup | References$ Play,TrigCleanup,DBCleanup +SVar:DBEffect:DB$ Effect | Name$ Spelljack Effect | RememberObjects$ Remembered | StaticAbilities$ Play | Duration$ Permanent | ForgetOnMoved$ Exile | SubAbility$ DBCleanup SVar:Play:Mode$ Continuous | MayPlay$ True | MayPlayWithoutManaCost$ True | EffectZone$ Command | Affected$ Card.IsRemembered | AffectedZone$ Exile | Description$ You may play cards exiled with Spelljack. -SVar:TrigCleanup:Mode$ ChangesZone | ValidCard$ Card.IsRemembered | Origin$ Exile | Destination$ Any | TriggerZones$ Command | Execute$ DBCleanup | Static$ True -SVar:DBCleanup:DB$ ChangeZone | Defined$ Self | Origin$ Command | Destination$ Exile -SVar:Picture:http://www.wizards.com/global/images/magic/general/spelljack.jpg +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True Oracle:Counter target spell. If that spell is countered this way, exile it instead of putting it into its owner's graveyard. You may play it without paying its mana cost for as long as it remains exiled. (If it has X in its mana cost, X is 0.) diff --git a/forge-gui/src/main/java/forge/player/HumanPlaySpellAbility.java b/forge-gui/src/main/java/forge/player/HumanPlaySpellAbility.java index b41b6d92617..699ff204ed7 100644 --- a/forge-gui/src/main/java/forge/player/HumanPlaySpellAbility.java +++ b/forge-gui/src/main/java/forge/player/HumanPlaySpellAbility.java @@ -203,6 +203,7 @@ public class HumanPlaySpellAbility { // skip GameAction oldCard.getZone().remove(oldCard); fromZone.add(oldCard, zonePosition >= 0 ? Integer.valueOf(zonePosition) : null); + ability.setHostCard(oldCard); } ability.clearTargets(); From 72f2c8b2cb250176ff40a0c0b4d076f2104b9558 Mon Sep 17 00:00:00 2001 From: Hans Mackowiak Date: Tue, 2 Feb 2021 17:06:19 +0000 Subject: [PATCH 67/74] ExchangeControl: add Optional and two Targets also TargetsWithSameCardType --- .../forge/ai/ability/ControlExchangeAi.java | 53 ++++++++++++++++++- .../forge/game/ability/AbilityFactory.java | 3 ++ .../effects/ControlExchangeEffect.java | 50 ++++++++++++----- .../forge/game/spellability/SpellAbility.java | 47 +++++++++------- .../game/spellability/TargetRestrictions.java | 16 ++++++ .../res/cardsfolder/c/cliffside_market.txt | 4 +- .../cardsfolder/c/confusion_in_the_ranks.txt | 1 - forge-gui/res/cardsfolder/d/daring_thief.txt | 3 +- .../res/cardsfolder/g/gauntlets_of_chaos.txt | 3 +- forge-gui/res/cardsfolder/j/juxtapose.txt | 5 +- .../cardsfolder/k/karona_false_god_avatar.txt | 2 +- forge-gui/res/cardsfolder/l/legerdemain.txt | 2 +- forge-gui/res/cardsfolder/r/role_reversal.txt | 3 +- .../res/cardsfolder/s/shifting_loyalties.txt | 4 +- .../upcoming/the_trickster_gods_heist.txt | 11 ++++ forge-gui/res/languages/de-DE.properties | 2 + forge-gui/res/languages/en-US.properties | 2 + forge-gui/res/languages/es-ES.properties | 2 + forge-gui/res/languages/it-IT.properties | 2 + forge-gui/res/languages/zh-CN.properties | 2 + 20 files changed, 163 insertions(+), 54 deletions(-) create mode 100644 forge-gui/res/cardsfolder/upcoming/the_trickster_gods_heist.txt diff --git a/forge-ai/src/main/java/forge/ai/ability/ControlExchangeAi.java b/forge-ai/src/main/java/forge/ai/ability/ControlExchangeAi.java index f21b327b3fd..e93f30ff733 100644 --- a/forge-ai/src/main/java/forge/ai/ability/ControlExchangeAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/ControlExchangeAi.java @@ -81,7 +81,12 @@ public class ControlExchangeAi extends SpellAbilityAi { final TargetRestrictions tgt = sa.getTargetRestrictions(); - CardCollection list = CardLists.getValidCards(aiPlayer.getGame().getCardsIn(ZoneType.Battlefield), + // for TrigTwoTargets logic, only get the opponents' cards for the first target + CardCollectionView unfilteredList = "TrigTwoTargets".equals(sa.getParam("AILogic")) ? + aiPlayer.getOpponents().getCardsIn(ZoneType.Battlefield) : + aiPlayer.getGame().getCardsIn(ZoneType.Battlefield); + + CardCollection list = CardLists.getValidCards(unfilteredList, tgt.getValidTgts(), aiPlayer, sa.getHostCard(), sa); // only select the cards that can be targeted @@ -106,7 +111,51 @@ public class ControlExchangeAi extends SpellAbilityAi { // add best Target sa.getTargets().add(best); + + // second target needed (the AI's own worst) + if ("TrigTwoTargets".equals(sa.getParam("AILogic"))) { + return doTrigTwoTargetsLogic(aiPlayer, sa, best); + } + return true; } - + + private boolean doTrigTwoTargetsLogic(Player ai, SpellAbility sa, Card bestFirstTgt) { + final TargetRestrictions tgt = sa.getTargetRestrictions(); + final int creatureThreshold = 100; // TODO: make this configurable from the AI profile + final int nonCreatureThreshold = 2; + + CardCollection list = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), + tgt.getValidTgts(), ai, sa.getHostCard(), sa); + + // only select the cards that can be targeted + list = CardLists.getTargetableCards(list, sa); + + if (list.isEmpty()) { + return false; + } + + Card aiWorst = ComputerUtilCard.getWorstAI(list); + if (aiWorst == null) { + return false; + } + + if (aiWorst != null && aiWorst != bestFirstTgt) { + if (bestFirstTgt.isCreature() && aiWorst.isCreature()) { + if ((ComputerUtilCard.evaluateCreature(bestFirstTgt) > ComputerUtilCard.evaluateCreature(aiWorst) + creatureThreshold) || sa.isMandatory()) { + sa.getTargets().add(aiWorst); + return true; + } + } else { + // TODO: compare non-creatures by CMC - can be improved, at least shouldn't give control of things like the Power Nine + if ((bestFirstTgt.getCMC() > aiWorst.getCMC() + nonCreatureThreshold) || sa.isMandatory()) { + sa.getTargets().add(aiWorst); + return true; + } + } + } + + sa.clearTargets(); + return false; + } } diff --git a/forge-game/src/main/java/forge/game/ability/AbilityFactory.java b/forge-game/src/main/java/forge/game/ability/AbilityFactory.java index f45d73068dd..b64e55edf59 100644 --- a/forge-game/src/main/java/forge/game/ability/AbilityFactory.java +++ b/forge-game/src/main/java/forge/game/ability/AbilityFactory.java @@ -352,6 +352,9 @@ public final class AbilityFactory { if (mapParams.containsKey("TargetsWithSameCreatureType")) { abTgt.setWithSameCreatureType(true); } + if (mapParams.containsKey("TargetsWithSameCardType")) { + abTgt.setWithSameCardType(true); + } if (mapParams.containsKey("TargetsWithSameController")) { abTgt.setSameController(true); } diff --git a/forge-game/src/main/java/forge/game/ability/effects/ControlExchangeEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ControlExchangeEffect.java index 0e2ea5a6ce6..7a2daa09be0 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/ControlExchangeEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/ControlExchangeEffect.java @@ -1,14 +1,16 @@ package forge.game.ability.effects; import com.google.common.collect.Lists; + +import forge.game.Game; import forge.game.ability.AbilityUtils; import forge.game.ability.SpellAbilityEffect; import forge.game.card.Card; import forge.game.player.Player; import forge.game.spellability.SpellAbility; -import forge.game.spellability.TargetRestrictions; +import forge.util.CardTranslation; +import forge.util.Localizer; -import java.util.ArrayList; import java.util.List; @@ -21,21 +23,27 @@ public class ControlExchangeEffect extends SpellAbilityEffect { protected String getStackDescription(SpellAbility sa) { Card object1 = null; Card object2 = null; - final TargetRestrictions tgt = sa.getTargetRestrictions(); - List tgts = tgt == null ? new ArrayList<>() : Lists.newArrayList(sa.getTargets().getTargetCards()); - if (tgts.size() > 0) { - object1 = tgts.get(0); + List tgts = null; + if (sa.usesTargeting()) { + tgts = Lists.newArrayList(sa.getTargets().getTargetCards()); + if (tgts.size() > 0) { + object1 = tgts.get(0); + } } if (sa.hasParam("Defined")) { List cards = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("Defined"), sa); object2 = cards.isEmpty() ? null : cards.get(0); - if (cards.size() > 1 && sa.hasParam("BothDefined")) { + if (cards.size() > 1 && !sa.usesTargeting()) { object1 = cards.get(1); } } else if (tgts.size() > 1) { object2 = tgts.get(1); } + if (object1 == null || object2 == null) { + return ""; + } + return object1 + " exchanges controller with " + object2; } @@ -44,17 +52,22 @@ public class ControlExchangeEffect extends SpellAbilityEffect { */ @Override public void resolve(SpellAbility sa) { + Card host = sa.getHostCard(); + Game game = host.getGame(); Card object1 = null; Card object2 = null; - final TargetRestrictions tgt = sa.getTargetRestrictions(); - List tgts = tgt == null ? new ArrayList<>() : Lists.newArrayList(sa.getTargets().getTargetCards()); - if (tgts.size() > 0) { - object1 = tgts.get(0); + + List tgts = null; + if (sa.usesTargeting()) { + tgts = Lists.newArrayList(sa.getTargets().getTargetCards()); + if (tgts.size() > 0) { + object1 = tgts.get(0); + } } if (sa.hasParam("Defined")) { - final List cards = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("Defined"), sa); + final List cards = AbilityUtils.getDefinedCards(host, sa.getParam("Defined"), sa); object2 = cards.isEmpty() ? null : cards.get(0); - if (cards.size() > 1 && sa.hasParam("BothDefined")) { + if (cards.size() > 1 && !sa.usesTargeting()) { object1 = cards.get(1); } } else if (tgts.size() > 1) { @@ -73,7 +86,16 @@ public class ControlExchangeEffect extends SpellAbilityEffect { return; } - final long tStamp = sa.getActivatingPlayer().getGame().getNextTimestamp(); + if (sa.hasParam("Optional")) { + if (!sa.getActivatingPlayer().getController().confirmAction(sa, null, + Localizer.getInstance().getMessage("lblExchangeControl", + CardTranslation.getTranslatedName(object1.getName()), + CardTranslation.getTranslatedName(object2.getName())))) { + return; + } + } + + final long tStamp = game.getNextTimestamp(); object2.setController(player1, tStamp); object1.setController(player2, tStamp); if (sa.hasParam("RememberExchanged")) { 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 bc63cb582f7..23d12ed06c5 100644 --- a/forge-game/src/main/java/forge/game/spellability/SpellAbility.java +++ b/forge-game/src/main/java/forge/game/spellability/SpellAbility.java @@ -1056,29 +1056,26 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit final Card c = (Card) entity; CardCollection pl = AbilityUtils.getDefinedCards(getHostCard(), getParam("TargetsWithSharedCardType"), this); for (final Card crd : pl) { - if (!c.sharesCardTypeWith(crd)) { - return false; + // one of those types + if (hasParam("TargetsWithSharedTypes")) { + boolean flag = false; + for (final String type : getParam("TargetsWithSharedTypes").split(",")) { + if (c.getType().hasStringType(type) && crd.getType().hasStringType(type)) { + flag = true; + break; + } + } + if (!flag) { + return false; + } + } else { + if (!c.sharesCardTypeWith(crd)) { + return false; + } } } } - if (hasParam("TargetsWithSharedTypes") && entity instanceof Card) { - final Card c = (Card) entity; - final SpellAbility parent = getParentTargetingCard(); - final Card parentTargeted = parent != null ? parent.getTargetCard() : null; - if (parentTargeted == null) { - return false; - } - boolean flag = false; - for (final String type : getParam("TargetsWithSharedTypes").split(",")) { - if (c.getType().hasStringType(type) && parentTargeted.getType().hasStringType(type)) { - flag = true; - break; - } - } - if (!flag) { - return false; - } - } + if (hasParam("TargetsWithControllerProperty") && entity instanceof Card) { final String prop = getParam("TargetsWithControllerProperty"); final Card c = (Card) entity; @@ -1172,6 +1169,16 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit } } + if (tr.isWithSameCardType()) { + if (entity instanceof Card) { + for (final Card c : targetChosen.getTargetCards()) { + if (entity != c && !c.sharesCardTypeWith((Card) entity)) { + return false; + } + } + } + } + String[] validTgt = tr.getValidTgts(); if (entity instanceof GameEntity) { GameEntity e = (GameEntity)entity; diff --git a/forge-game/src/main/java/forge/game/spellability/TargetRestrictions.java b/forge-game/src/main/java/forge/game/spellability/TargetRestrictions.java index d917a4058f9..eef0d88b541 100644 --- a/forge-game/src/main/java/forge/game/spellability/TargetRestrictions.java +++ b/forge-game/src/main/java/forge/game/spellability/TargetRestrictions.java @@ -65,6 +65,7 @@ public class TargetRestrictions { private boolean sameController = false; private boolean withoutSameCreatureType = false; private boolean withSameCreatureType = false; + private boolean withSameCardType = false; private boolean singleTarget = false; private boolean randomTarget = false; @@ -108,6 +109,7 @@ public class TargetRestrictions { this.sameController = target.isSameController(); this.withoutSameCreatureType = target.isWithoutSameCreatureType(); this.withSameCreatureType = target.isWithSameCreatureType(); + this.withSameCardType = target.isWithSameCardType(); this.singleTarget = target.isSingleTarget(); this.randomTarget = target.isRandomTarget(); } @@ -622,6 +624,20 @@ public class TargetRestrictions { this.withSameCreatureType = b; } + /** + * @return the withSameCardType + */ + public boolean isWithSameCardType() { + return withSameCardType; + } + + /** + * @param b the withSameCardType to set + */ + public void setWithSameCardType(boolean b) { + this.withSameCardType = b; + } + /** *

* copy. diff --git a/forge-gui/res/cardsfolder/c/cliffside_market.txt b/forge-gui/res/cardsfolder/c/cliffside_market.txt index 00db712d6b2..c6a038093a8 100644 --- a/forge-gui/res/cardsfolder/c/cliffside_market.txt +++ b/forge-gui/res/cardsfolder/c/cliffside_market.txt @@ -5,9 +5,7 @@ T:Mode$ PlaneswalkedTo | ValidCard$ Card.Self | Execute$ TrigLife | OptionalDeci T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | Execute$ TrigLife | TriggerZones$ Command | Secondary$ True | OptionalDecider$ You | TriggerDescription$ When you planeswalk to CARDNAME or at the beginning of your upkeep, you may exchange life totals with target player. SVar:TrigLife:DB$ ExchangeLife | Optional$ True | ValidTgts$ Player | TgtPrompt$ Select target player to exchange life totals with T:Mode$ PlanarDice | Result$ Chaos | TriggerZones$ Command | Execute$ RolledChaos | TriggerDescription$ Whenever you roll {CHAOS}, exchange control of two target permanents that share a card type. -SVar:RolledChaos:DB$ Pump | ValidTgts$ Permanent | TgtPrompt$ Select target permanent | SubAbility$ DBExchange -SVar:DBExchange:DB$ ExchangeControl | Defined$ ParentTarget | ValidTgts$ Permanent | TgtPrompt$ Select target permanent card | TargetsWithSharedTypes$ Creature,Artifact,Enchantment,Planeswalker,Land,Tribal | TargetUnique$ True +SVar:RolledChaos:DB$ ExchangeControl | TargetMin$ 2 | TargetMax$ 2 | ValidTgts$ Permanent | TgtPrompt$ Select target permanents that share a permanent type | TargetsWithSameCardType$ True AI:RemoveDeck:All AI:RemoveDeck:Random -SVar:Picture:http://www.wizards.com/global/images/magic/general/cliffside_market.jpg Oracle:When you planeswalk to Cliffside Market or at the beginning of your upkeep, you may exchange life totals with target player.\nWhenever you roll {CHAOS}, exchange control of two target permanents that share a card type. \ No newline at end of file diff --git a/forge-gui/res/cardsfolder/c/confusion_in_the_ranks.txt b/forge-gui/res/cardsfolder/c/confusion_in_the_ranks.txt index e6f94222468..7a025cdcf7c 100644 --- a/forge-gui/res/cardsfolder/c/confusion_in_the_ranks.txt +++ b/forge-gui/res/cardsfolder/c/confusion_in_the_ranks.txt @@ -4,5 +4,4 @@ Types:Enchantment T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Artifact,Creature,Enchantment | TriggerZones$ Battlefield | Execute$ TrigExchangeControl | TriggerDescription$ Whenever an artifact, creature, or enchantment enters the battlefield, its controller chooses target permanent another player controls that shares a card type with it. Exchange control of those permanents. SVar:TrigExchangeControl:DB$ ExchangeControl | Defined$ TriggeredCard | TargetingPlayer$ TriggeredCardController | TargetsWithDefinedController$ NonTriggeredCardController | ValidTgts$ Permanent | TargetsWithSharedCardType$ TriggeredCard AI:RemoveDeck:Random -SVar:Picture:http://www.wizards.com/global/images/magic/general/confusion_in_the_ranks.jpg Oracle:Whenever an artifact, creature, or enchantment enters the battlefield, its controller chooses target permanent another player controls that shares a card type with it. Exchange control of those permanents. diff --git a/forge-gui/res/cardsfolder/d/daring_thief.txt b/forge-gui/res/cardsfolder/d/daring_thief.txt index 875fae5c210..b76e71babdd 100644 --- a/forge-gui/res/cardsfolder/d/daring_thief.txt +++ b/forge-gui/res/cardsfolder/d/daring_thief.txt @@ -4,8 +4,7 @@ Types:Creature Human Rogue PT:2/3 T:Mode$ Untaps | ValidCard$ Card.Self | TriggerZones$ Battlefield | Execute$ TrigExchangeControl | OptionalDecider$ You | TriggerDescription$ Inspired — Whenever CARDNAME becomes untapped, you may exchange control of target nonland permanent you control and target permanent an opponent controls that shares a card type with it. SVar:TrigExchangeControl:DB$ Pump | ValidTgts$ Permanent.YouCtrl+nonLand | TgtPrompt$ Select target nonland permanent you control | SubAbility$ DBExchange -SVar:DBExchange:DB$ ExchangeControl | Defined$ ParentTarget | ValidTgts$ Permanent.OppCtrl | TgtPrompt$ Select target permanent an opponent controls that shares a card type with it | TargetsWithSharedTypes$ Creature,Artifact,Enchantment,Planeswalker,Tribal +SVar:DBExchange:DB$ ExchangeControl | Defined$ ParentTarget | ValidTgts$ Permanent.OppCtrl | TgtPrompt$ Select target permanent an opponent controls that shares a card type with it | TargetsWithSharedCardType$ ParentTarget AI:RemoveDeck:All AI:RemoveDeck:Random -SVar:Picture:http://www.wizards.com/global/images/magic/general/daring_thief.jpg Oracle:Inspired — Whenever Daring Thief becomes untapped, you may exchange control of target nonland permanent you control and target permanent an opponent controls that shares a card type with it. diff --git a/forge-gui/res/cardsfolder/g/gauntlets_of_chaos.txt b/forge-gui/res/cardsfolder/g/gauntlets_of_chaos.txt index 6d5d5f43151..991a6868a74 100644 --- a/forge-gui/res/cardsfolder/g/gauntlets_of_chaos.txt +++ b/forge-gui/res/cardsfolder/g/gauntlets_of_chaos.txt @@ -2,9 +2,8 @@ Name:Gauntlets of Chaos ManaCost:5 Types:Artifact A:AB$ Pump | Cost$ 5 Sac<1/CARDNAME> | ValidTgts$ Artifact.YouCtrl,Creature.YouCtrl,Land.YouCtrl | TgtPrompt$ target artifact, creature, or land you control | StackDescription$ None | SubAbility$ DBExchange | SpellDescription$ Exchange control of target artifact, creature, or land you control and target permanent an opponent controls that shares one of those types with it. If those permanents are exchanged this way, destroy all Auras attached to them. -SVar:DBExchange:DB$ ExchangeControl | Defined$ ParentTarget | ValidTgts$ Permanent.OppCtrl | TgtPrompt$ Select target permanent an opponent controls that shares one of those types | TargetsWithSharedTypes$ Artifact,Creature,Land | RememberExchanged$ True | SubAbility$ DBDestroyAll +SVar:DBExchange:DB$ ExchangeControl | Defined$ ParentTarget | ValidTgts$ Permanent.OppCtrl | TgtPrompt$ Select target permanent an opponent controls that shares one of those types | TargetsWithSharedCardType$ ParentTarget | TargetsWithSharedTypes$ Artifact,Creature,Land | RememberExchanged$ True | SubAbility$ DBDestroyAll SVar:DBDestroyAll:DB$ DestroyAll | ValidCards$ Aura.AttachedTo Card.IsRemembered | SubAbility$ DBCleanup SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True AI:RemoveDeck:All -SVar:Picture:http://www.wizards.com/global/images/magic/general/gauntlets_of_chaos.jpg Oracle:{5}, Sacrifice Gauntlets of Chaos: Exchange control of target artifact, creature, or land you control and target permanent an opponent controls that shares one of those types with it. If those permanents are exchanged this way, destroy all Auras attached to them. diff --git a/forge-gui/res/cardsfolder/j/juxtapose.txt b/forge-gui/res/cardsfolder/j/juxtapose.txt index 07faf62f196..c71022b24c0 100644 --- a/forge-gui/res/cardsfolder/j/juxtapose.txt +++ b/forge-gui/res/cardsfolder/j/juxtapose.txt @@ -3,16 +3,15 @@ ManaCost:3 U Types:Sorcery A:SP$ ChooseCard | Cost$ 3 U | ValidTgts$ Player | Choices$ Creature.cmcEQY | TargetControls$ True | References$ Y | Mandatory$ True | AILogic$ WorstCard | RememberChosen$ True | SubAbility$ DBChooseCreatureYou | SpellDescription$ You and target player exchange control of the creature you each control with the highest converted mana cost. Then exchange control of artifacts the same way. If two or more permanents a player controls are tied for highest cost, their controller chooses one of them. SVar:DBChooseCreatureYou:DB$ ChooseCard | Choices$ Creature.YouCtrl+cmcEQX | References$ X | Mandatory$ True | RememberChosen$ True | SubAbility$ DBExchangeCreature -SVar:DBExchangeCreature:DB$ ExchangeControl | BothDefined$ True | Defined$ Remembered | SubAbility$ DBCleanCreature +SVar:DBExchangeCreature:DB$ ExchangeControl | Defined$ Remembered | SubAbility$ DBCleanCreature SVar:DBCleanCreature:DB$ Cleanup | ClearRemembered$ True | SubAbility$ DBChooseArtifactYou SVar:DBChooseArtifactYou:DB$ ChooseCard | Choices$ Artifact.YouCtrl+cmcEQZ | References$ Z | Mandatory$ True | RememberChosen$ True | SubAbility$ DBChooseArtifactOpp SVar:DBChooseArtifactOpp:DB$ ChooseCard | Defined$ ParentTarget | Choices$ Artifact.cmcEQW | TargetControls$ True | References$ W | Mandatory$ True | AILogic$ WorstCard | RememberChosen$ True | SubAbility$ DBExchangeArtifact -SVar:DBExchangeArtifact:DB$ ExchangeControl | BothDefined$ True | Defined$ Remembered | SubAbility$ DBCleanup +SVar:DBExchangeArtifact:DB$ ExchangeControl | Defined$ Remembered | SubAbility$ DBCleanup SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True SVar:X:Count$HighestCMC_Creature.YouCtrl+inZoneBattlefield SVar:Y:Count$HighestCMC_Creature.TargetedPlayerCtrl+inZoneBattlefield SVar:Z:Count$HighestCMC_Artifact.YouCtrl+inZoneBattlefield SVar:W:Count$HighestCMC_Artifact.TargetedPlayerCtrl+inZoneBattlefield AI:RemoveDeck:All -SVar:Picture:http://www.wizards.com/global/images/magic/general/juxtapose.jpg Oracle:You and target player exchange control of the creature you each control with the highest converted mana cost. Then exchange control of artifacts the same way. If two or more permanents a player controls are tied for highest cost, their controller chooses one of them. diff --git a/forge-gui/res/cardsfolder/k/karona_false_god_avatar.txt b/forge-gui/res/cardsfolder/k/karona_false_god_avatar.txt index 799a9dbe070..5bb48983663 100644 --- a/forge-gui/res/cardsfolder/k/karona_false_god_avatar.txt +++ b/forge-gui/res/cardsfolder/k/karona_false_god_avatar.txt @@ -5,7 +5,7 @@ HandLifeModifier:-1/+8 T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Command | Execute$ TrigExchangeChoose | TriggerDescription$ At the beginning of your upkeep, exchange control of a permanent you control chosen at random and a permanent target opponent controls chosen at random. SVar:TrigExchangeChoose:DB$ ChooseCard | ValidTgts$ Opponent | Choices$ Permanent.TargetedPlayerCtrl | AtRandom$ True | Amount$ 1 | RememberChosen$ True | SubAbility$ ChooseYou SVar:ChooseYou:DB$ ChooseCard | Choices$ Permanent.YouCtrl | Amount$ 1 | AtRandom$ True | RememberChosen$ True | SubAbility$ DBExchange -SVar:DBExchange:DB$ ExchangeControl | Defined$ Remembered | BothDefined$ True | SubAbility$ DBCleanup +SVar:DBExchange:DB$ ExchangeControl | Defined$ Remembered | SubAbility$ DBCleanup SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True SVar:Picture:https://downloads.cardforge.org/images/cards/VAN/Karona, False God Avatar.full.jpg Oracle:Hand -1, life +8\nAt the beginning of your upkeep, exchange control of a permanent you control chosen at random and a permanent target opponent controls chosen at random. diff --git a/forge-gui/res/cardsfolder/l/legerdemain.txt b/forge-gui/res/cardsfolder/l/legerdemain.txt index aac8a000596..273619bb132 100644 --- a/forge-gui/res/cardsfolder/l/legerdemain.txt +++ b/forge-gui/res/cardsfolder/l/legerdemain.txt @@ -2,7 +2,7 @@ Name:Legerdemain ManaCost:2 U U Types:Sorcery A:SP$ Pump | Cost$ 2 U U | ValidTgts$ Artifact,Creature | TgtPrompt$ target artifact or creature | StackDescription$ None | SubAbility$ DBExchange | SpellDescription$ Exchange control of target artifact or creature and another target permanent that shares one of those types with it. (This effect lasts indefinitely.) -SVar:DBExchange:DB$ ExchangeControl | Defined$ ParentTarget | ValidTgts$ Permanent | TgtPrompt$ Select target permanent that shares one of those types | TargetsWithSharedTypes$ Artifact,Creature | TargetUnique$ True +SVar:DBExchange:DB$ ExchangeControl | Defined$ ParentTarget | ValidTgts$ Permanent | TgtPrompt$ Select target permanent that shares one of those types | TargetsWithSharedCardType$ ParentTarget | TargetsWithSharedTypes$ Artifact,Creature | TargetUnique$ True AI:RemoveDeck:All SVar:Picture:http://www.wizards.com/global/images/magic/general/legerdemain.jpg Oracle:Exchange control of target artifact or creature and another target permanent that shares one of those types with it. (This effect lasts indefinitely.) diff --git a/forge-gui/res/cardsfolder/r/role_reversal.txt b/forge-gui/res/cardsfolder/r/role_reversal.txt index beb8f76cfa1..077734bd24a 100644 --- a/forge-gui/res/cardsfolder/r/role_reversal.txt +++ b/forge-gui/res/cardsfolder/r/role_reversal.txt @@ -1,8 +1,7 @@ Name:Role Reversal ManaCost:U U R Types:Sorcery -A:SP$ Pump | Cost$ U U R | ValidTgts$ Permanent | TgtPrompt$ Select target permanent | SubAbility$ DBExchange | StackDescription$ None | SpellDescription$ Exchange control of two target permanents that share a permanent type. -SVar:DBExchange:DB$ ExchangeControl | Defined$ ParentTarget | ValidTgts$ Permanent | TgtPrompt$ Select target permanent shares a card type with it | TargetsWithSharedTypes$ Creature,Artifact,Enchantment,Planeswalker,Land | TargetUnique$ True +A:SP$ ExchangeControl | Cost$ U U R | TargetMin$ 2 | TargetMax$ 2 | ValidTgts$ Permanent | TgtPrompt$ Select target permanents that share a permanent type | TargetsWithSameCardType$ True | SpellDescription$ Exchange control of two target permanents that share a permanent type. AI:RemoveDeck:All AI:RemoveDeck:Random Oracle:Exchange control of two target permanents that share a permanent type. diff --git a/forge-gui/res/cardsfolder/s/shifting_loyalties.txt b/forge-gui/res/cardsfolder/s/shifting_loyalties.txt index 4e45b9a0191..ca71f08f973 100644 --- a/forge-gui/res/cardsfolder/s/shifting_loyalties.txt +++ b/forge-gui/res/cardsfolder/s/shifting_loyalties.txt @@ -1,9 +1,7 @@ Name:Shifting Loyalties ManaCost:5 U Types:Sorcery -A:SP$ Pump | Cost$ 5 U | ValidTgts$ Permanent | TgtPrompt$ Select target permanent | SubAbility$ DBExchange | StackDescription$ None | SpellDescription$ Exchange control of two target permanents that share a card type. -SVar:DBExchange:DB$ ExchangeControl | Defined$ ParentTarget | ValidTgts$ Permanent | TgtPrompt$ Select target permanent shares a card type with it | TargetsWithSharedTypes$ Creature,Artifact,Enchantment,Planeswalker,Land,Tribal | TargetUnique$ True +A:SP$ ExchangeControl | Cost$ 5 U | TargetMin$ 2 | TargetMax$ 2 | ValidTgts$ Permanent | TgtPrompt$ Select target permanents that share a permanent type | TargetsWithSameCardType$ True | SpellDescription$ Exchange control of two target permanents that share a card type. AI:RemoveDeck:All AI:RemoveDeck:Random -SVar:Picture:http://www.wizards.com/global/images/magic/general/shifting_loyalties.jpg Oracle:Exchange control of two target permanents that share a card type. (Artifact, creature, enchantment, land, and planeswalker are card types.) diff --git a/forge-gui/res/cardsfolder/upcoming/the_trickster_gods_heist.txt b/forge-gui/res/cardsfolder/upcoming/the_trickster_gods_heist.txt new file mode 100644 index 00000000000..502fe6ae380 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/the_trickster_gods_heist.txt @@ -0,0 +1,11 @@ +Name:The Trickster-God's Heist +ManaCost:2 U B +Types:Enchantment Saga +K:Saga:3:DBCreature,DBNonCreature,DBDrain +SVar:DBCreature:DB$ ExchangeControl | ValidTgts$ Creature | TargetMin$ 2 | TargetMax$ 2 | TgtPrompt$ Choose two target creatures | Optional$ True | AILogic$ TrigTwoTargets | StackDescription$ SpellDescription | SpellDescription$ You may exchange control of two target creatures. +SVar:DBNonCreature:DB$ ExchangeControl | ValidTgts$ Card.nonBasic+nonCreature | TargetMin$ 2 | TargetMax$ 2 | ValidTgts$ Permanent | TgtPrompt$ Choose two target nonbasic, noncreature permanent | TargetsWithSameCardType$ True | Optional$ True | AILogic$ TrigTwoTargets | StackDescription$ SpellDescription | SpellDescription$ You may exchange control of two target nonbasic, noncreature permanents that share a card type. +SVar:DBDrain:DB$ LoseLife | ValidTgts$ Player | TgtPrompt$ Select a player | LifeAmount$ 3 | SubAbility$ DBGainLife | SpellDescription$ Target player loses 3 life and you gain 3 life. +SVar:DBGainLife:DB$ GainLife | Defined$ You | LifeAmount$ 3 +DeckHas:Ability$LifeGain +AI:RemoveDeck:Random +Oracle:I - You may exchange control of two target creatures.\nII - You may exchange control of two target nonbasic, noncreature permanents that share a card type.\nIII - Target player loses 3 life and you gain 3 life. diff --git a/forge-gui/res/languages/de-DE.properties b/forge-gui/res/languages/de-DE.properties index 3534dfe9fea..f8acfd3ebb0 100644 --- a/forge-gui/res/languages/de-DE.properties +++ b/forge-gui/res/languages/de-DE.properties @@ -1769,6 +1769,8 @@ lblWinsClash=gewinnt Fehde lblLosesClash=verliert Fehde #CloneEffect.java lblDoYouWantCopy=Möchtest du {0} kopieren? +#ControlExchangeEffect.java +lblExchangeControl=Do you want to exchange control of {0} and {1}? #ControlExchangeVariantEffect.java lblChooseCards=Wähle Karten #CopyPermanentEffect.java diff --git a/forge-gui/res/languages/en-US.properties b/forge-gui/res/languages/en-US.properties index c52e1f54380..99b38444ceb 100644 --- a/forge-gui/res/languages/en-US.properties +++ b/forge-gui/res/languages/en-US.properties @@ -1769,6 +1769,8 @@ lblWinsClash=wins clash lblLosesClash=loses clash #CloneEffect.java lblDoYouWantCopy=Do you want to copy {0}? +#ControlExchangeEffect.java +lblExchangeControl=Do you want to exchange control of {0} and {1}? #ControlExchangeVariantEffect.java lblChooseCards=Choose cards #CopyPermanentEffect.java diff --git a/forge-gui/res/languages/es-ES.properties b/forge-gui/res/languages/es-ES.properties index 2b90de1b841..820f6a45f28 100644 --- a/forge-gui/res/languages/es-ES.properties +++ b/forge-gui/res/languages/es-ES.properties @@ -1769,6 +1769,8 @@ lblWinsClash=gana el enfrentamiento lblLosesClash=pierde el enfrentamiento #CloneEffect.java lblDoYouWantCopy=¿Quieres copiar {0}? +#ControlExchangeEffect.java +lblExchangeControl=¿Quieres intercambiar de {0} y {1}? #ControlExchangeVariantEffect.java lblChooseCards=Elige las cartas #CopyPermanentEffect.java diff --git a/forge-gui/res/languages/it-IT.properties b/forge-gui/res/languages/it-IT.properties index a13e803e8db..13cd1cabab3 100644 --- a/forge-gui/res/languages/it-IT.properties +++ b/forge-gui/res/languages/it-IT.properties @@ -1769,6 +1769,8 @@ lblWinsClash=wins clash lblLosesClash=loses clash #CloneEffect.java lblDoYouWantCopy=Do you want to copy {0}? +#ControlExchangeEffect.java +lblExchangeControl=Do you want to exchange control of {0} and {1}? #ControlExchangeVariantEffect.java lblChooseCards=Choose cards #CopyPermanentEffect.java diff --git a/forge-gui/res/languages/zh-CN.properties b/forge-gui/res/languages/zh-CN.properties index 164f9732f66..6b2f709982b 100644 --- a/forge-gui/res/languages/zh-CN.properties +++ b/forge-gui/res/languages/zh-CN.properties @@ -1769,6 +1769,8 @@ lblWinsClash=比点赢了 lblLosesClash=比点输了 #CloneEffect.java lblDoYouWantCopy=你想要复制{0}吗? +#ControlExchangeEffect.java +lblExchangeControl=Do you want to exchange control of {0} and {1}? #ControlExchangeVariantEffect.java lblChooseCards=选择牌 #CopyPermanentEffect.java From 8d773e6002496d0924bcccae6b8a502a7547b634 Mon Sep 17 00:00:00 2001 From: John Date: Tue, 2 Feb 2021 17:11:57 +0000 Subject: [PATCH 68/74] Add new file --- the_three_seasons.txt | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 the_three_seasons.txt diff --git a/the_three_seasons.txt b/the_three_seasons.txt new file mode 100644 index 00000000000..3cd6195eff0 --- /dev/null +++ b/the_three_seasons.txt @@ -0,0 +1,10 @@ +Name:The Three Seasons +ManaCost:G U +Types:Enchantment Saga +K:Saga:3:DBMill,DBChangeZone1,DBRepeatEach +SVar:DBMill:DB$ Mill | NumCards$ 3 | Defined$ You | SpellDescription$ Mill three cards. +SVar:DBChangeZone1:DB$ ChangeZone | Origin$ Graveyard | Destination$ Hand | TargetMin$ 0 | TargetMax$ 2 | TgtPrompt$ Choose target snow permanent cards in your graveyard | ValidTgts$ Permanent.Snow+YouOwn | SpellDescription$ Return up to two target snow permanent cards from your graveyard to your hand. +SVar:DBRepeatEach:DB$ RepeatEach | RepeatPlayers$ Player | RepeatSubAbility$ DBChangeZone2 | SpellDescription$ Choose three cards in each graveyard. Their owners shuffle those cards into their libraries. +SVar:DBChangeZone2:DB$ ChangeZone | Origin$ Graveyard | Destination$ Library | ChangeType$ Card.RememberedPlayerCtrl | DefinedPlayer$ Player.IsRemembered | Chooser$ You | ChangeNum$ 3 | Hidden$ True | Shuffle$ True | Mandatory$ True +DeckHas:Ability$Mill +Oracle:(As this Saga enters and after your draw step, add a lore counter. Sacrifice after III.)\nI - Mill three cards.\nII - Return up to two target snow permanent cards from your graveyard to your hand.\nIII - Choose three cards in each graveyard. Their owners shuffle those cards into their libraries. From 196ac120b5c99d986668c497e904e8feca3b884d Mon Sep 17 00:00:00 2001 From: John Date: Tue, 2 Feb 2021 17:31:48 +0000 Subject: [PATCH 69/74] Update harald_king_of_skemfar.txt --- forge-gui/res/cardsfolder/upcoming/harald_king_of_skemfar.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/forge-gui/res/cardsfolder/upcoming/harald_king_of_skemfar.txt b/forge-gui/res/cardsfolder/upcoming/harald_king_of_skemfar.txt index 15bface1ab9..22b15fc5e5e 100644 --- a/forge-gui/res/cardsfolder/upcoming/harald_king_of_skemfar.txt +++ b/forge-gui/res/cardsfolder/upcoming/harald_king_of_skemfar.txt @@ -5,5 +5,5 @@ PT:3/2 K:Menace T:Mode$ ChangesZone | ValidCard$ Card.Self | Origin$ Any | Destination$ Battlefield | Execute$ TrigDig | TriggerDescription$ When CARDNAME enters the battlefield, look at the top five cards of your library. You may reveal an Elf, Warrior, or Tyvar 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$ 5 | ChangeNum$ 1 | Optional$ True | ForceRevealToController$ True | ChangeValid$ Elf,Warrior,Tyvar | RestRandomOrder$ True -DeckHints:Type$Elf +DeckHints:Type$Elf|Tyvar Oracle:Menace (This creature can’t be blocked except by two or more creatures.)\nWhen Harald, King of Skemfar enters the battlefield, look at the top five cards of your library. You may reveal an Elf, Warrior, or Tyvar card from among them and put it into your hand. Put the rest on the bottom of your library in a random order. From 989b0661d7257e74cd38e9e34fc615ea4334d4f4 Mon Sep 17 00:00:00 2001 From: John Date: Tue, 2 Feb 2021 17:32:30 +0000 Subject: [PATCH 70/74] Update harald_unites_the_elves.txt --- forge-gui/res/cardsfolder/upcoming/harald_unites_the_elves.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/forge-gui/res/cardsfolder/upcoming/harald_unites_the_elves.txt b/forge-gui/res/cardsfolder/upcoming/harald_unites_the_elves.txt index ed07a0599d9..0e6271f39a7 100644 --- a/forge-gui/res/cardsfolder/upcoming/harald_unites_the_elves.txt +++ b/forge-gui/res/cardsfolder/upcoming/harald_unites_the_elves.txt @@ -8,6 +8,6 @@ SVar:DBPutCounterAll:DB$ PutCounterAll | ValidCards$ Creature.Elf+YouCtrl | Coun SVar:DBEffect:DB$ Effect | Triggers$ TrigAttack | SpellDescription$ Whenever an Elf you control attacks this turn, target creature an opponent controls gets -1/-1 until end of turn. SVar:TrigAttack:Mode$ Attacks | ValidCard$ Creature.Elf+YouCtrl | Execute$ TrigPump | TriggerZones$ Battlefield | TriggerDescription$ Whenever an Elf you control attacks this turn, target creature an opponent controls gets -1/-1 until end of turn. SVar:TrigPump:DB$ Pump | ValidTgts$ Creature.OppCtrl | TgtPrompt$ Select target creature | NumAtt$ -1 | NumDef$ -1 | IsCurse$ True -DeckHints:Type$Elf +DeckHints:Type$Elf|Tyvar DeckHas:Ability$Counters Oracle:(As this Saga enters and after your draw step, add a lore counter. Sacrifice after III.)\nI - Mill three cards. You may put an Elf card or Tyvar card from your graveyard onto the battlefield.\nII - Put a +1/+1 counter on each Elf you control.\nIII - Whenever an Elf you control attacks this turn, target creature an opponent controls gets -1/-1 until end of turn. From a011518b894af1b2eafff3259e1fa28ded859465 Mon Sep 17 00:00:00 2001 From: John Date: Tue, 2 Feb 2021 18:25:27 +0000 Subject: [PATCH 71/74] Add new file --- .../res/cardsfolder/upcoming/kardur_doomscourge.txt | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 forge-gui/res/cardsfolder/upcoming/kardur_doomscourge.txt diff --git a/forge-gui/res/cardsfolder/upcoming/kardur_doomscourge.txt b/forge-gui/res/cardsfolder/upcoming/kardur_doomscourge.txt new file mode 100644 index 00000000000..249a3e428b6 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/kardur_doomscourge.txt @@ -0,0 +1,11 @@ +Name:Kardur, Doomscourge +ManaCost:2 B R +Types:Legendary Creature Demon Berserker +PT:4/3 +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigGoad | TriggerDescription$ When CARDNAME enters the battlefield, until your next turn, creatures your opponents control attack each combat if able and attack a player other than you if able. +SVar:TrigGoad:DB$ Goad | Defined$ Valid Creature.YouDontCtrl +T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Creature.attackingLKI | Execute$ TrigDrain | TriggerZones$ Battlefield | TriggerDescription$ Whenever an attacking creature dies, each opponent loses 1 life and you gain 1 life. +SVar:TrigDrain:DB$ LoseLife | Defined$ Player.Opponent | LifeAmount$ 1 | SubAbility$ DBGainOneLife +SVar:DBGainOneLife:DB$ GainLife | Defined$ You | LifeAmount$ 1 +DeckHas:Ability$LifeGain +Oracle:When Kardur Doomscourge enters the battlefield, until your next turn, creatures your opponents control attack each combat if able and attack a player other than you if able.\nWhenever an attacking creature dies, each opponent loses 1 life and you gain 1 life. From 5b80f48461ae6c6a7f0e55a0178b5e790140e385 Mon Sep 17 00:00:00 2001 From: Hans Mackowiak Date: Tue, 2 Feb 2021 20:19:46 +0100 Subject: [PATCH 72/74] ~ fix the_three_seasons --- .../res/cardsfolder/upcoming/the_three_seasons.txt | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename the_three_seasons.txt => forge-gui/res/cardsfolder/upcoming/the_three_seasons.txt (100%) diff --git a/the_three_seasons.txt b/forge-gui/res/cardsfolder/upcoming/the_three_seasons.txt similarity index 100% rename from the_three_seasons.txt rename to forge-gui/res/cardsfolder/upcoming/the_three_seasons.txt From 524ac3ae0cf57d5c596b2d56a0a15749e35beb98 Mon Sep 17 00:00:00 2001 From: Anthony Calosa Date: Wed, 3 Feb 2021 03:37:44 +0800 Subject: [PATCH 73/74] [Mobile] add Shahrazad support --- .../forge/game/event/GameEventSubgameStart.java | 1 - forge-gui-mobile/src/forge/Forge.java | 6 +++++- .../screens/match/winlose/ControlWinLose.java | 11 ++++++++++- .../forge/control/FControlGameEventHandler.java | 15 +++++++++++++++ .../main/java/forge/match/AbstractGuiGame.java | 2 +- .../src/main/java/forge/match/HostedMatch.java | 13 +++++++++++-- 6 files changed, 42 insertions(+), 6 deletions(-) diff --git a/forge-game/src/main/java/forge/game/event/GameEventSubgameStart.java b/forge-game/src/main/java/forge/game/event/GameEventSubgameStart.java index 674427a6f91..8d9f733a52b 100644 --- a/forge-game/src/main/java/forge/game/event/GameEventSubgameStart.java +++ b/forge-game/src/main/java/forge/game/event/GameEventSubgameStart.java @@ -1,7 +1,6 @@ package forge.game.event; import forge.game.Game; -import forge.game.card.Card; public class GameEventSubgameStart extends GameEvent { public final Game subgame; diff --git a/forge-gui-mobile/src/forge/Forge.java b/forge-gui-mobile/src/forge/Forge.java index e667a157c97..c4a516cefea 100644 --- a/forge-gui-mobile/src/forge/Forge.java +++ b/forge-gui-mobile/src/forge/Forge.java @@ -202,6 +202,10 @@ public class Forge implements ApplicationListener { ImageCache.preloadCache(filteredkeys); } + public static void openHomeScreen() { + openScreen(HomeScreen.instance); + } + private void afterDbLoaded() { stopContinuousRendering(); //save power consumption by disabling continuous rendering once assets loaded @@ -210,7 +214,7 @@ public class Forge implements ApplicationListener { SoundSystem.instance.setBackgroundMusic(MusicPlaylist.MENUS); //start background music destroyThis = false; //Allow back() Gdx.input.setCatchKey(Keys.MENU, true); - openScreen(HomeScreen.instance); + openHomeScreen(); splashScreen = null; boolean isLandscapeMode = isLandscapeMode(); diff --git a/forge-gui-mobile/src/forge/screens/match/winlose/ControlWinLose.java b/forge-gui-mobile/src/forge/screens/match/winlose/ControlWinLose.java index 9efa454a3d5..b829424e7c9 100644 --- a/forge-gui-mobile/src/forge/screens/match/winlose/ControlWinLose.java +++ b/forge-gui-mobile/src/forge/screens/match/winlose/ControlWinLose.java @@ -77,14 +77,23 @@ public class ControlWinLose { /** Action performed when "quit" button is pressed in default win/lose UI. */ public void actionOnQuit() { + boolean openHomeScreen = false; // Reset other stuff saveOptions(); - try { MatchController.getHostedMatch().endCurrentGame(); + try { + if(MatchController.getHostedMatch().subGameCount > 0) { + openHomeScreen = true; + MatchController.getHostedMatch().subGameCount--; + } + MatchController.getHostedMatch().endCurrentGame(); } catch (NullPointerException e) {} view.hide(); if(humancount == 0) { Forge.back(); } + //todo Refresh the layout + if (openHomeScreen) + Forge.openHomeScreen(); } /** diff --git a/forge-gui/src/main/java/forge/control/FControlGameEventHandler.java b/forge-gui/src/main/java/forge/control/FControlGameEventHandler.java index 0a28a6fea00..d9a8ba3be7c 100644 --- a/forge-gui/src/main/java/forge/control/FControlGameEventHandler.java +++ b/forge-gui/src/main/java/forge/control/FControlGameEventHandler.java @@ -280,6 +280,21 @@ public class FControlGameEventHandler extends IGameEventVisitor.Base { return null; } + @Override + public Void visit(final GameEventSubgameEnd event) { + if (event.maingame != null) { + for (Player p : event.maingame.getPlayers()) { + updateZone(p, ZoneType.Battlefield); + updateZone(p, ZoneType.Hand); + updateZone(p, ZoneType.Graveyard); + updateZone(p, ZoneType.Exile); + updateZone(p, ZoneType.Command); + } + return processEvent(); + } + return null; + } + @Override public Void visit(final GameEventZone event) { if (event.player != null) { diff --git a/forge-gui/src/main/java/forge/match/AbstractGuiGame.java b/forge-gui/src/main/java/forge/match/AbstractGuiGame.java index 44a7a024ed8..4d90d993e5c 100644 --- a/forge-gui/src/main/java/forge/match/AbstractGuiGame.java +++ b/forge-gui/src/main/java/forge/match/AbstractGuiGame.java @@ -185,7 +185,7 @@ public abstract class AbstractGuiGame implements IGuiGame, IMayViewCards { return true; //if not in game, card can be shown } if(GuiBase.getInterface().isLibgdxPort()){ - if(gameView.isGameOver()) { + if(gameView != null && gameView.isGameOver()) { return true; } if(spectator!=null) { //workaround fix!! this is needed on above code or it will diff --git a/forge-gui/src/main/java/forge/match/HostedMatch.java b/forge-gui/src/main/java/forge/match/HostedMatch.java index 7c66b7ea1d0..ba2bd1f9c10 100644 --- a/forge-gui/src/main/java/forge/match/HostedMatch.java +++ b/forge-gui/src/main/java/forge/match/HostedMatch.java @@ -69,6 +69,7 @@ public class HostedMatch { private final MatchUiEventVisitor visitor = new MatchUiEventVisitor(); private final Map nextGameDecisions = Maps.newHashMap(); private boolean isMatchOver = false; + public int subGameCount = 0; public HostedMatch() {} @@ -362,6 +363,7 @@ public class HostedMatch { @Override public Void visit(final GameEventSubgameStart event) { + subGameCount++; event.subgame.subscribeToEvents(SoundSystem.instance); event.subgame.subscribeToEvents(visitor); @@ -387,7 +389,10 @@ public class HostedMatch { } } }; - GuiBase.getInterface().invokeInEdtAndWait(switchGameView); + if (GuiBase.getInterface().isLibgdxPort()) + GuiBase.getInterface().invokeInEdtNow(switchGameView); + else + GuiBase.getInterface().invokeInEdtAndWait(switchGameView); //ensure opponents set properly for (final Player p : event.subgame.getPlayers()) { @@ -419,7 +424,11 @@ public class HostedMatch { } } }; - GuiBase.getInterface().invokeInEdtAndWait(switchGameView); + if (GuiBase.getInterface().isLibgdxPort()) + GuiBase.getInterface().invokeInEdtNow(switchGameView); + else + GuiBase.getInterface().invokeInEdtAndWait(switchGameView); + return null; } From cf3cd47ad45c9b13323bb538f2273a29acc43c2b Mon Sep 17 00:00:00 2001 From: Anthony Calosa Date: Wed, 3 Feb 2021 06:12:12 +0800 Subject: [PATCH 74/74] [Mobile] update layout --- forge-gui-mobile/src/forge/Forge.java | 6 ++++-- forge-gui-mobile/src/forge/screens/home/HomeScreen.java | 4 ++++ .../src/forge/screens/match/winlose/ControlWinLose.java | 3 +-- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/forge-gui-mobile/src/forge/Forge.java b/forge-gui-mobile/src/forge/Forge.java index c4a516cefea..2b46062fd46 100644 --- a/forge-gui-mobile/src/forge/Forge.java +++ b/forge-gui-mobile/src/forge/Forge.java @@ -202,8 +202,10 @@ public class Forge implements ApplicationListener { ImageCache.preloadCache(filteredkeys); } - public static void openHomeScreen() { + public static void openHomeScreen(boolean openNewGameMenu) { openScreen(HomeScreen.instance); + if(openNewGameMenu) + HomeScreen.instance.openNewGamMenu(); } private void afterDbLoaded() { @@ -214,7 +216,7 @@ public class Forge implements ApplicationListener { SoundSystem.instance.setBackgroundMusic(MusicPlaylist.MENUS); //start background music destroyThis = false; //Allow back() Gdx.input.setCatchKey(Keys.MENU, true); - openHomeScreen(); + openHomeScreen(false); splashScreen = null; boolean isLandscapeMode = isLandscapeMode(); diff --git a/forge-gui-mobile/src/forge/screens/home/HomeScreen.java b/forge-gui-mobile/src/forge/screens/home/HomeScreen.java index d24656c29eb..1ba7a46b3ff 100644 --- a/forge-gui-mobile/src/forge/screens/home/HomeScreen.java +++ b/forge-gui-mobile/src/forge/screens/home/HomeScreen.java @@ -135,6 +135,10 @@ public class HomeScreen extends FScreen { QuestWorld = questWorld; } + public void openNewGamMenu(){ + NewGameMenu.getPreferredScreen().open(); + } + public boolean getQuestCommanderMode() { return QuestCommander; } diff --git a/forge-gui-mobile/src/forge/screens/match/winlose/ControlWinLose.java b/forge-gui-mobile/src/forge/screens/match/winlose/ControlWinLose.java index b829424e7c9..c05f8d4cd5a 100644 --- a/forge-gui-mobile/src/forge/screens/match/winlose/ControlWinLose.java +++ b/forge-gui-mobile/src/forge/screens/match/winlose/ControlWinLose.java @@ -91,9 +91,8 @@ public class ControlWinLose { if(humancount == 0) { Forge.back(); } - //todo Refresh the layout if (openHomeScreen) - Forge.openHomeScreen(); + Forge.openHomeScreen(true); } /**