From 97981b1cbedb985d2487cc67a1d36fe10d311771 Mon Sep 17 00:00:00 2001 From: Renato Santos Date: Wed, 9 Feb 2022 00:54:21 +0000 Subject: [PATCH 01/10] Tested card scripts for NEO (2022-06-09) --- forge-gui/res/cardsfolder/upcoming/iron_apprentice.txt | 8 ++++++++ forge-gui/res/cardsfolder/upcoming/papercraft_decoy.txt | 7 +++++++ .../res/cardsfolder/upcoming/patchwork_automaton.txt | 8 ++++++++ forge-gui/res/cardsfolder/upcoming/reito_sentinel.txt | 9 +++++++++ .../res/cardsfolder/upcoming/searchlight_companion.txt | 8 ++++++++ forge-gui/res/cardsfolder/upcoming/shrine_steward.txt | 7 +++++++ .../res/cardsfolder/upcoming/thundersteel_colossus.txt | 8 ++++++++ forge-gui/res/cardsfolder/upcoming/towashi_guide_bot.txt | 9 +++++++++ 8 files changed, 64 insertions(+) create mode 100644 forge-gui/res/cardsfolder/upcoming/iron_apprentice.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/papercraft_decoy.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/patchwork_automaton.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/reito_sentinel.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/searchlight_companion.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/shrine_steward.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/thundersteel_colossus.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/towashi_guide_bot.txt diff --git a/forge-gui/res/cardsfolder/upcoming/iron_apprentice.txt b/forge-gui/res/cardsfolder/upcoming/iron_apprentice.txt new file mode 100644 index 00000000000..f127873dd80 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/iron_apprentice.txt @@ -0,0 +1,8 @@ +Name:Iron Apprentice +ManaCost:1 +Types:Artifact Creature Construct +PT:0/0 +K:etbCounter:P1P1:1 +T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Self+HasCounters | Execute$ TrigPutCounter | TriggerDescription$ When CARDNAME dies, if it had counters on it, put those counters on target creature you control. +SVar:TrigPutCounter:DB$ PutCounter | ValidTgts$ Creature.YouCtrl | TgtPrompt$ Select target creature you control | CounterType$ EachFromSource | EachFromSource$ TriggeredCardLKICopy +Oracle:Iron Apprentice enters the battlefield with a +1/+1 counter on it.\nWhen Iron Apprentice dies, if it had counters on it, put those counters on target creature you control. diff --git a/forge-gui/res/cardsfolder/upcoming/papercraft_decoy.txt b/forge-gui/res/cardsfolder/upcoming/papercraft_decoy.txt new file mode 100644 index 00000000000..1c2d3930744 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/papercraft_decoy.txt @@ -0,0 +1,7 @@ +Name:Papercraft Decoy +ManaCost:2 +Types:Artifact Creature Frog +PT:2/1 +T:Mode$ ChangesZone | Origin$ Battlefield | ValidCard$ Card.Self | Execute$ TrigDraw1 | TriggerDescription$ When CARDNAME leaves the battlefield, you may pay {2}. If you do, draw a card. +SVar:TrigDraw1:AB$ Draw | Cost$ 2 | Defined$ You | NumCards$ 1 +Oracle:When Papercraft Decoy leaves the battlefield, you may pay {2}. If you do, draw a card. diff --git a/forge-gui/res/cardsfolder/upcoming/patchwork_automaton.txt b/forge-gui/res/cardsfolder/upcoming/patchwork_automaton.txt new file mode 100644 index 00000000000..6d3bc0c59d9 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/patchwork_automaton.txt @@ -0,0 +1,8 @@ +Name:Patchwork Automaton +ManaCost:2 +Types:Artifact Creature Construct +PT:1/1 +K:Ward:2 +T:Mode$ SpellCast | ValidCard$ Artifact | ValidActivatingPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigPutCounter | TriggerDescription$ Whenever you cast an artifact spell, put a +1/+1 counter on CARDNAME. +SVar:TrigPutCounter:DB$ PutCounter | Defined$ Self | CounterType$ P1P1 | CounterNum$ 1 +Oracle:Ward {2} (Whenever this creature becomes the target of a spell or ability an opponent controls, counter it unless that player pays {2}.)\nWhenever you cast an artifact spell, put a +1/+1 counter on Patchwork Automaton. diff --git a/forge-gui/res/cardsfolder/upcoming/reito_sentinel.txt b/forge-gui/res/cardsfolder/upcoming/reito_sentinel.txt new file mode 100644 index 00000000000..d9574e8dffd --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/reito_sentinel.txt @@ -0,0 +1,9 @@ +Name:Reito Sentinel +ManaCost:3 +Types:Artifact Creature Construct +PT:3/3 +K:Defender +T:Mode$ ChangesZone | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigMill | TriggerDescription$ When CARDNAME enters the battlefield, target player mills three cards. +SVar:TrigMill:DB$ Mill | NumCards$ 3 | ValidTgts$ Player | TgtPrompt$ Select target player +A:AB$ ChangeZone | Cost$ 3 | ValidTgts$ Card | TgtPrompt$ Select target card in a graveyard | Origin$ Graveyard | Destination$ Library | LibraryPosition$ -1 | SpellDescription$ Put target card from a graveyard on the bottom of its owner's library. +Oracle:Defender\nWhen Reito Sentinel enters the battlefield, target player mills three cards. (They put the top three cards of their library into their graveyard.)\n{3}: Put target card from a graveyard on the bottom of its owner's library. diff --git a/forge-gui/res/cardsfolder/upcoming/searchlight_companion.txt b/forge-gui/res/cardsfolder/upcoming/searchlight_companion.txt new file mode 100644 index 00000000000..05309979e9d --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/searchlight_companion.txt @@ -0,0 +1,8 @@ +Name:Searchlight Companion +ManaCost:3 +Types:Artifact Creature Drone +PT:1/1 +K:Flying +T:Mode$ ChangesZone | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigToken | TriggerDescription$ When CARDNAME enters the battlefield, create a 1/1 colorless Spirit creature token. +SVar:TrigToken:DB$ Token | TokenAmount$ 1 | TokenScript$ c_1_1_spirit +Oracle:Flying\nWhen Searchlight Companion enters the battlefield, create a 1/1 colorless Spirit creature token. diff --git a/forge-gui/res/cardsfolder/upcoming/shrine_steward.txt b/forge-gui/res/cardsfolder/upcoming/shrine_steward.txt new file mode 100644 index 00000000000..54dfe02b11b --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/shrine_steward.txt @@ -0,0 +1,7 @@ +Name:Shrine Steward +ManaCost:5 +Types:Artifact Creature Construct +PT:3/2 +T:Mode$ ChangesZone | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigChange | OptionalDecider$ You | TriggerDescription$ When CARDNAME enters the battlefield, you may search your library for an Aura or Shrine card, reveal it, put it into your hand, then shuffle. +SVar:TrigChange:DB$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Aura,Shrine | ChangeNum$ 1 | ChangeTypeDesc$ Aura or Shrine card | ShuffleNonMandatory$ True +Oracle:When Shrine Steward enters the battlefield, you may search your library for an Aura or Shrine card, reveal it, put it into your hand, then shuffle. diff --git a/forge-gui/res/cardsfolder/upcoming/thundersteel_colossus.txt b/forge-gui/res/cardsfolder/upcoming/thundersteel_colossus.txt new file mode 100644 index 00000000000..0d276413a99 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/thundersteel_colossus.txt @@ -0,0 +1,8 @@ +Name:Thundersteel Colossus +ManaCost:7 +Types:Artifact Vehicle +PT:7/7 +K:Trample +K:Haste +K:Crew:2 +Oracle:Trample, haste\nCrew 2 (Tap any number of creatures you control with total power 2 or more: This Vehicle becomes an artifact creature until end of turn.) diff --git a/forge-gui/res/cardsfolder/upcoming/towashi_guide_bot.txt b/forge-gui/res/cardsfolder/upcoming/towashi_guide_bot.txt new file mode 100644 index 00000000000..35cc46dfbad --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/towashi_guide_bot.txt @@ -0,0 +1,9 @@ +Name:Towashi Guide-Bot +ManaCost:4 +Types:Artifact Creature Construct +PT:3/2 +T:Mode$ ChangesZone | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigPutCounter | TriggerDescription$ When CARDNAME enters the battlefield, put a +1/+1 counter on target creature you control. +SVar:TrigPutCounter:DB$ PutCounter | ValidTgts$ Creature.YouCtrl | TgtPrompt$ Select target creature you control | CounterType$ P1P1 | CounterNum$ 1 +A:AB$ Draw | Cost$ 4 T | NumCards$ 1 | ReduceCost$ X | SpellDescription$ Draw a card. This ability costs {1} less to activate for each modified creature you control. +SVar:X:Count$Valid Creature.modified+YouCtrl +Oracle:When Towashi Guide-Bot enters the battlefield, put a +1/+1 counter on target creature you control.\n{4}, {T}: Draw a card. This ability costs {1} less to activate for each modified creature you control. (Equipment, Auras you control, and counters are modifications.) From 3a9fc0606f1ba88e47d8f4c12e260aa09b1cb954 Mon Sep 17 00:00:00 2001 From: Renato Santos Date: Wed, 9 Feb 2022 17:02:43 +0000 Subject: [PATCH 02/10] Requested corrections --- forge-gui/res/cardsfolder/upcoming/iron_apprentice.txt | 1 + forge-gui/res/cardsfolder/upcoming/patchwork_automaton.txt | 2 ++ forge-gui/res/cardsfolder/upcoming/reito_sentinel.txt | 1 + forge-gui/res/cardsfolder/upcoming/searchlight_companion.txt | 1 + forge-gui/res/cardsfolder/upcoming/shrine_steward.txt | 1 + forge-gui/res/cardsfolder/upcoming/towashi_guide_bot.txt | 2 ++ 6 files changed, 8 insertions(+) diff --git a/forge-gui/res/cardsfolder/upcoming/iron_apprentice.txt b/forge-gui/res/cardsfolder/upcoming/iron_apprentice.txt index f127873dd80..56391e119e5 100644 --- a/forge-gui/res/cardsfolder/upcoming/iron_apprentice.txt +++ b/forge-gui/res/cardsfolder/upcoming/iron_apprentice.txt @@ -5,4 +5,5 @@ PT:0/0 K:etbCounter:P1P1:1 T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Self+HasCounters | Execute$ TrigPutCounter | TriggerDescription$ When CARDNAME dies, if it had counters on it, put those counters on target creature you control. SVar:TrigPutCounter:DB$ PutCounter | ValidTgts$ Creature.YouCtrl | TgtPrompt$ Select target creature you control | CounterType$ EachFromSource | EachFromSource$ TriggeredCardLKICopy +DeckHas:Ability$Counters Oracle:Iron Apprentice enters the battlefield with a +1/+1 counter on it.\nWhen Iron Apprentice dies, if it had counters on it, put those counters on target creature you control. diff --git a/forge-gui/res/cardsfolder/upcoming/patchwork_automaton.txt b/forge-gui/res/cardsfolder/upcoming/patchwork_automaton.txt index 6d3bc0c59d9..e5f99e5d535 100644 --- a/forge-gui/res/cardsfolder/upcoming/patchwork_automaton.txt +++ b/forge-gui/res/cardsfolder/upcoming/patchwork_automaton.txt @@ -5,4 +5,6 @@ PT:1/1 K:Ward:2 T:Mode$ SpellCast | ValidCard$ Artifact | ValidActivatingPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigPutCounter | TriggerDescription$ Whenever you cast an artifact spell, put a +1/+1 counter on CARDNAME. SVar:TrigPutCounter:DB$ PutCounter | Defined$ Self | CounterType$ P1P1 | CounterNum$ 1 +DeckHas:Ability$Counters +DeckHints:Type$Artifact Oracle:Ward {2} (Whenever this creature becomes the target of a spell or ability an opponent controls, counter it unless that player pays {2}.)\nWhenever you cast an artifact spell, put a +1/+1 counter on Patchwork Automaton. diff --git a/forge-gui/res/cardsfolder/upcoming/reito_sentinel.txt b/forge-gui/res/cardsfolder/upcoming/reito_sentinel.txt index d9574e8dffd..fb5b0e0b55e 100644 --- a/forge-gui/res/cardsfolder/upcoming/reito_sentinel.txt +++ b/forge-gui/res/cardsfolder/upcoming/reito_sentinel.txt @@ -6,4 +6,5 @@ K:Defender T:Mode$ ChangesZone | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigMill | TriggerDescription$ When CARDNAME enters the battlefield, target player mills three cards. SVar:TrigMill:DB$ Mill | NumCards$ 3 | ValidTgts$ Player | TgtPrompt$ Select target player A:AB$ ChangeZone | Cost$ 3 | ValidTgts$ Card | TgtPrompt$ Select target card in a graveyard | Origin$ Graveyard | Destination$ Library | LibraryPosition$ -1 | SpellDescription$ Put target card from a graveyard on the bottom of its owner's library. +DeckHas:Ability$Mill|Graveyard Oracle:Defender\nWhen Reito Sentinel enters the battlefield, target player mills three cards. (They put the top three cards of their library into their graveyard.)\n{3}: Put target card from a graveyard on the bottom of its owner's library. diff --git a/forge-gui/res/cardsfolder/upcoming/searchlight_companion.txt b/forge-gui/res/cardsfolder/upcoming/searchlight_companion.txt index 05309979e9d..11f3ac80e8a 100644 --- a/forge-gui/res/cardsfolder/upcoming/searchlight_companion.txt +++ b/forge-gui/res/cardsfolder/upcoming/searchlight_companion.txt @@ -5,4 +5,5 @@ PT:1/1 K:Flying T:Mode$ ChangesZone | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigToken | TriggerDescription$ When CARDNAME enters the battlefield, create a 1/1 colorless Spirit creature token. SVar:TrigToken:DB$ Token | TokenAmount$ 1 | TokenScript$ c_1_1_spirit +DeckHas:Ability$Token & Type$Spirit Oracle:Flying\nWhen Searchlight Companion enters the battlefield, create a 1/1 colorless Spirit creature token. diff --git a/forge-gui/res/cardsfolder/upcoming/shrine_steward.txt b/forge-gui/res/cardsfolder/upcoming/shrine_steward.txt index 54dfe02b11b..0dab6c9f5ea 100644 --- a/forge-gui/res/cardsfolder/upcoming/shrine_steward.txt +++ b/forge-gui/res/cardsfolder/upcoming/shrine_steward.txt @@ -4,4 +4,5 @@ Types:Artifact Creature Construct PT:3/2 T:Mode$ ChangesZone | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigChange | OptionalDecider$ You | TriggerDescription$ When CARDNAME enters the battlefield, you may search your library for an Aura or Shrine card, reveal it, put it into your hand, then shuffle. SVar:TrigChange:DB$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Aura,Shrine | ChangeNum$ 1 | ChangeTypeDesc$ Aura or Shrine card | ShuffleNonMandatory$ True +DeckNeeds:Type$Aura|Shrine Oracle:When Shrine Steward enters the battlefield, you may search your library for an Aura or Shrine card, reveal it, put it into your hand, then shuffle. diff --git a/forge-gui/res/cardsfolder/upcoming/towashi_guide_bot.txt b/forge-gui/res/cardsfolder/upcoming/towashi_guide_bot.txt index 35cc46dfbad..5dde7a6fa14 100644 --- a/forge-gui/res/cardsfolder/upcoming/towashi_guide_bot.txt +++ b/forge-gui/res/cardsfolder/upcoming/towashi_guide_bot.txt @@ -6,4 +6,6 @@ T:Mode$ ChangesZone | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ SVar:TrigPutCounter:DB$ PutCounter | ValidTgts$ Creature.YouCtrl | TgtPrompt$ Select target creature you control | CounterType$ P1P1 | CounterNum$ 1 A:AB$ Draw | Cost$ 4 T | NumCards$ 1 | ReduceCost$ X | SpellDescription$ Draw a card. This ability costs {1} less to activate for each modified creature you control. SVar:X:Count$Valid Creature.modified+YouCtrl +DeckHas:Ability$Counters +DeckHints:Type$Aura|Equipment & Ability$Counters Oracle:When Towashi Guide-Bot enters the battlefield, put a +1/+1 counter on target creature you control.\n{4}, {T}: Draw a card. This ability costs {1} less to activate for each modified creature you control. (Equipment, Auras you control, and counters are modifications.) From 0ede2ba068fe87c1d0d30394a7b8df16ab29a162 Mon Sep 17 00:00:00 2001 From: John Williams Date: Wed, 9 Feb 2022 17:52:47 +0000 Subject: [PATCH 03/10] Update Secret Lair Drop Series.txt --- forge-gui/res/editions/Secret Lair Drop Series.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/forge-gui/res/editions/Secret Lair Drop Series.txt b/forge-gui/res/editions/Secret Lair Drop Series.txt index a61c9244ba8..0ff244777f4 100644 --- a/forge-gui/res/editions/Secret Lair Drop Series.txt +++ b/forge-gui/res/editions/Secret Lair Drop Series.txt @@ -463,6 +463,7 @@ ScryfallCode=SLD 588 R Sphere of Safety @Johannes Voss 589 R Arcane Signet @Dan Frazier 591 R Crash Through @Tyler Walpole +596 R Persistent Petitioners @Crom 597 R Persistent Petitioners @Death Burger 598 R Persistent Petitioners @Feifei Ruan 603 M Eldrazi Monument @Cosmin Podar From 3eb0e1ee4159c6196a97ce59a97721a5ece5ad85 Mon Sep 17 00:00:00 2001 From: John Williams Date: Wed, 9 Feb 2022 17:55:46 +0000 Subject: [PATCH 04/10] Add new file --- forge-gui/res/editions/Year of the Tiger 2022.txt | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 forge-gui/res/editions/Year of the Tiger 2022.txt diff --git a/forge-gui/res/editions/Year of the Tiger 2022.txt b/forge-gui/res/editions/Year of the Tiger 2022.txt new file mode 100644 index 00000000000..48aa2f5f050 --- /dev/null +++ b/forge-gui/res/editions/Year of the Tiger 2022.txt @@ -0,0 +1,13 @@ +[metadata] +Code=PL22 +Date=2022-01-25 +Name=Year of the Tiger 2022 +Type=Promo +ScryfallCode=PL22 + +[cards] +1 R Temur Sabertooth @ +2 R Jedit Ojanen @ +3 M Yuriko, the Tiger's Shadow @ +4 M Snapdax, Apex of the Hunt @ +5 R Herald's Horn @ From ec534fc13bfa7576c60de9fb32ec071ac0e6f973 Mon Sep 17 00:00:00 2001 From: Anthony Calosa Date: Thu, 10 Feb 2022 15:04:58 +0800 Subject: [PATCH 05/10] [Mobile] Fix dropdown for Android --- forge-gui-mobile/src/forge/menu/FDropDown.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/forge-gui-mobile/src/forge/menu/FDropDown.java b/forge-gui-mobile/src/forge/menu/FDropDown.java index 20da17b4a5d..84ccde8799d 100644 --- a/forge-gui-mobile/src/forge/menu/FDropDown.java +++ b/forge-gui-mobile/src/forge/menu/FDropDown.java @@ -7,6 +7,7 @@ import forge.Graphics; import forge.assets.FSkinColor; import forge.assets.FSkinColor.Colors; import forge.assets.FSkinTexture; +import forge.gui.GuiBase; import forge.screens.FScreen; import forge.toolbox.FContainer; import forge.toolbox.FDisplayObject; @@ -221,19 +222,22 @@ public abstract class FDropDown extends FScrollPane { @Override public boolean pan(float x, float y, float deltaX, float deltaY, boolean moreVertical) { - hide(); //always hide if backdrop panned + if (!GuiBase.isAndroid()) + hide(); //always hide if backdrop panned return false; //allow pan to pass through to object behind backdrop } @Override public boolean fling(float velocityX, float velocityY) { - hide(); //always hide if backdrop flung + if (!GuiBase.isAndroid()) + hide(); //always hide if backdrop flung return false; //allow fling to pass through to object behind backdrop } @Override public boolean zoom(float x, float y, float amount) { - hide(); //always hide if backdrop zoomed + if (!GuiBase.isAndroid()) + hide(); //always hide if backdrop zoomed return false; //allow zoom to pass through to object behind backdrop } From 01a9e14cc14bc185319582efd32f9c92cc3a570c Mon Sep 17 00:00:00 2001 From: TRT <> Date: Thu, 10 Feb 2022 09:14:05 +0100 Subject: [PATCH 06/10] Only creatures can be modified --- forge-ai/src/main/java/forge/ai/ability/CountersAi.java | 1 + forge-game/src/main/java/forge/game/card/Card.java | 3 +++ 2 files changed, 4 insertions(+) diff --git a/forge-ai/src/main/java/forge/ai/ability/CountersAi.java b/forge-ai/src/main/java/forge/ai/ability/CountersAi.java index 9dcc8a43597..7be51bdecf7 100644 --- a/forge-ai/src/main/java/forge/ai/ability/CountersAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/CountersAi.java @@ -101,6 +101,7 @@ public abstract class CountersAi extends SpellAbilityAi { Card choice = null; if (type.equals("P1P1")) { + // TODO look for modified choice = ComputerUtilCard.getBestCreatureAI(list); if (choice == null) { 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 2a8d5d8447b..190218694b2 100644 --- a/forge-game/src/main/java/forge/game/card/Card.java +++ b/forge-game/src/main/java/forge/game/card/Card.java @@ -3491,6 +3491,9 @@ public class Card extends GameEntity implements Comparable, IHasSVars { } public final boolean isModified() { + if (!isCreature()) { + return false; + } if (this.isEquipped() || this.hasCounters()) { return true; } From a63d22855670d7f66be19539d5e5da6a0a6ece7d Mon Sep 17 00:00:00 2001 From: John Williams Date: Thu, 10 Feb 2022 08:47:42 +0000 Subject: [PATCH 07/10] Update Year of the Tiger 2022.txt --- forge-gui/res/editions/Year of the Tiger 2022.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/forge-gui/res/editions/Year of the Tiger 2022.txt b/forge-gui/res/editions/Year of the Tiger 2022.txt index 48aa2f5f050..6997068ab13 100644 --- a/forge-gui/res/editions/Year of the Tiger 2022.txt +++ b/forge-gui/res/editions/Year of the Tiger 2022.txt @@ -1,13 +1,13 @@ [metadata] Code=PL22 -Date=2022-01-25 +Date=2022-02-25 Name=Year of the Tiger 2022 Type=Promo ScryfallCode=PL22 [cards] -1 R Temur Sabertooth @ +1 R Temur Sabertooth @tswck 2 R Jedit Ojanen @ 3 M Yuriko, the Tiger's Shadow @ 4 M Snapdax, Apex of the Hunt @ -5 R Herald's Horn @ +5 R Herald's Horn @tswck From 60cd9ab3693652cf63ba43a7e535ece41fbfc1b0 Mon Sep 17 00:00:00 2001 From: Bug Hunter Date: Thu, 10 Feb 2022 12:58:01 +0000 Subject: [PATCH 08/10] Hinata & X spells fix --- .../src/main/java/forge/ai/ComputerUtil.java | 4 +- .../java/forge/ai/ComputerUtilAbility.java | 2 +- .../main/java/forge/ai/ComputerUtilCard.java | 2 +- .../main/java/forge/ai/ComputerUtilCost.java | 2 +- .../main/java/forge/ai/ability/AnimateAi.java | 4 +- .../java/forge/ai/ability/ChooseCardAi.java | 4 +- .../main/java/forge/ai/ability/CloneAi.java | 2 +- .../forge/ai/ability/CopyPermanentAi.java | 2 +- .../java/forge/ai/ability/DamageAllAi.java | 2 +- .../java/forge/ai/ability/DestroyAllAi.java | 4 +- .../java/forge/ai/ability/DigUntilAi.java | 2 +- .../forge/ai/ability/RegenerateAllAi.java | 2 +- .../java/forge/ai/ability/SacrificeAi.java | 6 +-- .../main/java/forge/ai/ability/TokenAi.java | 4 +- .../java/forge/ai/ability/UntapAllAi.java | 4 +- .../main/java/forge/game/CardTraitBase.java | 4 +- .../java/forge/game/ability/AbilityUtils.java | 44 +++++++------------ .../ability/effects/AnimateAllEffect.java | 2 +- .../game/ability/effects/CharmEffect.java | 4 +- .../effects/CopySpellAbilityEffect.java | 2 +- .../forge/game/ability/effects/DigEffect.java | 2 +- .../game/ability/effects/DiscardEffect.java | 5 +-- .../ability/effects/RegenerateAllEffect.java | 2 +- .../game/ability/effects/RepeatEffect.java | 2 +- .../ability/effects/UnattachAllEffect.java | 2 +- .../game/ability/effects/UntapAllEffect.java | 2 +- .../java/forge/game/card/CardZoneTable.java | 2 +- .../java/forge/game/cost/CostAdjustment.java | 3 +- .../game/spellability/AbilityManaPart.java | 2 +- .../game/staticability/StaticAbility.java | 2 +- .../StaticAbilityContinuous.java | 2 +- .../trigger/TriggerAttackersDeclared.java | 2 +- .../java/forge/game/trigger/TriggerDrawn.java | 2 +- .../forge/game/trigger/TriggerLifeGained.java | 2 +- 34 files changed, 62 insertions(+), 72 deletions(-) diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtil.java b/forge-ai/src/main/java/forge/ai/ComputerUtil.java index c144ca7c8ed..4b07f185213 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtil.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtil.java @@ -1343,7 +1343,7 @@ public class ComputerUtil { } final CardCollection typeList = - CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), type.split(","), source.getController(), source, sa); + CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), type, source.getController(), source, sa); for (Card c : typeList) { if (c.getSVar("SacMe").equals("6")) { return true; @@ -1620,7 +1620,7 @@ public class ComputerUtil { objects = AbilityUtils.getDefinedObjects(source, topStack.getParam("Defined"), topStack); } else if (topStack.hasParam("ValidCards")) { CardCollectionView battleField = aiPlayer.getCardsIn(ZoneType.Battlefield); - objects = CardLists.getValidCards(battleField, topStack.getParam("ValidCards").split(","), source.getController(), source, topStack); + objects = CardLists.getValidCards(battleField, topStack.getParam("ValidCards"), source.getController(), source, topStack); } else { return threatened; } diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilAbility.java b/forge-ai/src/main/java/forge/ai/ComputerUtilAbility.java index b590b3e23a3..8766eff1f05 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtilAbility.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtilAbility.java @@ -36,7 +36,7 @@ public class ComputerUtilAbility { public boolean apply(final Card c) { if (!c.getSVar("NeedsToPlay").isEmpty()) { final String needsToPlay = c.getSVar("NeedsToPlay"); - CardCollection list = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), needsToPlay.split(","), c.getController(), c, null); + CardCollection list = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), needsToPlay, c.getController(), c, null); if (list.isEmpty()) { return false; } diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java b/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java index 6043bfd27f0..eae57125e27 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java @@ -1951,7 +1951,7 @@ public class ComputerUtilCard { CardCollectionView list = game.getCardsIn(ZoneType.Battlefield); - list = CardLists.getValidCards(list, needsToPlay.split(","), card.getController(), card, sa); + list = CardLists.getValidCards(list, needsToPlay, card.getController(), card, sa); if (list.isEmpty()) { return AiPlayDecision.MissingNeededCards; } diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilCost.java b/forge-ai/src/main/java/forge/ai/ComputerUtilCost.java index 756926fe490..847f2ee12f5 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtilCost.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtilCost.java @@ -144,7 +144,7 @@ public class ComputerUtilCost { return true; } } - final CardCollection typeList = CardLists.getValidCards(hand, type.split(","), source.getController(), source, sa); + final CardCollection typeList = CardLists.getValidCards(hand, type, source.getController(), source, sa); if (typeList.size() > ai.getMaxHandSize()) { continue; } diff --git a/forge-ai/src/main/java/forge/ai/ability/AnimateAi.java b/forge-ai/src/main/java/forge/ai/ability/AnimateAi.java index 49876c64fb4..0272d980cf6 100644 --- a/forge-ai/src/main/java/forge/ai/ability/AnimateAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/AnimateAi.java @@ -69,14 +69,14 @@ public class AnimateAi extends SpellAbilityAi { final String valid = topStack.getParamOrDefault("SacValid", "Card.Self"); String num = topStack.getParamOrDefault("Amount", "1"); final int nToSac = AbilityUtils.calculateAmount(topStack.getHostCard(), num, topStack); - CardCollection list = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), valid.split(","), + CardCollection list = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), valid, ai.getWeakestOpponent(), topStack.getHostCard(), topStack); list = CardLists.filter(list, CardPredicates.canBeSacrificedBy(topStack, true)); ComputerUtilCard.sortByEvaluateCreature(list); if (!list.isEmpty() && list.size() == nToSac && ComputerUtilCost.canPayCost(sa, ai, sa.isTrigger())) { Card animatedCopy = becomeAnimated(source, sa); list.add(animatedCopy); - list = CardLists.getValidCards(list, valid.split(","), ai.getWeakestOpponent(), topStack.getHostCard(), + list = CardLists.getValidCards(list, valid, ai.getWeakestOpponent(), topStack.getHostCard(), topStack); list = CardLists.filter(list, CardPredicates.canBeSacrificedBy(topStack, true)); if (ComputerUtilCard.evaluateCreature(animatedCopy) < ComputerUtilCard.evaluateCreature(list.get(0)) diff --git a/forge-ai/src/main/java/forge/ai/ability/ChooseCardAi.java b/forge-ai/src/main/java/forge/ai/ability/ChooseCardAi.java index 3690b981f2c..ad73c11051c 100644 --- a/forge-ai/src/main/java/forge/ai/ability/ChooseCardAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/ChooseCardAi.java @@ -184,7 +184,7 @@ public class ChooseCardAi extends SpellAbilityAi { choice = ComputerUtilCard.getBestCreatureAI(options); } else if (logic.equals("Clone")) { final String filter = "Permanent.YouDontCtrl,Permanent.nonLegendary"; - CardCollection newOptions = CardLists.getValidCards(options, filter.split(","), ctrl, host, sa); + CardCollection newOptions = CardLists.getValidCards(options, filter, ctrl, host, sa); if (!newOptions.isEmpty()) { options = newOptions; } @@ -194,7 +194,7 @@ public class ChooseCardAi extends SpellAbilityAi { choice = Aggregates.random(options); } else if (logic.equals("Untap")) { final String filter = "Permanent.YouCtrl,Permanent.tapped"; - CardCollection newOptions = CardLists.getValidCards(options, filter.split(","), ctrl, host, sa); + CardCollection newOptions = CardLists.getValidCards(options, filter, ctrl, host, sa); if (!newOptions.isEmpty()) { options = newOptions; } diff --git a/forge-ai/src/main/java/forge/ai/ability/CloneAi.java b/forge-ai/src/main/java/forge/ai/ability/CloneAi.java index d9a7070f29c..4a719fa2ea5 100644 --- a/forge-ai/src/main/java/forge/ai/ability/CloneAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/CloneAi.java @@ -198,7 +198,7 @@ public class CloneAi extends SpellAbilityAi { filter = filter.replace(".nonLegendary+", ".").replace(".nonLegendary", ""); } - CardCollection newOptions = CardLists.getValidCards(options, filter.split(","), ctrl, host, sa); + CardCollection newOptions = CardLists.getValidCards(options, filter, ctrl, host, sa); if (!newOptions.isEmpty()) { options = newOptions; } diff --git a/forge-ai/src/main/java/forge/ai/ability/CopyPermanentAi.java b/forge-ai/src/main/java/forge/ai/ability/CopyPermanentAi.java index f74fa8115ed..990a5596af0 100644 --- a/forge-ai/src/main/java/forge/ai/ability/CopyPermanentAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/CopyPermanentAi.java @@ -248,7 +248,7 @@ public class CopyPermanentAi extends SpellAbilityAi { final boolean canCopyLegendary = sa.hasParam("NonLegendary"); final String filter = canCopyLegendary ? "Permanent" : "Permanent.YouDontCtrl,Permanent.nonLegendary"; // TODO add filter to not select Legendary from Other Player when ai already have a Legendary with that name - return CardLists.getValidCards(options, filter.split(","), ctrl, host, sa); + return CardLists.getValidCards(options, filter, ctrl, host, sa); } @Override diff --git a/forge-ai/src/main/java/forge/ai/ability/DamageAllAi.java b/forge-ai/src/main/java/forge/ai/ability/DamageAllAi.java index 56e2bfcd06b..1396fc3a5d3 100644 --- a/forge-ai/src/main/java/forge/ai/ability/DamageAllAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/DamageAllAi.java @@ -251,7 +251,7 @@ public class DamageAllAi extends SpellAbilityAi { // TODO: X may be something different than X paid CardCollection list = - CardLists.getValidCards(player.getCardsIn(ZoneType.Battlefield), validC.split(","), source.getController(), source, sa); + CardLists.getValidCards(player.getCardsIn(ZoneType.Battlefield), validC, source.getController(), source, sa); final Predicate filterKillable = new Predicate() { @Override diff --git a/forge-ai/src/main/java/forge/ai/ability/DestroyAllAi.java b/forge-ai/src/main/java/forge/ai/ability/DestroyAllAi.java index c21be6ff6f7..a4cd917937a 100644 --- a/forge-ai/src/main/java/forge/ai/ability/DestroyAllAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/DestroyAllAi.java @@ -92,8 +92,8 @@ public class DestroyAllAi extends SpellAbilityAi { // TODO should probably sort results when targeted to use on biggest threat instead of first match for (Player opponent: ai.getOpponents()) { - CardCollection opplist = CardLists.getValidCards(opponent.getCardsIn(ZoneType.Battlefield), valid.split(","), source.getController(), source, sa); - CardCollection ailist = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), valid.split(","), source.getController(), source, sa); + CardCollection opplist = CardLists.getValidCards(opponent.getCardsIn(ZoneType.Battlefield), valid, source.getController(), source, sa); + CardCollection ailist = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), valid, source.getController(), source, sa); opplist = CardLists.filter(opplist, predicate); ailist = CardLists.filter(ailist, predicate); diff --git a/forge-ai/src/main/java/forge/ai/ability/DigUntilAi.java b/forge-ai/src/main/java/forge/ai/ability/DigUntilAi.java index 8d09f66e886..3bbed335861 100644 --- a/forge-ai/src/main/java/forge/ai/ability/DigUntilAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/DigUntilAi.java @@ -68,7 +68,7 @@ public class DigUntilAi extends SpellAbilityAi { } else { if (sa.hasParam("Valid")) { final String valid = sa.getParam("Valid"); - if (CardLists.getValidCards(ai.getCardsIn(ZoneType.Library), valid.split(","), source.getController(), source, sa).isEmpty()) { + if (CardLists.getValidCards(ai.getCardsIn(ZoneType.Library), valid, source.getController(), source, sa).isEmpty()) { return false; } } diff --git a/forge-ai/src/main/java/forge/ai/ability/RegenerateAllAi.java b/forge-ai/src/main/java/forge/ai/ability/RegenerateAllAi.java index f4add7b107e..909fe257786 100644 --- a/forge-ai/src/main/java/forge/ai/ability/RegenerateAllAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/RegenerateAllAi.java @@ -29,7 +29,7 @@ public class RegenerateAllAi extends SpellAbilityAi { final String valid = sa.getParamOrDefault("ValidCards", ""); CardCollectionView list = game.getCardsIn(ZoneType.Battlefield); - list = CardLists.getValidCards(list, valid.split(","), hostCard.getController(), hostCard, sa); + list = CardLists.getValidCards(list, valid, hostCard.getController(), hostCard, sa); list = CardLists.filter(list, CardPredicates.isController(ai)); if (list.size() == 0) { diff --git a/forge-ai/src/main/java/forge/ai/ability/SacrificeAi.java b/forge-ai/src/main/java/forge/ai/ability/SacrificeAi.java index 3cf0cb67bcc..87754161cd9 100644 --- a/forge-ai/src/main/java/forge/ai/ability/SacrificeAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/SacrificeAi.java @@ -79,7 +79,7 @@ public class SacrificeAi extends SpellAbilityAi { List list = null; try { - list = CardLists.getValidCards(opp.getCardsIn(ZoneType.Battlefield), valid.split(","), sa.getActivatingPlayer(), source, sa); + list = CardLists.getValidCards(opp.getCardsIn(ZoneType.Battlefield), valid, sa.getActivatingPlayer(), source, sa); } catch (NullPointerException e) { return false; } finally { @@ -141,7 +141,7 @@ public class SacrificeAi extends SpellAbilityAi { List humanList = null; try { - humanList = CardLists.getValidCards(ai.getStrongestOpponent().getCardsIn(ZoneType.Battlefield), valid.split(","), sa.getActivatingPlayer(), source, sa); + humanList = CardLists.getValidCards(ai.getStrongestOpponent().getCardsIn(ZoneType.Battlefield), valid, sa.getActivatingPlayer(), source, sa); } catch (NullPointerException e) { return false; } finally { @@ -155,7 +155,7 @@ public class SacrificeAi extends SpellAbilityAi { } else if (defined.equals("You")) { List computerList = null; try { - computerList = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), valid.split(","), sa.getActivatingPlayer(), source, sa); + computerList = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), valid, sa.getActivatingPlayer(), source, sa); } catch (NullPointerException e) { return false; } finally { diff --git a/forge-ai/src/main/java/forge/ai/ability/TokenAi.java b/forge-ai/src/main/java/forge/ai/ability/TokenAi.java index 4605604cabc..b1ce0ea688a 100644 --- a/forge-ai/src/main/java/forge/ai/ability/TokenAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/TokenAi.java @@ -241,14 +241,14 @@ public class TokenAi extends SpellAbilityAi { final String valid = topStack.getParamOrDefault("SacValid", "Card.Self"); String num = sa.getParamOrDefault("Amount", "1"); final int nToSac = AbilityUtils.calculateAmount(topStack.getHostCard(), num, topStack); - CardCollection list = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), valid.split(","), + CardCollection list = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), valid, ai.getWeakestOpponent(), topStack.getHostCard(), sa); list = CardLists.filter(list, CardPredicates.canBeSacrificedBy(topStack, true)); // only care about saving single creature for now if (!list.isEmpty() && nTokens > 0 && list.size() == nToSac) { ComputerUtilCard.sortByEvaluateCreature(list); list.add(token); - list = CardLists.getValidCards(list, valid.split(","), ai.getWeakestOpponent(), topStack.getHostCard(), sa); + list = CardLists.getValidCards(list, valid, ai.getWeakestOpponent(), topStack.getHostCard(), sa); list = CardLists.filter(list, CardPredicates.canBeSacrificedBy(topStack, true)); return ComputerUtilCard.evaluateCreature(token) < ComputerUtilCard.evaluateCreature(list.get(0)) && list.contains(token); diff --git a/forge-ai/src/main/java/forge/ai/ability/UntapAllAi.java b/forge-ai/src/main/java/forge/ai/ability/UntapAllAi.java index 97d70b3fafa..1535f2a2a23 100644 --- a/forge-ai/src/main/java/forge/ai/ability/UntapAllAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/UntapAllAi.java @@ -26,7 +26,7 @@ public class UntapAllAi extends SpellAbilityAi { } CardCollectionView list = CardLists.filter(aiPlayer.getGame().getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.TAPPED); final String valid = sa.getParamOrDefault("ValidCards", ""); - list = CardLists.getValidCards(list, valid.split(","), source.getController(), source, sa); + list = CardLists.getValidCards(list, valid, source.getController(), source, sa); // don't untap if only opponent benefits PlayerCollection goodControllers = aiPlayer.getAllies(); goodControllers.add(aiPlayer); @@ -43,7 +43,7 @@ public class UntapAllAi extends SpellAbilityAi { if (sa.hasParam("ValidCards")) { String valid = sa.getParam("ValidCards"); CardCollectionView list = CardLists.filter(aiPlayer.getGame().getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.TAPPED); - list = CardLists.getValidCards(list, valid.split(","), source.getController(), source, sa); + list = CardLists.getValidCards(list, valid, source.getController(), source, sa); return mandatory || !list.isEmpty(); } diff --git a/forge-game/src/main/java/forge/game/CardTraitBase.java b/forge-game/src/main/java/forge/game/CardTraitBase.java index d13706a086f..52b6b9722bc 100644 --- a/forge-game/src/main/java/forge/game/CardTraitBase.java +++ b/forge-game/src/main/java/forge/game/CardTraitBase.java @@ -368,7 +368,7 @@ public abstract class CardTraitBase extends GameObject implements IHasCardView, list.addAll(p.getCardsIn(presentZone)); } } - list = CardLists.getValidCards(list, sIsPresent.split(","), this.getHostCard().getController(), this.getHostCard(), this); + list = CardLists.getValidCards(list, sIsPresent, this.getHostCard().getController(), this.getHostCard(), this); final String rightString = presentCompare.substring(2); int right = AbilityUtils.calculateAmount(getHostCard(), rightString, this); @@ -397,7 +397,7 @@ public abstract class CardTraitBase extends GameObject implements IHasCardView, } } - list = CardLists.getValidCards(list, sIsPresent.split(","), this.getHostCard().getController(), this.getHostCard(), this); + list = CardLists.getValidCards(list, sIsPresent, this.getHostCard().getController(), this.getHostCard(), this); final String rightString = presentCompare.substring(2); int right = AbilityUtils.calculateAmount(getHostCard(), rightString, this); diff --git a/forge-game/src/main/java/forge/game/ability/AbilityUtils.java b/forge-game/src/main/java/forge/game/ability/AbilityUtils.java index b497023ff16..4426492e8c5 100644 --- a/forge-game/src/main/java/forge/game/ability/AbilityUtils.java +++ b/forge-game/src/main/java/forge/game/ability/AbilityUtils.java @@ -346,7 +346,7 @@ public class AbilityUtils { candidates = game.getCardsIn(ZoneType.smartValueOf(zone)); validDefined = s[1]; } - cards.addAll(CardLists.getValidCards(candidates, validDefined.split(","), hostCard.getController(), hostCard, sa)); + cards.addAll(CardLists.getValidCards(candidates, validDefined, hostCard.getController(), hostCard, sa)); return cards; } else { CardCollection list = null; @@ -977,7 +977,7 @@ public class AbilityUtils { String var = sa.getParam("AbilityCount"); valid = TextUtil.fastReplace(valid, var, Integer.toString(calculateAmount(source, var, sa))); } - return CardLists.getValidCards(list, valid.split(","), sa.getActivatingPlayer(), source, sa); + return CardLists.getValidCards(list, valid, sa.getActivatingPlayer(), source, sa); } /** @@ -1884,7 +1884,7 @@ public class AbilityUtils { return doXMath(0, expr, c, ctb); } } - list = CardLists.getValidCards(list, k[1].split(","), sa.getActivatingPlayer(), c, sa); + list = CardLists.getValidCards(list, k[1], sa.getActivatingPlayer(), c, sa); if (k[0].contains("TotalToughness")) { return doXMath(Aggregates.sum(list, CardPredicates.Accessors.fnGetNetToughness), expr, c, ctb); } @@ -1911,7 +1911,7 @@ public class AbilityUtils { return doXMath(0, expr, c, ctb); } } - list = CardLists.getValidCards(list, k[1].split(","), sa.getActivatingPlayer(), c, sa); + list = CardLists.getValidCards(list, k[1], sa.getActivatingPlayer(), c, sa); return doXMath(list.size(), expr, c, ctb); } @@ -1940,14 +1940,14 @@ public class AbilityUtils { if (sq[0].startsWith("LastStateBattlefield")) { final String[] k = l[0].split(" "); CardCollection list = new CardCollection(game.getLastStateBattlefield()); - list = CardLists.getValidCards(list, k[1].split(","), player, c, ctb); + list = CardLists.getValidCards(list, k[1], player, c, ctb); return doXMath(list.size(), expr, c, ctb); } if (sq[0].startsWith("LastStateGraveyard")) { final String[] k = l[0].split(" "); CardCollection list = new CardCollection(game.getLastStateGraveyard()); - list = CardLists.getValidCards(list, k[1].split(","), player, c, ctb); + list = CardLists.getValidCards(list, k[1], player, c, ctb); return doXMath(list.size(), expr, c, ctb); } @@ -2176,7 +2176,7 @@ public class AbilityUtils { if (sq[0].startsWith("Devoured")) { final String validDevoured = sq[0].split(" ")[1]; - CardCollection cl = CardLists.getValidCards(c.getDevouredCards(), validDevoured.split(","), player, c, ctb); + CardCollection cl = CardLists.getValidCards(c.getDevouredCards(), validDevoured, player, c, ctb); return doXMath(cl.size(), expr, c, ctb); } @@ -2451,8 +2451,7 @@ public class AbilityUtils { if (sq[0].startsWith("ColorsCtrl")) { final String restriction = l[0].substring(11); - final String[] rest = restriction.split(","); - final CardCollection list = CardLists.getValidCards(player.getCardsIn(ZoneType.Battlefield), rest, player, c, ctb); + final CardCollection list = CardLists.getValidCards(player.getCardsIn(ZoneType.Battlefield), restriction, player, c, ctb); byte n = 0; for (final Card card : list) { n |= card.getColor().getColor(); @@ -2711,8 +2710,7 @@ public class AbilityUtils { // Count$SumPower_valid if (sq[0].startsWith("SumPower")) { final String[] restrictions = l[0].split("_"); - final String[] rest = restrictions[1].split(","); - CardCollection filteredCards = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), rest, player, c, ctb); + CardCollection filteredCards = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), restrictions[1], player, c, ctb); return doXMath(Aggregates.sum(filteredCards, CardPredicates.Accessors.fnGetNetPower), expr, c, ctb); } @@ -2723,9 +2721,8 @@ public class AbilityUtils { if (sq[0].contains("Graveyard")) zone = ZoneType.Graveyard; final String[] restrictions = l[0].split("_"); - final String[] rest = restrictions[1].split(","); CardCollectionView cardsonbattlefield = game.getCardsIn(zone); - CardCollection filteredCards = CardLists.getValidCards(cardsonbattlefield, rest, player, c, ctb); + CardCollection filteredCards = CardLists.getValidCards(cardsonbattlefield, restrictions[1], player, c, ctb); return Aggregates.sum(filteredCards, CardPredicates.Accessors.fnGetCmc); } @@ -2821,8 +2818,7 @@ public class AbilityUtils { if (sq[0].startsWith("GreatestToughness_")) { final String restriction = l[0].substring(18); - final String[] rest = restriction.split(","); - CardCollection list = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), rest, player, c, ctb); + CardCollection list = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), restriction, player, c, ctb); int highest = 0; for (final Card crd : list) { if (crd.getNetToughness() > highest) { @@ -2834,8 +2830,7 @@ public class AbilityUtils { if (sq[0].startsWith("HighestCMC_")) { final String restriction = l[0].substring(11); - final String[] rest = restriction.split(","); - CardCollection list = CardLists.getValidCards(game.getCardsInGame(), rest, player, c, ctb); + CardCollection list = CardLists.getValidCards(game.getCardsInGame(), restriction, player, c, ctb); int highest = 0; for (final Card crd : list) { // dont check for Split card anymore @@ -2874,8 +2869,7 @@ public class AbilityUtils { if (sq[0].startsWith("DifferentCardNames_")) { final List crdname = Lists.newArrayList(); final String restriction = l[0].substring(19); - final String[] rest = restriction.split(","); - CardCollection list = CardLists.getValidCards(game.getCardsInGame(), rest, player, c, ctb); + CardCollection list = CardLists.getValidCards(game.getCardsInGame(), restriction, player, c, ctb); for (final Card card : list) { String name = card.getName(); // CR 201.2b Those objects have different names only if each of them has at least one name and no two objects in that group have a name in common @@ -2889,8 +2883,7 @@ public class AbilityUtils { if (sq[0].startsWith("DifferentPower_")) { final List powers = Lists.newArrayList(); final String restriction = l[0].substring(15); - final String[] rest = restriction.split(","); - CardCollection list = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), rest, player, c, ctb); + CardCollection list = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), restriction, player, c, ctb); for (final Card card : list) { Integer pow = card.getNetPower(); if (!powers.contains(pow)) { @@ -2915,8 +2908,7 @@ public class AbilityUtils { if (sq[0].startsWith("ColorsCtrl")) { final String restriction = l[0].substring(11); - final String[] rest = restriction.split(","); - final CardCollection list = CardLists.getValidCards(player.getCardsIn(ZoneType.Battlefield), rest, player, c, ctb); + final CardCollection list = CardLists.getValidCards(player.getCardsIn(ZoneType.Battlefield), restriction, player, c, ctb); byte n = 0; for (final Card card : list) { n |= card.getColor().getColor(); @@ -3417,16 +3409,14 @@ public class AbilityUtils { String[] lparts = l[0].split(" ", 2); final List vZone = ZoneType.listValueOf(lparts[0].split("Valid")[1]); String restrictions = TextUtil.fastReplace(l[0], TextUtil.addSuffix(lparts[0]," "), ""); - final String[] rest = restrictions.split(","); - CardCollection cards = CardLists.getValidCards(game.getCardsIn(vZone), rest, player, source, ctb); + CardCollection cards = CardLists.getValidCards(game.getCardsIn(vZone), restrictions, player, source, ctb); return doXMath(cards.size(), m, source, ctb); } // count valid cards on the battlefield if (l[0].startsWith("Valid ")) { final String restrictions = l[0].substring(6); - final String[] rest = restrictions.split(","); - CardCollection cardsonbattlefield = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), rest, player, source, ctb); + CardCollection cardsonbattlefield = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), restrictions, player, source, ctb); return doXMath(cardsonbattlefield.size(), m, source, ctb); } diff --git a/forge-game/src/main/java/forge/game/ability/effects/AnimateAllEffect.java b/forge-game/src/main/java/forge/game/ability/effects/AnimateAllEffect.java index 5a0168055f9..efd631c5773 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/AnimateAllEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/AnimateAllEffect.java @@ -130,7 +130,7 @@ public class AnimateAllEffect extends AnimateEffectBase { list = getTargetPlayers(sa).getCardsIn(ZoneType.Battlefield); } - list = CardLists.getValidCards(list, valid.split(","), host.getController(), host, sa); + list = CardLists.getValidCards(list, valid, host.getController(), host, sa); for (final Card c : list) { doAnimate(c, sa, power, toughness, types, removeTypes, finalColors, diff --git a/forge-game/src/main/java/forge/game/ability/effects/CharmEffect.java b/forge-game/src/main/java/forge/game/ability/effects/CharmEffect.java index 23caee7e56c..31addb73b60 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/CharmEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/CharmEffect.java @@ -67,7 +67,7 @@ public class CharmEffect extends SpellAbilityEffect { } else { num = Math.min(AbilityUtils.calculateAmount(source, sa.getParamOrDefault("CharmNum", "1"), sa), list.size()); } - final int min = sa.hasParam("MinCharmNum") ? AbilityUtils.calculateAmount(source, sa.getParamOrDefault("MinCharmNum", "1"), sa) : num; + final int min = sa.hasParam("MinCharmNum") ? AbilityUtils.calculateAmount(source, sa.getParam("MinCharmNum"), sa) : num; boolean repeat = sa.hasParam("CanRepeatModes"); boolean random = sa.hasParam("Random"); @@ -120,7 +120,7 @@ public class CharmEffect extends SpellAbilityEffect { } if (additionalDesc) { - String addDescS = (sa.getParam("AdditionalDescription")); + String addDescS = sa.getParam("AdditionalDescription"); if (optional) { sb.append(". ").append(addDescS.trim()); } else if (addDescS.startsWith(("."))) { diff --git a/forge-game/src/main/java/forge/game/ability/effects/CopySpellAbilityEffect.java b/forge-game/src/main/java/forge/game/ability/effects/CopySpellAbilityEffect.java index 414e344ef6f..0af8297d976 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/CopySpellAbilityEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/CopySpellAbilityEffect.java @@ -132,7 +132,7 @@ public class CopySpellAbilityEffect extends SpellAbilityEffect { } } } - valid = CardLists.getValidCards(valid, type.split(","), chosenSA.getActivatingPlayer(), chosenSA.getHostCard(), sa); + valid = CardLists.getValidCards(valid, type, chosenSA.getActivatingPlayer(), chosenSA.getHostCard(), sa); Card originalTarget = Iterables.getFirst(getTargetCards(chosenSA), null); valid.remove(originalTarget); diff --git a/forge-game/src/main/java/forge/game/ability/effects/DigEffect.java b/forge-game/src/main/java/forge/game/ability/effects/DigEffect.java index bce2405944c..5706e0f1732 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/DigEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/DigEffect.java @@ -209,7 +209,7 @@ public class DigEffect extends SpellAbilityEffect { if (changeValid.contains("ChosenType")) { changeValid = changeValid.replace("ChosenType", host.getChosenType()); } - valid = CardLists.getValidCards(top, changeValid.split(","), cont, host, sa); + valid = CardLists.getValidCards(top, changeValid, cont, host, sa); if (totalCMC) { valid = CardLists.getValidCards(valid, "Card.cmcLE" + totcmc, cont, host, sa); } diff --git a/forge-game/src/main/java/forge/game/ability/effects/DiscardEffect.java b/forge-game/src/main/java/forge/game/ability/effects/DiscardEffect.java index e07b925e3ff..f9b9160b9e1 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/DiscardEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/DiscardEffect.java @@ -233,7 +233,7 @@ public class DiscardEffect extends SpellAbilityEffect { "X", Integer.toString(AbilityUtils.calculateAmount(source, "X", sa))); } - toBeDiscarded = CardLists.getValidCards(dPHand, valid.split(","), source.getController(), source, sa); + toBeDiscarded = CardLists.getValidCards(dPHand, valid, source.getController(), source, sa); toBeDiscarded = CardLists.filter(toBeDiscarded, Presets.NON_TOKEN); if (toBeDiscarded.size() > 1) { toBeDiscarded = GameActionUtil.orderCardsByTheirOwners(game, toBeDiscarded, ZoneType.Graveyard, sa); @@ -250,8 +250,7 @@ public class DiscardEffect extends SpellAbilityEffect { } final String valid = sa.getParamOrDefault("DiscardValid", "Card"); - String[] dValid = valid.split(","); - CardCollection validCards = CardLists.getValidCards(dPHand, dValid, source.getController(), source, sa); + CardCollection validCards = CardLists.getValidCards(dPHand, valid, source.getController(), source, sa); Player chooser = p; if (mode.equals("RevealYouChoose")) { diff --git a/forge-game/src/main/java/forge/game/ability/effects/RegenerateAllEffect.java b/forge-game/src/main/java/forge/game/ability/effects/RegenerateAllEffect.java index f936eb2e662..16587a8e95b 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/RegenerateAllEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/RegenerateAllEffect.java @@ -29,7 +29,7 @@ public class RegenerateAllEffect extends RegenerateBaseEffect { final String valid = sa.getParamOrDefault("ValidCards", ""); CardCollectionView list = game.getCardsIn(ZoneType.Battlefield); - list = CardLists.getValidCards(list, valid.split(","), hostCard.getController(), hostCard, sa); + list = CardLists.getValidCards(list, valid, hostCard.getController(), hostCard, sa); // create Effect for Regeneration createRegenerationEffect(sa, list); diff --git a/forge-game/src/main/java/forge/game/ability/effects/RepeatEffect.java b/forge-game/src/main/java/forge/game/ability/effects/RepeatEffect.java index 43cb3018645..ede9d56820f 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/RepeatEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/RepeatEffect.java @@ -84,7 +84,7 @@ public class RepeatEffect extends SpellAbilityEffect { } else { list = game.getCardsIn(ZoneType.Battlefield); } - list = CardLists.getValidCards(list, repeatPresent.split(","), sa.getActivatingPlayer(), sa.getHostCard(), sa); + list = CardLists.getValidCards(list, repeatPresent, sa.getActivatingPlayer(), sa.getHostCard(), sa); final String rightString = repeatCompare.substring(2); int right = AbilityUtils.calculateAmount(sa.getHostCard(), rightString, sa); diff --git a/forge-game/src/main/java/forge/game/ability/effects/UnattachAllEffect.java b/forge-game/src/main/java/forge/game/ability/effects/UnattachAllEffect.java index e3f834be626..c25aae259d5 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/UnattachAllEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/UnattachAllEffect.java @@ -140,7 +140,7 @@ public class UnattachAllEffect extends SpellAbilityEffect { String valid = sa.getParam("UnattachValid"); CardCollectionView unattachList = game.getCardsIn(ZoneType.Battlefield); - unattachList = CardLists.getValidCards(unattachList, valid.split(","), source.getController(), source, sa); + unattachList = CardLists.getValidCards(unattachList, valid, source.getController(), source, sa); for (final Card c : unattachList) { handleUnattachment((GameEntity) o, c); } diff --git a/forge-game/src/main/java/forge/game/ability/effects/UntapAllEffect.java b/forge-game/src/main/java/forge/game/ability/effects/UntapAllEffect.java index 89e5ec12511..da5a181d6b7 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/UntapAllEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/UntapAllEffect.java @@ -37,7 +37,7 @@ public class UntapAllEffect extends SpellAbilityEffect { } list = list2; } - list = CardLists.getValidCards(list, valid.split(","), card.getController(), card, sa); + list = CardLists.getValidCards(list, valid, card.getController(), card, sa); boolean remember = sa.hasParam("RememberUntapped"); for (Card c : list) { diff --git a/forge-game/src/main/java/forge/game/card/CardZoneTable.java b/forge-game/src/main/java/forge/game/card/CardZoneTable.java index cea052c8179..f0e9bb8d387 100644 --- a/forge-game/src/main/java/forge/game/card/CardZoneTable.java +++ b/forge-game/src/main/java/forge/game/card/CardZoneTable.java @@ -90,7 +90,7 @@ public class CardZoneTable extends ForwardingTable defenders = new FCollection<>(); for (Card attacker : attackers) { defenders.add(attacker.getGame().getCombat().getDefenderByAttacker(attacker)); diff --git a/forge-game/src/main/java/forge/game/trigger/TriggerDrawn.java b/forge-game/src/main/java/forge/game/trigger/TriggerDrawn.java index 62d7b2f7f6d..02d7abc6527 100644 --- a/forge-game/src/main/java/forge/game/trigger/TriggerDrawn.java +++ b/forge-game/src/main/java/forge/game/trigger/TriggerDrawn.java @@ -74,7 +74,7 @@ public class TriggerDrawn extends Trigger { final String sIsPresent = this.getParam("ValidPlayerControls"); final Player p = ((Player)runParams.get(AbilityKey.Player)); CardCollection list = (CardCollection) p.getCardsIn(ZoneType.Battlefield); - list = CardLists.getValidCards(list, sIsPresent.split(","), this.getHostCard().getController(), + list = CardLists.getValidCards(list, sIsPresent, this.getHostCard().getController(), this.getHostCard(), this); if (list.size() == 0) { return false; diff --git a/forge-game/src/main/java/forge/game/trigger/TriggerLifeGained.java b/forge-game/src/main/java/forge/game/trigger/TriggerLifeGained.java index e46e0ab46e9..fe7d4d23a29 100644 --- a/forge-game/src/main/java/forge/game/trigger/TriggerLifeGained.java +++ b/forge-game/src/main/java/forge/game/trigger/TriggerLifeGained.java @@ -64,7 +64,7 @@ public class TriggerLifeGained extends Trigger { final String sIsPresent = this.getParam("ValidPlayerControls"); final Player p = ((Player)runParams.get(AbilityKey.Player)); CardCollection list = (CardCollection) p.getCardsIn(ZoneType.Battlefield); - list = CardLists.getValidCards(list, sIsPresent.split(","), this.getHostCard().getController(), + list = CardLists.getValidCards(list, sIsPresent, this.getHostCard().getController(), this.getHostCard(), this); if (list.size() == 0) { return false; From d7749303fdb4940c91ad64611b869eb70edc9fc1 Mon Sep 17 00:00:00 2001 From: Tim Mocny Date: Thu, 10 Feb 2022 12:58:20 +0000 Subject: [PATCH 09/10] Improve support for "Tap another untapped X" costs --- .../src/main/java/forge/game/cost/Cost.java | 10 +++++-- .../main/java/forge/game/cost/CostPart.java | 7 ++++- .../java/forge/game/cost/CostTapType.java | 28 ++++++++++++++----- .../res/cardsfolder/b/black_oak_of_odunos.txt | 2 +- .../cardsfolder/k/kumena_tyrant_of_orazca.txt | 7 +++-- .../cardsfolder/r/radiant_serra_archangel.txt | 2 +- forge-gui/res/cardsfolder/r/rangers_hawk.txt | 2 +- .../res/cardsfolder/s/shadow_stinger.txt | 2 +- .../cardsfolder/s/sure_footed_infiltrator.txt | 2 +- .../res/cardsfolder/v/veteran_warleader.txt | 2 +- 10 files changed, 45 insertions(+), 19 deletions(-) diff --git a/forge-game/src/main/java/forge/game/cost/Cost.java b/forge-game/src/main/java/forge/game/cost/Cost.java index a17246b9711..40fde192e8e 100644 --- a/forge-game/src/main/java/forge/game/cost/Cost.java +++ b/forge-game/src/main/java/forge/game/cost/Cost.java @@ -22,6 +22,7 @@ import java.util.Collections; import java.util.Comparator; import java.util.List; +import forge.card.CardType; import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; @@ -834,9 +835,14 @@ public class Cost implements Serializable { sb.append(Cost.NUM_NAMES[i]); } - sb.append(" ").append(type); + sb.append(" "); if (1 != i) { - sb.append("s"); + String [] typewords = type.split(" "); + String lastWord = typewords[typewords.length - 1]; + sb.append(CardType.isASubType(lastWord) ? type.replace(lastWord, CardType.getPluralType(lastWord)) + : type + "s"); + } else { + sb.append(type); } return sb.toString(); diff --git a/forge-game/src/main/java/forge/game/cost/CostPart.java b/forge-game/src/main/java/forge/game/cost/CostPart.java index b5a4ef19db9..b2482b2a73f 100644 --- a/forge-game/src/main/java/forge/game/cost/CostPart.java +++ b/forge-game/src/main/java/forge/game/cost/CostPart.java @@ -19,6 +19,7 @@ package forge.game.cost; import java.io.Serializable; +import forge.card.CardType; import org.apache.commons.lang3.StringUtils; import forge.game.CardTraitBase; @@ -106,7 +107,11 @@ public abstract class CostPart implements Comparable, Cloneable, Seria public final String getDescriptiveType() { String typeDesc = this.getTypeDescription(); - return typeDesc == null ? this.getType() : typeDesc; + if (typeDesc == null) { + String typeS = this.getType(); + typeDesc = CardType.CoreType.isValidEnum(typeS) ? typeS.toLowerCase() : typeS; + } + return typeDesc; } /** diff --git a/forge-game/src/main/java/forge/game/cost/CostTapType.java b/forge-game/src/main/java/forge/game/cost/CostTapType.java index ea8eda92a58..44c7e80982e 100644 --- a/forge-game/src/main/java/forge/game/cost/CostTapType.java +++ b/forge-game/src/main/java/forge/game/cost/CostTapType.java @@ -17,6 +17,7 @@ */ package forge.game.cost; +import forge.card.CardType; import forge.game.card.Card; import forge.game.card.CardCollection; import forge.game.card.CardLists; @@ -87,20 +88,33 @@ public class CostTapType extends CostPartWithList { @Override public final String toString() { final StringBuilder sb = new StringBuilder(); - sb.append("Tap "); final Integer i = this.convertAmount(); final String desc = this.getDescriptiveType(); final String type = this.getType(); - + + if (type.contains("+withTotalPowerGE")) { + String num = type.split("\\+withTotalPowerGE")[1]; + sb.append("Tap any number of untapped creatures you control other than CARDNAME with total power "); + sb.append(num).append("or greater"); + return sb.toString(); + } + + sb.append("Tap "); if (type.contains("sharesCreatureTypeWith")) { sb.append("two untapped creatures you control that share a creature type"); - } else if (type.contains("+withTotalPowerGE")) { - String num = type.split("\\+withTotalPowerGE")[1]; - sb.append("Tap any number of untapped creatures you control other than CARDNAME with total power ").append(num).append("or greater"); + } else if (type.contains("Other")) { + String rep = type.contains(".Other") ? ".Other" : "+Other"; + String descTrim = desc.replace(rep, ""); + if (CardType.CoreType.isValidEnum(descTrim)) { + descTrim = descTrim.toLowerCase(); + } + sb.append("another untapped ").append(descTrim); + if (!descTrim.contains("you control")) { + sb.append(" you control"); + } } else { - sb.append(Cost.convertAmountTypeToWords(i, this.getAmount(), "untapped " + desc)); - sb.append(" you control"); + sb.append(Cost.convertAmountTypeToWords(i, this.getAmount(), "untapped " + desc)).append(" you control"); } return sb.toString(); } diff --git a/forge-gui/res/cardsfolder/b/black_oak_of_odunos.txt b/forge-gui/res/cardsfolder/b/black_oak_of_odunos.txt index 643edf4d5da..a8f88851f5c 100644 --- a/forge-gui/res/cardsfolder/b/black_oak_of_odunos.txt +++ b/forge-gui/res/cardsfolder/b/black_oak_of_odunos.txt @@ -3,5 +3,5 @@ ManaCost:2 B Types:Creature Zombie Treefolk PT:0/5 K:Defender -A:AB$ Pump | Cost$ B tapXType<1/Creature.Other/another creature> | CostDesc$ {B}, Tap another untapped creature you control: | Defined$ Self | NumAtt$ +1 | NumDef$ +1 | SpellDescription$ CARDNAME gets +1/+1 until end of turn. +A:AB$ Pump | Cost$ B tapXType<1/Creature.Other> | Defined$ Self | NumAtt$ +1 | NumDef$ +1 | SpellDescription$ CARDNAME gets +1/+1 until end of turn. Oracle:Defender\n{B}, Tap another untapped creature you control: Black Oak of Odunos gets +1/+1 until end of turn. diff --git a/forge-gui/res/cardsfolder/k/kumena_tyrant_of_orazca.txt b/forge-gui/res/cardsfolder/k/kumena_tyrant_of_orazca.txt index 3b59592d351..9c4cd3ceb58 100644 --- a/forge-gui/res/cardsfolder/k/kumena_tyrant_of_orazca.txt +++ b/forge-gui/res/cardsfolder/k/kumena_tyrant_of_orazca.txt @@ -2,9 +2,10 @@ Name:Kumena, Tyrant of Orazca ManaCost:1 G U Types:Legendary Creature Merfolk Shaman PT:2/4 -A:AB$ Pump | Cost$ tapXType<1/Merfolk.Other> | CostDesc$ Tap another untapped Merfolk you control: | Defined$ Self | KW$ HIDDEN Unblockable | AILogic$ BeforeCombat | SpellDescription$ CARDNAME can't be blocked this turn. -A:AB$ Draw | Cost$ tapXType<3/Merfolk> | CostDesc$ Tap three untapped Merfolk you control: | NumCards$ 1 | AILogic$ AtOppEOT | SpellDescription$ Draw a card. -A:AB$ PutCounterAll | Cost$ tapXType<5/Merfolk> | CostDesc$ Tap five untapped Merfolk you control: | ValidCards$ Merfolk.YouCtrl | CounterType$ P1P1 | CounterNum$ 1 | AILogic$ AtOppEOT | SpellDescription$ Put a +1/+1 counter on each Merfolk you control. +A:AB$ Pump | Cost$ tapXType<1/Merfolk.Other> | Defined$ Self | KW$ HIDDEN Unblockable | AILogic$ BeforeCombat | SpellDescription$ CARDNAME can't be blocked this turn. +A:AB$ Draw | Cost$ tapXType<3/Merfolk> | NumCards$ 1 | AILogic$ AtOppEOT | SpellDescription$ Draw a card. +A:AB$ PutCounterAll | Cost$ tapXType<5/Merfolk> | ValidCards$ Merfolk.YouCtrl | CounterType$ P1P1 | CounterNum$ 1 | AILogic$ AtOppEOT | SpellDescription$ Put a +1/+1 counter on each Merfolk you control. DeckHints:Type$Merfolk SVar:BuffedBy:Merfolk +DeckHas:Ability$Counters Oracle:Tap another untapped Merfolk you control: Kumena, Tyrant of Orazca can't be blocked this turn.\nTap three untapped Merfolk you control: Draw a card.\nTap five untapped Merfolk you control: Put a +1/+1 counter on each Merfolk you control. diff --git a/forge-gui/res/cardsfolder/r/radiant_serra_archangel.txt b/forge-gui/res/cardsfolder/r/radiant_serra_archangel.txt index 3bdacf1b69a..bfb04b92f43 100644 --- a/forge-gui/res/cardsfolder/r/radiant_serra_archangel.txt +++ b/forge-gui/res/cardsfolder/r/radiant_serra_archangel.txt @@ -4,7 +4,7 @@ Types:Legendary Creature Angel PT:6/4 K:Flying K:Partner -A:AB$ Protection | Cost$ tapXType<1/Creature.untapped+withFlying+Other/another creature you control> | CostDesc$ Tap another untapped creature you control with flying: | Gains$ Choice | Choices$ AnyColor | SpellDescription$ CARDNAME gains protection from the color of your choice until end of turn. +A:AB$ Protection | Cost$ tapXType<1/Creature.withFlying+Other/creature you control with flying> | Gains$ Choice | Choices$ AnyColor | SpellDescription$ CARDNAME gains protection from the color of your choice until end of turn. SVar:BuffedBy:Creature.withFlying DeckNeeds:Keyword$Flying Oracle:Flying\nTap another untapped creature you control with flying: Radiant, Serra Archangel gains protection from the color of your choice until end of turn.\nPartner (You can have two commanders if both have partner.) diff --git a/forge-gui/res/cardsfolder/r/rangers_hawk.txt b/forge-gui/res/cardsfolder/r/rangers_hawk.txt index cd4b90c5194..0475d998cb6 100644 --- a/forge-gui/res/cardsfolder/r/rangers_hawk.txt +++ b/forge-gui/res/cardsfolder/r/rangers_hawk.txt @@ -3,5 +3,5 @@ ManaCost:W Types:Creature Bird PT:1/1 K:Flying -A:AB$ Venture | Cost$ 3 T tapXType<1/Creature/creature> | SorcerySpeed$ True | SpellDescription$ Venture into the dungeon. Activate only as a sorcery. (Enter the first room or advance to the next room.) +A:AB$ Venture | Cost$ 3 T tapXType<1/Creature.Other> | SorcerySpeed$ True | SpellDescription$ Venture into the dungeon. Activate only as a sorcery. (Enter the first room or advance to the next room.) Oracle:Flying\n{3}, {T}, Tap another untapped creature you control: Venture into the dungeon. Activate only as a sorcery. (Enter the first room or advance to the next room.) diff --git a/forge-gui/res/cardsfolder/s/shadow_stinger.txt b/forge-gui/res/cardsfolder/s/shadow_stinger.txt index 8f72805b53e..40abf26ec0a 100644 --- a/forge-gui/res/cardsfolder/s/shadow_stinger.txt +++ b/forge-gui/res/cardsfolder/s/shadow_stinger.txt @@ -2,7 +2,7 @@ Name:Shadow Stinger ManaCost:2 B Types:Creature Vampire Rogue PT:1/4 -A:AB$ Pump | Cost$ tapXType<1/Rogue> | CostDesc$ Tap another untapped Rogue you control: | Defined$ Self | KW$ Deathtouch | SpellDescription$ CARDNAME gains deathtouch until end of turn. +A:AB$ Pump | Cost$ tapXType<1/Rogue> | Defined$ Self | KW$ Deathtouch | SpellDescription$ CARDNAME gains deathtouch until end of turn. T:Mode$ DamageDone | ValidSource$ Card.Self | ValidTarget$ Player | CombatDamage$ True | Execute$ TrigMill | TriggerZones$ Battlefield | TriggerDescription$ Whenever CARDNAME deals combat damage to a player, that player mills three cards. (They put the top three cards of their library into their graveyard.) SVar:TrigMill:DB$ Mill | Defined$ TriggeredTarget | NumCards$ 3 DeckHas:Ability$Mill diff --git a/forge-gui/res/cardsfolder/s/sure_footed_infiltrator.txt b/forge-gui/res/cardsfolder/s/sure_footed_infiltrator.txt index 0e20a4aa378..2a372b10d3a 100644 --- a/forge-gui/res/cardsfolder/s/sure_footed_infiltrator.txt +++ b/forge-gui/res/cardsfolder/s/sure_footed_infiltrator.txt @@ -2,7 +2,7 @@ Name:Sure-Footed Infiltrator ManaCost:3 U Types:Creature Merfolk Rogue PT:2/3 -A:AB$ Pump | Cost$ tapXType<1/Rogue.Other> | CostDesc$ Tap another untapped Rogue you control: | Defined$ Self | KW$ HIDDEN Unblockable | StackDescription$ SpellDescription | SpellDescription$ CARDNAME can't be blocked this turn. +A:AB$ Pump | Cost$ tapXType<1/Rogue.Other> | Defined$ Self | KW$ HIDDEN Unblockable | StackDescription$ SpellDescription | SpellDescription$ CARDNAME can't be blocked this turn. T:Mode$ DamageDone | ValidSource$ Card.Self | ValidTarget$ Player | CombatDamage$ True | Execute$ TrigDraw | TriggerDescription$ Whenever CARDNAME deals combat damage to a player, draw a card. SVar:TrigDraw:DB$ Draw | Defined$ You | NumCards$ 1 DeckNeeds:Type$Rogue diff --git a/forge-gui/res/cardsfolder/v/veteran_warleader.txt b/forge-gui/res/cardsfolder/v/veteran_warleader.txt index 969b2e0c112..9f2b1eb80cb 100644 --- a/forge-gui/res/cardsfolder/v/veteran_warleader.txt +++ b/forge-gui/res/cardsfolder/v/veteran_warleader.txt @@ -4,7 +4,7 @@ Types:Creature Human Soldier Ally PT:*/* S:Mode$ Continuous | EffectZone$ All | CharacteristicDefining$ True | SetPower$ X | SetToughness$ X | Description$ CARDNAME's power and toughness are each equal to the number of creatures you control. SVar:X:Count$Valid Creature.YouCtrl -A:AB$ GenericChoice | Cost$ tapXType<1/Ally.Other> | CostDesc$ Tap another untapped Ally you control: | Choices$ ChooseFirstStrike,ChooseVigilance,ChooseTrample | SpellDescription$ CARDNAME gains your choice of first strike, vigilance, or trample until end of turn. +A:AB$ GenericChoice | Cost$ tapXType<1/Ally.Other> | Choices$ ChooseFirstStrike,ChooseVigilance,ChooseTrample | SpellDescription$ CARDNAME gains your choice of first strike, vigilance, or trample until end of turn. SVar:ChooseFirstStrike:DB$ Pump | Defined$ Self | KW$ First Strike | SpellDescription$ CARDNAME gains first strike until end of turn. SVar:ChooseVigilance:DB$ Pump | Defined$ Self | KW$ Vigilance | SpellDescription$ CARDNAME gains vigilance until end of turn. SVar:ChooseTrample:DB$ Pump | Defined$ Self | KW$ Trample | SpellDescription$ CARDNAME gains trample until end of turn. From 4569439385a44ded234abc724318589bac844ac4 Mon Sep 17 00:00:00 2001 From: Bug Hunter Date: Thu, 10 Feb 2022 12:58:47 +0000 Subject: [PATCH 10/10] Miracle is optional --- .../main/java/forge/ai/AiAttackController.java | 11 ++++++++--- .../main/java/forge/ai/AiBlockController.java | 14 ++++++-------- .../src/main/java/forge/ai/ComputerUtil.java | 2 -- .../main/java/forge/ai/ComputerUtilCombat.java | 2 +- .../forge/game/ability/effects/PlayEffect.java | 17 +++++++++++++++-- .../java/forge/game/card/CardFactoryUtil.java | 2 +- forge-gui/res/cardsfolder/d/dazzling_sphinx.txt | 2 +- .../java/forge/player/HumanCostDecision.java | 3 ++- .../forge/player/PlayerControllerHuman.java | 2 +- 9 files changed, 35 insertions(+), 20 deletions(-) diff --git a/forge-ai/src/main/java/forge/ai/AiAttackController.java b/forge-ai/src/main/java/forge/ai/AiAttackController.java index fa600d9bbce..9d6623523b0 100644 --- a/forge-ai/src/main/java/forge/ai/AiAttackController.java +++ b/forge-ai/src/main/java/forge/ai/AiAttackController.java @@ -153,10 +153,15 @@ public class AiAttackController { } } - /** Choose opponent for AI to attack here. Expand as necessary. */ + /** + * Choose opponent for AI to attack here. Expand as necessary. + * No strategy to secure a second place instead, since Forge has no variant for that + */ public static Player choosePreferredDefenderPlayer(Player ai) { Player defender = ai.getWeakestOpponent(); //Concentrate on opponent within easy kill range + // TODO connect with evaluateBoardPosition and only fall back to random when no player is the biggest threat by a fair margin + if (defender.getLife() > 8) { //Otherwise choose a random opponent to ensure no ganging up on players // TODO should we cache the random for each turn? some functions like shouldPumpCard base their decisions on the assumption who will be attacked return ai.getOpponents().get(MyRandom.getRandom().nextInt(ai.getOpponents().size())); @@ -720,7 +725,7 @@ public class AiAttackController { continue; } boolean mustAttack = false; - // TODO for nextTurn check if it was temporary + // TODO this might result into attacking the wrong player if (attacker.isGoaded()) { mustAttack = true; } else if (attacker.getSVar("MustAttack").equals("True")) { @@ -737,7 +742,7 @@ public class AiAttackController { mustAttack = true; } } - if (mustAttack || (attacker.getController().getMustAttackEntity() != null && nextTurn) || (attacker.getController().getMustAttackEntityThisTurn() != null && !nextTurn)) { + if (mustAttack ||attacker.getController().getMustAttackEntityThisTurn() != null) { combat.addAttacker(attacker, defender); attackersLeft.remove(attacker); numForcedAttackers++; diff --git a/forge-ai/src/main/java/forge/ai/AiBlockController.java b/forge-ai/src/main/java/forge/ai/AiBlockController.java index 5f37ddf5716..83dac98417b 100644 --- a/forge-ai/src/main/java/forge/ai/AiBlockController.java +++ b/forge-ai/src/main/java/forge/ai/AiBlockController.java @@ -426,7 +426,6 @@ public class AiBlockController { } attackersLeft = new ArrayList<>(currentAttackers); - currentAttackers = new ArrayList<>(attackersLeft); boolean considerTripleBlock = true; @@ -437,6 +436,11 @@ public class AiBlockController { continue; } + // AI can't handle good blocks with more than three creatures yet + if (CombatUtil.getMinNumBlockersForAttacker(attacker, ai) > (considerTripleBlock ? 3 : 2)) { + continue; + } + int evalAttackerValue = ComputerUtilCard.evaluateCreature(attacker); blockers = getPossibleBlockers(combat, attacker, blockersLeft, false); @@ -446,11 +450,6 @@ public class AiBlockController { int currentValue; // The value of the creatures in the blockgang boolean foundDoubleBlock = false; // if true, a good double block is found - // AI can't handle good blocks with more than three creatures yet - if (CombatUtil.getMinNumBlockersForAttacker(attacker, ai) > (considerTripleBlock ? 3 : 2)) { - continue; - } - // Try to add blockers that could be destroyed, but are worth less than the attacker // Don't use blockers without First Strike or Double Strike if attacker has it usableBlockers = CardLists.filter(blockers, new Predicate() { @@ -460,8 +459,7 @@ public class AiBlockController { && !ComputerUtilCombat.dealsFirstStrikeDamage(c, false, combat)) { return false; } - final boolean randomTrade = wouldLikeToRandomlyTrade(attacker, c, combat); - return lifeInDanger || randomTrade || ComputerUtilCard.evaluateCreature(c) + diff < ComputerUtilCard.evaluateCreature(attacker); + return lifeInDanger || wouldLikeToRandomlyTrade(attacker, c, combat) || ComputerUtilCard.evaluateCreature(c) + diff < ComputerUtilCard.evaluateCreature(attacker); } }); if (usableBlockers.size() < 2) { diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtil.java b/forge-ai/src/main/java/forge/ai/ComputerUtil.java index c144ca7c8ed..82a8255f8a3 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtil.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtil.java @@ -2822,8 +2822,6 @@ public class ComputerUtil { pRating /= 5; } - System.out.println("Board position evaluation for " + p + ": " + pRating); - if (pRating > bestBoardRating) { bestBoardRating = pRating; bestBoardPosition = p; diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java b/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java index acb2554c4de..5131f6968b6 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java @@ -830,7 +830,7 @@ public class ComputerUtilCombat { } // defender == null means unblocked - if ((defender == null) && mode == TriggerType.AttackerUnblocked) { + if (defender == null && mode == TriggerType.AttackerUnblocked) { willTrigger = true; if (!trigger.matchesValidParam("ValidCard", attacker)) { return false; diff --git a/forge-game/src/main/java/forge/game/ability/effects/PlayEffect.java b/forge-game/src/main/java/forge/game/ability/effects/PlayEffect.java index 23e4601b293..5befcfaad0b 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/PlayEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/PlayEffect.java @@ -25,6 +25,9 @@ import forge.game.card.Card; import forge.game.card.CardCollection; import forge.game.card.CardFactoryUtil; import forge.game.cost.Cost; +import forge.game.cost.CostDiscard; +import forge.game.cost.CostPart; +import forge.game.cost.CostReveal; import forge.game.mana.ManaCostBeingPaid; import forge.game.player.Player; import forge.game.replacement.ReplacementEffect; @@ -70,7 +73,7 @@ public class PlayEffect extends SpellAbilityEffect { Player controlledByPlayer = null; long controlledByTimeStamp = -1; final Game game = activator.getGame(); - final boolean optional = sa.hasParam("Optional"); + boolean optional = sa.hasParam("Optional"); boolean remember = sa.hasParam("RememberPlayed"); int amount = 1; boolean hasTotalCMCLimit = sa.hasParam("WithTotalCMC"); @@ -330,7 +333,17 @@ public class PlayEffect extends SpellAbilityEffect { } if (!optional) { - tgtSA.getPayCosts().setMandatory(true); + // 118.8c + for (CostPart cost : tgtSA.getPayCosts().getCostParts()) { + if ((cost instanceof CostDiscard || cost instanceof CostReveal) + && !cost.getType().equals("Card") && !cost.getType().equals("Random")) { + optional = true; + break; + } + } + if (!optional) { + tgtSA.getPayCosts().setMandatory(true); + } } if (sa.hasParam("PlayReduceCost")) { 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 45f5429b828..014f784fdd6 100644 --- a/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java +++ b/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java @@ -1452,7 +1452,7 @@ public class CardFactoryUtil { final String manacost = k[1]; final String abStrReveal = "DB$ Reveal | Defined$ You | RevealDefined$ Self" + " | MiracleCost$ " + manacost; - final String abStrPlay = "DB$ Play | Defined$ Self | PlayCost$ " + manacost; + final String abStrPlay = "DB$ Play | Defined$ Self | Optional$ True | PlayCost$ " + manacost; String revealed = "DB$ ImmediateTrigger | TriggerDescription$ CARDNAME - Miracle"; diff --git a/forge-gui/res/cardsfolder/d/dazzling_sphinx.txt b/forge-gui/res/cardsfolder/d/dazzling_sphinx.txt index 09ba0184963..b8e017aaa3d 100644 --- a/forge-gui/res/cardsfolder/d/dazzling_sphinx.txt +++ b/forge-gui/res/cardsfolder/d/dazzling_sphinx.txt @@ -6,6 +6,6 @@ K:Flying T:Mode$ DamageDone | ValidSource$ Card.Self | ValidTarget$ Player | CombatDamage$ True | Execute$ TrigDigUntil | TriggerZones$ Battlefield | TriggerDescription$ Whenever CARDNAME deals combat damage to a player, that player exiles cards from the top of their library until they exile an instant or sorcery card. You may cast that card without paying its mana cost. Then that player puts the exiled cards that weren't cast this way on the bottom of their library in a random order. SVar:TrigDigUntil:DB$ DigUntil | Defined$ TriggeredTarget | Valid$ Instant,Sorcery | ValidDescription$ instant or sorcery | FoundDestination$ Exile | RevealedDestination$ Exile | RememberFound$ True | RememberRevealed$ True | IsCurse$ True | SubAbility$ DBPlay | SpellDescription$ Whenever CARDNAME deals combat damage to a player, that player exiles cards from the top of their library until they exile an instant or sorcery card. You may cast that card without paying its mana cost. Then that player puts the exiled cards that weren't cast this way on the bottom of their library in a random order. SVar:DBPlay:DB$ Play | Defined$ Remembered | ValidZone$ Exile | Valid$ Instant.IsRemembered,Sorcery.IsRemembered | ValidSA$ Spell | WithoutManaCost$ True | RememberObjects$ Remembered | Optional$ True | ForgetTargetRemembered$ True | SubAbility$ DBRestRandomOrder -SVar:DBRestRandomOrder:DB$ ChangeZoneAll | ChangeType$ Card.IsRemembered | Origin$ Library | Destination$ Library | LibraryPosition$ -1 | RandomOrder$ True | SubAbility$ DBCleanup +SVar:DBRestRandomOrder:DB$ ChangeZoneAll | ChangeType$ Card.IsRemembered | Origin$ Exile | Destination$ Library | LibraryPosition$ -1 | RandomOrder$ True | SubAbility$ DBCleanup SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True Oracle:Flying\nWhenever Dazzling Sphinx deals combat damage to a player, that player exiles cards from the top of their library until they exile an instant or sorcery card. You may cast that card without paying its mana cost. Then that player puts the exiled cards that weren't cast this way on the bottom of their library in a random order. diff --git a/forge-gui/src/main/java/forge/player/HumanCostDecision.java b/forge-gui/src/main/java/forge/player/HumanCostDecision.java index 58e3e5e5919..54886c64129 100644 --- a/forge-gui/src/main/java/forge/player/HumanCostDecision.java +++ b/forge-gui/src/main/java/forge/player/HumanCostDecision.java @@ -760,7 +760,8 @@ public class HumanCostDecision extends CostDecisionMakerBase { if (num == 0) { return PaymentDecision.number(0); } - if (hand.size() == num) { + // player might not want to pay if from a trigger + if (!ability.hasSVar("IsCastFromPlayEffect") && hand.size() == num) { return PaymentDecision.card(hand); } diff --git a/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java b/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java index 39c02c9a9bb..1a7b26f5b8f 100644 --- a/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java +++ b/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java @@ -472,7 +472,7 @@ public class PlayerControllerHuman extends PlayerController implements IGameCont return null; } - String announceTitle = ("X".equals(announce)) ? announce : ability.getParamOrDefault("AnnounceTitle", announce); + String announceTitle = "X".equals(announce) ? announce : ability.getParamOrDefault("AnnounceTitle", announce); if (cost.isMandatory()) { return chooseNumber(ability, localizer.getMessage("lblChooseAnnounceForCard", announceTitle, CardTranslation.getTranslatedName(ability.getHostCard().getName())) , min, max);