From d327e789c120066b8320ca0c2c49fc6a1de10176 Mon Sep 17 00:00:00 2001 From: Churrufli Date: Fri, 10 Sep 2021 20:41:21 +0200 Subject: [PATCH 01/58] Net Decks Archive Updates --- .../res/lists/net-decks-archive-block.txt | 2 ++ .../res/lists/net-decks-archive-legacy.txt | 21 +++++++++++++ .../res/lists/net-decks-archive-modern.txt | 31 +++++++++++++++++++ .../res/lists/net-decks-archive-pauper.txt | 12 +++++++ .../res/lists/net-decks-archive-pioneer.txt | 25 +++++++++++++++ .../res/lists/net-decks-archive-standard.txt | 13 ++++++++ .../res/lists/net-decks-archive-vintage.txt | 17 ++++++++++ 7 files changed, 121 insertions(+) diff --git a/forge-gui/res/lists/net-decks-archive-block.txt b/forge-gui/res/lists/net-decks-archive-block.txt index e04e0aab3d7..776dd2ba03d 100644 --- a/forge-gui/res/lists/net-decks-archive-block.txt +++ b/forge-gui/res/lists/net-decks-archive-block.txt @@ -974,3 +974,5 @@ 2021-06-27 Sealed Mh2 Block Champs (8 decks) | https://downloads.cardforge.org/decks/archive/block/2021-06-27-sealed-mh2-block-champs.zip 2021-07-25 Sealed Mh2 Block Super Qualifier (8 decks) | https://downloads.cardforge.org/decks/archive/block/2021-07-25-sealed-mh2-block-super-qualifier.zip 2021-08-15 Sealed Mh2 Block Super Qualifier (8 decks) | https://downloads.cardforge.org/decks/archive/block/2021-08-15-sealed-mh2-block-super-qualifier.zip +2021-08-29 Sealed Mh2 Block Mocs (8 decks) | https://downloads.cardforge.org/decks/archive/block/2021-08-29-sealed-mh2-block-mocs.zip +2021-08-30 Sealed Mh2 Block Mocs (8 decks) | https://downloads.cardforge.org/decks/archive/block/2021-08-30-sealed-mh2-block-mocs.zip diff --git a/forge-gui/res/lists/net-decks-archive-legacy.txt b/forge-gui/res/lists/net-decks-archive-legacy.txt index a06c93ff292..4d7092db8c7 100644 --- a/forge-gui/res/lists/net-decks-archive-legacy.txt +++ b/forge-gui/res/lists/net-decks-archive-legacy.txt @@ -2088,3 +2088,24 @@ 2021-08-15 Legacy Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/legacy/2021-08-15-legacy-challenge.zip 2021-08-16 Legacy Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/legacy/2021-08-16-legacy-challenge.zip 2021-08-17 Legacy Preliminary (6 decks) | https://downloads.cardforge.org/decks/archive/legacy/2021-08-17-legacy-preliminary.zip +2021-08-20 Legacy Preliminary (5 decks) | https://downloads.cardforge.org/decks/archive/legacy/2021-08-20-legacy-preliminary.zip +2021-08-21 Legacy League (44 decks) | https://downloads.cardforge.org/decks/archive/legacy/2021-08-21-legacy-league.zip +2021-08-21 Legacy Preliminary (4 decks) | https://downloads.cardforge.org/decks/archive/legacy/2021-08-21-legacy-preliminary.zip +2021-08-22 Legacy Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/legacy/2021-08-22-legacy-challenge.zip +2021-08-23 Legacy Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/legacy/2021-08-23-legacy-challenge.zip +2021-08-24 Legacy Preliminary (5 decks) | https://downloads.cardforge.org/decks/archive/legacy/2021-08-24-legacy-preliminary.zip +2021-08-25 Legacy Preliminary (6 decks) | https://downloads.cardforge.org/decks/archive/legacy/2021-08-25-legacy-preliminary.zip +2021-08-28 Legacy League (53 decks) | https://downloads.cardforge.org/decks/archive/legacy/2021-08-28-legacy-league.zip +2021-08-28 Legacy Preliminary (6 decks) | https://downloads.cardforge.org/decks/archive/legacy/2021-08-28-legacy-preliminary.zip +2021-08-29 Legacy Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/legacy/2021-08-29-legacy-challenge.zip +2021-08-30 Legacy Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/legacy/2021-08-30-legacy-challenge.zip +2021-09-01 Legacy Preliminary (7 decks) | https://downloads.cardforge.org/decks/archive/legacy/2021-09-01-legacy-preliminary.zip +2021-09-02 Legacy Preliminary (4 decks) | https://downloads.cardforge.org/decks/archive/legacy/2021-09-02-legacy-preliminary.zip +2021-09-03 Legacy Preliminary (6 decks) | https://downloads.cardforge.org/decks/archive/legacy/2021-09-03-legacy-preliminary.zip +2021-09-04 Legacy League (52 decks) | https://downloads.cardforge.org/decks/archive/legacy/2021-09-04-legacy-league.zip +2021-09-04 Legacy Preliminary (9 decks) | https://downloads.cardforge.org/decks/archive/legacy/2021-09-04-legacy-preliminary.zip +2021-09-05 Legacy Super Qualifier (32 decks) | https://downloads.cardforge.org/decks/archive/legacy/2021-09-05-legacy-super-qualifier.zip +2021-09-06 Legacy Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/legacy/2021-09-06-legacy-challenge.zip +2021-09-07 Legacy Preliminary (6 decks) | https://downloads.cardforge.org/decks/archive/legacy/2021-09-07-legacy-preliminary.zip +2021-09-08 Legacy Preliminary (5 decks) | https://downloads.cardforge.org/decks/archive/legacy/2021-09-08-legacy-preliminary.zip +2021-09-09 Legacy Preliminary (4 decks) | https://downloads.cardforge.org/decks/archive/legacy/2021-09-09-legacy-preliminary.zip diff --git a/forge-gui/res/lists/net-decks-archive-modern.txt b/forge-gui/res/lists/net-decks-archive-modern.txt index 1362e311dc7..18adfba457b 100644 --- a/forge-gui/res/lists/net-decks-archive-modern.txt +++ b/forge-gui/res/lists/net-decks-archive-modern.txt @@ -2719,3 +2719,34 @@ 2021-08-16 Modern Super Qualifier (32 decks) | https://downloads.cardforge.org/decks/archive/modern/2021-08-16-modern-super-qualifier.zip 2021-08-17 Modern League (76 decks) | https://downloads.cardforge.org/decks/archive/modern/2021-08-17-modern-league.zip 2021-08-17 Modern Preliminary (9 decks) | https://downloads.cardforge.org/decks/archive/modern/2021-08-17-modern-preliminary.zip +2021-08-18 Modern Preliminary (8 decks) | https://downloads.cardforge.org/decks/archive/modern/2021-08-18-modern-preliminary.zip +2021-08-19 Modern Preliminary (8 decks) | https://downloads.cardforge.org/decks/archive/modern/2021-08-19-modern-preliminary.zip +2021-08-20 Modern League (64 decks) | https://downloads.cardforge.org/decks/archive/modern/2021-08-20-modern-league.zip +2021-08-20 Modern Preliminary (5 decks) | https://downloads.cardforge.org/decks/archive/modern/2021-08-20-modern-preliminary.zip +2021-08-21 Modern Preliminary (8 decks) | https://downloads.cardforge.org/decks/archive/modern/2021-08-21-modern-preliminary.zip +2021-08-22 Modern Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/modern/2021-08-22-modern-challenge.zip +2021-08-23 Modern Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/modern/2021-08-23-modern-challenge.zip +2021-08-24 Modern League (76 decks) | https://downloads.cardforge.org/decks/archive/modern/2021-08-24-modern-league.zip +2021-08-24 Modern Preliminary (9 decks) | https://downloads.cardforge.org/decks/archive/modern/2021-08-24-modern-preliminary.zip +2021-08-25 Modern Preliminary (7 decks) | https://downloads.cardforge.org/decks/archive/modern/2021-08-25-modern-preliminary.zip +2021-08-26 Modern Preliminary (6 decks) | https://downloads.cardforge.org/decks/archive/modern/2021-08-26-modern-preliminary.zip +2021-08-27 Modern League (61 decks) | https://downloads.cardforge.org/decks/archive/modern/2021-08-27-modern-league.zip +2021-08-27 Modern Preliminary (5 decks) | https://downloads.cardforge.org/decks/archive/modern/2021-08-27-modern-preliminary.zip +2021-08-28 Modern Preliminary (6 decks) | https://downloads.cardforge.org/decks/archive/modern/2021-08-28-modern-preliminary.zip +2021-08-29 Modern Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/modern/2021-08-29-modern-challenge.zip +2021-08-30 Modern Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/modern/2021-08-30-modern-challenge.zip +2021-08-31 Modern League (80 decks) | https://downloads.cardforge.org/decks/archive/modern/2021-08-31-modern-league.zip +2021-08-31 Modern Preliminary (8 decks) | https://downloads.cardforge.org/decks/archive/modern/2021-08-31-modern-preliminary.zip +2021-09-01 Modern Preliminary (6 decks) | https://downloads.cardforge.org/decks/archive/modern/2021-09-01-modern-preliminary.zip +2021-09-02 Modern Preliminary (9 decks) | https://downloads.cardforge.org/decks/archive/modern/2021-09-02-modern-preliminary.zip +2021-09-03 Modern League (53 decks) | https://downloads.cardforge.org/decks/archive/modern/2021-09-03-modern-league.zip +2021-09-03 Modern Preliminary (6 decks) | https://downloads.cardforge.org/decks/archive/modern/2021-09-03-modern-preliminary.zip +2021-09-04 Modern Preliminary (5 decks) | https://downloads.cardforge.org/decks/archive/modern/2021-09-04-modern-preliminary.zip +2021-09-05 Modern Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/modern/2021-09-05-modern-challenge.zip +2021-09-06 Modern Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/modern/2021-09-06-modern-challenge.zip +2021-09-07 Modern League (68 decks) | https://downloads.cardforge.org/decks/archive/modern/2021-09-07-modern-league.zip +2021-09-07 Modern Preliminary (9 decks) | https://downloads.cardforge.org/decks/archive/modern/2021-09-07-modern-preliminary.zip +2021-09-08 Modern Preliminary (5 decks) | https://downloads.cardforge.org/decks/archive/modern/2021-09-08-modern-preliminary.zip +2021-09-09 Modern Preliminary (9 decks) | https://downloads.cardforge.org/decks/archive/modern/2021-09-09-modern-preliminary.zip +2021-09-10 Modern League (65 decks) | https://downloads.cardforge.org/decks/archive/modern/2021-09-10-modern-league.zip +2021-09-10 Modern Preliminary (6 decks) | https://downloads.cardforge.org/decks/archive/modern/2021-09-10-modern-preliminary.zip diff --git a/forge-gui/res/lists/net-decks-archive-pauper.txt b/forge-gui/res/lists/net-decks-archive-pauper.txt index b09bbf25e64..022f4388125 100644 --- a/forge-gui/res/lists/net-decks-archive-pauper.txt +++ b/forge-gui/res/lists/net-decks-archive-pauper.txt @@ -1784,4 +1784,16 @@ 2021-08-11 Pauper League (22 decks) | https://downloads.cardforge.org/decks/archive/pauper/2021-08-11-pauper-league.zip 2021-08-15 Pauper Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/pauper/2021-08-15-pauper-challenge.zip 2021-08-16 Pauper Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/pauper/2021-08-16-pauper-challenge.zip +2021-08-18 Pauper League (22 decks) | https://downloads.cardforge.org/decks/archive/pauper/2021-08-18-pauper-league.zip +2021-08-20 Pauper Preliminary (4 decks) | https://downloads.cardforge.org/decks/archive/pauper/2021-08-20-pauper-preliminary.zip +2021-08-22 Pauper Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/pauper/2021-08-22-pauper-challenge.zip +2021-08-23 Pauper Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/pauper/2021-08-23-pauper-challenge.zip +2021-08-25 Pauper League (20 decks) | https://downloads.cardforge.org/decks/archive/pauper/2021-08-25-pauper-league.zip +2021-08-29 Pauper Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/pauper/2021-08-29-pauper-challenge.zip +2021-08-30 Pauper Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/pauper/2021-08-30-pauper-challenge.zip +2021-09-01 Pauper League (21 decks) | https://downloads.cardforge.org/decks/archive/pauper/2021-09-01-pauper-league.zip +2021-09-03 Pauper Preliminary (3 decks) | https://downloads.cardforge.org/decks/archive/pauper/2021-09-03-pauper-preliminary.zip +2021-09-05 Pauper Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/pauper/2021-09-05-pauper-challenge.zip +2021-09-06 Pauper Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/pauper/2021-09-06-pauper-challenge.zip +2021-09-08 Pauper League (17 decks) | https://downloads.cardforge.org/decks/archive/pauper/2021-09-08-pauper-league.zip Pauper League (20 decks) | https://downloads.cardforge.org/decks/archive/pauper/pauper-league.zip diff --git a/forge-gui/res/lists/net-decks-archive-pioneer.txt b/forge-gui/res/lists/net-decks-archive-pioneer.txt index 7a2935bb946..616a4cda8d1 100644 --- a/forge-gui/res/lists/net-decks-archive-pioneer.txt +++ b/forge-gui/res/lists/net-decks-archive-pioneer.txt @@ -701,3 +701,28 @@ 2021-08-16 Pioneer Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/pioneer/2021-08-16-pioneer-challenge.zip 2021-08-16 Pioneer League (19 decks) | https://downloads.cardforge.org/decks/archive/pioneer/2021-08-16-pioneer-league.zip 2021-08-17 Pioneer Preliminary (5 decks) | https://downloads.cardforge.org/decks/archive/pioneer/2021-08-17-pioneer-preliminary.zip +2021-08-18 Pioneer Preliminary (6 decks) | https://downloads.cardforge.org/decks/archive/pioneer/2021-08-18-pioneer-preliminary.zip +2021-08-19 Pioneer League (20 decks) | https://downloads.cardforge.org/decks/archive/pioneer/2021-08-19-pioneer-league.zip +2021-08-19 Pioneer Preliminary (9 decks) | https://downloads.cardforge.org/decks/archive/pioneer/2021-08-19-pioneer-preliminary.zip +2021-08-20 Pioneer Preliminary (6 decks) | https://downloads.cardforge.org/decks/archive/pioneer/2021-08-20-pioneer-preliminary.zip +2021-08-21 Pioneer Preliminary (12 decks) | https://downloads.cardforge.org/decks/archive/pioneer/2021-08-21-pioneer-preliminary.zip +2021-08-22 Pioneer Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/pioneer/2021-08-22-pioneer-challenge.zip +2021-08-22 Pioneer Preliminary (8 decks) | https://downloads.cardforge.org/decks/archive/pioneer/2021-08-22-pioneer-preliminary.zip +2021-08-22 Pioneer Premier (16 decks) | https://downloads.cardforge.org/decks/archive/pioneer/2021-08-22-pioneer-premier.zip +2021-08-23 Pioneer Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/pioneer/2021-08-23-pioneer-challenge.zip +2021-08-23 Pioneer League (21 decks) | https://downloads.cardforge.org/decks/archive/pioneer/2021-08-23-pioneer-league.zip +2021-08-24 Pioneer Preliminary (5 decks) | https://downloads.cardforge.org/decks/archive/pioneer/2021-08-24-pioneer-preliminary.zip +2021-08-25 Pioneer Preliminary (6 decks) | https://downloads.cardforge.org/decks/archive/pioneer/2021-08-25-pioneer-preliminary.zip +2021-08-26 Pioneer League (23 decks) | https://downloads.cardforge.org/decks/archive/pioneer/2021-08-26-pioneer-league.zip +2021-08-27 Pioneer Preliminary (4 decks) | https://downloads.cardforge.org/decks/archive/pioneer/2021-08-27-pioneer-preliminary.zip +2021-08-28 Pioneer Preliminary (6 decks) | https://downloads.cardforge.org/decks/archive/pioneer/2021-08-28-pioneer-preliminary.zip +2021-08-29 Pioneer Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/pioneer/2021-08-29-pioneer-challenge.zip +2021-08-30 Pioneer Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/pioneer/2021-08-30-pioneer-challenge.zip +2021-08-30 Pioneer League (21 decks) | https://downloads.cardforge.org/decks/archive/pioneer/2021-08-30-pioneer-league.zip +2021-09-02 Pioneer League (13 decks) | https://downloads.cardforge.org/decks/archive/pioneer/2021-09-02-pioneer-league.zip +2021-09-03 Pioneer Preliminary (5 decks) | https://downloads.cardforge.org/decks/archive/pioneer/2021-09-03-pioneer-preliminary.zip +2021-09-05 Pioneer Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/pioneer/2021-09-05-pioneer-challenge.zip +2021-09-06 Pioneer Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/pioneer/2021-09-06-pioneer-challenge.zip +2021-09-06 Pioneer League (20 decks) | https://downloads.cardforge.org/decks/archive/pioneer/2021-09-06-pioneer-league.zip +2021-09-08 Pioneer Preliminary (4 decks) | https://downloads.cardforge.org/decks/archive/pioneer/2021-09-08-pioneer-preliminary.zip +2021-09-09 Pioneer League (17 decks) | https://downloads.cardforge.org/decks/archive/pioneer/2021-09-09-pioneer-league.zip diff --git a/forge-gui/res/lists/net-decks-archive-standard.txt b/forge-gui/res/lists/net-decks-archive-standard.txt index 2827f9a44b6..d77aef384c2 100644 --- a/forge-gui/res/lists/net-decks-archive-standard.txt +++ b/forge-gui/res/lists/net-decks-archive-standard.txt @@ -2477,3 +2477,16 @@ 2021-08-15 Standard Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/standard/2021-08-15-standard-challenge.zip 2021-08-16 Standard Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/standard/2021-08-16-standard-challenge.zip 2021-08-16 Standard League (11 decks) | https://downloads.cardforge.org/decks/archive/standard/2021-08-16-standard-league.zip +2021-08-19 Standard League (10 decks) | https://downloads.cardforge.org/decks/archive/standard/2021-08-19-standard-league.zip +2021-08-22 Standard Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/standard/2021-08-22-standard-challenge.zip +2021-08-23 Standard Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/standard/2021-08-23-standard-challenge.zip +2021-08-23 Standard League (12 decks) | https://downloads.cardforge.org/decks/archive/standard/2021-08-23-standard-league.zip +2021-08-26 Standard League (7 decks) | https://downloads.cardforge.org/decks/archive/standard/2021-08-26-standard-league.zip +2021-08-29 Standard Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/standard/2021-08-29-standard-challenge.zip +2021-08-30 Standard Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/standard/2021-08-30-standard-challenge.zip +2021-08-30 Standard League (11 decks) | https://downloads.cardforge.org/decks/archive/standard/2021-08-30-standard-league.zip +2021-09-02 Standard League (10 decks) | https://downloads.cardforge.org/decks/archive/standard/2021-09-02-standard-league.zip +2021-09-05 Standard Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/standard/2021-09-05-standard-challenge.zip +2021-09-06 Standard Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/standard/2021-09-06-standard-challenge.zip +2021-09-06 Standard League (8 decks) | https://downloads.cardforge.org/decks/archive/standard/2021-09-06-standard-league.zip +2021-09-09 Standard League (11 decks) | https://downloads.cardforge.org/decks/archive/standard/2021-09-09-standard-league.zip diff --git a/forge-gui/res/lists/net-decks-archive-vintage.txt b/forge-gui/res/lists/net-decks-archive-vintage.txt index acc49ef7ae7..9e8232ae0e9 100644 --- a/forge-gui/res/lists/net-decks-archive-vintage.txt +++ b/forge-gui/res/lists/net-decks-archive-vintage.txt @@ -1500,3 +1500,20 @@ 2021-08-15 Vintage League (11 decks) | https://downloads.cardforge.org/decks/archive/vintage/2021-08-15-vintage-league.zip 2021-08-16 Vintage Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/vintage/2021-08-16-vintage-challenge.zip 2021-08-17 Vintage Preliminary (4 decks) | https://downloads.cardforge.org/decks/archive/vintage/2021-08-17-vintage-preliminary.zip +2021-08-19 Vintage Preliminary (5 decks) | https://downloads.cardforge.org/decks/archive/vintage/2021-08-19-vintage-preliminary.zip +2021-08-21 Vintage Preliminary (5 decks) | https://downloads.cardforge.org/decks/archive/vintage/2021-08-21-vintage-preliminary.zip +2021-08-22 Vintage Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/vintage/2021-08-22-vintage-challenge.zip +2021-08-22 Vintage League (12 decks) | https://downloads.cardforge.org/decks/archive/vintage/2021-08-22-vintage-league.zip +2021-08-23 Vintage Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/vintage/2021-08-23-vintage-challenge.zip +2021-08-26 Vintage Preliminary (5 decks) | https://downloads.cardforge.org/decks/archive/vintage/2021-08-26-vintage-preliminary.zip +2021-08-28 Vintage Preliminary (5 decks) | https://downloads.cardforge.org/decks/archive/vintage/2021-08-28-vintage-preliminary.zip +2021-08-29 Vintage Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/vintage/2021-08-29-vintage-challenge.zip +2021-08-29 Vintage League (13 decks) | https://downloads.cardforge.org/decks/archive/vintage/2021-08-29-vintage-league.zip +2021-08-30 Vintage Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/vintage/2021-08-30-vintage-challenge.zip +2021-09-02 Vintage Preliminary (7 decks) | https://downloads.cardforge.org/decks/archive/vintage/2021-09-02-vintage-preliminary.zip +2021-09-04 Vintage Preliminary (4 decks) | https://downloads.cardforge.org/decks/archive/vintage/2021-09-04-vintage-preliminary.zip +2021-09-05 Vintage Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/vintage/2021-09-05-vintage-challenge.zip +2021-09-05 Vintage League (8 decks) | https://downloads.cardforge.org/decks/archive/vintage/2021-09-05-vintage-league.zip +2021-09-06 Vintage Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/vintage/2021-09-06-vintage-challenge.zip +2021-09-06 Vintage Premier (16 decks) | https://downloads.cardforge.org/decks/archive/vintage/2021-09-06-vintage-premier.zip +2021-09-09 Vintage Preliminary (4 decks) | https://downloads.cardforge.org/decks/archive/vintage/2021-09-09-vintage-preliminary.zip From cefc86b13f42f3a93c215da1a114fafaf0dfb3eb Mon Sep 17 00:00:00 2001 From: Meerkov Date: Sat, 11 Sep 2021 08:04:08 +0000 Subject: [PATCH 02/58] The AI doesn't know how to use this card, and currently it's clogging up AI decks in LEB. --- forge-gui/res/cardsfolder/f/false_orders.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/forge-gui/res/cardsfolder/f/false_orders.txt b/forge-gui/res/cardsfolder/f/false_orders.txt index 8f9b3a24b26..688293c0fa3 100644 --- a/forge-gui/res/cardsfolder/f/false_orders.txt +++ b/forge-gui/res/cardsfolder/f/false_orders.txt @@ -6,4 +6,5 @@ A:SP$ RemoveFromCombat | Cost$ R | ActivationPhases$ Declare Blockers | ValidTgt SVar:ChooseAttacker:DB$ ChooseCard | Defined$ You | Choices$ Creature.attacking | RememberChosen$ True | MinAmount$ 0 | ChoiceTitle$ Choose an attacker to block | SubAbility$ Block SVar:Block:DB$ Block | DefinedAttacker$ Remembered | DefinedBlocker$ ParentTarget | SpellDescription$ You may have it block an attacking creature of your choice. SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True +AI:RemoveDeck:All Oracle:Cast this spell only during the declare blockers step.\nRemove target creature defending player controls from combat. Creatures it was blocking that had become blocked by only that creature this combat become unblocked. You may have it block an attacking creature of your choice. From 49fdfdbe61c3d9092b17541fc6cc8b18e77fa608 Mon Sep 17 00:00:00 2001 From: Meerkov Date: Sat, 11 Sep 2021 08:15:36 +0000 Subject: [PATCH 03/58] Unban Orcish Artillery --- forge-gui/res/cardsfolder/o/orcish_artillery.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/forge-gui/res/cardsfolder/o/orcish_artillery.txt b/forge-gui/res/cardsfolder/o/orcish_artillery.txt index 9fd1c514a97..81e016cda17 100644 --- a/forge-gui/res/cardsfolder/o/orcish_artillery.txt +++ b/forge-gui/res/cardsfolder/o/orcish_artillery.txt @@ -6,6 +6,5 @@ A:AB$ DealDamage | Cost$ T | ValidTgts$ Creature,Player,Planeswalker | TgtPrompt SVar:DBDealDamage:DB$DealDamage | Defined$ You | NumDmg$ 3 | SubAbility$ DBDamageResolve SVar:DBDamageResolve:DB$ DamageResolve SVar:SelfDamageAmount:3 -AI:RemoveDeck:Random SVar:Picture:http://resources.wizards.com/magic/cards/9ed/en-us/card83193.jpg Oracle:{T}: Orcish Artillery deals 2 damage to any target and 3 damage to you. From 052b73bda2381848c513f75e52eadcb92562baa5 Mon Sep 17 00:00:00 2001 From: tool4EvEr Date: Sat, 11 Sep 2021 10:26:01 +0200 Subject: [PATCH 04/58] Minor Cleanup --- .../src/main/java/forge/ai/ComputerUtil.java | 2 +- .../java/forge/ai/ability/ChangeZoneAi.java | 14 ++----------- .../game/ability/effects/ManaEffect.java | 7 +++---- .../main/java/forge/game/card/CardUtil.java | 5 +---- .../java/forge/game/phase/PhaseHandler.java | 13 ++++-------- .../game/spellability/AbilityManaPart.java | 9 +++----- .../main/java/forge/view/SimulateMatch.java | 1 - .../res/cardsfolder/m/mycosynth_lattice.txt | 2 +- .../src/main/java/forge/deck/DeckgenUtil.java | 2 -- .../main/java/forge/download/AutoUpdater.java | 6 +++--- .../limited/ArchetypeDeckBuilder.java | 2 -- .../gamemodes/limited/BoosterDraftAI.java | 1 - .../gamemodes/limited/LimitedPlayer.java | 3 --- .../limited/LimitedWinLoseController.java | 6 ++---- .../gamemodes/limited/ReadDraftRankings.java | 2 +- .../forge/gamemodes/quest/BoosterUtils.java | 1 - .../quest/QuestChallengeGenerator.java | 2 +- .../quest/QuestEventCommanderDuelManager.java | 21 ++++++++----------- .../gamemodes/quest/QuestEventDraft.java | 3 --- .../forge/gamemodes/quest/QuestSpellShop.java | 6 ++---- .../quest/QuestTournamentController.java | 3 +-- .../quest/bazaar/QuestItemCharmOfVigor.java | 1 - .../quest/bazaar/QuestItemCharmOfVim.java | 1 - .../quest/bazaar/QuestItemZeppelin.java | 1 - .../forge/gamemodes/quest/io/QuestDataIO.java | 3 --- .../tournament/system/AbstractTournament.java | 1 - .../tournament/system/TournamentPairing.java | 16 +++++++------- .../tournament/system/TournamentPlayer.java | 4 ++-- .../system/TournamentRoundRobin.java | 2 +- .../tournament/system/TournamentSwiss.java | 21 ++++++++----------- .../main/java/forge/sound/SoundSystem.java | 3 +-- 31 files changed, 55 insertions(+), 109 deletions(-) diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtil.java b/forge-ai/src/main/java/forge/ai/ComputerUtil.java index 0a4b0174478..e849022b9c9 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtil.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtil.java @@ -3025,7 +3025,7 @@ public class ComputerUtil { } } if (!containsAttacker) { - return false; + continue; } AiBlockController block = new AiBlockController(ai); block.assignBlockersForCombat(combat); diff --git a/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java b/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java index c8cd1122060..0cc5dac8aa2 100644 --- a/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java @@ -642,16 +642,7 @@ public class ChangeZoneAi extends SpellAbilityAi { * @return Card */ private static Card chooseCreature(final Player ai, CardCollection list) { - // Creating a new combat for testing purposes. - final Player opponent = ai.getWeakestOpponent(); - Combat combat = new Combat(opponent); - for (Card att : opponent.getCreaturesInPlay()) { - combat.addAttacker(att, ai); - } - AiBlockController block = new AiBlockController(ai); - block.assignBlockersForCombat(combat); - - if (ComputerUtilCombat.lifeInDanger(ai, combat)) { + if (ComputerUtil.aiLifeInDanger(ai, false, 0)) { // need something AI can cast now ComputerUtilCard.sortByEvaluateCreature(list); for (Card c : list) { @@ -1099,7 +1090,6 @@ public class ChangeZoneAi extends SpellAbilityAi { return false; } - if (!sa.hasParam("AITgtOwnCards")) { list = CardLists.filterControlledBy(list, ai.getOpponents()); list = CardLists.filter(list, new Predicate() { @@ -1819,7 +1809,7 @@ public class ChangeZoneAi extends SpellAbilityAi { source, sa); list = CardLists.filterControlledBy(list, aiPlayer.getOpponents()); if (list.isEmpty()) { - return false; // no valid targets + return false; // no valid targets } Map> data = Maps.newHashMap(); diff --git a/forge-game/src/main/java/forge/game/ability/effects/ManaEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ManaEffect.java index 537d7011279..123cdcffbff 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/ManaEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/ManaEffect.java @@ -141,7 +141,6 @@ public class ManaEffect extends SpellAbilityEffect { abMana.setExpressChoice(choice); } else if (abMana.isSpecialMana()) { - String type = abMana.getOrigProduced().split("Special ")[1]; if (type.equals("EnchantedManaCost")) { @@ -151,12 +150,12 @@ public class ManaEffect extends SpellAbilityEffect { StringBuilder sb = new StringBuilder(); int generic = enchanted.getManaCost().getGenericCost(); - if( generic > 0 ) + if (generic > 0) sb.append(generic); for (ManaCostShard s : enchanted.getManaCost()) { ColorSet cs = ColorSet.fromMask(s.getColorMask()); - if(cs.isColorless()) + if (cs.isColorless()) continue; sb.append(' '); if (cs.isMonoColor()) @@ -200,7 +199,7 @@ public class ManaEffect extends SpellAbilityEffect { for (Card c : AbilityUtils.getDefinedCards(card, res, sa)) { for (ManaCostShard s : c.getManaCost()) { ColorSet cs = ColorSet.fromMask(s.getColorMask()); - if(cs.isColorless()) + if (cs.isColorless()) continue; sb.append(' '); if (cs.isMonoColor()) 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 bb57f0115a5..397e7c98394 100644 --- a/forge-game/src/main/java/forge/game/card/CardUtil.java +++ b/forge-game/src/main/java/forge/game/card/CardUtil.java @@ -123,8 +123,7 @@ public final class CardUtil { for (Player p : game.getRegisteredPlayers()) { res.addAll(p.getZone(to).getCardsAddedThisTurn(from)); } - } - else { + } else { res.addAll(game.getStackZone().getCardsAddedThisTurn(from)); } return CardLists.getValidCardsAsList(res, valid, src.getController(), src, ctb); @@ -376,7 +375,6 @@ public final class CardUtil { public static CardState getFaceDownCharacteristic(Card c) { return getFaceDownCharacteristic(c, CardStateName.FaceDown); } - public static CardState getFaceDownCharacteristic(Card c, CardStateName state) { final CardType type = new CardType(false); type.add("Creature"); @@ -401,7 +399,6 @@ public final class CardUtil { public static Set getReflectableManaColors(final SpellAbility sa) { return getReflectableManaColors(sa, sa, Sets.newHashSet(), new CardCollection()); } - private static Set getReflectableManaColors(final SpellAbility abMana, final SpellAbility sa, Set colors, final CardCollection parents) { // Here's the problem with reflectable Mana. If more than one is out, 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 9f76c814a53..0c228121070 100644 --- a/forge-game/src/main/java/forge/game/phase/PhaseHandler.java +++ b/forge-game/src/main/java/forge/game/phase/PhaseHandler.java @@ -165,8 +165,7 @@ public class PhaseHandler implements java.io.Serializable { if (bRepeatCleanup) { // for when Cleanup needs to repeat itself bRepeatCleanup = false; - } - else { + } else { // If the phase that's ending has a stack of additional phases // Take the LIFO one and move to that instead of the normal one ExtraPhase extraPhase = null; @@ -179,8 +178,7 @@ public class PhaseHandler implements java.io.Serializable { extraPhases.remove(phase); } setPhase(nextPhase); - } - else { + } else { turnEnded = PhaseType.isLast(phase, isTopsy); setPhase(PhaseType.getNext(phase, isTopsy)); } @@ -263,8 +261,7 @@ public class PhaseHandler implements java.io.Serializable { if (isSkippingPhase(phase)) { skipped = true; givePriorityToPlayer = false; - } - else { + } else { // Perform turn-based actions switch (phase) { case UNTAP: @@ -999,7 +996,6 @@ public class PhaseHandler implements java.io.Serializable { public void startFirstTurn(Player goesFirst) { startFirstTurn(goesFirst, null); } - public void startFirstTurn(Player goesFirst, Runnable startGameHook) { StopWatch sw = new StopWatch(); @@ -1104,8 +1100,7 @@ public class PhaseHandler implements java.io.Serializable { if (game.getStack().isEmpty()) { if (playerTurn.hasLost()) { setPriority(game.getNextPlayerAfter(playerTurn)); - } - else { + } else { setPriority(playerTurn); } diff --git a/forge-game/src/main/java/forge/game/spellability/AbilityManaPart.java b/forge-game/src/main/java/forge/game/spellability/AbilityManaPart.java index 664a417122b..c0f5b542267 100644 --- a/forge-game/src/main/java/forge/game/spellability/AbilityManaPart.java +++ b/forge-game/src/main/java/forge/game/spellability/AbilityManaPart.java @@ -339,8 +339,7 @@ public class AbilityManaPart implements java.io.Serializable { if (sa.isAbility()) { if (restriction.startsWith("Activated")) { restriction = TextUtil.fastReplace(restriction, "Activated", "Card"); - } - else { + } else { continue; } } @@ -633,8 +632,7 @@ public class AbilityManaPart implements java.io.Serializable { return true; } } - } - else { + } else { // treat special mana if it always can be paid if (isSpecialMana()) { return true; @@ -649,5 +647,4 @@ public class AbilityManaPart implements java.io.Serializable { return false; } -} // end class AbilityMana - +} diff --git a/forge-gui-desktop/src/main/java/forge/view/SimulateMatch.java b/forge-gui-desktop/src/main/java/forge/view/SimulateMatch.java index 2789e4ca019..e47e1e9c7ff 100644 --- a/forge-gui-desktop/src/main/java/forge/view/SimulateMatch.java +++ b/forge-gui-desktop/src/main/java/forge/view/SimulateMatch.java @@ -172,7 +172,6 @@ public class SimulateMatch { System.out.println("\tq - Quiet flag. Output just the game result, not the entire game log."); } - public static void simulateSingleMatch(final Match mc, int iGame, boolean outputGamelog) { final StopWatch sw = new StopWatch(); sw.start(); diff --git a/forge-gui/res/cardsfolder/m/mycosynth_lattice.txt b/forge-gui/res/cardsfolder/m/mycosynth_lattice.txt index e56f91f4a87..5330ef704f6 100644 --- a/forge-gui/res/cardsfolder/m/mycosynth_lattice.txt +++ b/forge-gui/res/cardsfolder/m/mycosynth_lattice.txt @@ -2,7 +2,7 @@ Name:Mycosynth Lattice ManaCost:6 Types:Artifact S:Mode$ Continuous | Affected$ Permanent | AddType$ Artifact | Description$ All permanents are artifact in addition to their other types. -S:Mode$ Continuous| Affected$ Card | SetColor$ Colorless | AffectedZone$ Battlefield,Hand,Library,Graveyard,Exile,Stack,Command | Description$ All cards that aren't on the battlefield, spells, and permanents are colorless. +S:Mode$ Continuous | Affected$ Card | SetColor$ Colorless | AffectedZone$ Battlefield,Hand,Library,Graveyard,Exile,Stack,Command | Description$ All cards that aren't on the battlefield, spells, and permanents are colorless. S:Mode$ Continuous | Affected$ Player | ManaConversion$ AnyType->AnyColor | Description$ Players may spend mana as though it were mana of any color. SVar:NonStackingEffect:True AI:RemoveDeck:Random diff --git a/forge-gui/src/main/java/forge/deck/DeckgenUtil.java b/forge-gui/src/main/java/forge/deck/DeckgenUtil.java index 76e07081e85..a83b31277da 100644 --- a/forge-gui/src/main/java/forge/deck/DeckgenUtil.java +++ b/forge-gui/src/main/java/forge/deck/DeckgenUtil.java @@ -226,8 +226,6 @@ public class DeckgenUtil { return deck; } - - public static Deck buildLDACArchetypeDeck(GameFormat format, boolean isForAI){ List keys = new ArrayList<>(CardArchetypeLDAGenerator.ldaArchetypes.get(format.getName())); Archetype randomKey = keys.get( MyRandom.getRandom().nextInt(keys.size()) ); diff --git a/forge-gui/src/main/java/forge/download/AutoUpdater.java b/forge-gui/src/main/java/forge/download/AutoUpdater.java index f39547d1bce..bf656022b58 100644 --- a/forge-gui/src/main/java/forge/download/AutoUpdater.java +++ b/forge-gui/src/main/java/forge/download/AutoUpdater.java @@ -167,7 +167,7 @@ public class AutoUpdater { System.out.println(index); Pattern p = Pattern.compile(">forge-(.*SNAPSHOT)"); Matcher m = p.matcher(index); - while(m.find()){ + while (m.find()) { version = m.group(1); } } @@ -178,7 +178,7 @@ public class AutoUpdater { Pattern p = Pattern.compile("(.*)"); Matcher m = p.matcher(xml); - while(m.find()){ + while (m.find()) { version = m.group(1); } } @@ -229,7 +229,7 @@ public class AutoUpdater { }; SwingUtilities.invokeLater(callback); - // + return false; } diff --git a/forge-gui/src/main/java/forge/gamemodes/limited/ArchetypeDeckBuilder.java b/forge-gui/src/main/java/forge/gamemodes/limited/ArchetypeDeckBuilder.java index cfc3600f746..24746ba1ed1 100644 --- a/forge-gui/src/main/java/forge/gamemodes/limited/ArchetypeDeckBuilder.java +++ b/forge-gui/src/main/java/forge/gamemodes/limited/ArchetypeDeckBuilder.java @@ -57,6 +57,4 @@ public class ArchetypeDeckBuilder extends CardThemedDeckBuilder{ return super.getBasicLand(basicLand); } - - } diff --git a/forge-gui/src/main/java/forge/gamemodes/limited/BoosterDraftAI.java b/forge-gui/src/main/java/forge/gamemodes/limited/BoosterDraftAI.java index d5d61676818..eda3ad645f1 100644 --- a/forge-gui/src/main/java/forge/gamemodes/limited/BoosterDraftAI.java +++ b/forge-gui/src/main/java/forge/gamemodes/limited/BoosterDraftAI.java @@ -108,4 +108,3 @@ public class BoosterDraftAI { } } // BoosterDraftAI() - diff --git a/forge-gui/src/main/java/forge/gamemodes/limited/LimitedPlayer.java b/forge-gui/src/main/java/forge/gamemodes/limited/LimitedPlayer.java index e41acf06ab8..30ca03724d7 100644 --- a/forge-gui/src/main/java/forge/gamemodes/limited/LimitedPlayer.java +++ b/forge-gui/src/main/java/forge/gamemodes/limited/LimitedPlayer.java @@ -68,8 +68,6 @@ public class LimitedPlayer { chooseFrom.remove(bestPick); - - CardPool pool = deck.getOrCreate(section); pool.add(bestPick); draftedThisRound++; @@ -147,4 +145,3 @@ public class LimitedPlayer { } */ } - diff --git a/forge-gui/src/main/java/forge/gamemodes/limited/LimitedWinLoseController.java b/forge-gui/src/main/java/forge/gamemodes/limited/LimitedWinLoseController.java index 69a5ee01eef..e223b6cae1a 100644 --- a/forge-gui/src/main/java/forge/gamemodes/limited/LimitedWinLoseController.java +++ b/forge-gui/src/main/java/forge/gamemodes/limited/LimitedWinLoseController.java @@ -56,12 +56,10 @@ public abstract class LimitedWinLoseController { view.getBtnContinue().setEnabled(true); showTournamentInfo(localizer.getMessage("btnWonRound") + gauntlet.getCurrentRound() + "/" + gauntlet.getRounds()); - } - else { + } else { showTournamentInfo(localizer.getMessage("btnWonTournament")); } - } - else { + } else { view.getBtnContinue().setVisible(false); showTournamentInfo(localizer.getMessage("btnLoseRound") + gauntlet.getCurrentRound() + "/" + gauntlet.getRounds()); diff --git a/forge-gui/src/main/java/forge/gamemodes/limited/ReadDraftRankings.java b/forge-gui/src/main/java/forge/gamemodes/limited/ReadDraftRankings.java index 5707db096b2..656036c856f 100644 --- a/forge-gui/src/main/java/forge/gamemodes/limited/ReadDraftRankings.java +++ b/forge-gui/src/main/java/forge/gamemodes/limited/ReadDraftRankings.java @@ -89,7 +89,7 @@ public class ReadDraftRankings { * the card's edition * @return ranking */ - public Double getRanking(String cardName, String edition) { + public Double getRanking(String cardName, String edition) { Double rank = null; if (draftRankings.containsKey(edition)) { diff --git a/forge-gui/src/main/java/forge/gamemodes/quest/BoosterUtils.java b/forge-gui/src/main/java/forge/gamemodes/quest/BoosterUtils.java index da9c95d5154..e290d57e02b 100644 --- a/forge-gui/src/main/java/forge/gamemodes/quest/BoosterUtils.java +++ b/forge-gui/src/main/java/forge/gamemodes/quest/BoosterUtils.java @@ -544,7 +544,6 @@ public final class BoosterUtils { * @return List */ public static List generateCardRewardList(final String s) { - if (StringUtils.isBlank(s)) { return null; } diff --git a/forge-gui/src/main/java/forge/gamemodes/quest/QuestChallengeGenerator.java b/forge-gui/src/main/java/forge/gamemodes/quest/QuestChallengeGenerator.java index 810247d5f3f..7c59d18a569 100644 --- a/forge-gui/src/main/java/forge/gamemodes/quest/QuestChallengeGenerator.java +++ b/forge-gui/src/main/java/forge/gamemodes/quest/QuestChallengeGenerator.java @@ -25,7 +25,7 @@ public class QuestChallengeGenerator { QuestChallengeGenerator(GameFormat baseFormat){ this.baseFormat=baseFormat; - if(baseFormat.getName().equals((FModel.getFormats().getModern().getName()))){ + if (baseFormat.getName().equals((FModel.getFormats().getModern().getName()))) { formatMedium=FModel.getFormats().get("Legacy"); formatHard=FModel.getFormats().get("Vintage"); } diff --git a/forge-gui/src/main/java/forge/gamemodes/quest/QuestEventCommanderDuelManager.java b/forge-gui/src/main/java/forge/gamemodes/quest/QuestEventCommanderDuelManager.java index 2721a3f4ebf..686b37d7839 100644 --- a/forge-gui/src/main/java/forge/gamemodes/quest/QuestEventCommanderDuelManager.java +++ b/forge-gui/src/main/java/forge/gamemodes/quest/QuestEventCommanderDuelManager.java @@ -40,7 +40,7 @@ public class QuestEventCommanderDuelManager implements QuestEventDuelManagerInte /** * Assembles the list of all possible Commander duels via CommanderDeckGenerator. Should be done within constructor. */ - private void assembleDuels(){ + private void assembleDuels() { //isCardGen = true seemed to make slightly more difficult decks based purely on experience with a very small sample size. //Gotta work on this more, its making pretty average decks after further testing. expertCommanderDecks = CommanderDeckGenerator.getCommanderDecks(DeckFormat.Commander, true, true); @@ -86,11 +86,11 @@ public class QuestEventCommanderDuelManager implements QuestEventDuelManagerInte * title replaced as Random. * @return ArrayList of QuestEventDuels containing 4 duels. */ - public List generateDuels(){ + public List generateDuels() { final List duelOpponents = new ArrayList<>(); //While there are less than 4 duels chosen - while(duelOpponents.size() < 4){ + while (duelOpponents.size() < 4) { //Get a random duel from the possible duels list QuestEventCommanderDuel duel = (QuestEventCommanderDuel)commanderDuels.get(((int) (commanderDuels.size() * MyRandom.getRandom().nextDouble()))); @@ -105,8 +105,6 @@ public class QuestEventCommanderDuelManager implements QuestEventDuelManagerInte //Modify deck for difficulty modifyDuelForDifficulty(duel); - - } //Modify the stats of the final duel to hide the opponent, creating a "random" duel. @@ -135,7 +133,7 @@ public class QuestEventCommanderDuelManager implements QuestEventDuelManagerInte * @param dp The easy generation commander deck * @return The same commander's expert generation DeckProxy */ - private Deck getExpertGenDeck(DeckProxy dp){ + private Deck getExpertGenDeck(DeckProxy dp) { for(QuestEventDuel qed : commanderDuels){ QuestEventCommanderDuel cmdQED = (QuestEventCommanderDuel)qed; if(cmdQED.getDeckProxy().getName().equals(dp.getName())){ @@ -150,7 +148,7 @@ public class QuestEventCommanderDuelManager implements QuestEventDuelManagerInte * of the same commander's deck. Medium replaces 30%, Hard replaces 60%, Expert replaces 100%. * @param duel The QuestEventCommanderDuel to modify */ - private void modifyDuelForDifficulty(QuestEventCommanderDuel duel){ + private void modifyDuelForDifficulty(QuestEventCommanderDuel duel) { final QuestPreferences questPreferences = FModel.getQuestPreferences(); final int index = FModel.getQuest().getAchievements().getDifficulty(); final int numberOfWins = FModel.getQuest().getAchievements().getWin(); @@ -184,15 +182,14 @@ public class QuestEventCommanderDuelManager implements QuestEventDuelManagerInte List expertList = expertMain.toFlatList(); //Replace cards in the easy deck with cards from the expert deck up to the difficulty replacement percent - for(int i = 0; i < difficultyReplacementPercent; i++){ - if(!easyMain.contains(expertList.get(i))) { //ensure that the card being copied over isn't already in the deck + for (int i = 0; i < difficultyReplacementPercent; i++) { + if (!easyMain.contains(expertList.get(i))) { //ensure that the card being copied over isn't already in the deck easyMain.remove(easyList.get(i)); easyMain.add(expertList.get(i)); - } - else{ + } else { expertList.remove(expertList.get(i)); i--; - if(expertList.size() == 0) break; //break if there are no more cards to copy over + if (expertList.size() == 0) break; //break if there are no more cards to copy over } } } diff --git a/forge-gui/src/main/java/forge/gamemodes/quest/QuestEventDraft.java b/forge-gui/src/main/java/forge/gamemodes/quest/QuestEventDraft.java index bbe04e82ddc..576670a806b 100644 --- a/forge-gui/src/main/java/forge/gamemodes/quest/QuestEventDraft.java +++ b/forge-gui/src/main/java/forge/gamemodes/quest/QuestEventDraft.java @@ -265,7 +265,6 @@ public class QuestEventDraft implements IQuestEvent { } } } else { - String aiIndex = ""; for (int i = aiNames.length - 1; i >= 0; i--) { @@ -298,7 +297,6 @@ public class QuestEventDraft implements IQuestEvent { int boosterPrices = 0; for (final String boosterSet : boosterConfiguration.split("/")) { - int value; final String boosterName = FModel.getMagicDb().getEditions().get(boosterSet).getName() + " Booster Pack"; @@ -309,7 +307,6 @@ public class QuestEventDraft implements IQuestEvent { } boosterPrices += value; - } prizePool -= boosterPrices * 8; diff --git a/forge-gui/src/main/java/forge/gamemodes/quest/QuestSpellShop.java b/forge-gui/src/main/java/forge/gamemodes/quest/QuestSpellShop.java index 16d06e7b146..d672ebfae5b 100644 --- a/forge-gui/src/main/java/forge/gamemodes/quest/QuestSpellShop.java +++ b/forge-gui/src/main/java/forge/gamemodes/quest/QuestSpellShop.java @@ -56,8 +56,7 @@ public class QuestSpellShop { nsArt = card.getName() + " (" + artIndex + ")|" + pc.getEdition(); foil = ((PaperCard) card).isFoil(); - } - else { + } else { ns = card.getName(); nsArt = ns; } @@ -265,8 +264,7 @@ public class QuestSpellShop { GuiBase.getInterface().showCardList(booster.getName(), "You have found the following cards inside:", remainingCards); } - } - else { + } else { GuiBase.getInterface().showCardList(booster.getName(), "You have found the following cards inside:", newCards); } } diff --git a/forge-gui/src/main/java/forge/gamemodes/quest/QuestTournamentController.java b/forge-gui/src/main/java/forge/gamemodes/quest/QuestTournamentController.java index 04d12c8f13d..619123a89a3 100644 --- a/forge-gui/src/main/java/forge/gamemodes/quest/QuestTournamentController.java +++ b/forge-gui/src/main/java/forge/gamemodes/quest/QuestTournamentController.java @@ -364,8 +364,7 @@ public class QuestTournamentController { if (draft.playerHasMatchesLeft()) { view.getBtnLeaveTournament().setText(localizer.getMessage("btnLeaveTournament")); - } - else { + } else { view.getBtnLeaveTournament().setText(localizer.getMessage("lblCollectPrizes")); } } diff --git a/forge-gui/src/main/java/forge/gamemodes/quest/bazaar/QuestItemCharmOfVigor.java b/forge-gui/src/main/java/forge/gamemodes/quest/bazaar/QuestItemCharmOfVigor.java index 665a35f4fe8..708d8049472 100644 --- a/forge-gui/src/main/java/forge/gamemodes/quest/bazaar/QuestItemCharmOfVigor.java +++ b/forge-gui/src/main/java/forge/gamemodes/quest/bazaar/QuestItemCharmOfVigor.java @@ -44,5 +44,4 @@ public class QuestItemCharmOfVigor extends QuestItemBasic { return super.isAvailableForPurchase(qA, qCtrl); } - } diff --git a/forge-gui/src/main/java/forge/gamemodes/quest/bazaar/QuestItemCharmOfVim.java b/forge-gui/src/main/java/forge/gamemodes/quest/bazaar/QuestItemCharmOfVim.java index 760411d8e1b..966a14ddf50 100644 --- a/forge-gui/src/main/java/forge/gamemodes/quest/bazaar/QuestItemCharmOfVim.java +++ b/forge-gui/src/main/java/forge/gamemodes/quest/bazaar/QuestItemCharmOfVim.java @@ -31,5 +31,4 @@ public class QuestItemCharmOfVim extends QuestItemBasic { return super.isAvailableForPurchase(qA, qCtrl); } - } diff --git a/forge-gui/src/main/java/forge/gamemodes/quest/bazaar/QuestItemZeppelin.java b/forge-gui/src/main/java/forge/gamemodes/quest/bazaar/QuestItemZeppelin.java index 1402ab7b14f..5f410a48c4b 100644 --- a/forge-gui/src/main/java/forge/gamemodes/quest/bazaar/QuestItemZeppelin.java +++ b/forge-gui/src/main/java/forge/gamemodes/quest/bazaar/QuestItemZeppelin.java @@ -44,5 +44,4 @@ public class QuestItemZeppelin extends QuestItemBasic { return super.isAvailableForPurchase(qA, qCtrl) && qA.hasItem(QuestItemType.MAP); } - } diff --git a/forge-gui/src/main/java/forge/gamemodes/quest/io/QuestDataIO.java b/forge-gui/src/main/java/forge/gamemodes/quest/io/QuestDataIO.java index bdf872ae96a..3b0f28c4aa5 100644 --- a/forge-gui/src/main/java/forge/gamemodes/quest/io/QuestDataIO.java +++ b/forge-gui/src/main/java/forge/gamemodes/quest/io/QuestDataIO.java @@ -267,7 +267,6 @@ public class QuestDataIO { // Migrate DraftTournaments to use new Tournament class } - final QuestAssets qS = newData.getAssets(); final QuestAchievements qA = newData.getAchievements(); @@ -647,7 +646,6 @@ public class QuestDataIO { QuestEventDraftContainer output = new QuestEventDraftContainer(); while (reader.hasMoreChildren()) { - reader.moveDown(); // TODO Add Tournament String draftName = null; @@ -735,7 +733,6 @@ public class QuestDataIO { output.add(draft); reader.moveUp(); - } return output; diff --git a/forge-gui/src/main/java/forge/gamemodes/tournament/system/AbstractTournament.java b/forge-gui/src/main/java/forge/gamemodes/tournament/system/AbstractTournament.java index a1630810707..4747832c4b4 100644 --- a/forge-gui/src/main/java/forge/gamemodes/tournament/system/AbstractTournament.java +++ b/forge-gui/src/main/java/forge/gamemodes/tournament/system/AbstractTournament.java @@ -68,7 +68,6 @@ public abstract class AbstractTournament implements Serializable { public void setInitialized(boolean initialized) { this.initialized = initialized; } - public boolean isPlayerRemaining(TournamentPlayer player) { return remainingPlayers.contains(player); } diff --git a/forge-gui/src/main/java/forge/gamemodes/tournament/system/TournamentPairing.java b/forge-gui/src/main/java/forge/gamemodes/tournament/system/TournamentPairing.java index 338eb829cbb..78fcbdf4fd9 100644 --- a/forge-gui/src/main/java/forge/gamemodes/tournament/system/TournamentPairing.java +++ b/forge-gui/src/main/java/forge/gamemodes/tournament/system/TournamentPairing.java @@ -21,21 +21,21 @@ public class TournamentPairing { winner = null; } - public int getRound() { return round; } + public int getRound() { return round; } - public void setRound(int round) { this.round = round; } + public void setRound(int round) { this.round = round; } - public boolean isBye() { return bye; } + public boolean isBye() { return bye; } - public void setBye(boolean bye) { this.bye = bye; } + public void setBye(boolean bye) { this.bye = bye; } - public List getPairedPlayers() { return pairedPlayers; } + public List getPairedPlayers() { return pairedPlayers; } - public List getOutcomes() { return outcomes; } + public List getOutcomes() { return outcomes; } - public TournamentPlayer getWinner() { return winner; } + public TournamentPlayer getWinner() { return winner; } - public void setWinner(TournamentPlayer winner) { this.winner = winner; } + public void setWinner(TournamentPlayer winner) { this.winner = winner; } public void setWinnerByIndex(int index) { for(TournamentPlayer pl : pairedPlayers) { diff --git a/forge-gui/src/main/java/forge/gamemodes/tournament/system/TournamentPlayer.java b/forge-gui/src/main/java/forge/gamemodes/tournament/system/TournamentPlayer.java index 4dca1f2b529..e825e304dde 100644 --- a/forge-gui/src/main/java/forge/gamemodes/tournament/system/TournamentPlayer.java +++ b/forge-gui/src/main/java/forge/gamemodes/tournament/system/TournamentPlayer.java @@ -106,9 +106,9 @@ public class TournamentPlayer { this.active = active; } - public int getIndex() { return index; } + public int getIndex() { return index; } - public void setIndex(int index) { this.index = index; } + public void setIndex(int index) { this.index = index; } public List getPreviousOpponents() { return previousOpponents; } diff --git a/forge-gui/src/main/java/forge/gamemodes/tournament/system/TournamentRoundRobin.java b/forge-gui/src/main/java/forge/gamemodes/tournament/system/TournamentRoundRobin.java index 61f4b254ff8..3932d45f8e7 100644 --- a/forge-gui/src/main/java/forge/gamemodes/tournament/system/TournamentRoundRobin.java +++ b/forge-gui/src/main/java/forge/gamemodes/tournament/system/TournamentRoundRobin.java @@ -50,7 +50,7 @@ public class TournamentRoundRobin extends AbstractTournament { activeRound++; - for(int i = 0; i < numPlayers/2; i++) { + for (int i = 0; i < numPlayers/2; i++) { boolean bye = false; if (roundPairings.get(i).getPlayer().getName().equals("BYE")) { bye = true; diff --git a/forge-gui/src/main/java/forge/gamemodes/tournament/system/TournamentSwiss.java b/forge-gui/src/main/java/forge/gamemodes/tournament/system/TournamentSwiss.java index 066481c1555..6afe40902ec 100644 --- a/forge-gui/src/main/java/forge/gamemodes/tournament/system/TournamentSwiss.java +++ b/forge-gui/src/main/java/forge/gamemodes/tournament/system/TournamentSwiss.java @@ -45,7 +45,7 @@ public class TournamentSwiss extends AbstractTournament { if (allPlayers.size() % 2 == 1) { int i = allPlayers.size() - 1; - while(byePlayer == null) { + while (byePlayer == null) { TournamentPlayer pl = allPlayers.get(i); if (pl.getByes() == 0) { byePlayer = pl; @@ -72,8 +72,8 @@ public class TournamentSwiss extends AbstractTournament { } int score = groupPlayers.get(frontOfGroup).getScore(); - while(pairing) { - for(backOfGroup = frontOfGroup+1; backOfGroup < groupPlayers.size(); backOfGroup++) { + while (pairing) { + for (backOfGroup = frontOfGroup+1; backOfGroup < groupPlayers.size(); backOfGroup++) { if (allPlayers.get(backOfGroup).getScore() < score) { break; } @@ -115,10 +115,10 @@ public class TournamentSwiss extends AbstractTournament { int oppClashes = 0; List unpairedPlayers = Lists.newArrayList(); - for(TournamentPlayer tp : players) { + for (TournamentPlayer tp : players) { HashSet opponents = new HashSet<>(); List prevOpps = tp.getPreviousOpponents(); - for(TournamentPlayer opp : players) { + for (TournamentPlayer opp : players) { if (!prevOpps.contains(opp.getIndex()) && !opp.equals(tp)) { opponents.add(opp); } else if (!opp.equals(tp)) { @@ -141,20 +141,20 @@ public class TournamentSwiss extends AbstractTournament { } }); - while(players.size() > 1) { + while (players.size() > 1) { TournamentPlayer initialPlayer = players.get(0); players.remove(0); ArrayList pair = new ArrayList<>(); HashSet opposing = availableOpponents.get(initialPlayer); - for(TournamentPlayer opp : players) { + for (TournamentPlayer opp : players) { if (opposing.contains(opp)) { pair.add(opp); break; } } - for(TournamentPlayer opp : pair) { + for (TournamentPlayer opp : pair) { players.remove(opp); } @@ -192,8 +192,6 @@ public class TournamentSwiss extends AbstractTournament { return unpairable; } - - @Override public boolean reportMatchCompletion(TournamentPairing pairing) { // Returns whether there are more matches left in this round @@ -215,7 +213,7 @@ public class TournamentSwiss extends AbstractTournament { } for (TournamentPlayer tp : pairing.getPairedPlayers()) { - for(Integer i : oppIndexes) { + for (Integer i : oppIndexes) { if (i != null && !i.equals(tp.getIndex())) { tp.addOpponentIndex(i); } @@ -229,7 +227,6 @@ public class TournamentSwiss extends AbstractTournament { return true; } - @Override public void endTournament() { this.activePairings.clear(); diff --git a/forge-gui/src/main/java/forge/sound/SoundSystem.java b/forge-gui/src/main/java/forge/sound/SoundSystem.java index 77bd17b7100..d1dcab487c0 100644 --- a/forge-gui/src/main/java/forge/sound/SoundSystem.java +++ b/forge-gui/src/main/java/forge/sound/SoundSystem.java @@ -124,8 +124,7 @@ public class SoundSystem { public void play(final SoundEffectType type, final boolean isSynchronized) { if (isUsingAltSystem()) { GuiBase.getInterface().startAltSoundSystem(ForgeConstants.SOUND_DIR + type.getResourceFileName(), isSynchronized); - } - else { + } else { final IAudioClip snd = fetchResource(type); if (!isSynchronized || snd.isDone()) { snd.play(FModel.getPreferences().getPrefInt(FPref.UI_VOL_SOUNDS)/100f); From eddbc1e94ab26ca42341c4875369d91db89f727f Mon Sep 17 00:00:00 2001 From: Anthony Calosa Date: Sat, 11 Sep 2021 17:13:20 +0800 Subject: [PATCH 05/58] [Mobile] Disable cardBG Texture for Android 9 and below on Android 10+ (4 GB RAM) it works fine however Android 9 and below with at least 4 GB RAM, it crashes at startup if it tries to read and load the big texture file. Needs investigation --- forge-gui-mobile/src/forge/Forge.java | 8 ++++++++ forge-gui-mobile/src/forge/card/CardImageRenderer.java | 2 +- .../forge/localinstance/properties/ForgeConstants.java | 2 +- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/forge-gui-mobile/src/forge/Forge.java b/forge-gui-mobile/src/forge/Forge.java index 4376d0476e3..4dc7e0ca439 100644 --- a/forge-gui-mobile/src/forge/Forge.java +++ b/forge-gui-mobile/src/forge/Forge.java @@ -67,6 +67,7 @@ public class Forge implements ApplicationListener { public static float heigtModifier = 0.0f; private static boolean isloadingaMatch = false; public static boolean showFPS = false; + public static boolean allowCardBG = false; public static boolean altPlayerLayout = false; public static boolean altZoneTabs = false; public static String enableUIMask = "Crop"; @@ -111,6 +112,13 @@ public class Forge implements ApplicationListener { GuiBase.setIsAndroid(Gdx.app.getType() == Application.ApplicationType.Android); + if (!GuiBase.isAndroid() || androidVersion > 28) { + allowCardBG = true; + } else { + // don't allow to read and process + ForgeConstants.SPRITE_CARDBG_FILE = ""; + } + graphics = new Graphics(); splashScreen = new SplashScreen(); frameRate = new FrameRate(); diff --git a/forge-gui-mobile/src/forge/card/CardImageRenderer.java b/forge-gui-mobile/src/forge/card/CardImageRenderer.java index a7b267366f5..af2e1b36e18 100644 --- a/forge-gui-mobile/src/forge/card/CardImageRenderer.java +++ b/forge-gui-mobile/src/forge/card/CardImageRenderer.java @@ -120,7 +120,7 @@ public class CardImageRenderer { } else { borderColors = CardDetailUtil.getBorderColors(state, canShow); } - Color[] colors = useCardBGTexture ? drawCardBackgroundTexture(state, g, borderColors, x, y, w, h) : fillColorBackground(g, borderColors, x, y, w, h); + Color[] colors = useCardBGTexture && Forge.allowCardBG ? drawCardBackgroundTexture(state, g, borderColors, x, y, w, h) : fillColorBackground(g, borderColors, x, y, w, h); float artInset = blackBorderThickness * 0.5f; float outerBorderThickness = 2 * blackBorderThickness - artInset; diff --git a/forge-gui/src/main/java/forge/localinstance/properties/ForgeConstants.java b/forge-gui/src/main/java/forge/localinstance/properties/ForgeConstants.java index 313d27841b5..81eded8cce2 100644 --- a/forge-gui/src/main/java/forge/localinstance/properties/ForgeConstants.java +++ b/forge-gui/src/main/java/forge/localinstance/properties/ForgeConstants.java @@ -123,7 +123,7 @@ public final class ForgeConstants { public static final String SPRITE_PLANAR_CONQUEST_FILE = "sprite_planar_conquest.png"; public static final String SPRITE_SETLOGO_FILE = "sprite_setlogo.png"; public static final String SPRITE_WATERMARK_FILE = "sprite_watermark.png"; - public static final String SPRITE_CARDBG_FILE = "sprite_cardbg.png"; + public static String SPRITE_CARDBG_FILE = "sprite_cardbg.png"; public static final String FONT_FILE = "font1.ttf"; public static final String SPLASH_BG_FILE = "bg_splash.png"; public static final String MATCH_BG_FILE = "bg_match.jpg"; From 1bb053adaf4e68f61a1d01313cb141357a564443 Mon Sep 17 00:00:00 2001 From: paul_snoops Date: Sat, 11 Sep 2021 10:29:04 +0100 Subject: [PATCH 06/58] MID update --- .../Innistrad Midnight Hunt Commander.txt | 2 + .../res/editions/Innistrad Midnight Hunt.txt | 243 +++++++++++++++++- 2 files changed, 243 insertions(+), 2 deletions(-) diff --git a/forge-gui/res/editions/Innistrad Midnight Hunt Commander.txt b/forge-gui/res/editions/Innistrad Midnight Hunt Commander.txt index a278d392969..1110011d4e2 100644 --- a/forge-gui/res/editions/Innistrad Midnight Hunt Commander.txt +++ b/forge-gui/res/editions/Innistrad Midnight Hunt Commander.txt @@ -15,6 +15,7 @@ ScryfallCode=MIC 35 R Curse of Obsession @Irvin Rodriguez 36 R Visions of Ruin @Andrew Mar 37 R Visions of Dominance @Andrew Mar +38 M Lynde, Cheerful Tormenter @Anna Steinbauer 39 M Leinore, Autumn Sovereign @Fariba Khamseh 40 R Wilhelt, the Rotcleaver @Chris Rallis 69 M Avacyn's Memorial @Kasia 'Kafis' Zielińska @@ -24,3 +25,4 @@ ScryfallCode=MIC 73 R Curse of Obsession @Irvin Rodriguez 74 R Visions of Ruin @Andrew Mar 75 R Visions of Dominance @Andrew Mar +76 M Lynde, Cheerful Tormenter @Anna Steinbauer diff --git a/forge-gui/res/editions/Innistrad Midnight Hunt.txt b/forge-gui/res/editions/Innistrad Midnight Hunt.txt index a83f16e5c81..9d8ce43e720 100644 --- a/forge-gui/res/editions/Innistrad Midnight Hunt.txt +++ b/forge-gui/res/editions/Innistrad Midnight Hunt.txt @@ -10,100 +10,270 @@ Booster=10 Common, 3 Uncommon, 1 RareMythic, 1 BasicLand Prerelease=6 Boosters, 1 RareMythic+ [cards] +1 R Adeline, Resplendent Cathar @Bryan Sola +2 U Ambitious Farmhand @Bryan Sola 3 U Beloved Beggar @Francisco Miyara +4 U Bereaved Survivor @Zara Alfonso +5 C Blessed Defiance @Kim Sokol +6 U Borrowed Time @Andreas Zafiratos 7 R Brutal Cathar @Karl Kopinski 8 C Candlegrove Witch @Anna Christenson 9 C Candletrap @Manuel Castañón +10 C Cathar Commando @Evyn Fong +11 U Cathar's Call @Matt Stewart 12 C Celestus Sanctifier @Irina Nordsol 13 U Chaplain of Alms @Anastasia Ovchinnikova +14 C Clarion Cathars @Leanna Crossan +15 R Curse of Silence @Irina Nordsol +16 U Duelcraft Trainer @John Stanko 17 M Enduring Angel @Irina Nordsol +18 R Fateful Absence @Eric Deschamps +19 C Flare of Faith @Justyna Gil 20 U Gavony Dawnguard @Micah Epstein +21 C Gavony Silversmith @Volkan Baǵa +22 C Gavony Trapper @Caio Monteiro 23 C Hedgewitch's Mask @Ovidio Cartagena +24 C Homestead Courage @Colin Boyer +25 M Intrepid Adversary @Viktor Titov 26 U Loyal Gryff @Ilse Gort +27 C Lunarch Veteran @Igor Kieryluk +28 C Mourning Patrol @Greg Opalinski +29 U Odric's Outrider @Cristi Balanescu 30 C Ritual Guardian @Denman Rooke +31 U Ritual of Hope @Craig J Spearing +32 C Search Party Captain @Mike Bierek +33 R Sigarda's Splendor @Howard Lyon +34 M Sigardian Savior @David Rapoza +35 C Soul-Guide Gryff @Cristi Balanescu 36 C Sungold Barrage @Leanna Crossan +37 R Sungold Sentinel @Marta Nael +38 U Sunset Revelry @Antonio José Manzanedo +39 C Thraben Exorcism @Matt Stewart 40 C Unruly Mob @Ryan Pancoast +41 R Vanquish the Horde @Grzegorz Rutkowski 42 C Baithook Angler @Uriah Voth +43 C Component Collector @Mark Behm 44 C Consider @Zezhou Chen +45 U Covetous Castaway @Dan Scott +46 R Curse of Surveillance @Igor Kieryluk +47 U Delver of Secrets @Matt Stewart +48 C Devious Cover-Up @Colin Boyer 49 U Dissipate @David Palumbo +50 C Drownyard Amalgam @Alex Brock +51 U Fading Hope @Rovina Cai +52 C Falcon Abomination @Brent Hollowell +53 U Firmament Sage @Anastasia Ovchinnikova +54 C Flip the Switch @Campbell White +55 C Galedrifter @Daniel Ljunggren +56 C Geistwave @Olena Richards 57 R Grafted Identity @Manuel Castañón -64 U Neblegast Intruder @Volkan Baǵa +58 C Larder Zombie @E. M. Gist +59 M Lier, Disciple of the Drowned @Ekaterina Burmak +60 C Locked in the Cemetery @Tran Nguyen +61 R Malevolent Hermit @Daarken +62 R Memory Deluge @Lake Hurwitz +63 U Mysterious Tome @David Auden Nash +64 U Nebelgast Intruder @Volkan Baǵa 65 U Ominous Roost @Josu Hernaiz +66 C Organ Hoarder @Nicholas Gregory +67 C Otherworldly Gaze @Chris Cold +68 U Overwhelmed Archivist @Cristi Balanescu +69 R Patrician Geist @Marta Nael +70 U Phantom Carriage @Igor Kieryluk 71 M Poppet Stitcher @Simon Dominic +72 C Revenge of the Drowned @Chris Cold 73 C Secrets of the Key @Alix Branwyn +74 C Shipwreck Sifters @Svetlin Velinov +75 U Skaab Wrangler @Billy Christian 76 R Sludge Monster @Svetlin Velinov 77 M Spectral Adversary @Uriah Voth 78 C Startle @Craig J Spearing 79 C Stormrider Spirit @Lake Hurwitz +80 R Suspicious Stowaway @Jake Murray 81 R Triskaidekaphile @Slawomir Maniak +82 C Unblinking Observer @Filip Burburan +83 U Vivisection @Aaron Miller 84 C Arrogant Outlaw @Aurore Folny 85 U Baneblade Scoundrel @Marta Nael +86 C Bat Whisperer @Jodie Muir +87 C Bladebrand @Khurrum +88 C Blood Pact @Sam White +89 R Bloodline Culling @Liiga Smilshkalne +90 U Bloodtithe Collector @Maria Zolotukhina 91 R Champion of the Perished @Kekai Kotaki +92 U Covert Cutpurse @John Stanko +93 C Crawl from the Cellar @Igor Kieryluk 94 R Curse of Leeches @Uriah Voth 95 C Defenestrate @Darek Zabrocki +96 C Diregraf Horde @Alex Negrea +97 U Dreadhound @Joe Slucher +98 C Duress @Paul Scott Canavan +99 C Eaten Alive @Nicholas Gregory +100 C Ecstatic Awakener @Tuan Duong Chu 101 U Foul Play @Campbell White +102 U Ghoulish Procession @Vincent Proce 103 R Gisa, Glorious Resurrector @Yongjae Choi 104 R Graveyard Trespasser @Chris Rallis 105 U Heirloom Mirror @Josh Hass 106 C Hobbling Zombie @Josh Hass 107 U Infernal Grasp @Naomi Baker 108 R Jadar, Ghoulcaller of Nephalia @Yongjae Choi +109 M Jerren, Corrupted Bishop @Yongjae Choi +110 M Lord of the Forsaken @Kekai Kotaki +111 R Mask of Griselbrand @Jason Felix +112 M The Meathook Massacre @Chris Seaman +113 U Morbid Opportunist @Tyler Walpole +114 C Morkrut Behemoth @Milivoj Ćeran +115 U Necrosynthesis @Isis +116 C No Way Out @Sebastian Giacobino +117 C Novice Occultist @Zara Alfonso +118 C Olivia's Midnight Ambush @Chris Rallis +119 C Rotten Reunion @Aaron Miller +120 C Shady Traveler @Lie Setiawan +121 C Siege Zombie @Johann Bodin +122 R Slaughter Specialist @Kari Christensen +123 U Stromkirk Bloodthief @Caroline Gariba 124 M Tainted Adversary @Tuan Duong Chu +125 C Vampire Interloper @James Ryman +126 U Vengeful Strangler @Sean Sevestre +127 C Abandon the Post @Zoltan Boros +128 C Ardent Elementalist @Miguel Mercado +129 M Bloodthirsty Adversary @Heonhwa Choe 130 C Brimstone Vandal @Andreas Zafiratos +131 R Burn Down the House @Campbell White +132 C Burn the Accursed @David Auden Nash +133 U Cathartic Pyre @Ryan Yee 134 R Curse of Shaken Faith @Campbell White +135 C Electric Revelation @Liiga Smilshkalne +136 C Falkenrath Perforator @Marcela Medeiros +137 R Falkenrath Pit Fighter @Anna Fehr 138 C Famished Foragers @Lorenzo Mastroianni +139 U Fangblade Brigand @Vincent Proce 140 C Festival Crasher @Milivoj Ćeran 141 U Flame Channeler @Véronique Meignaud 142 R Geistflame Reservoir @Anna Christenson +143 C Harvesttide Infiltrator @Mathias Kollros 144 C Immolation @Olena Richards +145 C Lambholt Harrier @Jason Kang 146 R Light Up the Night @Wei Wei 147 U Lunar Frenzy @Alix Branwyn +148 C Moonrager's Slash @Lars Grant-West 149 M Moonveil Regent @Joshua Raphael +150 C Mounted Dreadknight @Josu Hernaiz +151 C Neonate's Rush @Justine Cruz +152 U Obsessive Astronomer @Josu Hernaiz +153 C Pack's Betrayal @Sam Rowan 154 U Play with Fire @Svetlin Velinov +155 U Purifying Dragon @Zezhou Chen +156 C Raze the Effigy @Cristi Balanescu +157 R Reckless Stormseeker @Manuel Castañón +158 U Seize the Storm @Deruchenko Alexander +159 R Smoldering Egg @Simon Dominic +160 U Spellrune Painter @Lucas Graciano +161 C Stolen Vitality @Mike Bierek +162 M Sunstreak Phoenix @Brian Valeza 163 C Tavern Ruffian @Zoltan Boros 164 U Thermo-Alchemist @Raymond Swanland 165 U Village Watch @Nestor Ossandon Leal 166 U Voldaren Ambusher @Evyn Fong +167 C Voldaren Stinger @Martina Fackova 168 R Augur of Autumn @Billy Christian +169 C Bird Admirer @Slawomir Maniak +170 C Bounding Wolf @Andrea Radeck +171 C Bramble Armor @Alessandra Pisano 172 R Briarbridge Tracker @Mathias Kollros +173 U Brood Weaver @Slawomir Maniak 174 U Burly Breaker @Justine Cruz 175 C Candlelit Cavalry @Viko Menezes 176 U Clear Shot @Craig J Spearing +177 M Consuming Blob @Simon Dominic 178 U Contortionist Troupe @Jesper Ejsing +179 U Dawnhart Mentor @Fariba Khamseh 180 C Dawnhart Rejuvenator @Darren Tan +181 U Deathbonnet Sprout @Filip Burburan 182 U Defend the Celestus @Andrey Kuzinskiy +183 U Dryad's Revival @Mila Pesic +184 C Duel for Dominance @Ryan Pancoast +185 C Eccentric Farmer @Tyler Walpole +186 C Harvesttide Sentry @Livia Prima +187 U Hound Tamer @Randy Vargas 188 C Howl of the Hunt @Josu Hernaiz 189 C Might of the Old Ways @Zezhou Chen +190 U Outland Liberator @Randy Vargas +191 C Path to the Festival @Darek Zabrocki 192 C Pestilent Wolf @Oriana Menendez +193 C Plummet @Nicholas Gregory +194 M Primal Adversary @Ilse Gort +195 C Return to Nature @Kim Sokol +196 U Rise of the Ants @Nicholas Gregory 197 R Saryth, the Viper's Fang @Igor Kieryluk +198 C Shadowbeast Sighting @Piotr Foksowicz 199 C Snarling Wolf @Ilse Gort +200 R Storm the Festival @Yigit Koroglu +201 C Tapping at the Window @Nils Hamm +202 C Timberland Guide @Zoltan Boros +203 C Tireless Hauler @Forrest Imel +204 R Tovolar's Huntmaster @Cristi Balanescu 205 U Turn the Earth @Alayna Danner 206 R Unnatural Growth @Svetlin Velinov +207 R Willow Geist @Ryan Yee 208 M Wrenn and Seven @Heonhwa Choe +209 R Angelfire Ignition @Yeong-Hao Han +210 U Arcane Infusion @Jason Felix 211 M Arlinn, the Pack's Hope @Anna Steinbauer 212 U Bladestitched Skaab @Dave Kendall 213 R Can't Stay Away @Milivoj Ćeran 214 U Corpse Cobble @Ravenna Tran 215 R Croaking Counterpart @Yeong-Hao Han +216 U Dawnhart Wardens @Joshua Raphael +217 R Dennick, Pious Apprentice @Chris Rallis 218 U Devoted Grafkeeper @Raoul Vitale +219 R Dire-Strain Rampage @Darek Zabrocki 220 U Diregraf Rebirth @Irina Nordsol 221 U Faithful Mending @Caroline Gariba 222 U Fleshtaker @Kev Walker +223 R Florian, Voldaren Scion @Justine Cruz 224 R Galvanic Iteration @Johann Bodin 225 R Ghoulcaller's Harvest @Anna Steinbauer -226 U Hungry for More @Bastien L. Deharme +226 U Grizzly Ghoul @Vincent Proce +227 R Hallowed Respite @Jason A. Engle +228 U Hungry for More @Bastien L. Deharme 229 U Join the Dance @Raoul Vitale +230 R Katilda, Dawnhart Prime @Bryan Sola 231 U Kessig Naturalist @Johan Grenier 232 R Liesa, Forgotten Archangel @Dmitry Burmak +233 R Ludevic, Necrogenius @Ryan Pancoast 234 R Old Stickfingers @Jehan Choo +235 R Rem Karolus, Stalwart Slayer @Francisco Miyara 236 R Rite of Harmony @Rovina Cai +237 U Rite of Oblivion @Martina Pilcerova +238 U Rootcoil Creeper @Iris Compiet +239 U Sacred Fire @Svetlin Velinov 240 M Sigarda, Champion of Light @Howard Lyon +241 R Siphon Insight @Livia Prima +242 R Slogurk, the Overslime @Nicholas Gregory +243 U Storm Skreelix @Darek Zabrocki +244 U Sunrise Cavalier @John Di Giovanni +245 M Teferi, Who Slows the Sunset @Heonhwa Choe 246 R Tovolar, Dire Overlord @Chris Rahn +247 U Unnatural Moonrise @Ryan Pancoast +248 R Vadrik, Astral Archmage @Kieran Yanner 249 U Vampire Socialite @Suzanne Helmigh +250 R Wake to Slaughter @Chris Cold +251 U Winterthorn Blessing @Yeong-Hao Han 252 R The Celestus @Jonas De Ro +253 C Crossroads Candleguide @Dan Scott +254 C Jack-o'-Lantern @Josu Hernaiz +255 U Moonsilver Key @Joseph Meehan +256 U Mystic Skull @Joe Slucher 257 R Pithing Needle @Ovidio Cartagena +258 C Silver Bolt @Anna Fehr +259 C Stuffed Bear @Joe Slucher 260 R Deserted Beach @Jonas De Ro +261 C Evolving Wilds @Alayna Danner +262 U Field of Ruin @Chris Ostrowski 263 R Haunted Ridge @Jonas De Ro +264 M Hostile Hostel @Daniel Ljunggren 265 R Overgrown Farmland @Jonas De Ro 266 R Rockfall Vale @Muhammad Firdaus 267 R Shipwreck Marsh @Jonas De Ro @@ -117,55 +287,124 @@ Prerelease=6 Boosters, 1 RareMythic+ 275 L Mountain @Dan Mumford 276 L Forest @Alayna Danner 277 L Forest @Dan Mumford + +[borderless] 278 M Wrenn and Seven @Bram Sels 279 M Arlinn, the Pack's Hope @Eric Deschamps +280 M Teferi, Who Slows the Sunset @Francisco Miyara 281 R Deserted Beach @Piotr Dura 282 R Haunted Ridge @Piotr Dura 283 R Overgrown Farmland @Donato Giancola 284 R Rockfall Vale @Piotr Dura 285 R Shipwreck Marsh @Steven Belledin + +[showcase] 286 R Brutal Cathar @Steve Ellis 287 C Candlegrove Witch @Brigitte Roka +288 R Suspicious Stowaway @Joshua Alvarado 289 U Baneblade Scoundrel @Andrea De Dominicis 290 R Graveyard Trespasser @Tyler Crook +291 C Shady Traveler @Joshua Alvarado +292 U Fangblade Brigand @Sami Makkonen +293 C Harvesttide Infiltrator @Sami Makkonen +294 R Reckless Stormseeker @Michael Walsh +295 U Spellrune Painter @Michael Walsh 296 C Tavern Ruffian @Michael Walsh 297 U Village Watch @Andrea De Dominicis +298 C Bird Admirer @Joshua Alvarado 299 U Burly Breaker @Sami Makkonen +300 U Dawnhart Mentor @Antonio Bravo 301 C Dawnhart Rejuvenator @Cabrol +302 U Hound Tamer @Cabrol +303 U Outland Liberator @Brigitte Roka 304 R Saryth, the Viper's Fang @Tyler Crook +305 C Tireless Hauler @Steve Ellis +306 R Tovolar's Huntmaster @Andrea De Dominicis 307 M Arlinn, the Pack's Hope @Emma Rios +308 U Dawnhart Wardens @Antonio Bravo +309 R Katilda, Dawnhart Prime @Tyler Crook 310 U Kessig Naturalist @Rafael Albuquerque 311 R Tovolar, Dire Overlord @Michael Walsh +312 R Adeline, Resplendent Cathar @rishxxv +313 M Lier, Disciple of the Drowned @Evan Cagle 314 R Gisa, Glorious Resurrector @Cabrol 315 R Jadar, Ghoulcaller of Nephalia @DZO +316 M Jerren, Corrupted Bishop @Andrew Mar +317 R Dennick, Pious Apprentice @Paul Jackson +318 R Florian, Voldaren Scion @DZO 319 R Liesa, Forgotten Archangel @Evan Cagle +320 R Ludevic, Necrogenius @Milivoj Ćeran 321 R Old Stickfingers @DZO +322 R Rem Karolus, Stalwart Slayer @Andy Brase 323 M Sigarda, Champion of Light @N.C. Winters +324 R Slogurk, the Overslime @Robbie Trevino +325 R Vadrik, Astral Archmage @Evan Cagle + +[extended art] 326 R Curse of Silence @Irina Nordsol 327 M Enduring Angel @Irina Nordsol +328 R Fateful Absence @Eric Deschamps +329 M Intrepid Adversary @Viktor Titov +330 R Sigarda's Splendor @Howard Lyon +331 M Sigardian Savior @David Rapoza +332 R Sungold Sentinel @Marta Nael +333 R Vanquish the Horde @Grzegorz Rutkowski +334 R Curse of Surveillance @Igor Kieryluk 335 R Grafted Identity @Manuel Castañón +336 R Malevolent Hermit @Daarken +337 R Memory Deluge @Lake Hurwitz +338 R Patrician Geist @Marta Nael 339 M Poppet Stitcher @Simon Dominic +340 R Sludge Monster @Svetlin Velinov 341 M Spectral Adversary @Uriah Voth +342 R Triskaidekaphile @Slawomir Maniak +343 R Bloodline Culling @Liiga Smilshkalne 344 R Champion of the Perished @Kekai Kotaki 345 R Curse of Leeches @Uriah Voth +346 M Lord of the Forsaken @Kekai Kotaki +347 R Mask of Griselbrand @Jason Felix +348 M The Meathook Massacre @Chris Seaman +349 R Slaughter Specialist @Kari Christensen 350 M Tainted Adversary @Tuan Duong Chu +351 M Bloodthirsty Adversary @Heonhwa Choe +352 R Burn Down the House @Campbell White +353 R Curse of Shaken Faith @Campbell White +354 R Falkenrath Pit Fighter @Anna Fehr 355 R Geistflame Reservoir @Anna Christenson 356 R Light Up the Night @Wei Wei +357 M Moonveil Regent @Joshua Raphael +358 R Smoldering Egg @Simon Dominic +359 M Sunstreak Phoenix @Brian Valeza +360 R Augur of Autumn @Billy Christian 361 R Briarbridge Tracker @Mathias Kollros +362 M Consuming Blob @Simon Dominic +363 M Primal Adversary @Ilse Gort +364 R Storm the Festival @Yigit Koroglu 365 R Unnatural Growth @Svetlin Velinov +366 R Willow Geist @Ryan Yee +367 R Angelfire Ignition @Yeong-Hao Han 368 R Can't Stay Away @Milivoj Ćeran 369 R Croaking Counterpart @Yeong-Hao Han +370 R Dire-Strain Rampage @Darek Zabrocki 371 R Galvanic Iteration @Johann Bodin 372 R Ghoulcaller's Harvest @Anna Steinbauer +373 R Hallowed Respite @Jason A. Engle 374 R Rite of Harmony @Rovina Cai +375 R Siphon Insight @Livia Prima +376 R Wake to Slaughter @Chris Cold 377 R The Celestus @Jonas De Ro 378 R Pithing Needle @Ovidio Cartagena +379 M Hostile Hostel @Daniel Ljunggren 380 L Plains @Andreas Rocha 381 L Island @Andreas Rocha 382 L Swamp @Kasia 'Kafis' Zielińska 383 L Mountain @Muhammad Firdaus 384 L Forest @Andreas Rocha + +[buy a box] 385 R Champion of the Perished @Daarken + +[promo] 386 R Triskaidekaphile @Mathias Kollros 387 U Gavony Dawnguard @Micah Epstein 388 C Consider @Zezhou Chen From 2dd5a49e6223d14b70bbd9b7a4fa6c409323a28e Mon Sep 17 00:00:00 2001 From: Anthony Calosa Date: Sat, 11 Sep 2021 10:23:49 +0000 Subject: [PATCH 07/58] Update Forge.java --- forge-gui-mobile/src/forge/Forge.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/forge-gui-mobile/src/forge/Forge.java b/forge-gui-mobile/src/forge/Forge.java index 4dc7e0ca439..ffcb2055a62 100644 --- a/forge-gui-mobile/src/forge/Forge.java +++ b/forge-gui-mobile/src/forge/Forge.java @@ -112,7 +112,7 @@ public class Forge implements ApplicationListener { GuiBase.setIsAndroid(Gdx.app.getType() == Application.ApplicationType.Android); - if (!GuiBase.isAndroid() || androidVersion > 28) { + if (!GuiBase.isAndroid() || (androidVersion > 28 && totalDeviceRAM > 7000)) { allowCardBG = true; } else { // don't allow to read and process From bcaf23c5ff87792cf7322d14ddce3c3ceb940b41 Mon Sep 17 00:00:00 2001 From: Mark Wiggans Date: Sat, 11 Sep 2021 11:32:37 -0400 Subject: [PATCH 08/58] Have ImageCache recognize images with white corners and crop them out --- .../src/main/java/forge/ImageCache.java | 20 ++++++++++++++++++- .../src/main/java/forge/toolbox/FSkin.java | 2 +- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/forge-gui-desktop/src/main/java/forge/ImageCache.java b/forge-gui-desktop/src/main/java/forge/ImageCache.java index 22317de4636..b52ed21c002 100644 --- a/forge-gui-desktop/src/main/java/forge/ImageCache.java +++ b/forge-gui-desktop/src/main/java/forge/ImageCache.java @@ -206,6 +206,7 @@ public class ImageCache { boolean noBorder = !useArtCrop && !isPreferenceEnabled(ForgePreferences.FPref.UI_RENDER_BLACK_BORDERS); boolean fetcherEnabled = isPreferenceEnabled(ForgePreferences.FPref.UI_ENABLE_ONLINE_IMAGE_FETCHER); boolean isPlaceholder = (original == null) && fetcherEnabled; + String setCode = imageKey.split("/")[0].trim().toUpperCase(); // If the user has indicated that they prefer Forge NOT render a black border, round the image corners // to account for JPEG images that don't have a transparency. @@ -213,7 +214,6 @@ public class ImageCache { // use a quadratic equation to calculate the needed radius from an image dimension int radius; float width = original.getWidth(); - String setCode = imageKey.split("/")[0].trim().toUpperCase(); if (setCode.equals("A")) { // Alpha // radius = 100; // 745 x 1040 // radius = 68; // 488 x 680 @@ -239,6 +239,15 @@ public class ImageCache { original = makeRoundedCorner(original, radius); } + // if image has white corners, get try to crop it out + if (original != null && isWhite(FSkin.getColorFromPixel(original.getRGB(0, 0)))) { + if (!isWhiteBorderSet(setCode)) { + int xSpacing = original.getWidth() / 40; + int ySpacing = original.getHeight() / 57; + original = original.getSubimage(xSpacing, ySpacing, original.getWidth() - (2* xSpacing), original.getHeight() - (2* ySpacing)); + } + } + // No image file exists for the given key so optionally associate with // a default "not available" image, however do not add it to the cache, // as otherwise it's problematic to update if the real image gets fetched. @@ -268,6 +277,15 @@ public class ImageCache { return Pair.of(original, isPlaceholder); } + private static boolean isWhite(Color color) { + return color.getRed() > 200 && color.getBlue() > 200 && color.getGreen() > 200; + } + + private static boolean isWhiteBorderSet(String setCode) { + return setCode.equals("U") || setCode.equals("R") || setCode.equals("4E") || setCode.equals("5E") || + setCode.equals("6E") || setCode.equals("7E") || setCode.equals("8E") || setCode.equals("9E"); + } + // cardView is for Emblem, since there is no paper card for them public static BufferedImage scaleImage(String key, final int width, final int height, boolean useDefaultImage, CardView cardView) { if (StringUtils.isEmpty(key) || (3 > width && -1 != width) || (3 > height && -1 != height)) { diff --git a/forge-gui-desktop/src/main/java/forge/toolbox/FSkin.java b/forge-gui-desktop/src/main/java/forge/toolbox/FSkin.java index 8d66a6ee640..1412034efe3 100644 --- a/forge-gui-desktop/src/main/java/forge/toolbox/FSkin.java +++ b/forge-gui-desktop/src/main/java/forge/toolbox/FSkin.java @@ -1498,7 +1498,7 @@ public class FSkin { * @param pixel * @return */ - private static Color getColorFromPixel(final int pixel) { + public static Color getColorFromPixel(final int pixel) { int r, g, b, a; a = (pixel >> 24) & 0x000000ff; r = (pixel >> 16) & 0x000000ff; From c03fc6cfd08bb612bc689c2e347384eff198521e Mon Sep 17 00:00:00 2001 From: Bug Hunter Date: Sat, 11 Sep 2021 19:41:15 +0000 Subject: [PATCH 09/58] Update forge-gui/res/cardsfolder/r/riftmarked_knight.txt --- forge-gui/res/cardsfolder/r/riftmarked_knight.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/forge-gui/res/cardsfolder/r/riftmarked_knight.txt b/forge-gui/res/cardsfolder/r/riftmarked_knight.txt index 9065e9b3cdd..d71ef872ff2 100644 --- a/forge-gui/res/cardsfolder/r/riftmarked_knight.txt +++ b/forge-gui/res/cardsfolder/r/riftmarked_knight.txt @@ -5,7 +5,7 @@ PT:2/2 K:Flanking K:Protection from black K:Suspend:3:1 W W -T:Mode$ CounterRemoved | ValidCard$ Card.Self | TriggerZones$ Exile | CounterType$ TIME | Execute$ TrigToken | IsPresent$ Card.Self+counters_GE1_TIME | PresentZone$ Exile | PresentCompare$ EQ0 | TriggerDescription$ When the last time counter is removed from CARDNAME while it's exiled, create a 2/2 black Knight creature token with flanking, protection from white, and haste. +T:Mode$ CounterRemoved | ValidCard$ Card.Self | TriggerZones$ Exile | CounterType$ TIME | Execute$ TrigToken | NewCounterAmount$ 0 | IsPresent$ Card.Self+counters_GE1_TIME | PresentZone$ Exile | PresentCompare$ EQ0 | TriggerDescription$ When the last time counter is removed from CARDNAME while it's exiled, create a 2/2 black Knight creature token with flanking, protection from white, and haste. SVar:TrigToken:DB$Token | TokenAmount$ 1 | TokenOwner$ You | TokenScript$ b_2_2_knight_flanking_pro_white_haste | LegacyImage$ b 2 2 knight flanking pro white haste plc SVar:Picture:http://www.wizards.com/global/images/magic/general/riftmarked_knight.jpg Oracle:Protection from black; flanking (Whenever a creature without flanking blocks this creature, the blocking creature gets -1/-1 until end of turn.)\nSuspend 3—{1}{W}{W}\nWhen the last time counter is removed from Riftmarked Knight while it's exiled, create a 2/2 black Knight creature token with flanking, protection from white, and haste. From fb092c77a48c570d0a95ff9604ae18ddf66a0d80 Mon Sep 17 00:00:00 2001 From: leriomaggio Date: Sun, 12 Sep 2021 00:56:44 +0100 Subject: [PATCH 10/58] BUG FIX for Smart Card Art selection This subtle bug occurred whenever the algorithm for smart card art selection wanted to add a card with multiple arts and the number of cards per art to add was not even. To avoid zeros, the cardsPerArtIndex was set at least to one, and so the rest - leading then to adding too many (extra) cards not originally present in the deck. Thanks to @Snoops for the heads up. --- forge-core/src/main/java/forge/deck/Deck.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/forge-core/src/main/java/forge/deck/Deck.java b/forge-core/src/main/java/forge/deck/Deck.java index 9d15cf578bb..11062d0d153 100644 --- a/forge-core/src/main/java/forge/deck/Deck.java +++ b/forge-core/src/main/java/forge/deck/Deck.java @@ -366,8 +366,8 @@ public class Deck extends DeckBase implements Iterable 0 ? totalNrToAdd % nrOfAvailableArts : 0; cardsPerArtIndex = Math.max(1, cardsPerArtIndex); // make sure is never zero - int restOfCardsToAdd = totalNrToAdd % nrOfAvailableArts; int cardsAdded = 0; PaperCard alternativeCardArt = null; for (int artIndex = 1; artIndex <= nrOfAvailableArts; artIndex++){ From abbe687419e84d957290bf08312727c961386a68 Mon Sep 17 00:00:00 2001 From: Northmoc Date: Sat, 11 Sep 2021 23:02:34 -0400 Subject: [PATCH 11/58] vampire_socialite.txt (Suthro) --- .../res/cardsfolder/upcoming/vampire_socialite.txt | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 forge-gui/res/cardsfolder/upcoming/vampire_socialite.txt diff --git a/forge-gui/res/cardsfolder/upcoming/vampire_socialite.txt b/forge-gui/res/cardsfolder/upcoming/vampire_socialite.txt new file mode 100644 index 00000000000..f12fd11eae0 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/vampire_socialite.txt @@ -0,0 +1,13 @@ +Name:Vampire Socialite +ManaCost:B R +Types:Creature Vampire Noble +PT:2/2 +K:Menace +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | CheckSVar$ X | Execute$ TrigPutCounter | TriggerDescription$ When CARDNAME enters the battlefield, if an opponent lost life this turn, put a +1/+1 counter on each other Vampire you control. +SVar:TrigPutCounter:DB$ PutCounterAll | ValidCards$ Vampire.YouCtrl+Other | CounterType$ P1P1 | CounterNum$ 1 +K:ETBReplacement:Other:AddExtraCounter:Mandatory:Battlefield:Vampire.YouCtrl+Other +SVar:AddExtraCounter:DB$ PutCounter | ETB$ True | Defined$ ReplacedCard | CounterType$ P1P1 | CounterNum$ 1 | ConditionCheckSVar$ X | ConditionSVarCompare$ GE1 | SpellDescription$ As long as an opponent lost life this turn, each other Vampire you control enters the battlefield with an additional +1/+1 counter on it. +SVar:X:Count$LifeOppsLostThisTurn +DeckNeeds:Type$Vampire +DeckHas:Ability$Counters +Oracle:Menace (This creature can’t be blocked except by two or more creatures.)\nWhen Vampire Socialite enters the battlefield, if an opponent lost life this turn, put a +1/+1 counter on each other Vampire you control.\nAs long as an opponent lost life this turn, each other Vampire you control enters the battlefield with an additional +1/+1 counter on it. From b13f4f4ae2808a815ed9ea147ab81ad4332fadf8 Mon Sep 17 00:00:00 2001 From: Northmoc Date: Sat, 11 Sep 2021 23:08:27 -0400 Subject: [PATCH 12/58] faithful_mending.txt --- forge-gui/res/cardsfolder/upcoming/faithful_mending.txt | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 forge-gui/res/cardsfolder/upcoming/faithful_mending.txt diff --git a/forge-gui/res/cardsfolder/upcoming/faithful_mending.txt b/forge-gui/res/cardsfolder/upcoming/faithful_mending.txt new file mode 100644 index 00000000000..a9f5681dcf1 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/faithful_mending.txt @@ -0,0 +1,9 @@ +Name:Faithful Mending +ManaCost:W U +Types:Instant +A:SP$ GainLife | Defined$ You | LifeAmount$ 2 | SubAbility$ DBDraw | StackDescription$ {p:You} gains 2 life, | SpellDescription$ You gain 2 life, draw two cards, then discard two cards. +SVar:DBDraw:DB$ Draw | NumCards$ 2 | SubAbility$ DBDiscard | StackDescription$ draws two cards, +SVar:DBDiscard:DB$ Discard | Defined$ You | NumCards$ 2 | Mode$ TgtChoose | StackDescription$ then discards two cards. +K:Flashback:1 W U +DeckHas:Ability$LifeGain & Ability$Discard & Ability$Graveyard +Oracle:You gain 2 life, draw two cards, then discard two cards.\nFlashback {1}{W}{U} (You may cast this card from your graveyard for its flashback cost. Then exile it.) From 314df20711d003b0be443a1e3e1ccf4fcb8652b4 Mon Sep 17 00:00:00 2001 From: Northmoc Date: Fri, 10 Sep 2021 10:33:07 -0400 Subject: [PATCH 13/58] enduring_angel_angelic_enforcer.txt --- .../enduring_angel_angelic_enforcer.txt | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 forge-gui/res/cardsfolder/upcoming/enduring_angel_angelic_enforcer.txt diff --git a/forge-gui/res/cardsfolder/upcoming/enduring_angel_angelic_enforcer.txt b/forge-gui/res/cardsfolder/upcoming/enduring_angel_angelic_enforcer.txt new file mode 100644 index 00000000000..11cebfb7dd1 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/enduring_angel_angelic_enforcer.txt @@ -0,0 +1,31 @@ +Name:Enduring Angel +ManaCost:2 W W W +Types:Creature Angel +PT:3/3 +K:Flying +K:Double Strike +S:Mode$ Continuous | Affected$ You | AddKeyword$ Hexproof | Description$ You have hexproof. +R:Event$ LifeReduced | ValidPlayer$ You | Result$ LE0 | ReplaceWith$ Transform | Description$ If your life total would be reduced to 0 or less, instead transform CARDNAME and your life total becomes 3. Then if CARDNAME didn't transform this way, you lose the game. +SVar:Transform:DB$ SetState | Defined$ Self | Mode$ Transform | RememberChanged$ True | SubAbility$ DBSetLife +SVar:DBSetLife:DB$ SetLife | Defined$ You | LifeAmount$ 3 | SubAbility$ DBLoseGame +SVar:DBLoseGame:DB$ LosesGame | Defined$ You | ConditionDefined$ Remembered | ConditionPresent$ Card | ConditionCompare$ NE1 | SubAbility$ DBCleanup +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True +AlternateMode:DoubleFaced +Oracle:Flying, double strike\nYou have hexproof.\nIf your life total would be reduced to 0 or less, instead transform Enduring Angel and your life total becomes 3. Then if Enduring Angel didn't transform this way, you lose the game. + +ALTERNATE + +Name:Angelic Enforcer +ManaCost:no cost +Colors:white +Types:Creature Angel +PT:*/* +K:Flying +S:Mode$ Continuous | Affected$ You | AddKeyword$ Hexproof | Description$ You have hexproof. +S:Mode$ Continuous | EffectZone$ All | CharacteristicDefining$ True | SetPower$ X | SetToughness$ X | Description$ CARDNAME's power and toughness are each equal to your life total. +T:Mode$ Attacks | ValidCard$ Card.Self | Execute$ TrigDoubleLife | TriggerDescription$ Whenever CARDNAME attacks, double your life total. +SVar:TrigDoubleLife:DB$ GainLife | LifeAmount$ X +SVar:X:Count$YourLifeTotal +SVar:HasAttackEffect:TRUE +DeckHas:Ability$LifeGain +Oracle:Flying\nYou have hexproof.\nAngelic Enforcer's power and toughness are each equal to your life total.\nWhenever Angelic Enforcer attacks, double your life total. From 0df55264522fbd9344994f35155cb79f33d12d9c Mon Sep 17 00:00:00 2001 From: Northmoc Date: Fri, 10 Sep 2021 10:42:58 -0400 Subject: [PATCH 14/58] add ReplaceLifeReduced --- .../game/replacement/ReplaceLifeReduced.java | 44 +++++++++++++++++++ .../game/replacement/ReplacementType.java | 1 + 2 files changed, 45 insertions(+) create mode 100644 forge-game/src/main/java/forge/game/replacement/ReplaceLifeReduced.java diff --git a/forge-game/src/main/java/forge/game/replacement/ReplaceLifeReduced.java b/forge-game/src/main/java/forge/game/replacement/ReplaceLifeReduced.java new file mode 100644 index 00000000000..d6b6f770ec7 --- /dev/null +++ b/forge-game/src/main/java/forge/game/replacement/ReplaceLifeReduced.java @@ -0,0 +1,44 @@ +package forge.game.replacement; + +import java.util.Map; + +import forge.game.ability.AbilityKey; +import forge.game.card.Card; +import forge.util.Expressions; + +/** + * TODO: Write javadoc for this type. + * + */ +public class ReplaceLifeReduced extends ReplacementEffect { + + /** + * Instantiates a new replace life reduced. + * + * @param map the map + * @param host the host + */ + public ReplaceLifeReduced(Map map, Card host, boolean intrinsic) { + super(map, host, intrinsic); + } + + /* (non-Javadoc) + * @see forge.card.replacement.ReplacementEffect#canReplace(java.util.HashMap) + */ + @Override + public boolean canReplace(Map runParams) { + if (!matchesValidParam("ValidPlayer", runParams.get(AbilityKey.Affected))) { + return false; + } + if (hasParam("Result")) { + final int n = (Integer)runParams.get(AbilityKey.Result); + String comparator = getParam("Result"); + final String operator = comparator.substring(0, 2); + final int operandValue = Integer.parseInt(comparator.substring(2)); + if (!Expressions.compare(n, operator, operandValue)) { + return false; + } + } + return true; + } +} diff --git a/forge-game/src/main/java/forge/game/replacement/ReplacementType.java b/forge-game/src/main/java/forge/game/replacement/ReplacementType.java index 335564f6c62..4a15d9aa846 100644 --- a/forge-game/src/main/java/forge/game/replacement/ReplacementType.java +++ b/forge-game/src/main/java/forge/game/replacement/ReplacementType.java @@ -29,6 +29,7 @@ public enum ReplacementType { GainLife(ReplaceGainLife.class), GameLoss(ReplaceGameLoss.class), Learn(ReplaceLearn.class), + LifeReduced(ReplaceLifeReduced.class), Mill(ReplaceMill.class), Moved(ReplaceMoved.class), ProduceMana(ReplaceProduceMana.class), From be9c1d7c91173b1242eb9d7af7d5fa29fed0018c Mon Sep 17 00:00:00 2001 From: Northmoc Date: Fri, 10 Sep 2021 10:43:20 -0400 Subject: [PATCH 15/58] fix desc --- .../src/main/java/forge/game/replacement/ReplaceGameLoss.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/forge-game/src/main/java/forge/game/replacement/ReplaceGameLoss.java b/forge-game/src/main/java/forge/game/replacement/ReplaceGameLoss.java index c39f669d26e..0b3d35d3e33 100644 --- a/forge-game/src/main/java/forge/game/replacement/ReplaceGameLoss.java +++ b/forge-game/src/main/java/forge/game/replacement/ReplaceGameLoss.java @@ -12,7 +12,7 @@ import forge.game.card.Card; public class ReplaceGameLoss extends ReplacementEffect { /** - * Instantiates a new replace gain life. + * Instantiates a new replace game loss. * * @param map the map * @param host the host From eb25257a182327924461f2b5ab616cf4ffa038b4 Mon Sep 17 00:00:00 2001 From: Northmoc Date: Fri, 10 Sep 2021 10:45:39 -0400 Subject: [PATCH 16/58] Player.java - add replacement to loseLife --- forge-game/src/main/java/forge/game/player/Player.java | 7 +++++++ 1 file changed, 7 insertions(+) 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 3a6ae63f5e8..963ed5be703 100644 --- a/forge-game/src/main/java/forge/game/player/Player.java +++ b/forge-game/src/main/java/forge/game/player/Player.java @@ -551,6 +551,13 @@ public class Player extends GameEntity implements Comparable { } if (toLose > 0) { int oldLife = life; + // Run applicable replacement effects + final Map repParams = AbilityKey.mapFromAffected(this); + repParams.put(AbilityKey.Result, oldLife-toLose); + if (game.getReplacementHandler().run(ReplacementType.LifeReduced, repParams) + != ReplacementResult.NotReplaced) { + return 0; + } life -= toLose; view.updateLife(this); lifeLost = toLose; From 60d4d143204dcc69bd9d02181ad2c98b3ad7c41e Mon Sep 17 00:00:00 2001 From: Northmoc Date: Sat, 11 Sep 2021 22:58:59 -0400 Subject: [PATCH 17/58] SetLife instead of GainLife --- forge-gui/res/cardsfolder/b/beacon_of_immortality.txt | 7 +++---- forge-gui/res/cardsfolder/r/revival_revenge.txt | 9 +++++---- .../upcoming/enduring_angel_angelic_enforcer.txt | 4 ++-- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/forge-gui/res/cardsfolder/b/beacon_of_immortality.txt b/forge-gui/res/cardsfolder/b/beacon_of_immortality.txt index 6311a3817fd..8294d127e6f 100644 --- a/forge-gui/res/cardsfolder/b/beacon_of_immortality.txt +++ b/forge-gui/res/cardsfolder/b/beacon_of_immortality.txt @@ -1,8 +1,7 @@ Name:Beacon of Immortality ManaCost:5 W Types:Instant -A:SP$ GainLife | Cost$ 5 W | ValidTgts$ Player | TgtPrompt$ Select target player | LifeAmount$ X | SubAbility$ DBShuffle | SpellDescription$ Double target player's life total. Shuffle CARDNAME into its owner's library. -SVar:DBShuffle:DB$ChangeZone | Origin$ Stack | Destination$ Library | Shuffle$ True -SVar:X:TargetedPlayer$LifeTotal -SVar:Picture:http://www.wizards.com/global/images/magic/general/beacon_of_immortality.jpg +A:SP$ SetLife | ValidTgts$ Player | TgtPrompt$ Select target player | LifeAmount$ X | SubAbility$ DBShuffle | SpellDescription$ Double target player's life total. Shuffle CARDNAME into its owner's library. +SVar:DBShuffle:DB$ ChangeZone | Origin$ Stack | Destination$ Library | Shuffle$ True +SVar:X:TargetedPlayer$LifeTotal/Twice Oracle:Double target player's life total. Shuffle Beacon of Immortality into its owner's library. diff --git a/forge-gui/res/cardsfolder/r/revival_revenge.txt b/forge-gui/res/cardsfolder/r/revival_revenge.txt index 0c21541fdc5..00c1c485b06 100644 --- a/forge-gui/res/cardsfolder/r/revival_revenge.txt +++ b/forge-gui/res/cardsfolder/r/revival_revenge.txt @@ -1,8 +1,9 @@ Name:Revival ManaCost:WB WB -AlternateMode: Split +AlternateMode:Split Types:Sorcery -A:SP$ ChangeZone | Cost$ WB WB | Origin$ Graveyard | Destination$ Battlefield | TgtPrompt$ Choose target creature in your graveyard | ValidTgts$ Creature.YouOwn+cmcLE3 | SpellDescription$ Return target creature card with mana value 3 or less from your graveyard to the battlefield. +A:SP$ ChangeZone | Origin$ Graveyard | Destination$ Battlefield | TgtPrompt$ Select target creature card in your graveyard with mana value 3 or less | ValidTgts$ Creature.YouOwn+cmcLE3 | SpellDescription$ Return target creature card with mana value 3 or less from your graveyard to the battlefield. +DeckHas:Ability$Graveyard Oracle:Return target creature card with mana value 3 or less from your graveyard to the battlefield. ALTERNATE @@ -10,8 +11,8 @@ ALTERNATE Name:Revenge ManaCost:4 W B Types:Sorcery -A:SP$ GainLife | Cost$ 4 W B | LifeAmount$ X | SubAbility$ DBLoseHalf | SpellDescription$ Double your life total. Target opponent loses half their life, rounded up. +A:SP$ SetLife | LifeAmount$ X | SubAbility$ DBLoseHalf | SpellDescription$ Double your life total. Target opponent loses half their life, rounded up. SVar:DBLoseHalf:DB$ LoseLife | ValidTgts$ Opponent | LifeAmount$ Y -SVar:X:Count$YourLifeTotal +SVar:X:Count$YourLifeTotal/Twice SVar:Y:Count$TargetedLifeTotal/HalfUp Oracle:Double your life total. Target opponent loses half their life, rounded up. diff --git a/forge-gui/res/cardsfolder/upcoming/enduring_angel_angelic_enforcer.txt b/forge-gui/res/cardsfolder/upcoming/enduring_angel_angelic_enforcer.txt index 11cebfb7dd1..9848f14e4bc 100644 --- a/forge-gui/res/cardsfolder/upcoming/enduring_angel_angelic_enforcer.txt +++ b/forge-gui/res/cardsfolder/upcoming/enduring_angel_angelic_enforcer.txt @@ -24,8 +24,8 @@ K:Flying S:Mode$ Continuous | Affected$ You | AddKeyword$ Hexproof | Description$ You have hexproof. S:Mode$ Continuous | EffectZone$ All | CharacteristicDefining$ True | SetPower$ X | SetToughness$ X | Description$ CARDNAME's power and toughness are each equal to your life total. T:Mode$ Attacks | ValidCard$ Card.Self | Execute$ TrigDoubleLife | TriggerDescription$ Whenever CARDNAME attacks, double your life total. -SVar:TrigDoubleLife:DB$ GainLife | LifeAmount$ X +SVar:TrigDoubleLife:DB$ SetLife | LifeAmount$ Y SVar:X:Count$YourLifeTotal +SVar:Y:Count$YourLifeTotal/Twice SVar:HasAttackEffect:TRUE -DeckHas:Ability$LifeGain Oracle:Flying\nYou have hexproof.\nAngelic Enforcer's power and toughness are each equal to your life total.\nWhenever Angelic Enforcer attacks, double your life total. From db988abb4f2abe7a90306d8477de7c572ab63c6f Mon Sep 17 00:00:00 2001 From: Northmoc Date: Thu, 9 Sep 2021 19:15:25 -0400 Subject: [PATCH 18/58] Change SimpleString to comma for multiple parts --- forge-game/src/main/java/forge/game/cost/Cost.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 d696a6b1c3e..f0d7d5e69b6 100644 --- a/forge-game/src/main/java/forge/game/cost/Cost.java +++ b/forge-game/src/main/java/forge/game/cost/Cost.java @@ -684,7 +684,7 @@ public class Cost implements Serializable { boolean first = true; for (final CostPart part : this.costParts) { if (!first) { - cost.append(" and "); + cost.append(", "); } cost.append(part.toString()); first = false; From 23967b3d85ddcdac4e33659fc30c1cb0bf26906d Mon Sep 17 00:00:00 2001 From: Northmoc Date: Thu, 9 Sep 2021 19:16:13 -0400 Subject: [PATCH 19/58] improve CostRemoveAnyCounter prompt formatting --- .../src/main/java/forge/game/cost/CostRemoveAnyCounter.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/forge-game/src/main/java/forge/game/cost/CostRemoveAnyCounter.java b/forge-game/src/main/java/forge/game/cost/CostRemoveAnyCounter.java index d800a3ae0fb..0bd3c0a95df 100644 --- a/forge-game/src/main/java/forge/game/cost/CostRemoveAnyCounter.java +++ b/forge-game/src/main/java/forge/game/cost/CostRemoveAnyCounter.java @@ -100,10 +100,11 @@ public class CostRemoveAnyCounter extends CostPart { public final String toString() { final StringBuilder sb = new StringBuilder(); - String counters = this.counter == null ? "counter" : this.counter.getName() + " counter"; + String counters = this.counter == null ? "counter" : this.counter.getName().toLowerCase() + " counter"; sb.append("Remove "); sb.append(Cost.convertAmountTypeToWords(this.convertAmount(), this.getAmount(), counters)); + sb.append(this.getAmount().equals("1") ? "" : "s"); final String desc = this.getTypeDescription() == null ? this.getType() : this.getTypeDescription(); sb.append(" from ").append(desc); From cfc143f398e46beee4a2beac2ee5a5b655ce770c Mon Sep 17 00:00:00 2001 From: Northmoc Date: Thu, 9 Sep 2021 19:16:42 -0400 Subject: [PATCH 20/58] improve CostRemoveCounter prompt formatting --- .../src/main/java/forge/game/cost/CostRemoveCounter.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) 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 517d092ca6c..afbd85ba6d4 100644 --- a/forge-game/src/main/java/forge/game/cost/CostRemoveCounter.java +++ b/forge-game/src/main/java/forge/game/cost/CostRemoveCounter.java @@ -108,10 +108,11 @@ public class CostRemoveCounter extends CostPart { } else { sb.append("Remove "); final Integer i = this.convertAmount(); - sb.append(Cost.convertAmountTypeToWords(i, this.getAmount(), this.counter.getName() + " counter")); - - if (this.getAmount().equals("All")) { - sb.append("s"); + if (this.getAmount().equals("X")) { + sb.append("any number of counters"); + } else { + sb.append(Cost.convertAmountTypeToWords(i, this.getAmount(), this.counter.getName().toLowerCase())); + sb.append(this.getAmount().equals("1") ? " counter" : " counters"); } sb.append(" from "); From 957e3ebfb0d0827d15940b948e117bab359a353a Mon Sep 17 00:00:00 2001 From: Northmoc Date: Thu, 9 Sep 2021 19:17:20 -0400 Subject: [PATCH 21/58] Various typos and formatting fixes --- forge-gui/res/languages/en-US.properties | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/forge-gui/res/languages/en-US.properties b/forge-gui/res/languages/en-US.properties index 2f60d98b709..7a138580850 100644 --- a/forge-gui/res/languages/en-US.properties +++ b/forge-gui/res/languages/en-US.properties @@ -1455,7 +1455,7 @@ lblLogAsPoisonCounters=(as poison counters) lblCombat=combat lblNonCombat=non-combat lblLogSourceDealsNDamageOfTypeToDest={0} deals {1} {2} damage to {3}{4}. -lblLogPlayerReceivesNPosionCounterFrom={0} receives {1} posion counter from {2} +lblLogPlayerReceivesNPosionCounterFrom={0} receives {1} poison counter from {2} lblLogPlayerAssignedAttackerToAttackTarget={0} assigned {1} to attack {2}. lblLogPlayerDidntBlockAttacker={0} didn''t block {1}. lblLogPlayerAssignedBlockerToBlockAttacker={0} assigned {1} to block {2}. @@ -2322,12 +2322,12 @@ lblPutZoneCardsToLibrary=Put cards from {0} to Library lblPutNTypeCounterOnTarget=Put {0} {1} counter on {2} lblReturnCardToHandConfirm=Return {0} to hand? lblNTypeCardsToHand=Return {0} {1} card(s) to Hand -lblSelectNCardOfSameColorToReveal=Select {0} Card of same color to reveal. +lblSelectNCardOfSameColorToReveal=Select {0} card(s) of same color to reveal. lblSelectNMoreTypeCardsTpReveal=Select {0} more {1} card(s) to reveal. lblSelectTargetCounter=Select {0} to remove a counter lblRemoveCounterFromCard=remove counter from card lblRemoveAllCountersConfirm=Remove all counters? -lblRemoveNTargetCounterFromCardPayCostConfirm=Pay Cost: Remove {0} {1} counter from {2}? +lblRemoveNTargetCounterFromCardPayCostConfirm=Remove {0} {1} counter(s) from {2}? lblRemoveCountersFromAInZoneCard=Remove counter(s) from a card in {0} lblSacrificeCardConfirm=Sacrifice {0}? lblSelectATargetToSacrifice=Select {0} to sacrifice ({1} left) From 6e0b6a2b9ee0b8c31de17aef50ef04916067c3d0 Mon Sep 17 00:00:00 2001 From: Northmoc Date: Thu, 9 Sep 2021 19:18:52 -0400 Subject: [PATCH 22/58] counter name to lower case in prompt --- forge-gui/src/main/java/forge/player/HumanCostDecision.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/forge-gui/src/main/java/forge/player/HumanCostDecision.java b/forge-gui/src/main/java/forge/player/HumanCostDecision.java index 068adbb1e94..053eb2b0dde 100644 --- a/forge-gui/src/main/java/forge/player/HumanCostDecision.java +++ b/forge-gui/src/main/java/forge/player/HumanCostDecision.java @@ -849,7 +849,7 @@ public class HumanCostDecision extends CostDecisionMakerBase { this.validChoices = validCards; counterType = cType; - setMessage(Localizer.getInstance().getMessage("lblRemoveNTargetCounterFromCardPayCostConfirm", "%d", counterType == null ? "any" : counterType.getName(), costPart.getDescriptiveType())); + setMessage(Localizer.getInstance().getMessage("lblRemoveNTargetCounterFromCardPayCostConfirm", "%d", counterType == null ? "any" : counterType.getName().toLowerCase(), costPart.getDescriptiveType())); } @Override @@ -967,7 +967,7 @@ public class HumanCostDecision extends CostDecisionMakerBase { if (maxCounters < cntRemoved) { return null; } - if (!player.getController().confirmPayment(cost, Localizer.getInstance().getMessage("lblRemoveNTargetCounterFromCardPayCostConfirm", String.valueOf(amount), cost.counter.getName(), CardTranslation.getTranslatedName(source.getName())), ability)) { + if (!player.getController().confirmPayment(cost, Localizer.getInstance().getMessage("lblRemoveNTargetCounterFromCardPayCostConfirm", amount, cost.counter.getName().toLowerCase(), CardTranslation.getTranslatedName(source.getName())), ability)) { return null; } } From 615ec642d28c9a6af91d35aee4c4fb6f16e9c1ac Mon Sep 17 00:00:00 2001 From: Meerkov Date: Sun, 12 Sep 2021 06:20:52 +0000 Subject: [PATCH 23/58] AI Ban Camouflage --- forge-gui/res/cardsfolder/c/camouflage.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/forge-gui/res/cardsfolder/c/camouflage.txt b/forge-gui/res/cardsfolder/c/camouflage.txt index d46d860dfd7..9ae4487b942 100644 --- a/forge-gui/res/cardsfolder/c/camouflage.txt +++ b/forge-gui/res/cardsfolder/c/camouflage.txt @@ -4,4 +4,5 @@ Types:Instant A:SP$ Effect | Cost$ G | ReplacementEffects$ RDeclareBlocker | ActivationPhases$ Declare Attackers | PlayerTurn$ True | AILogic$ Evasion | SpellDescription$ Cast this spell only during your declare attackers step. This turn, instead of declaring blockers, each defending player chooses any number of creatures they control and divides them into a number of piles equal to the number of attacking creatures for whom that player is the defending player. Creatures those players control that can block additional creatures may likewise be put into additional piles. Assign each pile to a different one of those attacking creatures at random. Each creature in a pile that can block the creature that pile is assigned to does so. (Piles can be empty.) SVar:RDeclareBlocker:Event$ DeclareBlocker | ValidPlayer$ Opponent | ReplaceWith$ DBCamouflage | Description$ This turn, instead of declaring blockers, each defending player chooses any number of creatures they control and divides them into a number of piles equal to the number of attacking creatures for whom that player is the defending player. Creatures those players control that can block additional creatures may likewise be put into additional piles. Assign each pile to a different one of those attacking creatures at random. Each creature in a pile that can block the creature that pile is assigned to does so. (Piles can be empty.) SVar:DBCamouflage:DB$ Camouflage | Defined$ ReplacedPlayer | Defender$ ReplacedDefendingPlayer | AILogic$ BestBlocker +AI:RemoveDeck:All Oracle:Cast this spell only during your declare attackers step.\nThis turn, instead of declaring blockers, each defending player chooses any number of creatures they control and divides them into a number of piles equal to the number of attacking creatures for whom that player is the defending player. Creatures those players control that can block additional creatures may likewise be put into additional piles. Assign each pile to a different one of those attacking creatures at random. Each creature in a pile that can block the creature that pile is assigned to does so. (Piles can be empty.) From 6f0ba03d241a93c533e159c9bafcce33f11d88f7 Mon Sep 17 00:00:00 2001 From: Anthony Calosa Date: Sun, 12 Sep 2021 15:52:57 +0800 Subject: [PATCH 24/58] [Mobile] Fix blinking card images while using online image fetcher - also cache card list for faster lookup --- forge-core/src/main/java/forge/ImageKeys.java | 306 ++++++++++++------ .../src/main/java/forge/item/PaperCard.java | 7 +- .../src/forge/assets/ImageCache.java | 32 +- .../src/forge/card/CardRenderer.java | 1 - .../src/forge/deck/FDeckEditor.java | 1 - .../src/forge/screens/home/LoadGameMenu.java | 7 +- .../main/java/forge/util/ImageFetcher.java | 5 + 7 files changed, 240 insertions(+), 119 deletions(-) diff --git a/forge-core/src/main/java/forge/ImageKeys.java b/forge-core/src/main/java/forge/ImageKeys.java index e825c09750a..9112855b0eb 100644 --- a/forge-core/src/main/java/forge/ImageKeys.java +++ b/forge-core/src/main/java/forge/ImageKeys.java @@ -65,6 +65,7 @@ public final class ImageKeys { return tokenKey.substring(ImageKeys.TOKEN_PREFIX.length()); } + private static final Map cachedCards = new HashMap<>(50000); public static File getImageFile(String key) { if (StringUtils.isEmpty(key)) return null; @@ -97,101 +98,142 @@ public final class ImageKeys { dir = CACHE_CARD_PICS_DIR; } - File file = findFile(dir, filename); - if (file != null) { return file; } + File cachedFile = cachedCards.get(filename); + if (cachedFile != null) { + return cachedFile; + } else { + File file = findFile(dir, filename); + if (file != null) { + cachedCards.put(filename, file); + return file; + } - // AE -> Ae and Ae -> AE for older cards with different file names - // on case-sensitive file systems - if (filename.contains("Ae")) { - file = findFile(dir, TextUtil.fastReplace(filename, "Ae", "AE")); - if (file != null) { return file; } - } else if (filename.contains("AE")) { - file = findFile(dir, TextUtil.fastReplace(filename, "AE", "Ae")); - if (file != null) { return file; } - } - //try fullborder... - if (filename.contains(".full")) { - String fullborderFile = TextUtil.fastReplace(filename, ".full", ".fullborder"); - file = findFile(dir, fullborderFile); - if (file != null) { return file; } - // if there's a 1st art variant try without it for .fullborder images - file = findFile(dir, TextUtil.fastReplace(fullborderFile, "1.fullborder", ".fullborder")); - if (file != null) { return file; } - // if there's a 1st art variant try with it for .fullborder images - file = findFile(dir, fullborderFile.replaceAll("[0-9]*.fullborder", "1.fullborder")); - if (file != null) { return file; } - // if there's an art variant try without it for .full images - file = findFile(dir, filename.replaceAll("[0-9].full",".full")); - if (file != null) { return file; } - // if there's a 1st art variant try with it for .full images - file = findFile(dir, filename.replaceAll("[0-9]*.full", "1.full")); - if (file != null) { return file; } - //setlookup - if (!StaticData.instance().getSetLookup().isEmpty()) { - for (String setKey : StaticData.instance().getSetLookup().keySet()) { - if (filename.startsWith(setKey)) { - for (String setLookup : StaticData.instance().getSetLookup().get(setKey)) { - //.fullborder lookup - file = findFile(dir, TextUtil.fastReplace(fullborderFile, setKey, getSetFolder(setLookup))); - if (file != null) { return file; } - file = findFile(dir, TextUtil.fastReplace(fullborderFile, setKey, getSetFolder(setLookup)).replaceAll("[0-9]*.fullborder", "1.fullborder")); - if (file != null) { return file; } - //.full lookup - file = findFile(dir, TextUtil.fastReplace(filename, setKey, getSetFolder(setLookup))); - if (file != null) { return file; } - file = findFile(dir, TextUtil.fastReplace(filename, setKey, getSetFolder(setLookup)).replaceAll("[0-9]*.full", "1.full")); - if (file != null) { return file; } + // AE -> Ae and Ae -> AE for older cards with different file names + // on case-sensitive file systems + if (filename.contains("Ae")) { + file = findFile(dir, TextUtil.fastReplace(filename, "Ae", "AE")); + if (file != null) { + cachedCards.put(filename, file); + return file; + } + } else if (filename.contains("AE")) { + file = findFile(dir, TextUtil.fastReplace(filename, "AE", "Ae")); + if (file != null) { + cachedCards.put(filename, file); + return file; + } + } + //try fullborder... + if (filename.contains(".full")) { + String fullborderFile = TextUtil.fastReplace(filename, ".full", ".fullborder"); + file = findFile(dir, fullborderFile); + if (file != null) { + cachedCards.put(filename, file); + return file; + } + // if there's a 1st art variant try without it for .fullborder images + file = findFile(dir, TextUtil.fastReplace(fullborderFile, "1.fullborder", ".fullborder")); + if (file != null) { + cachedCards.put(filename, file); + return file; + } + // if there's a 1st art variant try with it for .fullborder images + file = findFile(dir, fullborderFile.replaceAll("[0-9]*.fullborder", "1.fullborder")); + if (file != null) { + cachedCards.put(filename, file); + return file; + } + // if there's an art variant try without it for .full images + file = findFile(dir, filename.replaceAll("[0-9].full",".full")); + if (file != null) { + cachedCards.put(filename, file); + return file; + } + // if there's a 1st art variant try with it for .full images + file = findFile(dir, filename.replaceAll("[0-9]*.full", "1.full")); + if (file != null) { + cachedCards.put(filename, file); + return file; + } + //setlookup + file = setLookUpFile(filename, fullborderFile); + if (file != null) { + cachedCards.put(filename, file); + return file; + } + } + //if an image, like phenomenon or planes is missing .full in their filenames but you have an existing images that have .full/.fullborder + if (!filename.contains(".full")) { + file = findFile(dir, TextUtil.addSuffix(filename,".full")); + if (file != null) { + cachedCards.put(filename, file); + return file; + } + file = findFile(dir, TextUtil.addSuffix(filename,".fullborder")); + if (file != null) { + cachedCards.put(filename, file); + return file; + } + } + if (dir.equals(CACHE_TOKEN_PICS_DIR)) { + int index = filename.lastIndexOf('_'); + if (index != -1) { + String setlessFilename = filename.substring(0, index); + String setCode = filename.substring(index + 1); + // try with upper case set + file = findFile(dir, setlessFilename + "_" + setCode.toUpperCase()); + if (file != null) { + cachedCards.put(filename, file); + return file; + } + // try with lower case set + file = findFile(dir, setlessFilename + "_" + setCode.toLowerCase()); + if (file != null) { + cachedCards.put(filename, file); + return file; + } + // try without set name + file = findFile(dir, setlessFilename); + if (file != null) { + cachedCards.put(filename, file); + return file; + } + // if there's an art variant try without it + if (setlessFilename.matches(".*[0-9]*$")) { + file = findFile(dir, setlessFilename.replaceAll("[0-9]*$", "")); + if (file != null) { + cachedCards.put(filename, file); + return file; } } } + } else if (filename.contains("/")) { + String setlessFilename = filename.substring(filename.indexOf('/') + 1); + file = findFile(dir, setlessFilename); + if (file != null) { + cachedCards.put(filename, file); + return file; + } + + if (setlessFilename.contains(".full")) { + //try fullborder + String fullborderFile = TextUtil.fastReplace(setlessFilename, ".full", ".fullborder"); + file = findFile(dir, fullborderFile); + if (file != null) { + cachedCards.put(filename, file); + return file; + } + // try lowering the art index to the minimum for regular cards + file = findFile(dir, setlessFilename.replaceAll("[0-9]*[.]full", "1.full")); + if (file != null) { + cachedCards.put(filename, file); + return file; + } + } } } - //if an image, like phenomenon or planes is missing .full in their filenames but you have an existing images that have .full/.fullborder - if (!filename.contains(".full")) { - file = findFile(dir, TextUtil.addSuffix(filename,".full")); - if (file != null) { return file; } - file = findFile(dir, TextUtil.addSuffix(filename,".fullborder")); - if (file != null) { return file; } - } - - if (dir.equals(CACHE_TOKEN_PICS_DIR)) { - int index = filename.lastIndexOf('_'); - if (index != -1) { - String setlessFilename = filename.substring(0, index); - String setCode = filename.substring(index + 1); - // try with upper case set - file = findFile(dir, setlessFilename + "_" + setCode.toUpperCase()); - if (file != null) { return file; } - // try with lower case set - file = findFile(dir, setlessFilename + "_" + setCode.toLowerCase()); - if (file != null) { return file; } - // try without set name - file = findFile(dir, setlessFilename); - if (file != null) { return file; } - // if there's an art variant try without it - if (setlessFilename.matches(".*[0-9]*$")) { - file = findFile(dir, setlessFilename.replaceAll("[0-9]*$", "")); - if (file != null) { return file; } - } - } - } else if (filename.contains("/")) { - String setlessFilename = filename.substring(filename.indexOf('/') + 1); - file = findFile(dir, setlessFilename); - if (file != null) { return file; } - - if (setlessFilename.contains(".full")) { - //try fullborder - String fullborderFile = TextUtil.fastReplace(setlessFilename, ".full", ".fullborder"); - file = findFile(dir, fullborderFile); - if (file != null) { return file; } - // try lowering the art index to the minimum for regular cards - file = findFile(dir, setlessFilename.replaceAll("[0-9]*[.]full", "1.full")); - if (file != null) { return file; } - } - } // System.out.println("File not found, no image created: " + key); - return null; } @@ -200,16 +242,72 @@ public final class ImageKeys { ? StaticData.instance().getEditions().getCode2ByCode(edition) // by default 2-letter codes from MWS are used : CACHE_CARD_PICS_SUBDIR.get(edition); // may use custom paths though } - - private static File findFile(String dir, String filename) { - for (String ext : FILE_EXTENSIONS) { - File file = new File(dir, filename + ext); - if (file.exists()) { - if (file.isDirectory()) { - file.delete(); - continue; + private static File setLookUpFile(String filename, String fullborderFile) { + if (!StaticData.instance().getSetLookup().isEmpty()) { + for (String setKey : StaticData.instance().getSetLookup().keySet()) { + if (filename.startsWith(setKey)) { + for (String setLookup : StaticData.instance().getSetLookup().get(setKey)) { + String lookupDirectory = CACHE_CARD_PICS_DIR + setLookup; + File f = new File(lookupDirectory); + String[] cardNames = f.list(); + if (cardNames != null) { + Set cardList = new HashSet<>(Arrays.asList(cardNames)); + for (String ext : FILE_EXTENSIONS) { + if (ext.equals("")) + continue; + String fb1 = fullborderFile.replace(setKey+"/","")+ext; + if (cardList.contains(fb1)) { + return new File(lookupDirectory+"/"+fb1); + } + String fb2 = fullborderFile.replace(setKey+"/","").replaceAll("[0-9]*.fullborder", "1.fullborder")+ext; + if (cardList.contains(fb2)) { + return new File(lookupDirectory+"/"+fb2); + } + String f1 = filename.replace(setKey+"/","")+ext; + if (cardList.contains(f1)) { + return new File(lookupDirectory+"/"+f1); + } + String f2 = filename.replace(setKey+"/","").replaceAll("[0-9]*.full", "1.full")+ext; + if (cardList.contains(f2)) { + return new File(lookupDirectory+"/"+f2); + } + } + } + } + } + } + } + return null; + } + private static File findFile(String dir, String filename) { + if (dir.equals(CACHE_CARD_PICS_DIR)) { + File f = new File(dir+"/"+filename); + String initialDirectory = f.getParent(); + String cardName = f.getName(); + File parentDir = new File(initialDirectory); + String[] cardNames = parentDir.list(); + if (cardNames != null) { + Set cardList = new HashSet<>(Arrays.asList(cardNames)); + for (String ext : FILE_EXTENSIONS) { + if (ext.equals("")) + continue; + String cardLookup = cardName+ext; + if (cardList.contains(cardLookup)) { + return new File(parentDir+"/"+cardLookup); + } + } + } + } else { + //old method for tokens and others + for (String ext : FILE_EXTENSIONS) { + File file = new File(dir, filename + ext); + if (file.exists()) { + if (file.isDirectory()) { + file.delete(); + continue; + } + return file; } - return file; } } return null; @@ -217,8 +315,11 @@ public final class ImageKeys { //shortcut for determining if a card image exists for a given card //should only be called from PaperCard.hasImage() - static HashMap> cachedContent=new HashMap<>(); + static HashMap> cachedContent=new HashMap<>(50000); public static boolean hasImage(PaperCard pc) { + return hasImage(pc, false); + } + public static boolean hasImage(PaperCard pc, boolean update) { Boolean editionHasImage = editionImageLookup.get(pc.getEdition()); if (editionHasImage == null) { String setFolder = getSetFolder(pc.getEdition()); @@ -232,6 +333,10 @@ public final class ImageKeys { if (!filename.endsWith(".jpg") && !filename.endsWith(".png")) continue; // not image - not interested setFolderContent.add(filename.split("\\.")[0]); // get rid of any full or fullborder + //preload cachedCards at startUp + String key = setFolder + "/" + filename.replace(".fullborder", ".full").replace(".jpg", "").replace(".png", ""); + File value = new File(CACHE_CARD_PICS_DIR + setFolder + "/" + filename); + cachedCards.put(key, value); } cachedContent.put(setFolder, setFolderContent); } @@ -239,6 +344,13 @@ public final class ImageKeys { String[] keyParts = StringUtils.split(pc.getCardImageKey(), "//"); if (keyParts.length != 2) return false; + if (update && editionHasImage) { + try { + cachedContent.get(getSetFolder(pc.getEdition())).add(pc.getName()); + } catch (Exception e) { + System.err.println(e); + } + } HashSet content = cachedContent.getOrDefault(keyParts[0], null); //avoid checking for file if edition doesn't have any images return editionHasImage && hitCache(content, keyParts[1]); diff --git a/forge-core/src/main/java/forge/item/PaperCard.java b/forge-core/src/main/java/forge/item/PaperCard.java index 1ec639ba940..95d0b8a8f2e 100644 --- a/forge-core/src/main/java/forge/item/PaperCard.java +++ b/forge-core/src/main/java/forge/item/PaperCard.java @@ -136,8 +136,11 @@ public final class PaperCard implements Comparable, InventoryItemFro } public boolean hasImage() { - if (hasImage == null) { //cache value since it's not free to calculate - hasImage = ImageKeys.hasImage(this); + return hasImage(false); + } + public boolean hasImage(boolean update) { + if (hasImage == null || update) { //cache value since it's not free to calculate + hasImage = ImageKeys.hasImage(this, update); } return hasImage; } diff --git a/forge-gui-mobile/src/forge/assets/ImageCache.java b/forge-gui-mobile/src/forge/assets/ImageCache.java index 099da2b5a12..053c6d79da2 100644 --- a/forge-gui-mobile/src/forge/assets/ImageCache.java +++ b/forge-gui-mobile/src/forge/assets/ImageCache.java @@ -113,8 +113,6 @@ public class ImageCache { } public static void clear() { - cache.invalidateAll(); - cache.cleanUp(); missingIconKeys.clear(); } @@ -158,17 +156,26 @@ public class ImageCache { final String prefix = imageKey.substring(0, 2); + PaperCard paperCard = null; if (prefix.equals(ImageKeys.CARD_PREFIX)) { - PaperCard paperCard = ImageUtil.getPaperCardFromImageKey(imageKey); + try { + paperCard = ImageUtil.getPaperCardFromImageKey(imageKey); + } catch (Exception e) { + return false; + } if (paperCard == null) return false; - final boolean backFace = imageKey.endsWith(ImageKeys.BACKFACE_POSTFIX); - final String cardfilename = backFace ? paperCard.getCardAltImageKey() : paperCard.getCardImageKey(); - if (!new File(ForgeConstants.CACHE_CARD_PICS_DIR + "/" + cardfilename + ".jpg").exists()) - if (!new File(ForgeConstants.CACHE_CARD_PICS_DIR + "/" + cardfilename + ".png").exists()) - if (!new File(ForgeConstants.CACHE_CARD_PICS_DIR + "/" + TextUtil.fastReplace(cardfilename,".full", ".fullborder") + ".jpg").exists()) - return false; + if (!FModel.getPreferences().getPrefBoolean(ForgePreferences.FPref.UI_ENABLE_ONLINE_IMAGE_FETCHER)) { + return paperCard.hasImage(); + } else { + final boolean backFace = imageKey.endsWith(ImageKeys.BACKFACE_POSTFIX); + final String cardfilename = backFace ? paperCard.getCardAltImageKey() : paperCard.getCardImageKey(); + if (!new File(ForgeConstants.CACHE_CARD_PICS_DIR + "/" + cardfilename + ".jpg").exists()) + if (!new File(ForgeConstants.CACHE_CARD_PICS_DIR + "/" + cardfilename + ".png").exists()) + if (!new File(ForgeConstants.CACHE_CARD_PICS_DIR + "/" + TextUtil.fastReplace(cardfilename,".full", ".fullborder") + ".jpg").exists()) + return false; + } } else if (prefix.equals(ImageKeys.TOKEN_PREFIX)) { final String tokenfilename = imageKey.substring(2) + ".jpg"; @@ -235,12 +242,11 @@ public class ImageCache { // a default "not available" image and add to cache for given key. if (image == null) { if (useDefaultIfNotFound) { - image = defaultImage; + image = useOtherCache ? defaultImage : null; if (useOtherCache) otherCache.put(imageKey, defaultImage); - else - cache.put(imageKey, defaultImage); - if (imageBorder.get(image.toString()) == null) + + if (image != null && imageBorder.get(image.toString()) == null) imageBorder.put(image.toString(), Pair.of(Color.valueOf("#171717").toString(), false)); //black border } } diff --git a/forge-gui-mobile/src/forge/card/CardRenderer.java b/forge-gui-mobile/src/forge/card/CardRenderer.java index af5ad43624f..37631642716 100644 --- a/forge-gui-mobile/src/forge/card/CardRenderer.java +++ b/forge-gui-mobile/src/forge/card/CardRenderer.java @@ -51,7 +51,6 @@ import forge.gui.card.CardDetailUtil; import forge.gui.card.CardDetailUtil.DetailColors; import forge.item.IPaperCard; import forge.item.InventoryItem; -import forge.localinstance.properties.ForgeConstants; import forge.localinstance.properties.ForgeConstants.CounterDisplayType; import forge.localinstance.properties.ForgePreferences; import forge.localinstance.properties.ForgePreferences.FPref; diff --git a/forge-gui-mobile/src/forge/deck/FDeckEditor.java b/forge-gui-mobile/src/forge/deck/FDeckEditor.java index d3590ddbe96..834c095d953 100644 --- a/forge-gui-mobile/src/forge/deck/FDeckEditor.java +++ b/forge-gui-mobile/src/forge/deck/FDeckEditor.java @@ -27,7 +27,6 @@ import forge.assets.FSkin; import forge.assets.FSkinFont; import forge.assets.FSkinImage; import forge.assets.FTextureRegionImage; -import forge.card.CardDb; import forge.card.CardEdition; import forge.deck.io.DeckPreferences; import forge.gamemodes.limited.BoosterDraft; diff --git a/forge-gui-mobile/src/forge/screens/home/LoadGameMenu.java b/forge-gui-mobile/src/forge/screens/home/LoadGameMenu.java index e248e02fdf6..40082ecf6a4 100644 --- a/forge-gui-mobile/src/forge/screens/home/LoadGameMenu.java +++ b/forge-gui-mobile/src/forge/screens/home/LoadGameMenu.java @@ -105,11 +105,8 @@ public class LoadGameMenu extends FPopupMenu { protected void buildMenu() { FScreen currentScreen = Forge.getCurrentScreen(); for (LoadGameScreen lgs : LoadGameScreen.values()) { - //fixes the overlapping menu items when the user suddenly switch from load game screen index to another screen - if (HomeScreen.instance.getActiveButtonIndex() == 1) { - addItem(lgs.item); - lgs.item.setSelected(currentScreen == lgs.screen); - } + addItem(lgs.item); + lgs.item.setSelected(currentScreen == lgs.screen); } } } diff --git a/forge-gui/src/main/java/forge/util/ImageFetcher.java b/forge-gui/src/main/java/forge/util/ImageFetcher.java index 4c86431a4b7..b017f1d7ded 100644 --- a/forge-gui/src/main/java/forge/util/ImageFetcher.java +++ b/forge-gui/src/main/java/forge/util/ImageFetcher.java @@ -126,6 +126,11 @@ public abstract class ImageFetcher { if (destFile.exists()) { // TODO: Figure out why this codepath gets reached. // Ideally, fetchImage() wouldn't be called if we already have the image. + if (prefix.equals(ImageKeys.CARD_PREFIX)) { + PaperCard paperCard = ImageUtil.getPaperCardFromImageKey(imageKey); + if (paperCard != null) + paperCard.hasImage(true); + } return; } From df4c64070d202a8f6dae24e128304f09338cc288 Mon Sep 17 00:00:00 2001 From: tool4EvEr Date: Sun, 12 Sep 2021 10:29:49 +0200 Subject: [PATCH 25/58] Fix Fireball --- .../src/main/java/forge/ai/ability/EffectAi.java | 1 - .../src/main/java/forge/game/ability/AbilityKey.java | 1 - .../main/java/forge/game/ability/AbilityUtils.java | 1 - .../game/ability/effects/CountersRemoveEffect.java | 2 -- .../java/forge/game/ability/effects/DigEffect.java | 12 ++++-------- .../game/ability/effects/ReorderZoneEffect.java | 3 +-- .../forge/game/replacement/ReplacementHandler.java | 1 - forge-gui/res/cardsfolder/d/deadly_grub.txt | 2 +- forge-gui/res/cardsfolder/f/fireball.txt | 2 +- .../cardsfolder/k/kozilek_the_great_distortion.txt | 2 +- .../java/forge/gui/control/FControlGamePlayback.java | 2 +- .../main/java/forge/player/HumanCostDecision.java | 3 +-- 12 files changed, 10 insertions(+), 22 deletions(-) diff --git a/forge-ai/src/main/java/forge/ai/ability/EffectAi.java b/forge-ai/src/main/java/forge/ai/ability/EffectAi.java index 7b7ee50f573..b2249e45ebc 100644 --- a/forge-ai/src/main/java/forge/ai/ability/EffectAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/EffectAi.java @@ -137,7 +137,6 @@ public class EffectAi extends SpellAbilityAi { } randomReturn = true; } else if (logic.equals("Evasion")) { - if (!phase.isPlayerTurn(ai)) { return false; } diff --git a/forge-game/src/main/java/forge/game/ability/AbilityKey.java b/forge-game/src/main/java/forge/game/ability/AbilityKey.java index f4e4227b0dc..311fec449fd 100644 --- a/forge-game/src/main/java/forge/game/ability/AbilityKey.java +++ b/forge-game/src/main/java/forge/game/ability/AbilityKey.java @@ -155,7 +155,6 @@ public enum AbilityKey { } } return null; - } public static EnumMap newMap() { 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 36ed77f5824..8e8e64736d4 100644 --- a/forge-game/src/main/java/forge/game/ability/AbilityUtils.java +++ b/forge-game/src/main/java/forge/game/ability/AbilityUtils.java @@ -451,7 +451,6 @@ public class AbilityUtils { public static int calculateAmount(final Card card, String amount, final CardTraitBase ability) { return calculateAmount(card, amount, ability, false); } - public static int calculateAmount(final Card card, String amount, final CardTraitBase ability, boolean maxto) { // return empty strings and constants if (StringUtils.isBlank(amount)) { return 0; } diff --git a/forge-game/src/main/java/forge/game/ability/effects/CountersRemoveEffect.java b/forge-game/src/main/java/forge/game/ability/effects/CountersRemoveEffect.java index 239d59f47d7..d349a45d5e0 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/CountersRemoveEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/CountersRemoveEffect.java @@ -175,7 +175,6 @@ public class CountersRemoveEffect extends SpellAbilityEffect { String title = Localizer.getInstance().getMessage("lblSelectRemoveCountersNumberOfTarget", type); cntToRemove = pc.chooseNumber(sa, title, 0, cntToRemove, params); } - } if (cntToRemove > 0) { gameCard.subtractCounter(counterType, cntToRemove); @@ -199,7 +198,6 @@ public class CountersRemoveEffect extends SpellAbilityEffect { } } - protected void removeAnyType(GameEntity entity, int cntToRemove, SpellAbility sa) { boolean rememberRemoved = sa.hasParam("RememberRemoved"); 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 ad67308edc1..52e2896424d 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 @@ -45,8 +45,7 @@ public class DigEffect extends SpellAbilityEffect { if (tgtPlayers.contains(host.getController())) { sb.append("their "); - } - else { + } else { for (final Player p : tgtPlayers) { sb.append(Lang.getInstance().getPossesive(p.getName())).append(" "); } @@ -323,8 +322,7 @@ public class DigEffect extends SpellAbilityEffect { libraryPosition = zone.size(); } c = game.getAction().moveTo(zone, c, libraryPosition, sa); - } - else { + } else { c = game.getAction().moveTo(zone, c, sa); if (destZone1.equals(ZoneType.Battlefield)) { if (sa.hasParam("Tapped")) { @@ -383,8 +381,7 @@ public class DigEffect extends SpellAbilityEffect { Card m; if (destZone2 == ZoneType.Library) { m = game.getAction().moveToLibrary(c, libraryPosition2, sa); - } - else { + } else { m = game.getAction().moveToVariantDeck(c, destZone2, libraryPosition2, sa); } if (m != null && !origin.equals(m.getZone().getZoneType())) { @@ -394,8 +391,7 @@ public class DigEffect extends SpellAbilityEffect { host.addRemembered(m); } } - } - else { + } else { // just move them randomly for (int i = 0; i < rest.size(); i++) { Card c = rest.get(i); diff --git a/forge-game/src/main/java/forge/game/ability/effects/ReorderZoneEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ReorderZoneEffect.java index 0aec8a69275..1b2cb597848 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/ReorderZoneEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/ReorderZoneEffect.java @@ -34,8 +34,7 @@ public class ReorderZoneEffect extends SpellAbilityEffect { if (shuffle) { Collections.shuffle(list, MyRandom.getRandom()); p.getZone(zone).setCards(list); - } - else { + } else { p.getController().orderMoveToZoneList(list, zone, sa); } } diff --git a/forge-game/src/main/java/forge/game/replacement/ReplacementHandler.java b/forge-game/src/main/java/forge/game/replacement/ReplacementHandler.java index 1ddcd59b518..dd52d246a09 100644 --- a/forge-game/src/main/java/forge/game/replacement/ReplacementHandler.java +++ b/forge-game/src/main/java/forge/game/replacement/ReplacementHandler.java @@ -81,7 +81,6 @@ public class ReplacementHandler { //private final List tmpEffects = new ArrayList(); public List getReplacementList(final ReplacementType event, final Map runParams, final ReplacementLayer layer) { - final CardCollection preList = new CardCollection(); boolean checkAgain = false; Card affectedLKI = null; diff --git a/forge-gui/res/cardsfolder/d/deadly_grub.txt b/forge-gui/res/cardsfolder/d/deadly_grub.txt index 8efc35e3a67..be93d56917d 100644 --- a/forge-gui/res/cardsfolder/d/deadly_grub.txt +++ b/forge-gui/res/cardsfolder/d/deadly_grub.txt @@ -4,6 +4,6 @@ Types:Creature Insect PT:3/1 K:Vanishing:3 T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Self+counters_EQ0_TIME | Execute$ TrigToken | TriggerController$ TriggeredCardController | TriggerDescription$ When CARDNAME dies, if it had no time counters on it, create a 6/1 green Insect creature token with shroud. (It can't be the target of spells or abilities.) -SVar:TrigToken:DB$Token | TokenAmount$ 1 | TokenScript$ g_6_1_insect_shroud | TokenOwner$ TriggeredCardController | LegacyImage$ g 6 1 insect shroud plc +SVar:TrigToken:DB$ Token | TokenAmount$ 1 | TokenScript$ g_6_1_insect_shroud | TokenOwner$ TriggeredCardController | LegacyImage$ g 6 1 insect shroud plc SVar:Picture:http://www.wizards.com/global/images/magic/general/deadly_grub.jpg Oracle:Vanishing 3 (This creature enters the battlefield with three time counters on it. At the beginning of your upkeep, remove a time counter from it. When the last is removed, sacrifice it.)\nWhen Deadly Grub dies, if it had no time counters on it, create a 6/1 green Insect creature token with shroud. (It can't be the target of spells or abilities.) diff --git a/forge-gui/res/cardsfolder/f/fireball.txt b/forge-gui/res/cardsfolder/f/fireball.txt index 06eeb46a331..f5e0805aa95 100644 --- a/forge-gui/res/cardsfolder/f/fireball.txt +++ b/forge-gui/res/cardsfolder/f/fireball.txt @@ -2,7 +2,7 @@ Name:Fireball ManaCost:X R Types:Sorcery A:SP$ DealDamage | Cost$ X R | ValidTgts$ Creature,Player,Planeswalker | TgtPrompt$ Select any target | NumDmg$ X | TargetMin$ 0 | TargetMax$ MaxTargets | DivideEvenly$ RoundedDown | SpellDescription$ This spell costs {1} more to cast for each target beyond the first. -S:Mode$ RaiseCost | ValidCard$ Card.Self | Type$ Spell | Amount$ IncreaseCost | EffectZone$ All | Description$ CARDNAME deals X damage divided evenly, rounded down, among any number of targets. +S:Mode$ RaiseCost | ValidCard$ Card.Self | Type$ Spell | Amount$ IncreaseCost | AffectedAmount$ True | EffectZone$ All | Description$ CARDNAME deals X damage divided evenly, rounded down, among any number of targets. SVar:X:Count$xPaid SVar:MaxTargets:SVar$Maxplayer/Plus.Maxcreatureorplaneswalker SVar:Maxplayer:PlayerCountPlayers$Amount diff --git a/forge-gui/res/cardsfolder/k/kozilek_the_great_distortion.txt b/forge-gui/res/cardsfolder/k/kozilek_the_great_distortion.txt index 216119d18c6..014c7cabdeb 100644 --- a/forge-gui/res/cardsfolder/k/kozilek_the_great_distortion.txt +++ b/forge-gui/res/cardsfolder/k/kozilek_the_great_distortion.txt @@ -3,7 +3,7 @@ ManaCost:8 C C Types:Legendary Creature Eldrazi PT:12/12 T:Mode$ SpellCast | ValidCard$ Card.Self | Execute$ TrigDraw | CheckSVar$ Y | SVarCompare$ LT7 | TriggerDescription$ When you cast this spell, if you have fewer than seven cards in hand, draw cards equal to the difference. -SVar:TrigDraw:DB$Draw | Defined$ You | NumCards$ Difference +SVar:TrigDraw:DB$ Draw | Defined$ You | NumCards$ Difference SVar:Y:Count$InYourHand SVar:Difference:Number$7/Minus.Y K:Menace diff --git a/forge-gui/src/main/java/forge/gui/control/FControlGamePlayback.java b/forge-gui/src/main/java/forge/gui/control/FControlGamePlayback.java index 6ce805877d6..ba348be4225 100644 --- a/forge-gui/src/main/java/forge/gui/control/FControlGamePlayback.java +++ b/forge-gui/src/main/java/forge/gui/control/FControlGamePlayback.java @@ -87,7 +87,7 @@ public class FControlGamePlayback extends IGameEventVisitor.Base { try { final boolean isUiToStop = !humanController.getGui().isUiSetToSkipPhase(ev.playerTurn.getView(), ev.phase); - switch(ev.phase) { + switch (ev.phase) { case COMBAT_END: case COMBAT_DECLARE_ATTACKERS: case COMBAT_DECLARE_BLOCKERS: diff --git a/forge-gui/src/main/java/forge/player/HumanCostDecision.java b/forge-gui/src/main/java/forge/player/HumanCostDecision.java index 053eb2b0dde..88dd939bcfb 100644 --- a/forge-gui/src/main/java/forge/player/HumanCostDecision.java +++ b/forge-gui/src/main/java/forge/player/HumanCostDecision.java @@ -720,8 +720,7 @@ public class HumanCostDecision extends CostDecisionMakerBase { final CardView view = CardView.get(card); return player.getController().confirmPayment(cost, Localizer.getInstance().getMessage("lblReturnCardToHandConfirm", CardTranslation.getTranslatedName(view.getName())), ability) ? PaymentDecision.card(card) : null; } - } - else { + } else { final CardCollectionView validCards = CardLists.getValidCards(ability.getActivatingPlayer().getCardsIn(ZoneType.Battlefield), cost.getType().split(";"), player, source, ability); From 49ea4b99e75432bbc9bdb188632aa65e80304529 Mon Sep 17 00:00:00 2001 From: Michael Kamensky Date: Sun, 12 Sep 2021 18:45:13 +0300 Subject: [PATCH 26/58] - Added puzzles PS_AFR2 - PS_AFR4. - Tweaked GameState to allow precasting spells from alternate card faces. - Renamed puzzle PS_AFR1. --- .../src/main/java/forge/ai/GameState.java | 7 ++++++- .../res/puzzle/{PS_AFR01.pzl => PS_AFR1.pzl} | 0 forge-gui/res/puzzle/PS_AFR2.pzl | 20 +++++++++++++++++++ forge-gui/res/puzzle/PS_AFR3.pzl | 19 ++++++++++++++++++ forge-gui/res/puzzle/PS_AFR4.pzl | 16 +++++++++++++++ 5 files changed, 61 insertions(+), 1 deletion(-) rename forge-gui/res/puzzle/{PS_AFR01.pzl => PS_AFR1.pzl} (100%) create mode 100644 forge-gui/res/puzzle/PS_AFR2.pzl create mode 100644 forge-gui/res/puzzle/PS_AFR3.pzl create mode 100644 forge-gui/res/puzzle/PS_AFR4.pzl diff --git a/forge-ai/src/main/java/forge/ai/GameState.java b/forge-ai/src/main/java/forge/ai/GameState.java index 93fc295c084..628345369fa 100644 --- a/forge-ai/src/main/java/forge/ai/GameState.java +++ b/forge-ai/src/main/java/forge/ai/GameState.java @@ -1043,7 +1043,12 @@ public abstract class GameState { return; } - sa = c.getFirstSpellAbility(); + if (!c.getName().equals(spellDef) && c.hasAlternateState() && spellDef.equals(c.getAlternateState().getName())) { + sa = c.getAlternateState().getFirstSpellAbility(); + } else { + sa = c.getFirstSpellAbility(); + } + sa.setActivatingPlayer(activator); handleScriptedTargetingForSA(game, sa, tgtID); diff --git a/forge-gui/res/puzzle/PS_AFR01.pzl b/forge-gui/res/puzzle/PS_AFR1.pzl similarity index 100% rename from forge-gui/res/puzzle/PS_AFR01.pzl rename to forge-gui/res/puzzle/PS_AFR1.pzl diff --git a/forge-gui/res/puzzle/PS_AFR2.pzl b/forge-gui/res/puzzle/PS_AFR2.pzl new file mode 100644 index 00000000000..f9c6f3d2347 --- /dev/null +++ b/forge-gui/res/puzzle/PS_AFR2.pzl @@ -0,0 +1,20 @@ +[metadata] +Name:Possibility Storm - Adventures in the Forgotten Realms #02 +URL:https://i0.wp.com/www.possibilitystorm.com/wp-content/uploads/2021/08/180.-AFR2-scaled.jpg +Goal:Win +Turns:1 +Difficulty:Rare +Description:Win this turn. Your opponent's Mind Flayer is controlling your Treeshaker Chimera 'mutate stack'. Both players start with 12 cards remaining in their libraries. Assume whatever they are is not relevant to the solution. +[state] +humanlife=20 +ailife=18 +turn=1 +activeplayer=human +activephase=MAIN1 +humanhand=Lore Drakkis;Struggle for Skemfar;Witherbloom Command +humanlibrary=Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt +humanbattlefield=Mordenkainen|Counters:LOYALTY=3;Pestilent Cauldron;Koma's Faithful;Vineglimmer Snarl;Vineglimmer Snarl;Vineglimmer Snarl;Temple of Deceit;Temple of Deceit;Temple of Deceit;Island;Treeshaker Chimera|MergedCards:Dreamtail Heron,Dreamtail Heron,Dreamtail Heron|Counters:P1P1=1|Id:1 +ailibrary=Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt +aibattlefield=Mind Flayer|ExecuteScript:TrigChange->1;Cogwork Archivist|Tapped +humanprecast=Mordenkainen:2 +removesummoningsickness=true diff --git a/forge-gui/res/puzzle/PS_AFR3.pzl b/forge-gui/res/puzzle/PS_AFR3.pzl new file mode 100644 index 00000000000..3ede0e82057 --- /dev/null +++ b/forge-gui/res/puzzle/PS_AFR3.pzl @@ -0,0 +1,19 @@ +[metadata] +Name:Possibility Storm - Adventures in the Forgotten Realms #03 +URL:https://i0.wp.com/www.possibilitystorm.com/wp-content/uploads/2021/08/181.-AFR3-scaled.jpg +Goal:Win +Turns:1 +Difficulty:Mythic +Description:Start in your first main phase (with Elspeth's Nightmare chapter II ability resolving) and win this turn. You topdecked Pharika's Libation this draw step. Your opponent resolved Revel in Silence during your upkeep. Assume your opponent is tapped out. +[state] +humanlife=20 +ailife=3 +turn=1 +activeplayer=human +activephase=DRAW +activephaseadvance=MAIN1 +humanhand=Pharika's Libation +humanbattlefield=Spare Dagger;Elspeth's Nightmare|Counters:LORE=1;Hive of the Eye Tyrant;Hive of the Eye Tyrant;Hive of the Eye Tyrant;Swamp;Swamp;Swamp;Swamp;Swamp +aihand=Indomitable Will;Indomitable Will;Indomitable Will +aibattlefield=Nine Lives|Counters:INCARNATION=6;Speaker of the Heavens +aiprecast=Revel in Silence diff --git a/forge-gui/res/puzzle/PS_AFR4.pzl b/forge-gui/res/puzzle/PS_AFR4.pzl new file mode 100644 index 00000000000..75e97fbb608 --- /dev/null +++ b/forge-gui/res/puzzle/PS_AFR4.pzl @@ -0,0 +1,16 @@ +[metadata] +Name:Possibility Storm - Adventures in the Forgotten Realms #04 +URL:https://i2.wp.com/www.possibilitystorm.com/wp-content/uploads/2021/08/182.-AFR4-scaled.jpg +Goal:Win +Turns:1 +Difficulty:Uncommon +Description:Win this turn. Your opponent has no cards in hand and no mana available. +[state] +humanlife=20 +ailife=7 +turn=1 +activeplayer=human +activephase=MAIN1 +humanhand=Goblin Javelineer;Hulking Bugbear;You See a Pair of Goblins +humanbattlefield=Hobgoblin Bandit Lord;Battle Cry Goblin;Mountain;Mountain;Mountain;Mountain;Mountain +aibattlefield=Vadrok, Apex of Thunder;Yorion, Sky Nomad From bfc938be57bee6f4255b58820d0d15969604db6c Mon Sep 17 00:00:00 2001 From: Hans Mackowiak Date: Sun, 12 Sep 2021 15:48:34 +0000 Subject: [PATCH 27/58] Card: refactor hidden keywords into Table Structure --- .../java/forge/ai/AiAttackController.java | 76 ++++----- .../main/java/forge/ai/AiBlockController.java | 46 +++--- .../main/java/forge/ai/ComputerUtilCard.java | 23 +-- .../java/forge/ai/ComputerUtilCombat.java | 12 +- .../main/java/forge/ai/CreatureEvaluator.java | 21 +-- .../java/forge/ai/ability/ChangeZoneAi.java | 1 - .../java/forge/ai/simulation/GameCopier.java | 5 +- .../src/main/java/forge/game/GameAction.java | 27 ++- .../main/java/forge/game/StaticEffect.java | 18 +- .../ability/effects/AnimateAllEffect.java | 2 +- .../ability/effects/AnimateEffectBase.java | 13 +- .../ability/effects/CamouflageEffect.java | 3 +- .../ability/effects/ControlGainEffect.java | 16 +- .../ability/effects/CountersPutEffect.java | 8 +- .../game/ability/effects/DebuffEffect.java | 12 +- .../game/ability/effects/PumpAllEffect.java | 16 +- .../game/ability/effects/PumpEffect.java | 27 +-- .../src/main/java/forge/game/card/Card.java | 155 ++++++++++-------- .../java/forge/game/card/CardFactoryUtil.java | 8 +- .../java/forge/game/card/CardPredicates.java | 6 + .../forge/game/combat/AttackRequirement.java | 17 +- .../java/forge/game/combat/CombatUtil.java | 90 +++------- .../java/forge/game/cost/CostAdjustment.java | 8 +- .../forge/game/keyword/KeywordCollection.java | 7 - .../forge/game/keyword/KeywordInstance.java | 17 -- .../forge/game/keyword/KeywordInterface.java | 5 +- .../src/main/java/forge/game/phase/Untap.java | 3 +- .../main/java/forge/game/player/Player.java | 2 +- .../staticability/StaticAbilityAdapt.java | 38 +++++ .../StaticAbilityCantAttackBlock.java | 46 ++++++ .../StaticAbilityContinuous.java | 6 +- .../res/cardsfolder/a/alpha_authority.txt | 4 +- .../res/cardsfolder/b/battlefront_krushok.txt | 5 +- .../res/cardsfolder/b/biomancers_familiar.txt | 5 +- .../res/cardsfolder/b/bristling_boar.txt | 4 +- .../res/cardsfolder/c/challenger_troll.txt | 4 +- .../res/cardsfolder/c/charging_rhino.txt | 3 +- .../res/cardsfolder/f/familiar_ground.txt | 3 +- .../res/cardsfolder/g/gorilla_berserkers.txt | 3 +- forge-gui/res/cardsfolder/g/guile.txt | 3 +- .../cardsfolder/h/huang_zhong_shu_general.txt | 3 +- .../res/cardsfolder/h/hungering_hydra.txt | 2 +- forge-gui/res/cardsfolder/i/ironhoof_ox.txt | 3 +- .../k/kessig_prowler_sinuous_predator.txt | 22 +-- forge-gui/res/cardsfolder/k/krosan_vorine.txt | 3 +- .../res/cardsfolder/n/norwood_riders.txt | 3 +- .../res/cardsfolder/o/outland_colossus.txt | 3 +- .../res/cardsfolder/p/pathrazer_of_ulamog.txt | 3 +- .../res/cardsfolder/p/phyrexian_colossus.txt | 3 +- .../res/cardsfolder/s/sonorous_howlbonder.txt | 2 +- .../res/cardsfolder/s/stalking_tiger.txt | 3 +- forge-gui/res/cardsfolder/s/sunder_shaman.txt | 2 +- .../cardsfolder/t/tahngarth_first_mate.txt | 2 +- .../res/cardsfolder/t/the_akroan_war.txt | 4 +- .../res/cardsfolder/u/underworld_cerberus.txt | 2 +- .../res/cardsfolder/v/vigorspore_wurm.txt | 2 +- .../res/cardsfolder/v/vorrac_battlehorns.txt | 4 +- .../res/cardsfolder/w/wolfriders_saddle.txt | 3 +- .../y/yuan_shao_the_indecisive.txt | 3 +- forge-gui/res/lists/NonStackingKWList.txt | 4 - 60 files changed, 424 insertions(+), 420 deletions(-) create mode 100644 forge-game/src/main/java/forge/game/staticability/StaticAbilityAdapt.java diff --git a/forge-ai/src/main/java/forge/ai/AiAttackController.java b/forge-ai/src/main/java/forge/ai/AiAttackController.java index 10c511ed5c6..000443c6304 100644 --- a/forge-ai/src/main/java/forge/ai/AiAttackController.java +++ b/forge-ai/src/main/java/forge/ai/AiAttackController.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 . */ @@ -42,7 +42,6 @@ import forge.game.combat.Combat; import forge.game.combat.CombatUtil; import forge.game.combat.GlobalAttackRestrictions; import forge.game.keyword.Keyword; -import forge.game.keyword.KeywordInterface; import forge.game.player.Player; import forge.game.player.PlayerPredicates; import forge.game.spellability.SpellAbility; @@ -60,7 +59,7 @@ import forge.util.collect.FCollectionView; *

* ComputerUtil_Attack2 class. *

- * + * * @author Forge * @version $Id$ */ @@ -72,10 +71,10 @@ public class AiAttackController { private List oppList; // holds human player creatures private List myList; // holds computer creatures - + private final Player ai; private Player defendingOpponent; - + private int aiAggression = 0; // added by Masher, how aggressive the ai is attack will be depending on circumstances private final boolean nextTurn; @@ -108,7 +107,7 @@ public class AiAttackController { public AiAttackController(final Player ai, Card attacker) { this.ai = ai; - this.defendingOpponent = choosePreferredDefenderPlayer(ai); + this.defendingOpponent = choosePreferredDefenderPlayer(ai); this.oppList = getOpponentCreatures(this.defendingOpponent); this.myList = ai.getCreaturesInPlay(); this.attackers = new ArrayList<>(); @@ -118,7 +117,7 @@ public class AiAttackController { this.blockers = getPossibleBlockers(oppList, this.attackers); this.nextTurn = false; } // overloaded constructor to evaluate single specified attacker - + public static List getOpponentCreatures(final Player defender) { List defenders = new ArrayList<>(defender.getCreaturesInPlay()); Predicate canAnimate = new Predicate() { @@ -133,7 +132,7 @@ public class AiAttackController { } for (SpellAbility sa : c.getSpellAbilities()) { if (sa.getApi() == ApiType.Animate) { - if (ComputerUtilCost.canPayCost(sa, defender) + if (ComputerUtilCost.canPayCost(sa, defender) && sa.getRestrictions().checkOtherRestrictions(c, sa, defender)) { Card animatedCopy = AnimateAi.becomeAnimated(c, sa); defenders.add(animatedCopy); @@ -143,7 +142,7 @@ public class AiAttackController { } return defenders; } - + public void removeBlocker(Card blocker) { this.oppList.remove(blocker); } @@ -200,7 +199,7 @@ public class AiAttackController { *

* isEffectiveAttacker. *

- * + * * @param attacker * a {@link forge.game.card.Card} object. * @param combat @@ -501,7 +500,7 @@ public class AiAttackController { // Conservative prediction for vehicles: the AI tries to acknowledge the fact that // at least one creature will tap to crew a blocking vehicle when predicting if an // alpha strike for lethal is viable - int maxBlockersAfterCrew = remainingBlockers.size(); + int maxBlockersAfterCrew = remainingBlockers.size(); for (Card c : this.blockers) { CardTypeView cardType = c.getCurrentState().getType(); CardCollectionView oppBattlefield = c.getController().getCardsIn(ZoneType.Battlefield); @@ -514,7 +513,7 @@ public class AiAttackController { } else if (c.getName().equals("Peacewalker Colossus")) { // can activate other vehicles for {1}{W} // TODO: the AI should ideally predict how many times it can activate - // for now, unless the opponent is tapped out, break at this point + // for now, unless the opponent is tapped out, break at this point // and do not predict the blocker limit (which is safer) if (!CardLists.filter(oppBattlefield, Predicates.and(CardPredicates.Presets.UNTAPPED, CardPredicates.Presets.LANDS)).isEmpty()) { maxBlockersAfterCrew = Integer.MAX_VALUE; @@ -600,7 +599,7 @@ public class AiAttackController { maxBlockersAfterCrew--; } unblockedAttackers.addAll(remainingAttackers); - + int trampleDamage = 0; for (Card attacker : blockedAttackers) { if (attacker.hasKeyword(Keyword.TRAMPLE)) { @@ -674,7 +673,7 @@ public class AiAttackController { *

* Getter for the field attackers. *

- * + * * @return a {@link forge.game.combat.Combat} object. */ public final void declareAttackers(final Combat combat) { @@ -742,14 +741,9 @@ public class AiAttackController { // TODO: if there are other ways to tap this creature (like mana creature), then don't need to attack mustAttack = true; } else { - for (KeywordInterface inst : attacker.getKeywords()) { - String s = inst.getOriginal(); - if (s.equals("CARDNAME attacks each turn if able.") - || s.startsWith("CARDNAME attacks specific player each combat if able") - || s.equals("CARDNAME attacks each combat if able.")) { - mustAttack = true; - break; - } + // TODO move to static Ability + if (attacker.hasKeyword("CARDNAME attacks each combat if able.") || attacker.hasStartOfKeyword("CARDNAME attacks specific player each combat if able")) { + mustAttack = true; } } if (mustAttack || attacker.getController().getMustAttackEntity() != null || attacker.getController().getMustAttackEntityThisTurn() != null) { @@ -799,7 +793,7 @@ public class AiAttackController { } } } - + // Exalted if (combat.getAttackers().isEmpty()) { boolean exalted = ai.countExaltedBonus() > 2; @@ -857,7 +851,7 @@ public class AiAttackController { // examine the potential forces final List nextTurnAttackers = new ArrayList<>(); int candidateCounterAttackDamage = 0; - + final Player opp = this.defendingOpponent; // get the potential damage and strength of the AI forces final List candidateAttackers = new ArrayList<>(); @@ -1115,7 +1109,7 @@ public class AiAttackController { *

* getAttack. *

- * + * * @param c * a {@link forge.game.card.Card} object. * @return a int. @@ -1134,7 +1128,7 @@ public class AiAttackController { *

* shouldAttack. *

- * + * * @param attacker * a {@link forge.game.card.Card} object. * @param defenders @@ -1179,7 +1173,7 @@ public class AiAttackController { } boolean hasAttackEffect = attacker.getSVar("HasAttackEffect").equals("TRUE") || attacker.hasStartOfKeyword("Annihilator"); // is there a gain in attacking even when the blocker is not killed (Lifelink, Wither,...) - boolean hasCombatEffect = attacker.getSVar("HasCombatEffect").equals("TRUE") + boolean hasCombatEffect = attacker.getSVar("HasCombatEffect").equals("TRUE") || "Blocked".equals(attacker.getSVar("HasAttackEffect")); // contains only the defender's blockers that can actually block the attacker @@ -1201,13 +1195,9 @@ public class AiAttackController { int defPower = CardLists.getTotalPower(validBlockers, true, false); if (!hasCombatEffect) { - for (KeywordInterface inst : attacker.getKeywords()) { - String keyword = inst.getOriginal(); - if (keyword.equals("Wither") || keyword.equals("Infect") - || keyword.equals("Lifelink") || keyword.startsWith("Afflict")) { - hasCombatEffect = true; - break; - } + if (attacker.hasKeyword(Keyword.WITHER) || attacker.hasKeyword(Keyword.INFECT) + || attacker.hasKeyword(Keyword.LIFELINK) || attacker.hasKeyword(Keyword.AFFLICT)) { + hasCombatEffect = true; } } @@ -1262,7 +1252,7 @@ public class AiAttackController { } } } - + if (!attacker.hasKeyword(Keyword.VIGILANCE) && ComputerUtilCard.canBeKilledByRoyalAssassin(ai, attacker)) { canKillAllDangerous = false; canBeKilled = true; @@ -1272,7 +1262,7 @@ public class AiAttackController { } else if ((canKillAllDangerous || !canBeKilled) && ComputerUtilCard.canBeBlockedProfitably(defendingOpponent, attacker)) { canKillAllDangerous = false; canBeKilled = true; - } + } // if the creature cannot block and can kill all opponents they might as // well attack, they do nothing staying back @@ -1286,7 +1276,7 @@ public class AiAttackController { return true; } - if (numberOfPossibleBlockers > 2 + if (numberOfPossibleBlockers > 2 || (numberOfPossibleBlockers >= 1 && CombatUtil.canAttackerBeBlockedWithAmount(attacker, 1, this.defendingOpponent)) || (numberOfPossibleBlockers == 2 && CombatUtil.canAttackerBeBlockedWithAmount(attacker, 2, this.defendingOpponent))) { canBeBlocked = true; @@ -1416,7 +1406,7 @@ public class AiAttackController { return exerters; } - + /** * Find a protection type that will make an attacker unblockable. * @param sa ability belonging to ApiType.Protection @@ -1485,10 +1475,10 @@ public class AiAttackController { CardCollection attSorted = new CardCollection(attackersLeft); CardCollection attUnsafe = new CardCollection(); CardLists.sortByToughnessDesc(attSorted); - + int i = numForcedAttackers; int refPowerValue = 0; // Aggro profiles do not account for the possible blockers' power, conservative profiles do. - + if (!playAggro && this.blockers.size() > 0) { // Conservative play: check to ensure that the card can't be killed off while damaged // TODO: currently sorting a copy of this.blockers, but it looks safe to operate on this.blockers directly? @@ -1498,7 +1488,7 @@ public class AiAttackController { CardLists.sortByPowerDesc(blkSorted); refPowerValue += blkSorted.get(0).getCurrentPower(); } - + for (Card cre : attSorted) { i++; if (i + refPowerValue >= cre.getCurrentToughness()) { @@ -1507,7 +1497,7 @@ public class AiAttackController { continue; } } - + attackersLeft.removeAll(attUnsafe); } diff --git a/forge-ai/src/main/java/forge/ai/AiBlockController.java b/forge-ai/src/main/java/forge/ai/AiBlockController.java index 56a8514c9d8..87701edbc94 100644 --- a/forge-ai/src/main/java/forge/ai/AiBlockController.java +++ b/forge-ai/src/main/java/forge/ai/AiBlockController.java @@ -38,6 +38,7 @@ import forge.game.combat.Combat; import forge.game.combat.CombatUtil; import forge.game.keyword.Keyword; import forge.game.player.Player; +import forge.game.staticability.StaticAbilityCantAttackBlock; import forge.game.trigger.Trigger; import forge.game.trigger.TriggerType; import forge.game.zone.ZoneType; @@ -183,9 +184,7 @@ public class AiBlockController { List currentAttackers = new ArrayList<>(attackersLeft); for (final Card attacker : attackersLeft) { - if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT") - || attacker.hasKeyword("CARDNAME can't be blocked unless all creatures defending player controls block it.") - || attacker.hasKeyword(Keyword.MENACE)) { + if (StaticAbilityCantAttackBlock.getMinMaxBlocker(attacker, combat.getDefenderPlayerByAttacker(attacker)).getLeft() > 1) { continue; } @@ -296,8 +295,7 @@ public class AiBlockController { // 6. Blockers that don't survive until the next turn anyway for (final Card attacker : attackersLeft) { - if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT") || attacker.hasKeyword(Keyword.MENACE) - || attacker.hasKeyword("CARDNAME can't be blocked unless all creatures defending player controls block it.")) { + if (StaticAbilityCantAttackBlock.getMinMaxBlocker(attacker, combat.getDefenderPlayerByAttacker(attacker)).getLeft() > 1) { continue; } @@ -323,7 +321,17 @@ public class AiBlockController { attackersLeft = (new ArrayList<>(currentAttackers)); } - static final Predicate rampagesOrNeedsManyToBlock = Predicates.or(CardPredicates.containsKeyword("Rampage"), CardPredicates.containsKeyword("CantBeBlockedByAmount GT")); + private Predicate rampagesOrNeedsManyToBlock(final Combat combat) { + return Predicates.or(CardPredicates.hasKeyword(Keyword.RAMPAGE), new Predicate() { + + @Override + public boolean apply(Card input) { + // select creature that has a max blocker + return StaticAbilityCantAttackBlock.getMinMaxBlocker(input, combat.getDefenderPlayerByAttacker(input)).getRight() < Integer.MAX_VALUE; + } + + }); + } // Good Gang Blocks means a good trade or no trade /** @@ -334,7 +342,7 @@ public class AiBlockController { * @param combat a {@link forge.game.combat.Combat} object. */ private void makeGangBlocks(final Combat combat) { - List currentAttackers = CardLists.filter(attackersLeft, Predicates.not(rampagesOrNeedsManyToBlock)); + List currentAttackers = CardLists.filter(attackersLeft, Predicates.not(rampagesOrNeedsManyToBlock(combat))); List blockers; // Try to block an attacker without first strike with a gang of first strikers @@ -528,7 +536,7 @@ public class AiBlockController { // Try to block a Menace attacker with two blockers, neither of which will die for (final Card attacker : attackersLeft) { - if (!attacker.hasKeyword(Keyword.MENACE) && !attacker.hasStartOfKeyword("CantBeBlockedByAmount LT2")) { + if (StaticAbilityCantAttackBlock.getMinMaxBlocker(attacker, combat.getDefenderPlayerByAttacker(attacker)).getLeft() <= 1) { continue; } @@ -584,9 +592,7 @@ public class AiBlockController { List killingBlockers; for (final Card attacker : attackersLeft) { - if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT") - || attacker.hasKeyword(Keyword.MENACE) - || attacker.hasKeyword("CARDNAME can't be blocked unless all creatures defending player controls block it.")) { + if (StaticAbilityCantAttackBlock.getMinMaxBlocker(attacker, combat.getDefenderPlayerByAttacker(attacker)).getLeft() > 1) { continue; } if (ComputerUtilCombat.attackerHasThreateningAfflict(attacker, ai)) { @@ -635,10 +641,8 @@ public class AiBlockController { Card attacker = attackers.get(0); - if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT") + if (StaticAbilityCantAttackBlock.getMinMaxBlocker(attacker, combat.getDefenderPlayerByAttacker(attacker)).getLeft() > 1 || attacker.hasKeyword("You may have CARDNAME assign its combat damage as though it weren't blocked.") - || attacker.hasKeyword("CARDNAME can't be blocked unless all creatures defending player controls block it.") - || attacker.hasKeyword(Keyword.MENACE) || ComputerUtilCombat.attackerHasThreateningAfflict(attacker, ai)) { attackers.remove(0); makeChumpBlocks(combat, attackers); @@ -686,9 +690,7 @@ public class AiBlockController { List currentAttackers = new ArrayList<>(attackersLeft); for (final Card attacker : currentAttackers) { - if (!attacker.hasStartOfKeyword("CantBeBlockedByAmount LT") - && !attacker.hasKeyword(Keyword.MENACE) - && !attacker.hasKeyword("CARDNAME can't be blocked unless all creatures defending player controls block it.")) { + if (StaticAbilityCantAttackBlock.getMinMaxBlocker(attacker, combat.getDefenderPlayerByAttacker(attacker)).getLeft() <= 1) { continue; } List possibleBlockers = getPossibleBlockers(combat, attacker, blockersLeft, true); @@ -720,14 +722,14 @@ public class AiBlockController { List chumpBlockers; List tramplingAttackers = CardLists.getKeyword(attackers, Keyword.TRAMPLE); - tramplingAttackers = CardLists.filter(tramplingAttackers, Predicates.not(rampagesOrNeedsManyToBlock)); + tramplingAttackers = CardLists.filter(tramplingAttackers, Predicates.not(rampagesOrNeedsManyToBlock(combat))); // TODO - should check here for a "rampage-like" trigger that replaced the keyword: // "Whenever CARDNAME becomes blocked, it gets +1/+1 until end of turn for each creature blocking it." for (final Card attacker : tramplingAttackers) { - if (((attacker.hasStartOfKeyword("CantBeBlockedByAmount LT") || attacker.hasKeyword(Keyword.MENACE)) && !combat.isBlocked(attacker)) + if (((StaticAbilityCantAttackBlock.getMinMaxBlocker(attacker, combat.getDefenderPlayerByAttacker(attacker)).getLeft() > 1) && !combat.isBlocked(attacker)) || attacker.hasKeyword("You may have CARDNAME assign its combat damage as though it weren't blocked.") || attacker.hasKeyword("CARDNAME can't be blocked unless all creatures defending player controls block it.")) { continue; @@ -751,7 +753,7 @@ public class AiBlockController { private void reinforceBlockersToKill(final Combat combat) { List safeBlockers; List blockers; - List targetAttackers = CardLists.filter(blockedButUnkilled, Predicates.not(rampagesOrNeedsManyToBlock)); + List targetAttackers = CardLists.filter(blockedButUnkilled, Predicates.not(rampagesOrNeedsManyToBlock(combat))); // TODO - should check here for a "rampage-like" trigger that replaced // the keyword: "Whenever CARDNAME becomes blocked, it gets +1/+1 until end of turn for each creature blocking it." @@ -1076,8 +1078,7 @@ public class AiBlockController { } // assign blockers that have to block - chumpBlockers = CardLists.getKeyword(blockersLeft, "CARDNAME blocks each turn if able."); - chumpBlockers.addAll(CardLists.getKeyword(blockersLeft, "CARDNAME blocks each combat if able.")); + chumpBlockers = CardLists.getKeyword(blockersLeft, "CARDNAME blocks each combat if able."); // if an attacker with lure attacks - all that can block for (final Card blocker : blockersLeft) { if (CombatUtil.mustBlockAnAttacker(blocker, combat, null)) { @@ -1091,7 +1092,6 @@ public class AiBlockController { for (final Card blocker : blockers) { if (CombatUtil.canBlock(attacker, blocker, combat) && blockersLeft.contains(blocker) && (CombatUtil.mustBlockAnAttacker(blocker, combat, null) - || blocker.hasKeyword("CARDNAME blocks each turn if able.") || blocker.hasKeyword("CARDNAME blocks each combat if able."))) { combat.addBlocker(attacker, blocker); if (blocker.getMustBlockCards() != null) { diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java b/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java index 0e8c2ea385e..5a069e44a4a 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java @@ -1654,10 +1654,11 @@ public class ComputerUtilCard { Card pumped = CardFactory.copyCard(c, false); pumped.setSickness(c.hasSickness()); final long timestamp = c.getGame().getNextTimestamp(); - final List kws = new ArrayList<>(); + final List kws = Lists.newArrayList(); + final List hiddenKws = Lists.newArrayList(); for (String kw : keywords) { if (kw.startsWith("HIDDEN")) { - pumped.addHiddenExtrinsicKeyword(kw); + hiddenKws.add(kw.substring(7)); } else { kws.add(kw); } @@ -1686,7 +1687,13 @@ public class ComputerUtilCard { pumped.addNewPT(c.getCurrentPower(), c.getCurrentToughness(), timestamp); pumped.setPTBoost(c.getPTBoostTable()); pumped.addPTBoost(power + berserkPower, toughness, timestamp, 0); - pumped.addChangedCardKeywords(kws, null, false, false, timestamp, 0); + + if (!kws.isEmpty()) { + pumped.addChangedCardKeywords(kws, null, false, false, timestamp, 0); + } + if (!hiddenKws.isEmpty()) { + pumped.addHiddenExtrinsicKeywords(timestamp, 0, hiddenKws); + } Set types = c.getCounters().keySet(); for(CounterType ct : types) { pumped.addCounterFireNoEvents(ct, c.getCounters(ct), ai, sa, true, null); @@ -1699,14 +1706,10 @@ public class ComputerUtilCard { KeywordCollection copiedKeywords = new KeywordCollection(); copiedKeywords.insertAll(pumped.getKeywords()); List toCopy = Lists.newArrayList(); - for (KeywordInterface k : c.getKeywords()) { + for (KeywordInterface k : c.getUnhiddenKeywords()) { KeywordInterface copiedKI = k.copy(c, true); if (!copiedKeywords.contains(copiedKI.getOriginal())) { - if (copiedKI.getHidden()) { - pumped.addHiddenExtrinsicKeyword(copiedKI); - } else { - toCopy.add(copiedKI); - } + toCopy.add(copiedKI); } } final long timestamp2 = c.getGame().getNextTimestamp(); //is this necessary or can the timestamp be re-used? @@ -1744,7 +1747,7 @@ public class ComputerUtilCard { if (!stAb.hasParam("AddPower") && !stAb.hasParam("AddToughness")) { continue; } - if (!vCard.isValid(stAb.getParam("Affected").split(","), c.getController(), c, stAb)) { + if (!stAb.matchesValidParam("Affected", vCard)) { continue; } int att = 0; diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java b/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java index b025e10deca..1513b2c917b 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java @@ -110,7 +110,17 @@ public class ComputerUtilCombat { return false; } - for (final KeywordInterface inst : attacker.getKeywords()) { + // TODO replace with Static Ability + for (final String keyword : attacker.getHiddenExtrinsicKeywords()) { + if (keyword.startsWith("CARDNAME attacks specific player each combat if able")) { + final String defined = keyword.split(":")[1]; + final Player player = AbilityUtils.getDefinedPlayers(attacker, defined, null).get(0); + if (!defender.equals(player)) { + return false; + } + } + } + for (final KeywordInterface inst : attacker.getKeywords(Keyword.UNDEFINED)) { final String keyword = inst.getOriginal(); if (keyword.startsWith("CARDNAME attacks specific player each combat if able")) { final String defined = keyword.split(":")[1]; diff --git a/forge-ai/src/main/java/forge/ai/CreatureEvaluator.java b/forge-ai/src/main/java/forge/ai/CreatureEvaluator.java index 201f1436785..252800e9338 100644 --- a/forge-ai/src/main/java/forge/ai/CreatureEvaluator.java +++ b/forge-ai/src/main/java/forge/ai/CreatureEvaluator.java @@ -8,7 +8,6 @@ import forge.game.card.Card; import forge.game.card.CounterEnumType; import forge.game.cost.CostPayEnergy; import forge.game.keyword.Keyword; -import forge.game.keyword.KeywordInterface; import forge.game.spellability.SpellAbility; public class CreatureEvaluator implements Function { @@ -35,16 +34,15 @@ public class CreatureEvaluator implements Function { } int power = getEffectivePower(c); final int toughness = getEffectiveToughness(c); - for (KeywordInterface kw : c.getKeywords()) { - String keyword = kw.getOriginal(); - if (keyword.equals("Prevent all combat damage that would be dealt by CARDNAME.") - || keyword.equals("Prevent all damage that would be dealt by CARDNAME.") - || keyword.equals("Prevent all combat damage that would be dealt to and dealt by CARDNAME.") - || keyword.equals("Prevent all damage that would be dealt to and dealt by CARDNAME.")) { - power = 0; - break; - } + + // TODO replace with ReplacementEffect checks + if (c.hasKeyword("Prevent all combat damage that would be dealt by CARDNAME.") + || c.hasKeyword("Prevent all damage that would be dealt by CARDNAME.") + || c.hasKeyword("Prevent all combat damage that would be dealt to and dealt by CARDNAME.") + || c.hasKeyword("Prevent all damage that would be dealt to and dealt by CARDNAME.")) { + power = 0; } + if (considerPT) { value += addValue(power * 15, "power"); value += addValue(toughness * 10, "toughness: " + toughness); @@ -157,8 +155,7 @@ public class CreatureEvaluator implements Function { } if (c.hasKeyword("CARDNAME can't block.")) { value -= subValue(10, "cant-block"); - } else if (c.hasKeyword("CARDNAME attacks each turn if able.") - || c.hasKeyword("CARDNAME attacks each combat if able.")) { + } else if (c.hasKeyword("CARDNAME attacks each combat if able.")) { value -= subValue(10, "must-attack"); } else if (c.hasStartOfKeyword("CARDNAME attacks specific player each combat if able")) { value -= subValue(10, "must-attack-player"); diff --git a/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java b/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java index 0cc5dac8aa2..5229a00e729 100644 --- a/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java @@ -16,7 +16,6 @@ import com.google.common.collect.Lists; import com.google.common.collect.Maps; import forge.ai.AiAttackController; -import forge.ai.AiBlockController; import forge.ai.AiCardMemory; import forge.ai.AiController; import forge.ai.AiProps; diff --git a/forge-ai/src/main/java/forge/ai/simulation/GameCopier.java b/forge-ai/src/main/java/forge/ai/simulation/GameCopier.java index 5950f124f0f..58d4cea4fbe 100644 --- a/forge-ai/src/main/java/forge/ai/simulation/GameCopier.java +++ b/forge-ai/src/main/java/forge/ai/simulation/GameCopier.java @@ -24,7 +24,6 @@ import forge.game.card.CardFactory; import forge.game.card.CounterType; import forge.game.card.token.TokenInfo; import forge.game.combat.Combat; -import forge.game.keyword.KeywordInterface; import forge.game.phase.PhaseHandler; import forge.game.phase.PhaseType; import forge.game.player.Player; @@ -295,8 +294,8 @@ public class GameCopier { newCard.setChangedCardNames(c.getChangedCardNames()); // TODO: Is this correct? Does it not duplicate keywords from enchantments and such? - for (KeywordInterface kw : c.getHiddenExtrinsicKeywords()) - newCard.addHiddenExtrinsicKeyword(kw); + //for (KeywordInterface kw : c.getHiddenExtrinsicKeywords()) + // newCard.addHiddenExtrinsicKeyword(kw); if (c.isTapped()) { newCard.setTapped(true); } diff --git a/forge-game/src/main/java/forge/game/GameAction.java b/forge-game/src/main/java/forge/game/GameAction.java index 754462a481c..5dec3d908a9 100644 --- a/forge-game/src/main/java/forge/game/GameAction.java +++ b/forge-game/src/main/java/forge/game/GameAction.java @@ -74,6 +74,7 @@ import forge.game.replacement.ReplacementType; import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbilityPredicates; import forge.game.staticability.StaticAbility; +import forge.game.staticability.StaticAbilityCantAttackBlock; import forge.game.staticability.StaticAbilityLayer; import forge.game.trigger.TriggerType; import forge.game.zone.PlayerZone; @@ -682,17 +683,25 @@ public class GameAction { // need to refresh ability text for affected cards for (final StaticAbility stAb : c.getStaticAbilities()) { - if (stAb.isSecondary() || - !stAb.getParam("Mode").equals("CantBlockBy") || - stAb.isSuppressed() || !stAb.checkConditions() || - !stAb.hasParam("ValidAttacker") || - (stAb.hasParam("ValidBlocker") && stAb.getParam("ValidBlocker").equals("Creature.Self"))) { + if (stAb.isSuppressed() || !stAb.checkConditions()) { continue; } - final Card host = stAb.getHostCard(); - for (Card creature : Iterables.filter(game.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.CREATURES)) { - if (creature.isValid(stAb.getParam("ValidAttacker").split(","), host.getController(), host, stAb)) { - creature.updateAbilityTextForView(); + + if (stAb.getParam("Mode").equals("CantBlockBy")) { + if (!stAb.hasParam("ValidAttacker") || (stAb.hasParam("ValidBlocker") && stAb.getParam("ValidBlocker").equals("Creature.Self"))) { + continue; + } + for (Card creature : Iterables.filter(game.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.CREATURES)) { + if (stAb.matchesValidParam("ValidAttacker", creature)) { + creature.updateAbilityTextForView(); + } + } + } + if (stAb.getParam("Mode").equals(StaticAbilityCantAttackBlock.MinMaxBlockerMode)) { + for (Card creature : Iterables.filter(game.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.CREATURES)) { + if (stAb.matchesValidParam("ValidCard", creature)) { + creature.updateAbilityTextForView(); + } } } } diff --git a/forge-game/src/main/java/forge/game/StaticEffect.java b/forge-game/src/main/java/forge/game/StaticEffect.java index 7b53983812a..40e795e0cb2 100644 --- a/forge-game/src/main/java/forge/game/StaticEffect.java +++ b/forge-game/src/main/java/forge/game/StaticEffect.java @@ -174,18 +174,8 @@ public class StaticEffect { final CardCollectionView affectedCards = getAffectedCards(); final List affectedPlayers = getAffectedPlayers(); - boolean setPT = false; - String[] addHiddenKeywords = null; boolean removeMayPlay = false; - if (hasParam("SetPower") || hasParam("SetToughness")) { - setPT = true; - } - - if (hasParam("AddHiddenKeyword")) { - addHiddenKeywords = getParam("AddHiddenKeyword").split(" & "); - } - if (hasParam("MayPlay")) { removeMayPlay = true; } @@ -220,7 +210,7 @@ public class StaticEffect { affectedCard.removeChangedTextColorWord(getTimestamp(), ability.getId()); // remove set P/T - if (setPT) { + if (hasParam("SetPower") || hasParam("SetToughness")) { affectedCard.removeNewPT(getTimestamp()); } @@ -240,10 +230,8 @@ public class StaticEffect { affectedCard.removeCantHaveKeyword(getTimestamp()); } - if (addHiddenKeywords != null) { - for (final String k : addHiddenKeywords) { - affectedCard.removeHiddenExtrinsicKeyword(k); - } + if (hasParam("AddHiddenKeyword")) { + affectedCard.removeHiddenExtrinsicKeywords(timestamp, ability.getId()); } // remove abilities 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 44d7d2cbd43..7c37e2a7fb9 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 @@ -154,7 +154,7 @@ public class AnimateAllEffect extends AnimateEffectBase { @Override public void run() { - doUnanimate(c, sa, hiddenKeywords, timestamp); + doUnanimate(c, timestamp); game.fireEvent(new GameEventCardStatsChanged(c)); } diff --git a/forge-game/src/main/java/forge/game/ability/effects/AnimateEffectBase.java b/forge-game/src/main/java/forge/game/ability/effects/AnimateEffectBase.java index c86fe5e34ae..71c4de1eec3 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/AnimateEffectBase.java +++ b/forge-game/src/main/java/forge/game/ability/effects/AnimateEffectBase.java @@ -103,8 +103,8 @@ public abstract class AnimateEffectBase extends SpellAbilityEffect { c.addCantHaveKeyword(timestamp, Keyword.setValueOf(sa.getParam("CantHaveKeyword"))); } - for (final String k : hiddenKeywords) { - c.addHiddenExtrinsicKeyword(k); + if (!hiddenKeywords.isEmpty()) { + c.addHiddenExtrinsicKeywords(timestamp, 0, hiddenKeywords); } c.addColor(colors, !sa.hasParam("OverwriteColors"), timestamp, false); @@ -157,7 +157,7 @@ public abstract class AnimateEffectBase extends SpellAbilityEffect { @Override public void run() { - doUnanimate(c, sa, hiddenKeywords, timestamp); + doUnanimate(c, timestamp); c.removeChangedName(timestamp); c.updateStateForView(); @@ -217,8 +217,7 @@ public abstract class AnimateEffectBase extends SpellAbilityEffect { * @param timestamp * a long. */ - static void doUnanimate(final Card c, SpellAbility sa, - final List hiddenKeywords, final long timestamp) { + static void doUnanimate(final Card c, final long timestamp) { c.removeNewPT(timestamp); @@ -231,9 +230,7 @@ public abstract class AnimateEffectBase extends SpellAbilityEffect { c.removeCantHaveKeyword(timestamp); - for (final String k : hiddenKeywords) { - c.removeHiddenExtrinsicKeyword(k); - } + c.removeHiddenExtrinsicKeywords(timestamp, 0); // any other unanimate cleanup if (!c.isCreature()) { diff --git a/forge-game/src/main/java/forge/game/ability/effects/CamouflageEffect.java b/forge-game/src/main/java/forge/game/ability/effects/CamouflageEffect.java index c8528f91eff..ac5b0a5a32a 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/CamouflageEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/CamouflageEffect.java @@ -13,6 +13,7 @@ import forge.game.combat.Combat; import forge.game.combat.CombatUtil; import forge.game.player.Player; import forge.game.spellability.SpellAbility; +import forge.game.staticability.StaticAbilityCantAttackBlock; import forge.util.Localizer; public class CamouflageEffect extends SpellAbilityEffect { @@ -36,7 +37,7 @@ public class CamouflageEffect extends SpellAbilityEffect { continue; } - if (attacker.hasKeyword("CantBeBlockedByAmount GT1") && blockers.size() > 1) { + if (StaticAbilityCantAttackBlock.getMinMaxBlocker(attacker, defender).getRight() < blockers.size()) { // If no more than one creature can block, order the player to choose one to block Card chosen = declarer.getController().chooseCardsForEffect(blockers, sa, Localizer.getInstance().getMessage("lblChooseBlockerForAttacker", attacker.toString()), 1, 1, false, null).get(0); diff --git a/forge-game/src/main/java/forge/game/ability/effects/ControlGainEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ControlGainEffect.java index 9705fa0d885..8f05f4e8a3b 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/ControlGainEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/ControlGainEffect.java @@ -128,10 +128,11 @@ public class ControlGainEffect extends SpellAbilityEffect { } final List kws = Lists.newArrayList(); + final List hiddenKws = Lists.newArrayList(); if (null != keywords) { for (final String kw : keywords) { if (kw.startsWith("HIDDEN")) { - tgtC.addHiddenExtrinsicKeyword(kw); + hiddenKws.add(kw.substring(7)); } else { kws.add(kw); } @@ -142,6 +143,9 @@ public class ControlGainEffect extends SpellAbilityEffect { tgtC.addChangedCardKeywords(kws, Lists.newArrayList(), false, false, tStamp, 0); game.fireEvent(new GameEventCardStatsChanged(tgtC)); } + if (hiddenKws.isEmpty()) { + tgtC.addHiddenExtrinsicKeywords(tStamp, 0, hiddenKws); + } if (remember && !source.isRemembered(tgtC)) { source.addRemembered(tgtC); @@ -191,14 +195,8 @@ public class ControlGainEffect extends SpellAbilityEffect { @Override public void run() { - if (keywords.size() > 0) { - for (String kw : keywords) { - if (kw.startsWith("HIDDEN")) { - tgtC.removeHiddenExtrinsicKeyword(kw); - } - } - tgtC.removeChangedCardKeywords(tStamp, 0); - } + tgtC.removeHiddenExtrinsicKeywords(tStamp, 0); + tgtC.removeChangedCardKeywords(tStamp, 0); } }; game.getEndOfTurn().addUntil(untilKeywordEOT); diff --git a/forge-game/src/main/java/forge/game/ability/effects/CountersPutEffect.java b/forge-game/src/main/java/forge/game/ability/effects/CountersPutEffect.java index e845cc1d8ae..377c885a196 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/CountersPutEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/CountersPutEffect.java @@ -32,6 +32,7 @@ import forge.game.player.Player; import forge.game.player.PlayerActionConfirmMode; import forge.game.player.PlayerController; import forge.game.spellability.SpellAbility; +import forge.game.staticability.StaticAbilityAdapt; import forge.game.trigger.Trigger; import forge.game.trigger.TriggerHandler; import forge.game.trigger.TriggerType; @@ -288,8 +289,7 @@ public class CountersPutEffect extends SpellAbilityEffect { // Adapt need extra logic if (sa.hasParam("Adapt")) { - if (!(gameCard.getCounters(CounterEnumType.P1P1) == 0 - || gameCard.hasKeyword("CARDNAME adapts as though it had no +1/+1 counters"))) { + if (!(gameCard.getCounters(CounterEnumType.P1P1) == 0 || StaticAbilityAdapt.anyWithAdapt(sa, gameCard))) { continue; } } @@ -362,8 +362,6 @@ public class CountersPutEffect extends SpellAbilityEffect { game.getTriggerHandler().runTrigger(TriggerType.BecomeRenowned, AbilityKey.mapFromCard(gameCard), false); } if (sa.hasParam("Adapt")) { - // need to remove special keyword - gameCard.removeHiddenExtrinsicKeyword("CARDNAME adapts as though it had no +1/+1 counters"); game.getTriggerHandler().runTrigger(TriggerType.Adapt, AbilityKey.mapFromCard(gameCard), false); } } else { @@ -422,7 +420,7 @@ public class CountersPutEffect extends SpellAbilityEffect { List keywords = Arrays.asList(sa.getParam("SharedKeywords").split(" & ")); List zones = ZoneType.listValueOf(sa.getParam("SharedKeywordsZone")); String[] restrictions = sa.hasParam("SharedRestrictions") ? sa.getParam("SharedRestrictions").split(",") : new String[]{"Card"}; - keywords = CardFactoryUtil.sharedKeywords(keywords, restrictions, zones, card); + keywords = CardFactoryUtil.sharedKeywords(keywords, restrictions, zones, card, sa); for (String k : keywords) { resolvePerType(sa, placer, CounterType.getType(k), counterAmount, table); } diff --git a/forge-game/src/main/java/forge/game/ability/effects/DebuffEffect.java b/forge-game/src/main/java/forge/game/ability/effects/DebuffEffect.java index 72f4a9c40bf..187a68dc0c3 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/DebuffEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/DebuffEffect.java @@ -13,6 +13,7 @@ import forge.card.MagicColor; import forge.game.Game; import forge.game.ability.SpellAbilityEffect; import forge.game.card.Card; +import forge.game.keyword.Keyword; import forge.game.keyword.KeywordInterface; import forge.game.spellability.SpellAbility; @@ -72,17 +73,16 @@ public class DebuffEffect extends SpellAbilityEffect { final List removedKW = Lists.newArrayList(); if (tgtC.isInPlay() && (!sa.usesTargeting() || tgtC.canBeTargetedBy(sa))) { if (sa.hasParam("AllSuffixKeywords")) { - String suffix = sa.getParam("AllSuffixKeywords"); - for (final KeywordInterface kw : tgtC.getKeywords()) { - String keyword = kw.getOriginal(); - if (keyword.endsWith(suffix)) { - kws.add(keyword); + // this only for walk abilities, may to try better + if (sa.getParam("AllSuffixKeywords").equals("walk")) { + for (final KeywordInterface kw : tgtC.getKeywords(Keyword.LANDWALK)) { + removedKW.add(kw.getOriginal()); } } } // special for Protection:Card.:Protection from :* - for (final KeywordInterface inst : tgtC.getKeywords()) { + for (final KeywordInterface inst : tgtC.getUnhiddenKeywords()) { String keyword = inst.getOriginal(); if (keyword.startsWith("Protection:")) { for (final String kw : kws) { diff --git a/forge-game/src/main/java/forge/game/ability/effects/PumpAllEffect.java b/forge-game/src/main/java/forge/game/ability/effects/PumpAllEffect.java index 156cea21c08..4501345e813 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/PumpAllEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/PumpAllEffect.java @@ -31,7 +31,7 @@ public class PumpAllEffect extends SpellAbilityEffect { for (String kw : keywords) { if (kw.startsWith("HIDDEN")) { - hiddenkws.add(kw); + hiddenkws.add(kw.substring(7)); } else { kws.add(kw); } @@ -57,13 +57,15 @@ public class PumpAllEffect extends SpellAbilityEffect { redrawPT = true; } - tgtC.addChangedCardKeywords(kws, null, false, false, timestamp, 0); + if (!kws.isEmpty()) { + tgtC.addChangedCardKeywords(kws, null, false, false, timestamp, 0); + } if (redrawPT) { tgtC.updatePowerToughnessForView(); } - for (String kw : hiddenkws) { - tgtC.addHiddenExtrinsicKeyword(kw); + if (!hiddenkws.isEmpty()) { + tgtC.addHiddenExtrinsicKeywords(timestamp, 0, hiddenkws); } if (sa.hasParam("RememberAllPumped")) { @@ -79,10 +81,8 @@ public class PumpAllEffect extends SpellAbilityEffect { public void run() { tgtC.removePTBoost(timestamp, 0); tgtC.removeChangedCardKeywords(timestamp, 0); + tgtC.removeHiddenExtrinsicKeywords(timestamp, 0); - for (String kw : hiddenkws) { - tgtC.removeHiddenExtrinsicKeyword(kw); - } tgtC.updatePowerToughnessForView(); game.fireEvent(new GameEventCardStatsChanged(tgtC)); @@ -156,7 +156,7 @@ public class PumpAllEffect extends SpellAbilityEffect { String[] restrictions = new String[] {"Card"}; if (sa.hasParam("SharedRestrictions")) restrictions = sa.getParam("SharedRestrictions").split(","); - keywords = CardFactoryUtil.sharedKeywords(keywords, restrictions, zones, sa.getHostCard()); + keywords = CardFactoryUtil.sharedKeywords(keywords, restrictions, zones, sa.getHostCard(), sa); } applyPumpAll(sa, list, a, d, keywords, affectedZones); diff --git a/forge-game/src/main/java/forge/game/ability/effects/PumpEffect.java b/forge-game/src/main/java/forge/game/ability/effects/PumpEffect.java index e92d031ec0e..7921dc15703 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/PumpEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/PumpEffect.java @@ -20,7 +20,6 @@ import forge.game.card.CardCollection; import forge.game.card.CardFactoryUtil; import forge.game.card.CardUtil; import forge.game.event.GameEventCardStatsChanged; -import forge.game.keyword.KeywordInterface; import forge.game.player.Player; import forge.game.player.PlayerCollection; import forge.game.spellability.SpellAbility; @@ -50,11 +49,12 @@ public class PumpEffect extends SpellAbilityEffect { return; } final List kws = Lists.newArrayList(); + final List hiddenKws = Lists.newArrayList(); boolean redrawPT = false; for (String kw : keywords) { if (kw.startsWith("HIDDEN")) { - gameCard.addHiddenExtrinsicKeyword(kw); + hiddenKws.add(kw.substring(7)); redrawPT |= kw.contains("CARDNAME's power and toughness are switched"); } else { kws.add(kw); @@ -66,7 +66,12 @@ public class PumpEffect extends SpellAbilityEffect { redrawPT = true; } - gameCard.addChangedCardKeywords(kws, Lists.newArrayList(), false, false, timestamp, 0); + if (!kws.isEmpty()) { + gameCard.addChangedCardKeywords(kws, Lists.newArrayList(), false, false, timestamp, 0); + } + if (!hiddenKws.isEmpty()) { + gameCard.addHiddenExtrinsicKeywords(timestamp, 0, hiddenKws); + } if (redrawPT) { gameCard.updatePowerToughnessForView(); } @@ -95,12 +100,7 @@ public class PumpEffect extends SpellAbilityEffect { updateText |= gameCard.removeCanBlockAdditional(timestamp); if (keywords.size() > 0) { - - for (String kw : keywords) { - if (kw.startsWith("HIDDEN")) { - gameCard.removeHiddenExtrinsicKeyword(kw); - } - } + gameCard.removeHiddenExtrinsicKeywords(timestamp, 0); gameCard.removeChangedCardKeywords(timestamp, 0); } gameCard.updatePowerToughnessForView(); @@ -244,7 +244,7 @@ public class PumpEffect extends SpellAbilityEffect { if (sa.hasParam("SharedKeywordsZone")) { List zones = ZoneType.listValueOf(sa.getParam("SharedKeywordsZone")); String[] restrictions = sa.hasParam("SharedRestrictions") ? sa.getParam("SharedRestrictions").split(",") : new String[]{"Card"}; - keywords = CardFactoryUtil.sharedKeywords(keywords, restrictions, zones, sa.getHostCard()); + keywords = CardFactoryUtil.sharedKeywords(keywords, restrictions, zones, sa.getHostCard(), sa); } List tgts = Lists.newArrayList(); @@ -290,9 +290,10 @@ public class PumpEffect extends SpellAbilityEffect { List choice = Lists.newArrayList(); List total = Lists.newArrayList(keywords); if (sa.hasParam("NoRepetition")) { - for (KeywordInterface inst : tgtCards.get(0).getKeywords()) { - final String kws = inst.getOriginal(); - total.remove(kws); + for (String kw : keywords) { + if (tgtCards.get(0).hasKeyword(kw)) { + total.remove(kw); + } } } final int min = Math.min(total.size(), numkw); 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 4db52c941bc..b5b109b83ef 100644 --- a/forge-game/src/main/java/forge/game/card/Card.java +++ b/forge-game/src/main/java/forge/game/card/Card.java @@ -55,6 +55,7 @@ import forge.game.replacement.ReplacementResult; import forge.game.replacement.ReplacementType; import forge.game.spellability.*; import forge.game.staticability.StaticAbility; +import forge.game.staticability.StaticAbilityCantAttackBlock; import forge.game.trigger.Trigger; import forge.game.trigger.TriggerType; import forge.game.zone.Zone; @@ -99,7 +100,8 @@ public class Card extends GameEntity implements Comparable, IHasSVars { private CardDamageHistory damageHistory = new CardDamageHistory(); // Hidden keywords won't be displayed on the card - private final KeywordCollection hiddenExtrinsicKeyword = new KeywordCollection(); + // x=timestamp y=StaticAbility id + private final Table> hiddenExtrinsicKeywords = TreeBasedTable.create(); // cards attached or otherwise linked to this card private CardCollection hauntedBy, devouredCards, exploitedCards, delvedCards, convokedCards, imprintedCards, encodedCards; @@ -1867,7 +1869,37 @@ public class Card extends GameEntity implements Comparable, IHasSVars { // get the text that does not belong to a cards abilities (and is not really // there rules-wise) public final String getNonAbilityText() { - return keywordsToText(getHiddenExtrinsicKeywords()); + final StringBuilder sb = new StringBuilder(); + final StringBuilder sbLong = new StringBuilder(); + + for (String keyword : getHiddenExtrinsicKeywords()) { + if (keyword.startsWith("CantBeCounteredBy")) { + final String[] p = keyword.split(":"); + sbLong.append(p[2]).append("\r\n"); + } else if (keyword.equals("Unblockable")) { + sbLong.append(getName()).append(" can't be blocked.\r\n"); + } else if (keyword.equals("AllNonLegendaryCreatureNames")) { + sbLong.append(getName()).append(" has all names of nonlegendary creature cards.\r\n"); + } else if (keyword.startsWith("IfReach")) { + String[] k = keyword.split(":"); + sbLong.append(getName()).append(" can block ") + .append(CardType.getPluralType(k[1])) + .append(" as though it had reach.\r\n"); + } else { + sbLong.append(keyword).append("\r\n"); + } + } + if (sb.length() > 0) { + sb.append("\r\n"); + if (sbLong.length() > 0) { + sb.append("\r\n"); + } + } + if (sbLong.length() > 0) { + sbLong.append("\r\n"); + } + sb.append(sbLong); + return CardTranslation.translateMultipleDescriptionText(sb.toString(), getName()); } // convert a keyword list to the String that should be displayed in game @@ -2120,9 +2152,6 @@ public class Card extends GameEntity implements Comparable, IHasSVars { || keyword.startsWith("Encore") || keyword.startsWith("Mutate") || keyword.startsWith("Dungeon") || keyword.startsWith("Class") || keyword.startsWith("Saga")) { // keyword parsing takes care of adding a proper description - } else if (keyword.startsWith("CantBeBlockedByAmount")) { - sbLong.append(getName()).append(" can't be blocked "); - sbLong.append(getTextForKwCantBeBlockedByAmount(keyword)); } else if (keyword.equals("Unblockable")) { sbLong.append(getName()).append(" can't be blocked.\r\n"); } else if (keyword.equals("AllNonLegendaryCreatureNames")) { @@ -2173,14 +2202,6 @@ public class Card extends GameEntity implements Comparable, IHasSVars { return CardTranslation.translateMultipleDescriptionText(sb.toString(), getName()); } - private static String getTextForKwCantBeBlockedByAmount(final String keyword) { - final String restriction = keyword.split(" ", 2)[1]; - final boolean isLT = "LT".equals(restriction.substring(0,2)); - final String byClause = isLT ? "except by " : "by more than "; - final int cnt = Integer.parseInt(restriction.substring(2)); - return byClause + Lang.nounWithNumeral(cnt, isLT ? "or more creature" : "creature"); - } - // get the text of the abilities of a card public String getAbilityText() { return getAbilityText(currentState); @@ -2367,15 +2388,27 @@ public class Card extends GameEntity implements Comparable, IHasSVars { continue; } for (final StaticAbility stAb : ca.getStaticAbilities()) { - if (stAb.isSecondary() || - !stAb.getParam("Mode").equals("CantBlockBy") || - stAb.isSuppressed() || !stAb.checkConditions() || - !stAb.hasParam("ValidAttacker") || - (stAb.hasParam("ValidBlocker") && stAb.getParam("ValidBlocker").equals("Creature.Self"))) { + if (stAb.isSuppressed() || !stAb.checkConditions()) { continue; } - final Card host = stAb.getHostCard(); - if (isValid(stAb.getParam("ValidAttacker").split(","), host.getController(), host, stAb)) { + + boolean found = false; + if (stAb.getParam("Mode").equals("CantBlockBy")) { + if (!stAb.hasParam("ValidAttacker") || (stAb.hasParam("ValidBlocker") && stAb.getParam("ValidBlocker").equals("Creature.Self"))) { + continue; + } + if (stAb.matchesValidParam("ValidAttacker", this)) { + found = true; + } + } else if (stAb.getParam("Mode").equals(StaticAbilityCantAttackBlock.MinMaxBlockerMode)) { + if (stAb.matchesValidParam("ValidCard", this)) { + found = true; + } + } + + if (found) { + final Card host = stAb.getHostCard(); + String currentName = host.getName(); String desc1 = TextUtil.fastReplace(stAb.toString(), "CARDNAME", currentName); String desc = TextUtil.fastReplace(desc1,"NICKNAME", currentName.split(",")[0]); @@ -4119,7 +4152,6 @@ public class Card extends GameEntity implements Comparable, IHasSVars { // of lists. Optimizes common operations such as hasKeyword(). public final void visitKeywords(CardState state, Visitor visitor) { visitUnhiddenKeywords(state, visitor); - visitHiddenExtrinsicKeywords(visitor); } @Override @@ -4140,8 +4172,10 @@ public class Card extends GameEntity implements Comparable, IHasSVars { } // shortcut for hidden keywords - if (this.hiddenExtrinsicKeyword.contains(keyword)) { - return true; + for (List kw : this.hiddenExtrinsicKeywords.values()) { + if (kw.contains(keyword)) { + return true; + } } HasKeywordVisitor visitor = new HasKeywordVisitor(keyword, false); @@ -4418,41 +4452,33 @@ public class Card extends GameEntity implements Comparable, IHasSVars { } // Hidden Keywords will be returned without the indicator HIDDEN - public final List getHiddenExtrinsicKeywords() { - ListKeywordVisitor visitor = new ListKeywordVisitor(); - visitHiddenExtrinsicKeywords(visitor); - return visitor.getKeywords(); - } - private void visitHiddenExtrinsicKeywords(Visitor visitor) { - for (KeywordInterface inst : hiddenExtrinsicKeyword.getValues()) { - if (!visitor.visit(inst)) { - return; - } - } + public final Iterable getHiddenExtrinsicKeywords() { + return Iterables.concat(this.hiddenExtrinsicKeywords.values()); } - public final void addHiddenExtrinsicKeyword(String s) { - if (s.startsWith("HIDDEN")) { - s = s.substring(7); - } - if (hiddenExtrinsicKeyword.add(s) != null) { - view.updateNonAbilityText(this); - updateKeywords(); - } + public final void addHiddenExtrinsicKeywords(long timestamp, long staticId, Iterable keywords) { + // TODO if some keywords aren't removed anymore, then no need for extra Array List + hiddenExtrinsicKeywords.put(timestamp, staticId, Lists.newArrayList(keywords)); + + view.updateNonAbilityText(this); + updateKeywords(); } - public final void addHiddenExtrinsicKeyword(KeywordInterface k) { - if (hiddenExtrinsicKeyword.insert(k)) { + public final void removeHiddenExtrinsicKeywords(long timestamp, long staticId) { + if (hiddenExtrinsicKeywords.remove(timestamp, staticId) != null) { view.updateNonAbilityText(this); updateKeywords(); } } public final void removeHiddenExtrinsicKeyword(String s) { - if (s.startsWith("HIDDEN")) { - s = s.substring(7); + boolean updated = false; + for (List list : hiddenExtrinsicKeywords.values()) { + if (list.remove(s)) { + updated = true; + } } - if (hiddenExtrinsicKeyword.remove(s)) { + if (updated) { view.updateNonAbilityText(this); updateKeywords(); } @@ -4710,6 +4736,12 @@ public class Card extends GameEntity implements Comparable, IHasSVars { return hasStartOfKeyword(keyword, currentState); } public final boolean hasStartOfKeyword(String keyword, CardState state) { + for (String s : this.getHiddenExtrinsicKeywords()) { + if (s.startsWith(keyword)) { + return true; + } + } + HasKeywordVisitor visitor = new HasKeywordVisitor(keyword, true); visitKeywords(state, visitor); return visitor.getResult(); @@ -4741,9 +4773,10 @@ public class Card extends GameEntity implements Comparable, IHasSVars { return getAmountOfKeyword(k, currentState); } public final int getAmountOfKeyword(final String k, CardState state) { + int count = Iterables.frequency(this.getHiddenExtrinsicKeywords(), k); CountKeywordVisitor visitor = new CountKeywordVisitor(k); visitKeywords(state, visitor); - return visitor.getCount(); + return count + visitor.getCount(); } public final int getAmountOfKeyword(final Keyword k) { @@ -5619,11 +5652,8 @@ public class Card extends GameEntity implements Comparable, IHasSVars { final boolean colorlessDamage = damageSource && source.hasKeyword("Colorless Damage Source"); - for (final KeywordInterface inst : getKeywords()) { + for (final KeywordInterface inst : getKeywords(Keyword.PROTECTION)) { String kw = inst.getOriginal(); - if (!kw.startsWith("Protection")) { - continue; - } if (kw.equals("Protection from white")) { if (source.isWhite() && !colorlessDamage) { return true; @@ -5698,11 +5728,8 @@ public class Card extends GameEntity implements Comparable, IHasSVars { public String getProtectionKey() { String protectKey = ""; boolean pR = false; boolean pG = false; boolean pB = false; boolean pU = false; boolean pW = false; - for (final KeywordInterface inst : getKeywords()) { + for (final KeywordInterface inst : getKeywords(Keyword.PROTECTION)) { String kw = inst.getOriginal(); - if (!kw.startsWith("Protection")) { - continue; - } if (kw.contains("Protection from red")) { if (!pR) { pR = true; @@ -5755,11 +5782,8 @@ public class Card extends GameEntity implements Comparable, IHasSVars { public String getHexproofKey() { String hexproofKey = ""; boolean hR = false; boolean hG = false; boolean hB = false; boolean hU = false; boolean hW = false; - for (final KeywordInterface inst : getKeywords()) { + for (final KeywordInterface inst : getKeywords(Keyword.HEXPROOF)) { String kw = inst.getOriginal(); - if (!kw.startsWith("Hexproof")) { - continue; - } if (kw.equals("Hexproof")) { hexproofKey += "generic:"; } @@ -5859,6 +5883,7 @@ public class Card extends GameEntity implements Comparable, IHasSVars { final Card source = sa.getHostCard(); if (sa.isSpell()) { + // TODO replace with Static Ability for(KeywordInterface inst : source.getKeywords()) { String kw = inst.getOriginal(); if(!kw.startsWith("SpellCantTarget")) { @@ -6506,23 +6531,16 @@ public class Card extends GameEntity implements Comparable, IHasSVars { private static final class CountKeywordVisitor extends Visitor { private String keyword; private int count; - private boolean startOf; private CountKeywordVisitor(String keyword) { this.keyword = keyword; this.count = 0; - this.startOf = false; - } - - private CountKeywordVisitor(String keyword, boolean startOf) { - this(keyword); - this.startOf = startOf; } @Override public boolean visit(KeywordInterface inst) { final String kw = inst.getOriginal(); - if ((startOf && kw.startsWith(keyword)) || kw.equals(keyword)) { + if (kw.equals(keyword)) { count++; } return true; @@ -6551,7 +6569,6 @@ public class Card extends GameEntity implements Comparable, IHasSVars { } return result.isFalse(); } - public boolean getResult() { return result.isTrue(); } 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 c7710814b49..57a5f070984 100644 --- a/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java +++ b/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java @@ -42,6 +42,7 @@ import forge.card.ICardFace; import forge.card.MagicColor; import forge.card.mana.ManaCost; import forge.card.mana.ManaCostParser; +import forge.game.CardTraitBase; import forge.game.Game; import forge.game.GameEntityCounterTable; import forge.game.GameLogEntryType; @@ -264,8 +265,7 @@ public class CardFactoryUtil { return false; } - for (KeywordInterface k : c.getKeywords()) { - final String o = k.getOriginal(); + for (String o : c.getHiddenExtrinsicKeywords()) { if (o.startsWith("CantBeCounteredBy")) { final String[] m = o.split(":"); if (sa.isValid(m[1].split(","), c.getController(), c, null)) { @@ -510,7 +510,7 @@ public class CardFactoryUtil { * @return a List. */ public static List sharedKeywords(final Iterable kw, final String[] restrictions, - final Iterable zones, final Card host) { + final Iterable zones, final Card host, CardTraitBase ctb) { final List filteredkw = Lists.newArrayList(); final Player p = host.getController(); CardCollectionView cardlist = p.getGame().getCardsIn(zones); @@ -521,7 +521,7 @@ public class CardFactoryUtil { final Set tramplekw = Sets.newHashSet(); final Set allkw = Sets.newHashSet(); - for (Card c : CardLists.getValidCards(cardlist, restrictions, p, host, null)) { + for (Card c : CardLists.getValidCards(cardlist, restrictions, p, host, ctb)) { for (KeywordInterface inst : c.getKeywords()) { final String k = inst.getOriginal(); if (k.endsWith("walk")) { diff --git a/forge-game/src/main/java/forge/game/card/CardPredicates.java b/forge-game/src/main/java/forge/game/card/CardPredicates.java index ee03689e056..d8712ecacab 100644 --- a/forge-game/src/main/java/forge/game/card/CardPredicates.java +++ b/forge-game/src/main/java/forge/game/card/CardPredicates.java @@ -21,6 +21,7 @@ import java.util.Comparator; import com.google.common.base.Function; import com.google.common.base.Predicate; +import com.google.common.collect.Iterables; import forge.card.CardStateName; import forge.game.CardTraitBase; @@ -31,6 +32,7 @@ import forge.game.player.Player; import forge.game.spellability.SpellAbility; import forge.game.zone.Zone; import forge.game.zone.ZoneType; +import forge.util.PredicateString; import forge.util.collect.FCollectionView; @@ -109,6 +111,10 @@ public final class CardPredicates { return new Predicate() { @Override public boolean apply(final Card c) { + if (Iterables.any(c.getHiddenExtrinsicKeywords(), PredicateString.contains(keyword))) { + return true; + } + for (KeywordInterface k : c.getKeywords()) { if (k.getOriginal().contains(keyword)) { return true; diff --git a/forge-game/src/main/java/forge/game/combat/AttackRequirement.java b/forge-game/src/main/java/forge/game/combat/AttackRequirement.java index bdbf033b9e2..2d5570323a0 100644 --- a/forge-game/src/main/java/forge/game/combat/AttackRequirement.java +++ b/forge-game/src/main/java/forge/game/combat/AttackRequirement.java @@ -52,18 +52,27 @@ public class AttackRequirement { nAttackAnything += attacker.getGoaded().size(); } + // remove it when all of them are HIDDEN or static for (final KeywordInterface inst : attacker.getKeywords()) { final String keyword = inst.getOriginal(); if (keyword.startsWith("CARDNAME attacks specific player each combat if able")) { final String defined = keyword.split(":")[1]; final GameEntity mustAttack2 = AbilityUtils.getDefinedPlayers(attacker, defined, null).get(0); defenderSpecific.add(mustAttack2); - } else if (keyword.equals("CARDNAME attacks each combat if able.") || - (keyword.equals("CARDNAME attacks each turn if able.") - && !attacker.getDamageHistory().getCreatureAttackedThisTurn())) { + } else if (keyword.equals("CARDNAME attacks each combat if able.")) { nAttackAnything++; } } + for (final String keyword : attacker.getHiddenExtrinsicKeywords()) { + if (keyword.startsWith("CARDNAME attacks specific player each combat if able")) { + final String defined = keyword.split(":")[1]; + final GameEntity mustAttack2 = AbilityUtils.getDefinedPlayers(attacker, defined, null).get(0); + defenderSpecific.add(mustAttack2); + } else if (keyword.equals("CARDNAME attacks each combat if able.")) { + nAttackAnything++; + } + } + final GameEntity mustAttack3 = attacker.getMustAttackEntity(); if (mustAttack3 != null) { defenderSpecific.add(mustAttack3); @@ -149,7 +158,7 @@ public class AttackRequirement { int violations = 0; // first. check to see if "must attack X or Y with at least one creature" requirements are satisfied - List toRemoveFromDefSpecific = Lists.newArrayList(); + //List toRemoveFromDefSpecific = Lists.newArrayList(); if (!defenderOrPWSpecific.isEmpty()) { for (GameEntity def : defenderOrPWSpecific.keySet()) { if (defenderSpecificAlternatives.containsKey(def)) { diff --git a/forge-game/src/main/java/forge/game/combat/CombatUtil.java b/forge-game/src/main/java/forge/game/combat/CombatUtil.java index c25d7ca22f5..2f9d61dad8e 100644 --- a/forge-game/src/main/java/forge/game/combat/CombatUtil.java +++ b/forge-game/src/main/java/forge/game/combat/CombatUtil.java @@ -47,9 +47,9 @@ import forge.game.player.Player; import forge.game.player.PlayerController.ManaPaymentPurpose; import forge.game.spellability.SpellAbility; import forge.game.staticability.StaticAbility; +import forge.game.staticability.StaticAbilityCantAttackBlock; import forge.game.trigger.TriggerType; import forge.game.zone.ZoneType; -import forge.util.Expressions; import forge.util.TextUtil; import forge.util.collect.FCollection; import forge.util.collect.FCollectionView; @@ -251,12 +251,9 @@ public class CombatUtil { } // Keywords - for (final KeywordInterface keyword : attacker.getKeywords()) { - switch (keyword.getOriginal()) { - case "CARDNAME can't attack.": - case "CARDNAME can't attack or block.": - return false; - } + // replace with Static Ability if able + if (attacker.hasKeyword("CARDNAME can't attack.") || attacker.hasKeyword("CARDNAME can't attack or block.")) { + return false; } // CantAttack static abilities @@ -505,7 +502,7 @@ public class CombatUtil { } if ( combat != null ) { - if (attacker.hasStartOfKeyword("CantBeBlockedByAmount GT") && !combat.getBlockers(attacker).isEmpty()) { + if (StaticAbilityCantAttackBlock.getMinMaxBlocker(attacker, defendingPlayer).getRight() == combat.getBlockers(attacker).size()) { return false; } @@ -587,7 +584,7 @@ public class CombatUtil { } } - for (final KeywordInterface inst : attacker.getKeywords()) { + for (final KeywordInterface inst : attacker.getKeywords(Keyword.LANDWALK)) { String keyword = inst.getOriginal(); if (keyword.equals("Legendary landwalk")) { walkTypes.add("Land.Legendary"); @@ -762,11 +759,11 @@ public class CombatUtil { } // "CARDNAME blocks each turn/combat if able." - if (!blockers.contains(blocker) && (blocker.hasKeyword("CARDNAME blocks each turn if able.") || blocker.hasKeyword("CARDNAME blocks each combat if able."))) { + if (!blockers.contains(blocker) && (blocker.hasKeyword("CARDNAME blocks each combat if able."))) { for (final Card attacker : attackers) { if (canBlock(attacker, blocker, combat)) { boolean must = true; - if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT") || attacker.hasKeyword(Keyword.MENACE)) { + if (StaticAbilityCantAttackBlock.getMinMaxBlocker(attacker, defending).getLeft() > 1) { final List possibleBlockers = Lists.newArrayList(defendersArmy); possibleBlockers.remove(blocker); if (!canBeBlocked(attacker, possibleBlockers, combat)) { @@ -774,8 +771,7 @@ public class CombatUtil { } } if (must) { - String unit = blocker.hasKeyword("CARDNAME blocks each combat if able.") ? "combat," : "turn,"; - return TextUtil.concatWithSpace(blocker.toString(),"must block each", unit, "but was not assigned to block any attacker now."); + return TextUtil.concatWithSpace(blocker.toString(),"must block each combat but was not assigned to block any attacker now."); } } } @@ -848,6 +844,7 @@ public class CombatUtil { && combat.getBlockers(attacker).size() < 2)) { attackersWithLure.add(attacker); } else { + // TODO replace with Hidden Keyword or Static Ability for (KeywordInterface inst : attacker.getKeywords()) { String keyword = inst.getOriginal(); // MustBeBlockedBy @@ -875,8 +872,11 @@ public class CombatUtil { for (final Card attacker : attackersWithLure) { if (canBeBlocked(attacker, combat, defender) && canBlock(attacker, blocker)) { boolean canBe = true; - if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT") || attacker.hasKeyword(Keyword.MENACE)) { - final List blockers = combat.getDefenderPlayerByAttacker(attacker).getCreaturesInPlay(); + + Player defendingPlayer = combat.getDefenderPlayerByAttacker(attacker); + + if (StaticAbilityCantAttackBlock.getMinMaxBlocker(attacker, defendingPlayer).getLeft() > 1) { + final List blockers = defendingPlayer.getCreaturesInPlay(); blockers.remove(blocker); if (!canBeBlocked(attacker, blockers, combat)) { canBe = false; @@ -893,8 +893,9 @@ public class CombatUtil { if (canBeBlocked(attacker, combat, defender) && canBlock(attacker, blocker) && combat.isAttacking(attacker)) { boolean canBe = true; - if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT") || attacker.hasKeyword(Keyword.MENACE)) { - final List blockers = freeBlockers != null ? new CardCollection(freeBlockers) : combat.getDefenderPlayerByAttacker(attacker).getCreaturesInPlay(); + Player defendingPlayer = combat.getDefenderPlayerByAttacker(attacker); + if (StaticAbilityCantAttackBlock.getMinMaxBlocker(attacker, defendingPlayer).getLeft() > 1) { + final List blockers = freeBlockers != null ? new CardCollection(freeBlockers) : defendingPlayer.getCreaturesInPlay(); blockers.remove(blocker); if (!canBeBlocked(attacker, blockers, combat)) { canBe = false; @@ -968,6 +969,7 @@ public class CombatUtil { return false; } + // TODO remove with HiddenKeyword or Static Ability boolean mustBeBlockedBy = false; for (KeywordInterface inst : attacker.getKeywords()) { String keyword = inst.getOriginal(); @@ -1085,60 +1087,16 @@ public class CombatUtil { if (amount == 0) return false; // no block - List restrictions = Lists.newArrayList(); - for (KeywordInterface inst : attacker.getKeywords()) { - String kw = inst.getOriginal(); - if (kw.startsWith("CantBeBlockedByAmount")) { - restrictions.add(TextUtil.split(kw, ' ', 2)[1]); - } - if (kw.equals("Menace")) { - restrictions.add("LT2"); - } - } - for (String res : restrictions) { - int operand = Integer.parseInt(res.substring(2)); - String operator = res.substring(0,2); - if (Expressions.compare(amount, operator, operand) ) - return false; - } - if (defender != null && attacker.hasKeyword("CARDNAME can't be blocked unless all creatures defending player controls block it.")) { - return amount >= defender.getCreaturesInPlay().size(); + Pair minMaxBlock = StaticAbilityCantAttackBlock.getMinMaxBlocker(attacker, defender); + + if (minMaxBlock.getLeft() > amount || minMaxBlock.getRight() < amount) { + return false; } return true; } public static int getMinNumBlockersForAttacker(Card attacker, Player defender) { - List restrictions = Lists.newArrayList(); - for (KeywordInterface inst : attacker.getKeywords()) { - String kw = inst.getOriginal(); - if (kw.startsWith("CantBeBlockedByAmount")) { - restrictions.add(TextUtil.split(kw, ' ', 2)[1]); - } - if (kw.equals("Menace")) { - restrictions.add("LT2"); - } - } - int minBlockers = 1; - for (String res : restrictions) { - int operand = Integer.parseInt(res.substring(2)); - String operator = res.substring(0, 2); - if (operator.equals("LT") || operator.equals("GE")) { - if (minBlockers < operand) { - minBlockers = operand; - } - } else if (operator.equals("LE") || operator.equals("GT") || operator.equals("EQ")) { - if (minBlockers < operand + 1) { - minBlockers = operand + 1; - } - } - } - if (attacker.hasKeyword("CARDNAME can't be blocked unless all creatures defending player controls block it.")) { - if (defender != null) { - minBlockers = defender.getCreaturesInPlay().size(); - } - } - - return minBlockers; + return StaticAbilityCantAttackBlock.getMinMaxBlocker(attacker, defender).getLeft(); } } // end class CombatUtil diff --git a/forge-game/src/main/java/forge/game/cost/CostAdjustment.java b/forge-game/src/main/java/forge/game/cost/CostAdjustment.java index db947d0c9aa..89a70a2862f 100644 --- a/forge-game/src/main/java/forge/game/cost/CostAdjustment.java +++ b/forge-game/src/main/java/forge/game/cost/CostAdjustment.java @@ -284,12 +284,10 @@ public class CostAdjustment { private static void adjustCostByOffering(final ManaCostBeingPaid cost, final SpellAbility sa) { String offeringType = ""; - for (KeywordInterface inst : sa.getHostCard().getKeywords()) { + for (KeywordInterface inst : sa.getHostCard().getKeywords(Keyword.OFFERING)) { final String kw = inst.getOriginal(); - if (kw.endsWith(" offering")) { - offeringType = kw.split(" ")[0]; - break; - } + offeringType = kw.split(" ")[0]; + break; } Card toSac = null; diff --git a/forge-game/src/main/java/forge/game/keyword/KeywordCollection.java b/forge-game/src/main/java/forge/game/keyword/KeywordCollection.java index 56a10e903c1..25f90dd9b5c 100644 --- a/forge-game/src/main/java/forge/game/keyword/KeywordCollection.java +++ b/forge-game/src/main/java/forge/game/keyword/KeywordCollection.java @@ -12,7 +12,6 @@ import forge.game.card.Card; public class KeywordCollection implements Iterable { - private boolean hidden = false; private transient KeywordCollectionView view; // don't use enumKeys it causes a slow down @@ -21,11 +20,6 @@ public class KeywordCollection implements Iterable { public KeywordCollection() { super(); - this.hidden = false; - } - public KeywordCollection(boolean hidden) { - super(); - this.hidden = hidden; } public boolean contains(Keyword keyword) { @@ -50,7 +44,6 @@ public class KeywordCollection implements Iterable { public KeywordInterface add(String k) { KeywordInterface inst = Keyword.getInstance(k); - inst.setHidden(hidden); if (insert(inst)) { return inst; } diff --git a/forge-game/src/main/java/forge/game/keyword/KeywordInstance.java b/forge-game/src/main/java/forge/game/keyword/KeywordInstance.java index d740b3640e9..ad1f528aa11 100644 --- a/forge-game/src/main/java/forge/game/keyword/KeywordInstance.java +++ b/forge-game/src/main/java/forge/game/keyword/KeywordInstance.java @@ -23,8 +23,6 @@ public abstract class KeywordInstance> implements K private Keyword keyword; private String original; - private boolean hidden; - private List triggers = Lists.newArrayList(); private List replacements = Lists.newArrayList(); private List abilities = Lists.newArrayList(); @@ -205,21 +203,6 @@ public abstract class KeywordInstance> implements K staticAbilities.add(st); } - /* (non-Javadoc) - * @see forge.game.keyword.KeywordInterface#getHidden() - */ - @Override - public boolean getHidden() { - return hidden; - } - /* (non-Javadoc) - * @see forge.game.keyword.KeywordInterface#setHidden(boolean) - */ - @Override - public void setHidden(boolean val) { - hidden = val; - } - /* * (non-Javadoc) * @see forge.game.keyword.KeywordInterface#getTriggers() diff --git a/forge-game/src/main/java/forge/game/keyword/KeywordInterface.java b/forge-game/src/main/java/forge/game/keyword/KeywordInterface.java index 2832cdd2ee4..d508fbdab02 100644 --- a/forge-game/src/main/java/forge/game/keyword/KeywordInterface.java +++ b/forge-game/src/main/java/forge/game/keyword/KeywordInterface.java @@ -18,10 +18,7 @@ public interface KeywordInterface extends Cloneable { String getReminderText(); int getAmount(); - - boolean getHidden(); - void setHidden(boolean val); - + void createTraits(final Card host, final boolean intrinsic); void createTraits(final Card host, final boolean intrinsic, final boolean clear); diff --git a/forge-game/src/main/java/forge/game/phase/Untap.java b/forge-game/src/main/java/forge/game/phase/Untap.java index c6894964c1f..b13a12a64b1 100644 --- a/forge-game/src/main/java/forge/game/phase/Untap.java +++ b/forge-game/src/main/java/forge/game/phase/Untap.java @@ -205,11 +205,12 @@ public class Untap extends Phase { } // Remove temporary keywords + // TODO Replace with Static Abilities for (final Card c : player.getCardsIn(ZoneType.Battlefield)) { c.removeHiddenExtrinsicKeyword("This card doesn't untap during your next untap step."); if (c.hasKeyword("This card doesn't untap during your next two untap steps.")) { c.removeHiddenExtrinsicKeyword("This card doesn't untap during your next two untap steps."); - c.addHiddenExtrinsicKeyword("This card doesn't untap during your next untap step."); + c.addHiddenExtrinsicKeywords(game.getNextTimestamp(), 0, Lists.newArrayList("This card doesn't untap during your next untap step.")); } } 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 963ed5be703..93ea1fbfa89 100644 --- a/forge-game/src/main/java/forge/game/player/Player.java +++ b/forge-game/src/main/java/forge/game/player/Player.java @@ -3014,7 +3014,7 @@ public class Player extends GameEntity implements Comparable { game.getAction().checkStaticAbilities(false); for (final Card c : getCardsIn(ZoneType.Sideboard)) { - for (KeywordInterface inst : c.getKeywords()) { + for (KeywordInterface inst : c.getKeywords(Keyword.COMPANION)) { if (!(inst instanceof Companion)) { continue; } diff --git a/forge-game/src/main/java/forge/game/staticability/StaticAbilityAdapt.java b/forge-game/src/main/java/forge/game/staticability/StaticAbilityAdapt.java new file mode 100644 index 00000000000..e052d645a69 --- /dev/null +++ b/forge-game/src/main/java/forge/game/staticability/StaticAbilityAdapt.java @@ -0,0 +1,38 @@ +package forge.game.staticability; + +import forge.game.Game; +import forge.game.card.Card; +import forge.game.spellability.SpellAbility; +import forge.game.zone.ZoneType; + +public class StaticAbilityAdapt { + + static String MODE = "CanAdapt"; + + public static boolean anyWithAdapt(final SpellAbility sa, final Card card) { + final Game game = card.getGame(); + for (final Card ca : game.getCardsIn(ZoneType.STATIC_ABILITIES_SOURCE_ZONES)) { + for (final StaticAbility stAb : ca.getStaticAbilities()) { + if (!stAb.getParam("Mode").equals(MODE) || stAb.isSuppressed() || !stAb.checkConditions()) { + continue; + } + if (applyWithAdapt(stAb, sa, card)) { + return true; + } + } + } + return false; + } + + + public static boolean applyWithAdapt(final StaticAbility stAb, final SpellAbility sa, final Card card) { + if (!stAb.matchesValidParam("ValidCard", card)) { + return false; + } + + if (!stAb.matchesValidParam("ValidSA", sa)) { + return false; + } + return true; + } +} diff --git a/forge-game/src/main/java/forge/game/staticability/StaticAbilityCantAttackBlock.java b/forge-game/src/main/java/forge/game/staticability/StaticAbilityCantAttackBlock.java index b5970550a0a..c8ce0364203 100644 --- a/forge-game/src/main/java/forge/game/staticability/StaticAbilityCantAttackBlock.java +++ b/forge-game/src/main/java/forge/game/staticability/StaticAbilityCantAttackBlock.java @@ -17,14 +17,19 @@ */ package forge.game.staticability; +import org.apache.commons.lang3.tuple.MutablePair; +import org.apache.commons.lang3.tuple.Pair; + import com.google.common.collect.Iterables; +import forge.game.Game; import forge.game.GameEntity; import forge.game.ability.AbilityUtils; import forge.game.card.Card; import forge.game.card.CardCollectionView; import forge.game.card.CardPredicates; import forge.game.cost.Cost; +import forge.game.keyword.Keyword; import forge.game.keyword.KeywordInterface; import forge.game.player.Player; import forge.game.zone.ZoneType; @@ -34,6 +39,8 @@ import forge.game.zone.ZoneType; */ public class StaticAbilityCantAttackBlock { + public static String MinMaxBlockerMode = "MinMaxBlocker"; + /** * TODO Write javadoc for this method. * @@ -221,4 +228,43 @@ public class StaticAbilityCantAttackBlock { } return true; } + + public static Pair getMinMaxBlocker(final Card attacker, final Player defender) { + MutablePair result = MutablePair.of(1, Integer.MAX_VALUE); + + // Menace keyword + if (attacker.hasKeyword(Keyword.MENACE)) { + result.setLeft(2); + } + + final Game game = attacker.getGame(); + for (final Card ca : game.getCardsIn(ZoneType.STATIC_ABILITIES_SOURCE_ZONES)) { + for (final StaticAbility stAb : ca.getStaticAbilities()) { + if (!stAb.getParam("Mode").equals(MinMaxBlockerMode) || stAb.isSuppressed() || !stAb.checkConditions()) { + continue; + } + applyMinMaxBlockerAbility(stAb, attacker, defender, result); + } + } + if (attacker.hasKeyword("CARDNAME can't be blocked unless all creatures defending player controls block it.")) { + if (defender != null) { + result.setLeft(defender.getCreaturesInPlay().size()); + } + } + return result; + } + + public static void applyMinMaxBlockerAbility(final StaticAbility stAb, final Card attacker, final Player defender, MutablePair result) { + if (!stAb.matchesValidParam("ValidCard", attacker)) { + return; + } + + if (stAb.hasParam("Min")) { + result.setLeft(AbilityUtils.calculateAmount(stAb.getHostCard(), stAb.getParam("Min"), stAb)); + } + + if (stAb.hasParam("Max")) { + result.setRight(AbilityUtils.calculateAmount(stAb.getHostCard(), stAb.getParam("Max"), stAb)); + } + } } diff --git a/forge-game/src/main/java/forge/game/staticability/StaticAbilityContinuous.java b/forge-game/src/main/java/forge/game/staticability/StaticAbilityContinuous.java index 0d705998262..a51c9bb1ef4 100644 --- a/forge-game/src/main/java/forge/game/staticability/StaticAbilityContinuous.java +++ b/forge-game/src/main/java/forge/game/staticability/StaticAbilityContinuous.java @@ -326,7 +326,7 @@ public final class StaticAbilityContinuous { if (params.containsKey("SharedKeywordsZone")) { List zones = ZoneType.listValueOf(params.get("SharedKeywordsZone")); String[] restrictions = params.containsKey("SharedRestrictions") ? params.get("SharedRestrictions").split(",") : new String[] {"Card"}; - addKeywords = CardFactoryUtil.sharedKeywords(addKeywords, restrictions, zones, hostCard); + addKeywords = CardFactoryUtil.sharedKeywords(addKeywords, restrictions, zones, hostCard, stAb); } } @@ -708,9 +708,7 @@ public final class StaticAbilityContinuous { // add HIDDEN keywords if (!addHiddenKeywords.isEmpty()) { - for (final String k : addHiddenKeywords) { - affectedCard.addHiddenExtrinsicKeyword(k); - } + affectedCard.addHiddenExtrinsicKeywords(hostCard.getTimestamp(), stAb.getId(), addHiddenKeywords); } // add SVars diff --git a/forge-gui/res/cardsfolder/a/alpha_authority.txt b/forge-gui/res/cardsfolder/a/alpha_authority.txt index 13c0923edbe..dbf9d216d25 100644 --- a/forge-gui/res/cardsfolder/a/alpha_authority.txt +++ b/forge-gui/res/cardsfolder/a/alpha_authority.txt @@ -3,6 +3,6 @@ ManaCost:1 G Types:Enchantment Aura K:Enchant creature A:SP$ Attach | Cost$ 1 G | ValidTgts$ Creature | AILogic$ Pump -S:Mode$ Continuous | Affected$ Creature.EnchantedBy | AddKeyword$ Hexproof | AddHiddenKeyword$ CantBeBlockedByAmount GT1 | Description$ Enchanted creature has hexproof and can't be blocked by more than one creature. -SVar:Picture:http://www.wizards.com/global/images/magic/general/alpha_authority.jpg +S:Mode$ Continuous | Affected$ Creature.EnchantedBy | AddKeyword$ Hexproof | Description$ Enchanted creature has hexproof and can't be blocked by more than one creature. +S:Mode$ MinMaxBlocker | ValidCard$ Creature.EnchantedBy | Max$ 1 | Secondary$ True | Description$ Enchanted creature can't be blocked by more than one creature. Oracle:Enchant creature\nEnchanted creature has hexproof and can't be blocked by more than one creature. diff --git a/forge-gui/res/cardsfolder/b/battlefront_krushok.txt b/forge-gui/res/cardsfolder/b/battlefront_krushok.txt index c403246106d..b03891e8605 100644 --- a/forge-gui/res/cardsfolder/b/battlefront_krushok.txt +++ b/forge-gui/res/cardsfolder/b/battlefront_krushok.txt @@ -2,10 +2,9 @@ Name:Battlefront Krushok ManaCost:4 G Types:Creature Beast PT:3/4 -K:CantBeBlockedByAmount GT1 -S:Mode$ Continuous | Affected$ Creature.YouCtrl+counters_GE1_P1P1 | AddHiddenKeyword$ CantBeBlockedByAmount GT1 | Description$ Each creature you control with a +1/+1 counter on it can't be blocked by more than one creature. +S:Mode$ MinMaxBlocker | ValidCard$ Card.Self | Max$ 1 | Description$ CARDNAME can't be blocked by more than one creature. +S:Mode$ MinMaxBlocker | ValidCard$ Creature.YouCtrl+counters_GE1_P1P1 | Max$ 1 | Description$ Each creature you control with a +1/+1 counter on it can't be blocked by more than one creature. SVar:NonStackingEffect:True SVar:PlayMain1:TRUE DeckHints:Ability$Counters -SVar:Picture:http://www.wizards.com/global/images/magic/general/battlefront_krushok.jpg Oracle:Battlefront Krushok can't be blocked by more than one creature.\nEach creature you control with a +1/+1 counter on it can't be blocked by more than one creature. diff --git a/forge-gui/res/cardsfolder/b/biomancers_familiar.txt b/forge-gui/res/cardsfolder/b/biomancers_familiar.txt index 7e69f3e6c2a..efdf64330d2 100644 --- a/forge-gui/res/cardsfolder/b/biomancers_familiar.txt +++ b/forge-gui/res/cardsfolder/b/biomancers_familiar.txt @@ -3,6 +3,9 @@ ManaCost:G U Types:Creature Mutant PT:2/2 S:Mode$ ReduceCost | ValidCard$ Creature.YouCtrl | Type$ Ability | Amount$ 2 | MinMana$ 1 | AffectedZone$ Battlefield | Description$ Activated abilities of creatures you control cost {2} less to activate. This effect can't reduce the mana in that cost to less than one mana. -A:AB$ Pump | Cost$ T | ValidTgts$ Creature | KW$ HIDDEN CARDNAME adapts as though it had no +1/+1 counters | TgtPrompt$ Select target creature. | StackDescription$ SpellDescription | SpellDescription$ The next time target creature adapts this turn, it adapts as though it had no +1/+1 counters on it. +A:AB$ Effect | Cost$ T | ValidTgts$ Creature | RememberObjects$ ThisTargetedCard | StaticAbilities$ StaticAllowAdapt | Triggers$ TriggerClearAdapt | TgtPrompt$ Select target creature. | StackDescription$ SpellDescription | SpellDescription$ The next time target creature adapts this turn, it adapts as though it had no +1/+1 counters on it. +SVar:StaticAllowAdapt:Mode$ CanAdapt | ValidCard$ Card.IsRemembered | Description$ Remembered adapts as though it had no +1/+1 counters on it. +SVar:TriggerClearAdapt:Mode$ Adapt | ValidCard$ Card.IsRemembered | Execute$ ExileSelf | Static$ True +SVar:ExileSelf:DB$ ChangeZone | Defined$ Self | Origin$ Command | Destination$ Exile DeckHints:Keyword$Adapt Oracle:Activated abilities of creatures you control cost {2} less to activate. This effect can't reduce the mana in that cost to less than one mana.\n{T}: The next time target creature adapts this turn, it adapts as though it had no +1/+1 counters on it. diff --git a/forge-gui/res/cardsfolder/b/bristling_boar.txt b/forge-gui/res/cardsfolder/b/bristling_boar.txt index e2cb49f247a..317513c4abb 100644 --- a/forge-gui/res/cardsfolder/b/bristling_boar.txt +++ b/forge-gui/res/cardsfolder/b/bristling_boar.txt @@ -1,6 +1,6 @@ Name:Bristling Boar ManaCost:3 G Types:Creature Boar -K:CantBeBlockedByAmount GT1 +PT:4/3 +S:Mode$ MinMaxBlocker | ValidCard$ Card.Self | Max$ 1 | Description$ CARDNAME can't be blocked by more than one creature. Oracle:Bristling Boar can't be blocked by more than one creature. -PT:4/3 \ No newline at end of file diff --git a/forge-gui/res/cardsfolder/c/challenger_troll.txt b/forge-gui/res/cardsfolder/c/challenger_troll.txt index 597b351895c..ccdadc2c0f2 100644 --- a/forge-gui/res/cardsfolder/c/challenger_troll.txt +++ b/forge-gui/res/cardsfolder/c/challenger_troll.txt @@ -2,7 +2,7 @@ Name:Challenger Troll ManaCost:4 G Types:Creature Troll PT:6/5 -S:Mode$ Continuous | Affected$ Creature.YouCtrl+powerGE4 | AddHiddenKeyword$ CantBeBlockedByAmount GT1 | Description$ Each creature you control with power 4 or greater can't be blocked by more than one creature. +S:Mode$ MinMaxBlocker | ValidCard$ Creature.YouCtrl+powerGE4 | Max$ 1 | Description$ Each creature you control with power 4 or greater can't be blocked by more than one creature. SVar:PlayMain1:TRUE AI:RemoveDeck:Random -Oracle:Each creature you control with power 4 or greater can't be blocked by more than one creature. +Oracle:Each creature you control with power 4 or greater can't be blocked by more than one creature. \ No newline at end of file diff --git a/forge-gui/res/cardsfolder/c/charging_rhino.txt b/forge-gui/res/cardsfolder/c/charging_rhino.txt index 2bdf7706e56..07e8ffaaeef 100644 --- a/forge-gui/res/cardsfolder/c/charging_rhino.txt +++ b/forge-gui/res/cardsfolder/c/charging_rhino.txt @@ -2,6 +2,5 @@ Name:Charging Rhino ManaCost:3 G G Types:Creature Rhino PT:4/4 -K:CantBeBlockedByAmount GT1 -SVar:Picture:http://www.wizards.com/global/images/magic/general/charging_rhino.jpg +S:Mode$ MinMaxBlocker | ValidCard$ Card.Self | Max$ 1 | Description$ CARDNAME can't be blocked by more than one creature. Oracle:Charging Rhino can't be blocked by more than one creature. diff --git a/forge-gui/res/cardsfolder/f/familiar_ground.txt b/forge-gui/res/cardsfolder/f/familiar_ground.txt index 6e1d5b8e059..c5053c24d2c 100644 --- a/forge-gui/res/cardsfolder/f/familiar_ground.txt +++ b/forge-gui/res/cardsfolder/f/familiar_ground.txt @@ -1,8 +1,7 @@ Name:Familiar Ground ManaCost:2 G Types:Enchantment -S:Mode$ Continuous | Affected$ Creature.YouCtrl | AddHiddenKeyword$ CantBeBlockedByAmount GT1 | Description$ Each creature you control can't be blocked by more than one creature. +S:Mode$ MinMaxBlocker | ValidCard$ Creature.YouCtrl | Max$ 1 | Description$ Each creature you control can't be blocked by more than one creature. SVar:NonStackingEffect:True SVar:PlayMain1:TRUE -SVar:Picture:http://www.wizards.com/global/images/magic/general/familiar_ground.jpg Oracle:Each creature you control can't be blocked by more than one creature. diff --git a/forge-gui/res/cardsfolder/g/gorilla_berserkers.txt b/forge-gui/res/cardsfolder/g/gorilla_berserkers.txt index 94f5224de29..7cfcc7472e0 100644 --- a/forge-gui/res/cardsfolder/g/gorilla_berserkers.txt +++ b/forge-gui/res/cardsfolder/g/gorilla_berserkers.txt @@ -4,6 +4,5 @@ Types:Creature Ape Berserker PT:2/3 K:Trample K:Rampage:2 -K:CantBeBlockedByAmount LT3 -SVar:Picture:http://www.wizards.com/global/images/magic/general/gorilla_berserkers.jpg +S:Mode$ MinMaxBlocker | ValidCard$ Creature.Self | Min$ 3 | Description$ CARDNAME can't be blocked except by three or more creatures. Oracle:Trample; rampage 2 (Whenever this creature becomes blocked, it gets +2/+2 until end of turn for each creature blocking it beyond the first.)\nGorilla Berserkers can't be blocked except by three or more creatures. diff --git a/forge-gui/res/cardsfolder/g/guile.txt b/forge-gui/res/cardsfolder/g/guile.txt index e398bd5365a..1ccb363765f 100644 --- a/forge-gui/res/cardsfolder/g/guile.txt +++ b/forge-gui/res/cardsfolder/g/guile.txt @@ -2,11 +2,10 @@ Name:Guile ManaCost:3 U U U Types:Creature Elemental Incarnation PT:6/6 -K:CantBeBlockedByAmount LT3 +S:Mode$ MinMaxBlocker | ValidCard$ Creature.Self | Min$ 3 | Description$ CARDNAME can't be blocked except by three or more creatures. R:Event$ Counter | ActiveZones$ Battlefield | ValidType$ Spell | ValidCause$ Card.YouCtrl | ReplaceWith$ DBRemove | Description$ If a spell or ability you control would counter a spell, instead exile that spell and you may play that card without paying its mana cost. SVar:DBRemove:DB$ ChangeZone | Defined$ ReplacedCard | Origin$ Stack | Destination$ Exile | Fizzle$ True | SubAbility$ DBPlay SVar:DBPlay:DB$ Play | Defined$ ReplacedCard | WithoutManaCost$ True | Optional$ True T:Mode$ ChangesZone | Origin$ Any | Destination$ Graveyard | ValidCard$ Card.Self | Execute$ TrigShuffle | TriggerDescription$ When CARDNAME is put into a graveyard from anywhere, shuffle it into its owner's library. SVar:TrigShuffle:DB$ ChangeZone | Origin$ Graveyard | Destination$ Library | Shuffle$ True | Defined$ TriggeredCardLKICopy -SVar:Picture:http://www.wizards.com/global/images/magic/general/guile.jpg Oracle:Guile can't be blocked except by three or more creatures.\nIf a spell or ability you control would counter a spell, instead exile that spell and you may play that card without paying its mana cost.\nWhen Guile is put into a graveyard from anywhere, shuffle it into its owner's library. diff --git a/forge-gui/res/cardsfolder/h/huang_zhong_shu_general.txt b/forge-gui/res/cardsfolder/h/huang_zhong_shu_general.txt index 286ff2a35a4..f1d2c0bb0d2 100644 --- a/forge-gui/res/cardsfolder/h/huang_zhong_shu_general.txt +++ b/forge-gui/res/cardsfolder/h/huang_zhong_shu_general.txt @@ -2,6 +2,5 @@ Name:Huang Zhong, Shu General ManaCost:2 W W Types:Legendary Creature Human Soldier PT:2/3 -K:CantBeBlockedByAmount GT1 -SVar:Picture:http://www.wizards.com/global/images/magic/general/huang_zhong_shu_general.jpg +S:Mode$ MinMaxBlocker | ValidCard$ Card.Self | Max$ 1 | Description$ CARDNAME can't be blocked by more than one creature. Oracle:Huang Zhong, Shu General can't be blocked by more than one creature. diff --git a/forge-gui/res/cardsfolder/h/hungering_hydra.txt b/forge-gui/res/cardsfolder/h/hungering_hydra.txt index 1d08bc12649..31cfeea226a 100644 --- a/forge-gui/res/cardsfolder/h/hungering_hydra.txt +++ b/forge-gui/res/cardsfolder/h/hungering_hydra.txt @@ -2,7 +2,7 @@ Name:Hungering Hydra ManaCost:X G Types:Creature Hydra PT:0/0 -K:CantBeBlockedByAmount GT1 +S:Mode$ MinMaxBlocker | ValidCard$ Card.Self | Max$ 1 | Description$ CARDNAME can't be blocked by more than one creature. K:etbCounter:P1P1:X SVar:X:Count$xPaid T:Mode$ DamageDoneOnce | ValidTarget$ Card.Self | TriggerZones$ Battlefield | Execute$ TrigPutCounter | TriggerDescription$ Whenever CARDNAME is dealt damage, put that many +1/+1 counters on CARDNAME. diff --git a/forge-gui/res/cardsfolder/i/ironhoof_ox.txt b/forge-gui/res/cardsfolder/i/ironhoof_ox.txt index 1ad42910748..3b38365b7e4 100644 --- a/forge-gui/res/cardsfolder/i/ironhoof_ox.txt +++ b/forge-gui/res/cardsfolder/i/ironhoof_ox.txt @@ -2,6 +2,5 @@ Name:Ironhoof Ox ManaCost:3 G G Types:Creature Ox PT:4/4 -K:CantBeBlockedByAmount GT1 -SVar:Picture:http://resources.wizards.com/magic/cards/p2/en-us/card6628.jpg +S:Mode$ MinMaxBlocker | ValidCard$ Card.Self | Max$ 1 | Description$ CARDNAME can't be blocked by more than one creature. Oracle:Ironhoof Ox can't be blocked by more than one creature. diff --git a/forge-gui/res/cardsfolder/k/kessig_prowler_sinuous_predator.txt b/forge-gui/res/cardsfolder/k/kessig_prowler_sinuous_predator.txt index 5ca233feeeb..bcfb4ab31b3 100644 --- a/forge-gui/res/cardsfolder/k/kessig_prowler_sinuous_predator.txt +++ b/forge-gui/res/cardsfolder/k/kessig_prowler_sinuous_predator.txt @@ -1,36 +1,18 @@ Name:Kessig Prowler - ManaCost:G - Types:Creature Werewolf Horror - PT:2/1 - A:AB$SetState | Cost$ 4 G | Defined$ Self | Mode$ Transform | SpellDescription$ Transform CARDNAME. - -SVar:Picture:http://www.wizards.com/global/images/magic/general/kessig_prowler.jpg - AlternateMode:DoubleFaced - Oracle:{4}{G}: Transform Kessig Prowler. - ALTERNATE - Name:Sinuous Predator - ManaCost:no cost - Types:Creature Eldrazi Werewolf - PT:4/4 - -K:CantBeBlockedByAmount GT1 - -SVar:Picture:http://www.wizards.com/global/images/magic/general/sinuous_predator.jpg - -Oracle:Sinuous Predator can't be blocked by more than one creature. - +S:Mode$ MinMaxBlocker | ValidCard$ Card.Self | Max$ 1 | Description$ CARDNAME can't be blocked by more than one creature. +Oracle:Sinuous Predator can't be blocked by more than one creature. \ No newline at end of file diff --git a/forge-gui/res/cardsfolder/k/krosan_vorine.txt b/forge-gui/res/cardsfolder/k/krosan_vorine.txt index 81c57beebba..28f568436c9 100644 --- a/forge-gui/res/cardsfolder/k/krosan_vorine.txt +++ b/forge-gui/res/cardsfolder/k/krosan_vorine.txt @@ -3,6 +3,5 @@ ManaCost:3 G Types:Creature Cat Beast PT:3/2 K:Provoke -K:CantBeBlockedByAmount GT1 -SVar:Picture:http://www.wizards.com/global/images/magic/general/krosan_vorine.jpg +S:Mode$ MinMaxBlocker | ValidCard$ Card.Self | Max$ 1 | Description$ CARDNAME can't be blocked by more than one creature. Oracle:Provoke (Whenever this creature attacks, you may have target creature defending player controls untap and block it if able.)\nKrosan Vorine can't be blocked by more than one creature. diff --git a/forge-gui/res/cardsfolder/n/norwood_riders.txt b/forge-gui/res/cardsfolder/n/norwood_riders.txt index 5550682df3d..c18af6e7b92 100644 --- a/forge-gui/res/cardsfolder/n/norwood_riders.txt +++ b/forge-gui/res/cardsfolder/n/norwood_riders.txt @@ -2,6 +2,5 @@ Name:Norwood Riders ManaCost:3 G Types:Creature Elf PT:3/3 -K:CantBeBlockedByAmount GT1 -SVar:Picture:http://resources.wizards.com/magic/cards/p2/en-us/card6615.jpg +S:Mode$ MinMaxBlocker | ValidCard$ Card.Self | Max$ 1 | Description$ CARDNAME can't be blocked by more than one creature. Oracle:Norwood Riders can't be blocked by more than one creature. diff --git a/forge-gui/res/cardsfolder/o/outland_colossus.txt b/forge-gui/res/cardsfolder/o/outland_colossus.txt index 144504fe332..028f7fb1f68 100644 --- a/forge-gui/res/cardsfolder/o/outland_colossus.txt +++ b/forge-gui/res/cardsfolder/o/outland_colossus.txt @@ -3,7 +3,6 @@ ManaCost:3 G G Types:Creature Giant PT:6/6 K:Renown:6 -K:CantBeBlockedByAmount GT1 +S:Mode$ MinMaxBlocker | ValidCard$ Card.Self | Max$ 1 | Description$ CARDNAME can't be blocked by more than one creature. DeckHas:Ability$Counters -SVar:Picture:http://www.wizards.com/global/images/magic/general/outland_colossus.jpg Oracle:Renown 6 (When this creature deals combat damage to a player, if it isn't renowned, put six +1/+1 counters on it and it becomes renowned.)\nOutland Colossus can't be blocked by more than one creature. diff --git a/forge-gui/res/cardsfolder/p/pathrazer_of_ulamog.txt b/forge-gui/res/cardsfolder/p/pathrazer_of_ulamog.txt index 2ebff851e86..5544d18b79b 100644 --- a/forge-gui/res/cardsfolder/p/pathrazer_of_ulamog.txt +++ b/forge-gui/res/cardsfolder/p/pathrazer_of_ulamog.txt @@ -3,6 +3,5 @@ ManaCost:11 Types:Creature Eldrazi PT:9/9 K:Annihilator:3 -K:CantBeBlockedByAmount LT3 -SVar:Picture:http://www.wizards.com/global/images/magic/general/pathrazer_of_ulamog.jpg +S:Mode$ MinMaxBlocker | ValidCard$ Creature.Self | Min$ 3 | Description$ CARDNAME can't be blocked except by three or more creatures. Oracle:Annihilator 3 (Whenever this creature attacks, defending player sacrifices three permanents.)\nPathrazer of Ulamog can't be blocked except by three or more creatures. diff --git a/forge-gui/res/cardsfolder/p/phyrexian_colossus.txt b/forge-gui/res/cardsfolder/p/phyrexian_colossus.txt index 01257985344..c9907d8bf86 100644 --- a/forge-gui/res/cardsfolder/p/phyrexian_colossus.txt +++ b/forge-gui/res/cardsfolder/p/phyrexian_colossus.txt @@ -4,7 +4,6 @@ Types:Artifact Creature Phyrexian Golem PT:8/8 K:CARDNAME doesn't untap during your untap step. A:AB$ Untap | Cost$ PayLife<8> | SpellDescription$ Untap CARDNAME. -K:CantBeBlockedByAmount LT3 +S:Mode$ MinMaxBlocker | ValidCard$ Creature.Self | Min$ 3 | Description$ CARDNAME can't be blocked except by three or more creatures. AI:RemoveDeck:All -SVar:Picture:http://www.wizards.com/global/images/magic/general/phyrexian_colossus.jpg Oracle:Phyrexian Colossus doesn't untap during your untap step.\nPay 8 life: Untap Phyrexian Colossus.\nPhyrexian Colossus can't be blocked except by three or more creatures. diff --git a/forge-gui/res/cardsfolder/s/sonorous_howlbonder.txt b/forge-gui/res/cardsfolder/s/sonorous_howlbonder.txt index 53ef5c6700c..bca6e3c94fa 100755 --- a/forge-gui/res/cardsfolder/s/sonorous_howlbonder.txt +++ b/forge-gui/res/cardsfolder/s/sonorous_howlbonder.txt @@ -3,6 +3,6 @@ ManaCost:1 B/R B/R Types:Creature Human Warrior PT:2/2 K:Menace -S:Mode$ Continuous | Affected$ Creature.YouCtrl+withMenace | AddHiddenKeyword$ CantBeBlockedByAmount LT3 | Description$ Each creature you control with menace can't be blocked except by three or more creatures. +S:Mode$ MinMaxBlocker | ValidCard$ Creature.YouCtrl+withMenace | Min$ 3 | Description$ Each creature you control with menace can't be blocked except by three or more creatures. SVar:PlayMain1:TRUE Oracle:Menace\nEach creature you control with menace can't be blocked except by three or more creatures. diff --git a/forge-gui/res/cardsfolder/s/stalking_tiger.txt b/forge-gui/res/cardsfolder/s/stalking_tiger.txt index 58a93d527b5..9ca7ad00543 100644 --- a/forge-gui/res/cardsfolder/s/stalking_tiger.txt +++ b/forge-gui/res/cardsfolder/s/stalking_tiger.txt @@ -2,6 +2,5 @@ Name:Stalking Tiger ManaCost:3 G Types:Creature Cat PT:3/3 -K:CantBeBlockedByAmount GT1 -SVar:Picture:http://www.wizards.com/global/images/magic/general/stalking_tiger.jpg +S:Mode$ MinMaxBlocker | ValidCard$ Card.Self | Max$ 1 | Description$ CARDNAME can't be blocked by more than one creature. Oracle:Stalking Tiger can't be blocked by more than one creature. diff --git a/forge-gui/res/cardsfolder/s/sunder_shaman.txt b/forge-gui/res/cardsfolder/s/sunder_shaman.txt index 0c16a979a37..65ce6d16942 100644 --- a/forge-gui/res/cardsfolder/s/sunder_shaman.txt +++ b/forge-gui/res/cardsfolder/s/sunder_shaman.txt @@ -2,7 +2,7 @@ Name:Sunder Shaman ManaCost:R R G G Types:Creature Giant Shaman PT:5/5 -K:CantBeBlockedByAmount GT1 +S:Mode$ MinMaxBlocker | ValidCard$ Card.Self | Max$ 1 | Description$ CARDNAME can't be blocked by more than one creature. T:Mode$ DamageDone | ValidSource$ Card.Self | ValidTarget$ Player | CombatDamage$ True | Execute$ TrigDestroy | TriggerZones$ Battlefield | TriggerDescription$ Whenever CARDNAME deals combat damage to a player, destroy target artifact or enchantment that player controls. SVar:TrigDestroy:DB$ Destroy | ValidTgts$ Artifact.ControlledBy TriggeredDefendingPlayer,Enchantment.ControlledBy TriggeredDefendingPlayer | TgtPrompt$ Select target artifact or enchantment that player controls. Oracle:Sunder Shaman can't be blocked by more than one creature.\nWhenever Sunder Shaman deals combat damage to a player, destroy target artifact or enchantment that player controls. diff --git a/forge-gui/res/cardsfolder/t/tahngarth_first_mate.txt b/forge-gui/res/cardsfolder/t/tahngarth_first_mate.txt index 39487a986ac..f7b2a1c2aa9 100644 --- a/forge-gui/res/cardsfolder/t/tahngarth_first_mate.txt +++ b/forge-gui/res/cardsfolder/t/tahngarth_first_mate.txt @@ -2,7 +2,7 @@ Name:Tahngarth, First Mate ManaCost:2 R G Types:Legendary Creature Minotaur Warrior PT:5/5 -K:CantBeBlockedByAmount GT1 +S:Mode$ MinMaxBlocker | ValidCard$ Card.Self | Max$ 1 | Description$ CARDNAME can't be blocked by more than one creature. T:Mode$ AttackersDeclared | AttackingPlayer$ Player.Opponent | Execute$ TrigGainControl | TriggerZones$ Battlefield | OptionalDecider$ You | IsPresent$ Card.Self+tapped | TriggerDescription$ Whenever an opponent attacks with one or more creatures, if CARDNAME is tapped, you may have that opponent gain control of CARDNAME until end of combat. If you do, choose a player or planeswalker that opponent is attacking. CARDNAME is attacking that player or planeswalker. SVar:TrigGainControl:DB$ GainControl | Defined$ Self | NewController$ TriggeredAttackingPlayer | LoseControl$ EndOfCombat | Attacking$ Player.Defending | Chooser$ You | ChoosePlayerOrPlaneswalker$ True AI:RemoveDeck:All diff --git a/forge-gui/res/cardsfolder/t/the_akroan_war.txt b/forge-gui/res/cardsfolder/t/the_akroan_war.txt index d6e6f4ad224..efffc5c30bf 100644 --- a/forge-gui/res/cardsfolder/t/the_akroan_war.txt +++ b/forge-gui/res/cardsfolder/t/the_akroan_war.txt @@ -3,7 +3,9 @@ ManaCost:3 R Types:Enchantment Saga K:Saga:3:DBGainControl,DBAllAttack,DBDamageTapped SVar:DBGainControl:DB$ GainControl | ValidTgts$ Creature | TgtPrompt$ Select target creature | LoseControl$ LeavesPlay | SpellDescription$ Gain control of target creature for as long as CARDNAME remains on the battlefield. -SVar:DBAllAttack:DB$ PumpAll | ValidCards$ Creature.OppCtrl | Duration$ UntilYourNextTurn | KW$ HIDDEN CARDNAME attacks each combat if able. | SpellDescription$ Until your next turn, creatures your opponents control attack each turn if able. + +# Refactor as Effect +SVar:DBAllAttack:DB$ PumpAll | ValidCards$ Creature.OppCtrl | Duration$ UntilYourNextTurn | KW$ HIDDEN CARDNAME attacks each combat if able. | SpellDescription$ Until your next turn, creatures your opponents control attack each combat if able. SVar:DBDamageTapped:DB$ EachDamage | ValidCards$ Creature.tapped | NumDmg$ X | DamageDesc$ damage equal to its power | DefinedCards$ Self | SpellDescription$ Each tapped creature deals damage to itself equal to its power. SVar:X:Count$CardPower Oracle:(As this Saga enters and after your draw step, add a lore counter. Sacrifice after III.)\nI — Gain control of target creature for as long as The Akroan War remains on the battlefield.\nII — Until your next turn, creatures your opponents control attack each combat if able.\nIII — Each tapped creature deals damage to itself equal to its power. diff --git a/forge-gui/res/cardsfolder/u/underworld_cerberus.txt b/forge-gui/res/cardsfolder/u/underworld_cerberus.txt index 19110337cd3..4f2fac4949c 100644 --- a/forge-gui/res/cardsfolder/u/underworld_cerberus.txt +++ b/forge-gui/res/cardsfolder/u/underworld_cerberus.txt @@ -2,7 +2,7 @@ Name:Underworld Cerberus ManaCost:3 B R Types:Creature Dog PT:6/6 -K:CantBeBlockedByAmount LT3 +S:Mode$ MinMaxBlocker | ValidCard$ Creature.Self | Min$ 3 | Description$ CARDNAME can't be blocked except by three or more creatures. S:Mode$ CantTarget | AffectedZone$ Graveyard | Description$ Cards in graveyards can't be the targets of spells or abilities. T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Self | Execute$ TrigExile | TriggerController$ TriggeredCardController | TriggerDescription$ When CARDNAME dies, exile it and each player returns all creature cards from their graveyard to their hand. SVar:TrigExile:DB$ ChangeZone | Origin$ Graveyard | Destination$ Exile | Defined$ TriggeredNewCardLKICopy | SubAbility$ DBChangeZoneAll diff --git a/forge-gui/res/cardsfolder/v/vigorspore_wurm.txt b/forge-gui/res/cardsfolder/v/vigorspore_wurm.txt index 8602f5e3cb5..9c14d715067 100644 --- a/forge-gui/res/cardsfolder/v/vigorspore_wurm.txt +++ b/forge-gui/res/cardsfolder/v/vigorspore_wurm.txt @@ -2,7 +2,7 @@ Name:Vigorspore Wurm ManaCost:5 G Types:Creature Wurm PT:6/4 -K:CantBeBlockedByAmount GT1 +S:Mode$ MinMaxBlocker | ValidCard$ Card.Self | Max$ 1 | Description$ CARDNAME can't be blocked by more than one creature. T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigPump | TriggerDescription$ Undergrowth - When CARDNAME enters the battlefield, target creature gains vigilance and gets +X/+X until end of turn, where X is the number of creature cards in your graveyard. SVar:TrigPump:DB$ Pump | ValidTgts$ Creature | TgtPrompt$ Select target creature | NumAtt$ X | NumDef$ X | KW$ Vigilance SVar:X:Count$TypeInYourYard.Creature diff --git a/forge-gui/res/cardsfolder/v/vorrac_battlehorns.txt b/forge-gui/res/cardsfolder/v/vorrac_battlehorns.txt index 935e7e4df82..96a6198eee3 100644 --- a/forge-gui/res/cardsfolder/v/vorrac_battlehorns.txt +++ b/forge-gui/res/cardsfolder/v/vorrac_battlehorns.txt @@ -2,6 +2,6 @@ Name:Vorrac Battlehorns ManaCost:2 Types:Artifact Equipment K:Equip:1 -S:Mode$ Continuous | Affected$ Creature.EquippedBy | AddKeyword$ Trample | AddHiddenKeyword$ CantBeBlockedByAmount GT1 | Description$ Equipped creature has trample and can't be blocked by more than one creature. -SVar:Picture:http://www.wizards.com/global/images/magic/general/vorrac_battlehorns.jpg +S:Mode$ Continuous | Affected$ Creature.EquippedBy | AddKeyword$ Trample | Description$ Equipped creature has trample and can't be blocked by more than one creature. +S:Mode$ MinMaxBlocker | ValidCard$ Creature.EquippedBy | Max$ 1 | Secondary$ True | Description$ Equipped creature can't be blocked by more than one creature. Oracle:Equipped creature has trample and can't be blocked by more than one creature.\nEquip {1} ({1}: Attach to target creature you control. Equip only as a sorcery. This card enters the battlefield unattached and stays on the battlefield if the creature leaves.) diff --git a/forge-gui/res/cardsfolder/w/wolfriders_saddle.txt b/forge-gui/res/cardsfolder/w/wolfriders_saddle.txt index 540c9735b9f..34c6b072eeb 100644 --- a/forge-gui/res/cardsfolder/w/wolfriders_saddle.txt +++ b/forge-gui/res/cardsfolder/w/wolfriders_saddle.txt @@ -6,6 +6,7 @@ SVar:TrigToken:DB$ Token | LegacyImage$ g 2 2 wolf m20 | TokenAmount$ 1 | TokenS SVar:DBAttach:DB$ Attach | Defined$ Remembered | SubAbility$ DBCleanup SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True DeckHas:Ability$Token -S:Mode$ Continuous | Affected$ Creature.EquippedBy | AddPower$ 1 | AddToughness$ 1 | AddHiddenKeyword$ CantBeBlockedByAmount GT1 | Description$ Equipped creature gets +1/+1 and can't be blocked by more than one creature. +S:Mode$ Continuous | Affected$ Creature.EquippedBy | AddPower$ 1 | AddToughness$ 1 | Description$ Equipped creature gets +1/+1 and can't be blocked by more than one creature. +S:Mode$ MinMaxBlocker | ValidCard$ Creature.EquippedBy | Max$ 1 | Secondary$ True | Description$ Equipped creature can't be blocked by more than one creature. K:Equip:3 Oracle:When Wolfrider's Saddle enters the battlefield, create a 2/2 green Wolf creature token, then attach Wolfrider's Saddle to it.\nEquipped creature gets +1/+1 and can't be blocked by more than one creature.\nEquip {3} ({3}: Attach to target creature you control. Equip only as a sorcery.) diff --git a/forge-gui/res/cardsfolder/y/yuan_shao_the_indecisive.txt b/forge-gui/res/cardsfolder/y/yuan_shao_the_indecisive.txt index c319ae44531..78f74b0dfa7 100644 --- a/forge-gui/res/cardsfolder/y/yuan_shao_the_indecisive.txt +++ b/forge-gui/res/cardsfolder/y/yuan_shao_the_indecisive.txt @@ -3,6 +3,5 @@ ManaCost:4 R Types:Legendary Creature Human Soldier PT:2/3 K:Horsemanship -S:Mode$ Continuous | Affected$ Creature.YouCtrl | AddHiddenKeyword$ CantBeBlockedByAmount GT1 | Description$ Each creature you control can't be blocked by more than one creature. -SVar:Picture:http://www.wizards.com/global/images/magic/general/yuan_shao_the_indecisive.jpg +S:Mode$ MinMaxBlocker | ValidCard$ Creature.YouCtrl | Max$ 1 | Description$ Each creature you control can't be blocked by more than one creature. Oracle:Horsemanship (This creature can't be blocked except by creatures with horsemanship.)\nEach creature you control can't be blocked by more than one creature. diff --git a/forge-gui/res/lists/NonStackingKWList.txt b/forge-gui/res/lists/NonStackingKWList.txt index 0b6eb77cd78..1e9efe0a5fe 100644 --- a/forge-gui/res/lists/NonStackingKWList.txt +++ b/forge-gui/res/lists/NonStackingKWList.txt @@ -1,11 +1,7 @@ All creatures able to block CARDNAME do so. Banding -CantBeBlockedByAmount LT2 -CantBeBlockedByAmount LT3 CARDNAME's activated abilities can't be activated. -CARDNAME attacks each turn if able. CARDNAME attacks each combat if able. -CARDNAME blocks each turn if able. CARDNAME blocks each combat if able. CARDNAME can attack as though it didn't have defender. CARDNAME can block any number of creatures. From 819c92c28a7c87ee691852e3bdb53e8636a1cc45 Mon Sep 17 00:00:00 2001 From: tool4EvEr Date: Sun, 12 Sep 2021 21:08:23 +0200 Subject: [PATCH 28/58] Follow up fixes --- forge-gui/res/cardsfolder/f/fireball.txt | 4 ++-- forge-gui/res/cardsfolder/r/riftmarked_knight.txt | 2 +- forge-gui/res/cardsfolder/v/veiling_oddity.txt | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/forge-gui/res/cardsfolder/f/fireball.txt b/forge-gui/res/cardsfolder/f/fireball.txt index f5e0805aa95..fda14c63b5c 100644 --- a/forge-gui/res/cardsfolder/f/fireball.txt +++ b/forge-gui/res/cardsfolder/f/fireball.txt @@ -1,8 +1,8 @@ Name:Fireball ManaCost:X R Types:Sorcery -A:SP$ DealDamage | Cost$ X R | ValidTgts$ Creature,Player,Planeswalker | TgtPrompt$ Select any target | NumDmg$ X | TargetMin$ 0 | TargetMax$ MaxTargets | DivideEvenly$ RoundedDown | SpellDescription$ This spell costs {1} more to cast for each target beyond the first. -S:Mode$ RaiseCost | ValidCard$ Card.Self | Type$ Spell | Amount$ IncreaseCost | AffectedAmount$ True | EffectZone$ All | Description$ CARDNAME deals X damage divided evenly, rounded down, among any number of targets. +S:Mode$ RaiseCost | ValidCard$ Card.Self | Type$ Spell | Amount$ IncreaseCost | AffectedAmount$ True | EffectZone$ All | Description$ This spell costs {1} more to cast for each target beyond the first. +A:SP$ DealDamage | Cost$ X R | ValidTgts$ Creature,Player,Planeswalker | TgtPrompt$ Select any target | NumDmg$ X | TargetMin$ 0 | TargetMax$ MaxTargets | DivideEvenly$ RoundedDown | SpellDescription$ CARDNAME deals X damage divided evenly, rounded down, among any number of targets. SVar:X:Count$xPaid SVar:MaxTargets:SVar$Maxplayer/Plus.Maxcreatureorplaneswalker SVar:Maxplayer:PlayerCountPlayers$Amount diff --git a/forge-gui/res/cardsfolder/r/riftmarked_knight.txt b/forge-gui/res/cardsfolder/r/riftmarked_knight.txt index d71ef872ff2..98c1f7ac1fa 100644 --- a/forge-gui/res/cardsfolder/r/riftmarked_knight.txt +++ b/forge-gui/res/cardsfolder/r/riftmarked_knight.txt @@ -5,7 +5,7 @@ PT:2/2 K:Flanking K:Protection from black K:Suspend:3:1 W W -T:Mode$ CounterRemoved | ValidCard$ Card.Self | TriggerZones$ Exile | CounterType$ TIME | Execute$ TrigToken | NewCounterAmount$ 0 | IsPresent$ Card.Self+counters_GE1_TIME | PresentZone$ Exile | PresentCompare$ EQ0 | TriggerDescription$ When the last time counter is removed from CARDNAME while it's exiled, create a 2/2 black Knight creature token with flanking, protection from white, and haste. +T:Mode$ CounterRemoved | ValidCard$ Card.Self | TriggerZones$ Exile | CounterType$ TIME | Execute$ TrigToken | NewCounterAmount$ 0 | TriggerDescription$ When the last time counter is removed from CARDNAME while it's exiled, create a 2/2 black Knight creature token with flanking, protection from white, and haste. SVar:TrigToken:DB$Token | TokenAmount$ 1 | TokenOwner$ You | TokenScript$ b_2_2_knight_flanking_pro_white_haste | LegacyImage$ b 2 2 knight flanking pro white haste plc SVar:Picture:http://www.wizards.com/global/images/magic/general/riftmarked_knight.jpg Oracle:Protection from black; flanking (Whenever a creature without flanking blocks this creature, the blocking creature gets -1/-1 until end of turn.)\nSuspend 3—{1}{W}{W}\nWhen the last time counter is removed from Riftmarked Knight while it's exiled, create a 2/2 black Knight creature token with flanking, protection from white, and haste. diff --git a/forge-gui/res/cardsfolder/v/veiling_oddity.txt b/forge-gui/res/cardsfolder/v/veiling_oddity.txt index b1cec93eebc..2cabd38946c 100644 --- a/forge-gui/res/cardsfolder/v/veiling_oddity.txt +++ b/forge-gui/res/cardsfolder/v/veiling_oddity.txt @@ -3,7 +3,7 @@ ManaCost:3 U Types:Creature Illusion PT:2/3 K:Suspend:4:1 U -T:Mode$ CounterRemoved | ValidCard$ Card.Self | TriggerZones$ Exile | CounterType$ TIME | Execute$ TrigEffect | IsPresent$ Card.Self+counters_GE1_TIME | PresentZone$ Exile | PresentCompare$ EQ0 | TriggerDescription$ When the last time counter is removed from CARDNAME while it's exiled, creatures can't be blocked this turn. +T:Mode$ CounterRemoved | ValidCard$ Card.Self | TriggerZones$ Exile | CounterType$ TIME | Execute$ TrigEffect | NewCounterAmount$ 0 | TriggerDescription$ When the last time counter is removed from CARDNAME while it's exiled, creatures can't be blocked this turn. SVar:TrigEffect:DB$ Effect | Name$ Veiling Oddity Effect | StaticAbilities$ KWPump SVar:KWPump:Mode$ Continuous | EffectZone$ Command | AffectedZone$ Battlefield | Affected$ Creature | AddHiddenKeyword$ Unblockable | Description$ creatures can't be blocked this turn. SVar:Picture:http://www.wizards.com/global/images/magic/general/veiling_oddity.jpg From 901b7159479e616d792b25dd00556a9ccb2fb38a Mon Sep 17 00:00:00 2001 From: Northmoc Date: Sat, 11 Sep 2021 22:59:57 -0400 Subject: [PATCH 29/58] fix SpellDesc --- forge-gui/res/cardsfolder/g/gates_of_istfell.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/forge-gui/res/cardsfolder/g/gates_of_istfell.txt b/forge-gui/res/cardsfolder/g/gates_of_istfell.txt index 24fa83aafba..df1baecf1b1 100644 --- a/forge-gui/res/cardsfolder/g/gates_of_istfell.txt +++ b/forge-gui/res/cardsfolder/g/gates_of_istfell.txt @@ -3,7 +3,7 @@ ManaCost:no cost Types:Land K:CARDNAME enters the battlefield tapped. A:AB$ Mana | Cost$ T | Produced$ W | SpellDescription$ Add {W}. -A:AB$ GainLife | Cost$ 2 W U U T Sac<1/CARDNAME> | Defined$ You | LifeAmount$ 2 | SubAbility$ DBDraw | SpellDescription$ You gain 2 life and draw 2 cards. +A:AB$ GainLife | Cost$ 2 W U U T Sac<1/CARDNAME> | Defined$ You | LifeAmount$ 2 | SubAbility$ DBDraw | SpellDescription$ You gain 2 life and draw two cards. SVar:DBDraw:DB$ Draw | NumCards$ 2 DeckHas:Ability$GainLife & Ability$Sacrifice Oracle:Gates of Istfell enters the battlefield tapped.\n{T}: Add {W}.\n{2}{W}{U}{U}, {T}, Sacrifice Gates of Istfell: You gain 2 life and draw two cards. From daa3a66d5ea26d786143e04dfe5ca1d9e35a8053 Mon Sep 17 00:00:00 2001 From: Northmoc Date: Sun, 12 Sep 2021 20:29:22 -0400 Subject: [PATCH 30/58] minor typos --- forge-gui/res/cardsfolder/s/sparktongue_dragon.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/forge-gui/res/cardsfolder/s/sparktongue_dragon.txt b/forge-gui/res/cardsfolder/s/sparktongue_dragon.txt index 286950cfd28..02f57fb312c 100644 --- a/forge-gui/res/cardsfolder/s/sparktongue_dragon.txt +++ b/forge-gui/res/cardsfolder/s/sparktongue_dragon.txt @@ -3,7 +3,7 @@ ManaCost:3 R R Types:Creature Dragon K:Flying T:Mode$ ChangesZone | OptionalDecider$ You | ValidCard$ Card.Self | Origin$ Any | Destination$ Battlefield | Execute$ TrigPayCost | TriggerDescription$ When CARDNAME enters the battlefield, you may pay {2}{R}. When you do, it deals 3 damage to any target. -SVar:TrigPayCost:AB$ ImmediateTrigger | Cost$ 2 R | Execute$ TrigABDealDamage | TriggerDescription$ When you pay {2}{R}, CARDNAME deals 3 damage to any target +SVar:TrigPayCost:AB$ ImmediateTrigger | Cost$ 2 R | Execute$ TrigABDealDamage | TriggerDescription$ When you pay {2}{R}, CARDNAME deals 3 damage to any target. SVar:TrigABDealDamage:DB$ DealDamage | NumDmg$ 3 | ValidTgts$ Creature,Player,Planeswalker | TgtPrompt$ Select any target | SpellDescription$ CARDNAME deals 3 damage to any target. Oracle:Flying\nWhen Sparktongue Dragon enters the battlefield, you may pay {2}{R}. When you do, it deals 3 damage to any target. PT:3/3 \ No newline at end of file From accd7c667e546c3ea6fd37ec0bf1aa13776c29fd Mon Sep 17 00:00:00 2001 From: Northmoc Date: Sun, 12 Sep 2021 20:29:41 -0400 Subject: [PATCH 31/58] minor typos --- forge-gui/res/cardsfolder/p/primitive_justice.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/forge-gui/res/cardsfolder/p/primitive_justice.txt b/forge-gui/res/cardsfolder/p/primitive_justice.txt index cd61adc573d..5b21a626c38 100644 --- a/forge-gui/res/cardsfolder/p/primitive_justice.txt +++ b/forge-gui/res/cardsfolder/p/primitive_justice.txt @@ -2,7 +2,7 @@ Name:Primitive Justice ManaCost:1 R Types:Sorcery A:SP$ Destroy | Cost$ X 1 R | XColor$ RG | Announce$ AdditionalCostPayTimes | ValidTgts$ Artifact | TgtPrompt$ Select target artifact | TargetMin$ TargetNum | TargetMax$ TargetNum | SubAbility$ DBGainLife | SpellDescription$ Destroy target artifact. For each additional {1}{R} you paid, destroy another target artifact. For each additional {1}{G} you paid, destroy another target artifact, and you gain 1 life. -S:Mode$ RaiseCost | ValidCard$ Card.Self | Type$ Spell | Amount$ IncreaseCost | EffectZone$ All |Description$ As an additional cost to cast this spell, you may pay {1}{R} and/or {1}{G} any number of times. +S:Mode$ RaiseCost | ValidCard$ Card.Self | Type$ Spell | Amount$ IncreaseCost | EffectZone$ All | Description$ As an additional cost to cast this spell, you may pay {1}{R} and/or {1}{G} any number of times. SVar:DBGainLife:DB$ GainLife | Defined$ You | LifeAmount$ GreenManaPaid SVar:AdditionalCostPayTimes:Number$0 SVar:TargetNum:SVar$AdditionalCostPayTimes/Plus.1 From 074439200f6ba092a1f48eba01469a4a4ff256c1 Mon Sep 17 00:00:00 2001 From: Northmoc Date: Sun, 12 Sep 2021 14:15:31 -0400 Subject: [PATCH 32/58] croaking_counterpart.txt --- forge-gui/res/cardsfolder/upcoming/croaking_counterpart.txt | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 forge-gui/res/cardsfolder/upcoming/croaking_counterpart.txt diff --git a/forge-gui/res/cardsfolder/upcoming/croaking_counterpart.txt b/forge-gui/res/cardsfolder/upcoming/croaking_counterpart.txt new file mode 100644 index 00000000000..15e8f6745c4 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/croaking_counterpart.txt @@ -0,0 +1,6 @@ +Name:Croaking Counterpart +ManaCost:1 G U +Types:Sorcery +A:SP$ CopyPermanent | ValidTgts$ Creature.nonFrog | TgtPrompt$ Select target non-Frog creature | SetPower$ 1 | SetToughness$ 1 | SetColor$ Green | SetCreatureTypes$ Frog | StackDescription$ Create a token that's a copy of {c:Targeted}, except it's a 1/1 green Frog. | SpellDescription$ Create a token that's a copy of target non-Frog creature, except it's a 1/1 green Frog. +K:Flashback:3 G U +Oracle:Create a token that's a copy of target non-Frog creature, except it's a 1/1 green Frog.\nFlashback {3}{G}{U} (You may cast this card from your graveyard for its flashback cost. Then exile it.) From fcd29e27be60727a7cd2b5bea6f2777778a57103 Mon Sep 17 00:00:00 2001 From: Northmoc Date: Sun, 12 Sep 2021 14:45:03 -0400 Subject: [PATCH 33/58] diregraf_rebirth.txt --- forge-gui/res/cardsfolder/upcoming/diregraf_rebirth.txt | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 forge-gui/res/cardsfolder/upcoming/diregraf_rebirth.txt diff --git a/forge-gui/res/cardsfolder/upcoming/diregraf_rebirth.txt b/forge-gui/res/cardsfolder/upcoming/diregraf_rebirth.txt new file mode 100644 index 00000000000..fc7815ef7e3 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/diregraf_rebirth.txt @@ -0,0 +1,9 @@ +Name:Diregraf Rebirth +ManaCost:3 B G +Types:Sorcery +S:Mode$ ReduceCost | ValidCard$ Card.Self | Type$ Spell | Amount$ X | EffectZone$ All | Description$ This spell costs {1} less to cast for each creature that died this turn. +A:SP$ ChangeZone | Origin$ Graveyard | Destination$ Battlefield | TgtPrompt$ Choose target creature card in your graveyard | ValidTgts$ Creature.YouOwn | SpellDescription$ Return target creature card from your graveyard to the battlefield. +SVar:X:Count$ThisTurnEntered_Graveyard_from_Battlefield_Creature +K:Flashback:5 B G +DeckHas:Ability$Graveyard +Oracle:This spell costs {1} less to cast for each creature that died this turn.\nReturn target creature card from your graveyard to the battlefield.\nFlashback {5}{B}{G} (You may cast this card from your graveyard for its flashback cost. Then exile it.) From 2bf4821b8e66ab2b87078b700d7a9ae566850de6 Mon Sep 17 00:00:00 2001 From: Northmoc Date: Sun, 12 Sep 2021 14:45:24 -0400 Subject: [PATCH 34/58] unnatural_growth.txt --- .../res/cardsfolder/upcoming/unnatural_growth.txt | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 forge-gui/res/cardsfolder/upcoming/unnatural_growth.txt diff --git a/forge-gui/res/cardsfolder/upcoming/unnatural_growth.txt b/forge-gui/res/cardsfolder/upcoming/unnatural_growth.txt new file mode 100644 index 00000000000..12af8ba8c15 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/unnatural_growth.txt @@ -0,0 +1,10 @@ +Name:Unnatural Growth +ManaCost:1 G G G G +Types:Enchantment +T:Mode$ Phase | Phase$ BeginCombat | TriggerZones$ Battlefield | Execute$ TrigDouble | TriggerDescription$ At the beginning of each combat, double the power and toughness of each creature you control until end of turn. +SVar:TrigDouble:DB$ RepeatEach | RepeatCards$ Creature.YouCtrl | RepeatSubAbility$ DBDouble +SVar:DBDouble:DB$ Pump | Defined$ Remembered | NumAtt$ X | NumDef$ Y +SVar:X:Remembered$CardPower +SVar:Y:Remembered$CardToughness +SVar:PlayMain1:TRUE +Oracle:At the beginning of each combat, double the power and toughness of each creature you control until end of turn. From bd3ca5115b633ffeca0752c274ab458f41f787c8 Mon Sep 17 00:00:00 2001 From: Northmoc Date: Sun, 12 Sep 2021 15:04:02 -0400 Subject: [PATCH 35/58] fleshtaker.txt --- forge-gui/res/cardsfolder/upcoming/fleshtaker.txt | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 forge-gui/res/cardsfolder/upcoming/fleshtaker.txt diff --git a/forge-gui/res/cardsfolder/upcoming/fleshtaker.txt b/forge-gui/res/cardsfolder/upcoming/fleshtaker.txt new file mode 100644 index 00000000000..b086f563773 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/fleshtaker.txt @@ -0,0 +1,10 @@ +Name:Fleshtaker +ManaCost:W B +Types:Creature Human Assassin +PT:2/2 +T:Mode$ Sacrificed | ValidCard$ Creature.Other | Execute$ TrigGainLife | TriggerZones$ Battlefield | ValidPlayer$ You | TriggerDescription$ Whenever you sacrifice another creature, you gain 1 life and scry 1. (Look at the top card of your library. You may put that card on the bottom of your library.) +SVar:TrigGainLife:DB$ GainLife | Defined$ You | LifeAmount$ 1 | SubAbility$ DBScry +SVar:DBScry:DB$ Scry | ScryNum$ 1 +A:AB$ Pump | Cost$ 1 Sac<1/Creature.Other/another creature> | Defined$ Self | NumAtt$ +2 | NumDef$ +2 | SpellDescription$ CARDNAME gets +2/+2 until end of turn. +DeckHas:Ability$Sacrifice & Ability$LifeGain +Oracle:Whenever you sacrifice another creature, you gain 1 life and scry 1. (Look at the top card of your library. You may put that card on the bottom of your library.)\n{1}, Sacrifice another creature: Fleshtaker gets +2/+2 until end of turn. From 8257f16a5d3ce015fd3beedbf29fa4d051d04e5f Mon Sep 17 00:00:00 2001 From: Northmoc Date: Sun, 12 Sep 2021 15:05:34 -0400 Subject: [PATCH 36/58] ghoulcallers_harvest.txt --- .../res/cardsfolder/upcoming/ghoulcallers_harvest.txt | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 forge-gui/res/cardsfolder/upcoming/ghoulcallers_harvest.txt diff --git a/forge-gui/res/cardsfolder/upcoming/ghoulcallers_harvest.txt b/forge-gui/res/cardsfolder/upcoming/ghoulcallers_harvest.txt new file mode 100644 index 00000000000..3867fe85ac1 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/ghoulcallers_harvest.txt @@ -0,0 +1,8 @@ +Name:Ghoulcaller's Harvest +ManaCost:B G +Types:Sorcery +A:SP$ Token | TokenAmount$ X | TokenScript$ b_2_2_zombie_decayed | SpellDescription$ Create X 2/2 black Zombie creature tokens with decayed, where X is half the number of creature cards in your graveyard, rounded up. (A creature with decayed can't block. When it attacks, sacrifice it at end of combat.) +SVar:X:Count$ValidGraveyard Creature.YouOwn/HalfUp +K:Flashback:3 B G +DeckHas:Ability$Token & Ability$Graveyard +Oracle:Create X 2/2 black Zombie creature tokens with decayed, where X is half the number of creature cards in your graveyard, rounded up. (A creature with decayed can't block. When it attacks, sacrifice it at end of combat.)\nFlashback {3}{B}{G} (You may cast this card from your graveyard for its flashback cost. Then exile it.) From e4cd251a4b607bc83f2467456bc45e17b2856fa8 Mon Sep 17 00:00:00 2001 From: Northmoc Date: Sun, 12 Sep 2021 15:20:40 -0400 Subject: [PATCH 37/58] voldaren_ambusher.txt (Suthro) --- .../res/cardsfolder/upcoming/voldaren_ambusher.txt | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 forge-gui/res/cardsfolder/upcoming/voldaren_ambusher.txt diff --git a/forge-gui/res/cardsfolder/upcoming/voldaren_ambusher.txt b/forge-gui/res/cardsfolder/upcoming/voldaren_ambusher.txt new file mode 100644 index 00000000000..57258136eb6 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/voldaren_ambusher.txt @@ -0,0 +1,10 @@ +Name:Voldaren Ambusher +ManaCost:2 R +Types:Creature Vampire Archer +PT:2/2 +T:Mode$ ChangesZone | ValidCard$ Card.Self | Origin$ Any | Destination$ Battlefield | CheckSVar$ Y | Execute$ TrigDealDamage | TriggerDescription$ When CARDNAME enters the battlefield, if an opponent lost life this turn, it deals X damage to up to one target creature or planeswalker, where X is the number of Vampires you control. +SVar:TrigDealDamage:DB$ DealDamage | NumDmg$ X | TargetMin$ 0 | TargetMax$ 1 | ValidTgts$ Creature,Planeswalker | TgtPrompt$ Select target creature or planeswalker +SVar:X:Count$Valid Vampire.YouCtrl +SVar:Y:Count$LifeOppsLostThisTurn +DeckNeeds:Type$Vampire +Oracle:When Voldaren Ambusher enters the battlefield, if an opponent lost life this turn, it deals X damage to up to one target creature or planeswalker, where X is the number of Vampires you control. From b6c281529c63b25ffe6c69e4d0abcb8eb63132a4 Mon Sep 17 00:00:00 2001 From: Northmoc Date: Sun, 12 Sep 2021 15:24:51 -0400 Subject: [PATCH 38/58] dawnhart_wardens.txt --- forge-gui/res/cardsfolder/upcoming/dawnhart_wardens.txt | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 forge-gui/res/cardsfolder/upcoming/dawnhart_wardens.txt diff --git a/forge-gui/res/cardsfolder/upcoming/dawnhart_wardens.txt b/forge-gui/res/cardsfolder/upcoming/dawnhart_wardens.txt new file mode 100644 index 00000000000..40141d1c656 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/dawnhart_wardens.txt @@ -0,0 +1,9 @@ +Name:Dawnhart Wardens +ManaCost:1 G W +Types:Creature Human Warlock +PT:3/3 +K:Vigilance +T:Mode$ Phase | Phase$ BeginCombat | ValidPlayer$ You | TriggerZones$ Battlefield | CheckSVar$ X | SVarCompare$ GE3 | Execute$ TrigPumpAll | TriggerDescription$ Coven — At the beginning of combat on your turn, if you control three or more creatures with different powers, creatures you control get +1/+0 until end of turn. +SVar:TrigPumpAll:DB$ PumpAll | ValidCards$ Creature.YouCtrl | NumAtt$ +1 +SVar:X:Count$DifferentPower_Creature.YouCtrl +Oracle:Vigilance\nCoven — At the beginning of combat on your turn, if you control three or more creatures with different powers, creatures you control get +1/+0 until end of turn. From 2a46e193f355e2b3f31f92aae9377f1a9ca8500b Mon Sep 17 00:00:00 2001 From: Northmoc Date: Sun, 12 Sep 2021 15:48:13 -0400 Subject: [PATCH 39/58] purifying_dragon.txt --- .../res/cardsfolder/upcoming/purifying_dragon.txt | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 forge-gui/res/cardsfolder/upcoming/purifying_dragon.txt diff --git a/forge-gui/res/cardsfolder/upcoming/purifying_dragon.txt b/forge-gui/res/cardsfolder/upcoming/purifying_dragon.txt new file mode 100644 index 00000000000..f4577efa8ee --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/purifying_dragon.txt @@ -0,0 +1,10 @@ +Name:Purifying Dragon +ManaCost:3 R R +Types:Creature Dragon +PT:4/3 +K:Flying +T:Mode$ Attacks | ValidCard$ Card.Self | TriggerZones$ Battlefield | Execute$ TrigDealDamage | TriggerDescription$ Whenever CARDNAME attacks, it deals 1 damage to target creature an opponent controls. If that creature is a Zombie, CARDNAME deals 2 damage to that creature instead. +SVar:TrigDealDamage:DB$ DealDamage | ValidTgts$ Creature | TargetsWithDefinedController$ TriggeredDefendingPlayer | TgtPrompt$ Select target creature defending player controls | NumDmg$ X +SVar:X:Targeted$Valid Creature.Zombie/Plus.1 +SVar:HasAttackEffect:TRUE +Oracle:Flying\nWhenever Purifying Dragon attacks, it deals 1 damage to target creature defending player controls. If that creature is a Zombie, Purifying Dragon deals 2 damage to that creature instead. From fdec280baa30f5c6ebb0f11bd459a5d87f8e70bc Mon Sep 17 00:00:00 2001 From: Northmoc Date: Sun, 12 Sep 2021 15:48:41 -0400 Subject: [PATCH 40/58] turn_the_earth.txt --- forge-gui/res/cardsfolder/upcoming/turn_the_earth.txt | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 forge-gui/res/cardsfolder/upcoming/turn_the_earth.txt diff --git a/forge-gui/res/cardsfolder/upcoming/turn_the_earth.txt b/forge-gui/res/cardsfolder/upcoming/turn_the_earth.txt new file mode 100644 index 00000000000..ddff62b40b1 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/turn_the_earth.txt @@ -0,0 +1,8 @@ +Name:Turn the Earth +ManaCost:G +Types:Instant +A:SP$ ChangeZone | Origin$ Graveyard | Destination$ Library | Shuffle$ True | ValidTgts$ Card | TgtPrompt$ Choose up to three target cards in graveyards | TargetMin$ 0 | TargetMax$ 3 | SubAbility$ DBGainLife | StackDescription$ The owners of {c:Targeted} shuffle them into their libraries. | SpellDescription$ Choose up to three target cards in graveyards. The owners of those cards shuffle them into their libraries. You gain 2 life. +SVar:DBGainLife:DB$ GainLife | LifeAmount$ 2 | Defined$ You +K:Flashback:1 G +DeckHas:Ability$Graveyard & Ability$LifeGain +Oracle:Choose up to three target cards in graveyards. The owners of those cards shuffle them into their libraries. You gain 2 life.\nFlashback {1}{G} (You may cast this card from your graveyard for its flashback cost. Then exile it.) From e24d6a64959ac5c6b1df49f706f9057be8fcd6be Mon Sep 17 00:00:00 2001 From: Northmoc Date: Sun, 12 Sep 2021 16:59:07 -0400 Subject: [PATCH 41/58] augur_of_autumn.txt --- forge-gui/res/cardsfolder/upcoming/augur_of_autumn.txt | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 forge-gui/res/cardsfolder/upcoming/augur_of_autumn.txt diff --git a/forge-gui/res/cardsfolder/upcoming/augur_of_autumn.txt b/forge-gui/res/cardsfolder/upcoming/augur_of_autumn.txt new file mode 100644 index 00000000000..d4bf43eb042 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/augur_of_autumn.txt @@ -0,0 +1,9 @@ +Name:Augur of Autumn +ManaCost:1 G G +Types:Creature Human Druid +PT:2/3 +S:Mode$ Continuous | Affected$ Card.TopLibrary+YouCtrl | AffectedZone$ Library | MayLookAt$ You | Description$ You may look at the top card of your library any time. +S:Mode$ Continuous | Affected$ Land.TopLibrary+YouCtrl | AffectedZone$ Library | MayPlay$ True | Description$ You may play lands from the top of your library. +S:Mode$ Continuous | CheckSVar$ X | SVarCompare$ GE3 | Affected$ Creature.TopLibrary+YouCtrl | AffectedZone$ Library | MayPlay$ True | Description$ Coven — As long as you control three or more creatures with different powers, you may cast creature spells from the top of your library. +SVar:X:Count$DifferentPower_Creature.YouCtrl +Oracle:You may look at the top card of your library any time.\nYou may play lands from the top of your library.\nCoven — As long as you control three or more creatures with different powers, you may cast creature spells from the top of your library. From d5c8b0106d06466d891ae4c3f5f53bae20c6818b Mon Sep 17 00:00:00 2001 From: Northmoc Date: Sun, 12 Sep 2021 20:49:47 -0400 Subject: [PATCH 42/58] patrician_geist.txt --- forge-gui/res/cardsfolder/upcoming/patrician_geist.txt | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 forge-gui/res/cardsfolder/upcoming/patrician_geist.txt diff --git a/forge-gui/res/cardsfolder/upcoming/patrician_geist.txt b/forge-gui/res/cardsfolder/upcoming/patrician_geist.txt new file mode 100644 index 00000000000..66a3c27b9b1 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/patrician_geist.txt @@ -0,0 +1,9 @@ +Name:Patrician Geist +ManaCost:2 U +Types:Creature Spirit Knight +PT:2/2 +K:Flying +S:Mode$ Continuous | Affected$ Spirit.Other+YouCtrl | AddPower$ 1 | AddToughness$ 1 | Description$ Other Spirits you control get +1/+1. +S:Mode$ ReduceCost | ValidCard$ Card.wasCastFromGraveyard | Type$ Spell | Activator$ You | Amount$ 1 | Description$ Spells you cast from your graveyard cost {1} less to cast. +DeckHints:Ability$Graveyard & Type$ Spirit +Oracle:Flying\nOther Spirits you control get +1/+1.\nSpells you cast from your graveyard cost {1} less to cast. From 2d1c665fc0a3d838e14d7d588c6b3011c11fa28b Mon Sep 17 00:00:00 2001 From: Northmoc Date: Sun, 12 Sep 2021 20:50:44 -0400 Subject: [PATCH 43/58] gravebreaker_lamia.txt add AI hints --- forge-gui/res/cardsfolder/g/gravebreaker_lamia.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/forge-gui/res/cardsfolder/g/gravebreaker_lamia.txt b/forge-gui/res/cardsfolder/g/gravebreaker_lamia.txt index 52e7a51ec77..a1061084fe9 100755 --- a/forge-gui/res/cardsfolder/g/gravebreaker_lamia.txt +++ b/forge-gui/res/cardsfolder/g/gravebreaker_lamia.txt @@ -6,4 +6,5 @@ K:Lifelink T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Creature.Self | Execute$ TrigChange | TriggerDescription$ When CARDNAME enters the battlefield, search your library for a card, put it into your graveyard, then shuffle. SVar:TrigChange:DB$ ChangeZone | Origin$ Library | Destination$ Graveyard | ChangeNum$ 1 | ChangeType$ Card | Mandatory$ True S:Mode$ ReduceCost | ValidCard$ Card.wasCastFromGraveyard | Type$ Spell | Activator$ You | Amount$ 1 | Description$ Spells you cast from your graveyard cost {1} less to cast. +DeckHas:Ability$LifeGain & Ability$Graveyard Oracle:Lifelink\nWhen Gravebreaker Lamia enters the battlefield, search your library for a card, put it into your graveyard, then shuffle.\nSpells you cast from your graveyard cost {1} less to cast. From 257e32a4e300177a75e9a36f83364af290c82c47 Mon Sep 17 00:00:00 2001 From: Northmoc Date: Sun, 12 Sep 2021 21:18:07 -0400 Subject: [PATCH 44/58] candlegrove_witch.txt & covetous_castaway_ghostly_castigator.txt --- .../upcoming/candlegrove_witch.txt | 8 ++++++++ .../covetous_castaway_ghostly_castigator.txt | 19 +++++++++++++++++++ 2 files changed, 27 insertions(+) create mode 100644 forge-gui/res/cardsfolder/upcoming/candlegrove_witch.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/covetous_castaway_ghostly_castigator.txt diff --git a/forge-gui/res/cardsfolder/upcoming/candlegrove_witch.txt b/forge-gui/res/cardsfolder/upcoming/candlegrove_witch.txt new file mode 100644 index 00000000000..d234b739e91 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/candlegrove_witch.txt @@ -0,0 +1,8 @@ +Name:Candlegrove Witch +ManaCost:1 W +Types:Creature Human Warlock +PT:2/2 +T:Mode$ Phase | Phase$ BeginCombat | ValidPlayer$ You | TriggerZones$ Battlefield | CheckSVar$ X | SVarCompare$ GE3 | Execute$ TrigPump | TriggerDescription$ Coven — At the beginning of combat on your turn, if you control three or more creatures with different powers, CARDNAME gains flying until end of turn. +SVar:TrigPump:DB$ Pump | Defined$ Self | KW$ Flying +SVar:X:Count$DifferentPower_Creature.YouCtrl +Oracle:Coven — At the beginning of combat on your turn, if you control three or more creatures with different powers, Candlegrove Witch gains flying until end of turn. diff --git a/forge-gui/res/cardsfolder/upcoming/covetous_castaway_ghostly_castigator.txt b/forge-gui/res/cardsfolder/upcoming/covetous_castaway_ghostly_castigator.txt new file mode 100644 index 00000000000..6977969d5d6 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/covetous_castaway_ghostly_castigator.txt @@ -0,0 +1,19 @@ +Name:Covetous Castaway +ManaCost:1 U +Types:Creature Human +PT:1/3 +K:Disturb:3 U U +AlternateMode:DoubleFaced +Oracle:Disturb {3}{U}{U} (You may cast this card from your graveyard transformed for its disturb cost.) + +ALTERNATE + +Name:Ghostly Castigator +ManaCost:no cost +Types:Creature Spirit +Colors:blue +PT:3/4 +K:Flying +R:Event$ Moved | ValidCard$ Card.Self | Destination$ Graveyard | ReplaceWith$ Exile | Description$ If CARDNAME would be put into a graveyard from anywhere, exile it instead. +SVar:Exile:DB$ ChangeZone | Hidden$ True | Origin$ All | Destination$ Exile | Defined$ ReplacedCard +Oracle:Flying\nIf Ghostly Castigator would be put into a graveyard from anywhere, exile it instead. From 75d7f2abf3fa916d33a822e607bf670e88049fde Mon Sep 17 00:00:00 2001 From: Northmoc Date: Sun, 12 Sep 2021 21:20:22 -0400 Subject: [PATCH 45/58] ritual_guardian.txt --- forge-gui/res/cardsfolder/upcoming/ritual_guardian.txt | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 forge-gui/res/cardsfolder/upcoming/ritual_guardian.txt diff --git a/forge-gui/res/cardsfolder/upcoming/ritual_guardian.txt b/forge-gui/res/cardsfolder/upcoming/ritual_guardian.txt new file mode 100644 index 00000000000..d508efb678c --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/ritual_guardian.txt @@ -0,0 +1,9 @@ +Name:Ritual Guardian +ManaCost:2 W +Types:Creature Human Soldier +PT:3/2 +T:Mode$ Phase | Phase$ BeginCombat | ValidPlayer$ You | TriggerZones$ Battlefield | CheckSVar$ X | SVarCompare$ GE3 | Execute$ TrigPump | TriggerDescription$ Coven — At the beginning of combat on your turn, if you control three or more creatures with different powers, CARDNAME gains lifelink until end of turn. +SVar:TrigPump:DB$ Pump | Defined$ Self | KW$ Lifelink +SVar:X:Count$DifferentPower_Creature.YouCtrl +DeckHas:Ability$LifeGain +Oracle:Coven — At the beginning of combat on your turn, if you control three or more creatures with different powers, Ritual Guardian gains lifelink until end of turn. From db34d1f341b6200ff977c3f3c3b4eaa35351f92a Mon Sep 17 00:00:00 2001 From: Northmoc Date: Sun, 12 Sep 2021 21:45:32 -0400 Subject: [PATCH 46/58] covert_cutpurse_covetous_geist.txt add missing triggers --- .../covert_cutpurse_covetous_geist.txt | 22 +++++++++++++++++++ .../covetous_castaway_ghostly_castigator.txt | 9 ++++++-- 2 files changed, 29 insertions(+), 2 deletions(-) create mode 100644 forge-gui/res/cardsfolder/upcoming/covert_cutpurse_covetous_geist.txt diff --git a/forge-gui/res/cardsfolder/upcoming/covert_cutpurse_covetous_geist.txt b/forge-gui/res/cardsfolder/upcoming/covert_cutpurse_covetous_geist.txt new file mode 100644 index 00000000000..f31187ce3bd --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/covert_cutpurse_covetous_geist.txt @@ -0,0 +1,22 @@ +Name:Covert Cutpurse +ManaCost:2 B +Types:Creature Human Rogue +PT:2/1 +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigDestroy | TriggerDescription$ When CARDNAME enters the battlefield, destroy target creature you don't control that was dealt damage this turn. +SVar:TrigDestroy:DB$ Destroy | ValidTgts$ Creature.YouDontCtrl+wasDealtDamageThisTurn | TgtPrompt$ Select target creature you don't control that was dealt damage this turn +K:Disturb:4 B +AlternateMode:DoubleFaced +Oracle:When Covert Cutpurse enters the battlefield, destroy target creature you don't control that was dealt damage this turn.\nDisturb {4}{B} (You may cast this card from your graveyard transformed for its disturb cost.) + +ALTERNATE + +Name:Covetous Geist +ManaCost:no cost +Types:Creature Spirit Rogue +Colors:black +PT:2/2 +K:Flying +K:Deathtouch +R:Event$ Moved | ValidCard$ Card.Self | Destination$ Graveyard | ReplaceWith$ Exile | Description$ If CARDNAME would be put into a graveyard from anywhere, exile it instead. +SVar:Exile:DB$ ChangeZone | Hidden$ True | Origin$ All | Destination$ Exile | Defined$ ReplacedCard +Oracle:Flying, deathtouch\nIf Covetous Geist would be put into a graveyard from anywhere, exile it instead. diff --git a/forge-gui/res/cardsfolder/upcoming/covetous_castaway_ghostly_castigator.txt b/forge-gui/res/cardsfolder/upcoming/covetous_castaway_ghostly_castigator.txt index 6977969d5d6..453a787a0e5 100644 --- a/forge-gui/res/cardsfolder/upcoming/covetous_castaway_ghostly_castigator.txt +++ b/forge-gui/res/cardsfolder/upcoming/covetous_castaway_ghostly_castigator.txt @@ -3,8 +3,11 @@ ManaCost:1 U Types:Creature Human PT:1/3 K:Disturb:3 U U +T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Self | Execute$ TrigMill | TriggerDescription$ When CARDNAME dies, mill three cards. +SVar:TrigMill:DB$ Mill | NumCards$ 3 | Defined$ You AlternateMode:DoubleFaced -Oracle:Disturb {3}{U}{U} (You may cast this card from your graveyard transformed for its disturb cost.) +DeckHas:Ability$Mill +Oracle:When Covetous Castaway dies, mill three cards.\nDisturb {3}{U}{U} (You may cast this card from your graveyard transformed for its disturb cost.) ALTERNATE @@ -14,6 +17,8 @@ Types:Creature Spirit Colors:blue PT:3/4 K:Flying +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigChangeZone | TriggerDescription$ When CARDNAME enters the battlefield, shuffle up to three target cards from your graveyard into your library. +SVar:TrigChangeZone:DB$ ChangeZone | Origin$ Graveyard | Destination$ Library | ValidTgts$ Card.YouOwn | TgtPrompt$ Select up to three target cards from your graveyard | TargetMin$ 0 | TargetMax$ 3 | Shuffle$ True R:Event$ Moved | ValidCard$ Card.Self | Destination$ Graveyard | ReplaceWith$ Exile | Description$ If CARDNAME would be put into a graveyard from anywhere, exile it instead. SVar:Exile:DB$ ChangeZone | Hidden$ True | Origin$ All | Destination$ Exile | Defined$ ReplacedCard -Oracle:Flying\nIf Ghostly Castigator would be put into a graveyard from anywhere, exile it instead. +Oracle:Flying\nWhen Ghostly Castigator enters the battlefield, shuffle up to three target cards from your graveyard into your library.\nIf Ghostly Castigator would be put into a graveyard from anywhere, exile it instead. From 01516d2019d661df6090368bb62c2089480fc1c2 Mon Sep 17 00:00:00 2001 From: stubob Date: Sun, 12 Sep 2021 20:23:28 -0600 Subject: [PATCH 47/58] Initial Draft ranking from https://draftsim.com/MID-pick-order.php --- forge-gui/res/blockdata/blocks.txt | 1 + forge-gui/res/draft/rankings.txt | 270 ++++++++++++++++++++++++++++- 2 files changed, 270 insertions(+), 1 deletion(-) diff --git a/forge-gui/res/blockdata/blocks.txt b/forge-gui/res/blockdata/blocks.txt index c70bab58191..0732ee6fb51 100644 --- a/forge-gui/res/blockdata/blocks.txt +++ b/forge-gui/res/blockdata/blocks.txt @@ -98,3 +98,4 @@ Time Spiral Remastered, 3/6/KHM, TSR Strixhaven: School of Mages, 3/6/STX, STX Modern Horizons 2, 3/6/MH2, MH2 Adventures in the Forgotten Realms, 3/6/AFR, AFR +Innistrad: Midnight Hunt, 3/6/MID, MID diff --git a/forge-gui/res/draft/rankings.txt b/forge-gui/res/draft/rankings.txt index c419e06d371..a0c218b46b5 100644 --- a/forge-gui/res/draft/rankings.txt +++ b/forge-gui/res/draft/rankings.txt @@ -33653,4 +33653,272 @@ #257|Dust to Dust|U|ME4 #258|Gate to Phyrexia|U|ME4 #259|Tablet of Epityr|C|ME4 -#260|Leeches|R|ME4 \ No newline at end of file +#260|Leeches|R|ME4 +//Rank|Name|Rarity|Set +#1|Liesa, Forgotten Archangel|R|MID +#2|Sigarda, Champion of Light|M|MID +#3|Wrenn and Seven|M|MID +#4|Arlinn, the Pack's Hope|M|MID +#5|Tainted Adversary|M|MID +#6|Tovolar's Huntmaster|R|MID +#7|Consuming Blob|M|MID +#8|Intrepid Adversary|M|MID +#9|Florian, Voldaren Scion|R|MID +#10|Spectral Adversary|M|MID +#11|Tovolar, Dire Overlord|R|MID +#12|Enduring Angel|M|MID +#13|Primal Adversary|M|MID +#14|Lord of the Forsaken|M|MID +#15|Teferi, Who Slows the Sunset|M|MID +#16|Moonveil Regent|M|MID +#17|Gisa, Glorious Resurrector|R|MID +#18|Jerren, Corrupted Bishop|M|MID +#19|Dennick, Pious Apprentice|R|MID +#20|Slogurk, the Overslime|R|MID +#21|Suspicious Stowaway|R|MID +#22|Brutal Cathar|R|MID +#23|Katilda, Dawnhart Prime|R|MID +#24|Augur of Autumn|R|MID +#25|Bloodline Culling|R|MID +#26|Sungold Sentinel|R|MID +#27|Jadar, Ghoulcaller of Nephalia|R|MID +#28|Ludevic, Necrogenius|R|MID +#29|Reckless Stormseeker|R|MID +#30|Bloodthirsty Adversary|M|MID +#31|Sunstreak Phoenix|M|MID +#32|Mask of Griselbrand|R|MID +#33|Lier, Disciple of the Drowned|M|MID +#34|Briarbridge Tracker|R|MID +#35|Poppet Stitcher|M|MID +#36|The Meathook Massacre|M|MID +#37|Vadrik, Astral Archmage|R|MID +#38|Grafted Identity|R|MID +#39|Triskaidekaphile|R|MID +#40|Smoldering Egg|R|MID +#41|Borrowed Time|U|MID +#42|Saryth, the Viper's Fang|R|MID +#43|Infernal Grasp|U|MID +#44|Adeline, Resplendent Cathar|R|MID +#45|Light Up the Night|R|MID +#46|Hostile Hostel|M|MID +#47|Graveyard Trespasser|R|MID +#48|Cathartic Pyre|U|MID +#49|Fateful Absence|R|MID +#50|Willow Geist|R|MID +#51|Clear Shot|U|MID +#52|Burn Down the House|R|MID +#53|Rem Karolus, Stalwart Slayer|R|MID +#54|Wake to Slaughter|R|MID +#55|Old Stickfingers|R|MID +#56|Hound Tamer|U|MID +#57|Rootcoil Creeper|U|MID +#58|Moonrager's Slash|C|MID +#59|Sunrise Cavalier|U|MID +#60|Vanquish the Horde|R|MID +#61|Gavony Dawnguard|U|MID +#62|Sludge Monster|R|MID +#63|Foul Play|U|MID +#64|Overwhelmed Archivist|U|MID +#65|Kessig Naturalist|U|MID +#66|Devoted Grafkeeper|U|MID +#67|Heirloom Mirror|U|MID +#68|Morbid Opportunist|U|MID +#69|Seize the Storm|U|MID +#70|Dawnhart Mentor|U|MID +#71|Vampire Socialite|U|MID +#72|Unnatural Growth|R|MID +#73|Diregraf Rebirth|U|MID +#74|Defenestrate|C|MID +#75|Join the Dance|U|MID +#76|Play with Fire|U|MID +#77|Patrician Geist|R|MID +#78|Cathar's Call|U|MID +#79|Memory Deluge|R|MID +#80|Eaten Alive|C|MID +#81|Rite of Oblivion|U|MID +#82|Unnatural Moonrise|U|MID +#83|Duel for Dominance|C|MID +#84|Hungry for More|U|MID +#85|Falkenrath Pit Fighter|R|MID +#86|Fleshtaker|U|MID +#87|Ghoulcaller's Harvest|R|MID +#88|Dawnhart Wardens|U|MID +#89|Geistflame Reservoir|R|MID +#90|Bereaved Survivor|U|MID +#91|Burly Breaker|U|MID +#92|Covert Cutpurse|U|MID +#93|Curse of Leeches|R|MID +#94|Phantom Carriage|U|MID +#95|Winterthorn Blessing|U|MID +#96|Sacred Fire|U|MID +#97|Bladestitched Skaab|U|MID +#98|Ghoulish Procession|U|MID +#99|Voldaren Ambusher|U|MID +#100|Galvanic Iteration|R|MID +#101|Ambitious Farmhand|U|MID +#102|Spellrune Painter|U|MID +#103|Chaplain of Alms|U|MID +#104|Flame Channeler|U|MID +#105|Burn the Accursed|C|MID +#106|Shadowbeast Sighting|C|MID +#107|Dreadhound|U|MID +#108|Angelfire Ignition|R|MID +#109|Hallowed Respite|R|MID +#110|Obsessive Astronomer|U|MID +#111|Organ Hoarder|C|MID +#112|Slaughter Specialist|R|MID +#113|Dryad's Revival|U|MID +#114|Arcane Infusion|U|MID +#115|Immolation|C|MID +#116|Defend the Celestus|U|MID +#117|Candletrap|C|MID +#118|Ominous Roost|U|MID +#119|Thermo-Alchemist|U|MID +#120|Contortionist Troupe|U|MID +#121|Outland Liberator|U|MID +#122|Deathbonnet Sprout|U|MID +#123|Malevolent Hermit|R|MID +#124|Mysterious Tome|U|MID +#125|Can't Stay Away|R|MID +#126|Rise of the Ants|U|MID +#127|Nebelgast Intruder|U|MID +#128|Storm Skreelix|U|MID +#129|Stromkirk Bloodthief|U|MID +#130|Skaab Wrangler|U|MID +#131|Odric's Outrider|U|MID +#132|Brood Weaver|U|MID +#133|Loyal Gryff|U|MID +#134|The Celestus|R|MID +#135|Faithful Mending|U|MID +#136|Locked in the Cemetery|C|MID +#137|Champion of the Perished|R|MID +#138|Festival Crasher|C|MID +#139|Baneblade Scoundrel|U|MID +#140|Fangblade Brigand|U|MID +#141|Grizzly Ghoul|U|MID +#142|Sigarda's Splendor|R|MID +#143|Duelcraft Trainer|U|MID +#144|Search Party Captain|C|MID +#145|Unblinking Observer|C|MID +#146|Olivia's Midnight Ambush|C|MID +#147|Ardent Elementalist|C|MID +#148|Firmament Sage|U|MID +#149|Bloodtithe Collector|U|MID +#150|Siphon Insight|R|MID +#151|Curse of Surveillance|R|MID +#152|Falkenrath Perforator|C|MID +#153|Lunar Frenzy|U|MID +#154|Croaking Counterpart|R|MID +#155|Corpse Cobble|U|MID +#156|Galedrifter|C|MID +#157|Vengeful Strangler|U|MID +#158|Covetous Castaway|U|MID +#159|Cathar Commando|C|MID +#160|Clarion Cathars|C|MID +#161|Gavony Silversmith|C|MID +#162|Homestead Courage|C|MID +#163|Geistwave|C|MID +#164|Vampire Interloper|C|MID +#165|Timberland Guide|C|MID +#166|Evolving Wilds|C|MID +#167|Fading Hope|U|MID +#168|Storm the Festival|R|MID +#169|Sunset Revelry|U|MID +#170|Purifying Dragon|U|MID +#171|Candlegrove Witch|C|MID +#172|Dawnhart Rejuvenator|C|MID +#173|Rite of Harmony|R|MID +#174|Delver of Secrets|U|MID +#175|Mourning Patrol|C|MID +#176|Shady Traveler|C|MID +#177|Harvesttide Infiltrator|C|MID +#178|Tireless Hauler|C|MID +#179|Mystic Skull|U|MID +#180|Bird Admirer|C|MID +#181|Beloved Beggar|U|MID +#182|Village Watch|U|MID +#183|Ritual of Hope|U|MID +#184|Gavony Trapper|C|MID +#185|Falcon Abomination|C|MID +#186|Revenge of the Drowned|C|MID +#187|Bat Whisperer|C|MID +#188|Morkrut Behemoth|C|MID +#189|Lambholt Harrier|C|MID +#190|Harvesttide Sentry|C|MID +#191|Silver Bolt|C|MID +#192|Vivisection|U|MID +#193|Startle|C|MID +#194|Hobbling Zombie|C|MID +#195|Dissipate|U|MID +#196|Celestus Sanctifier|C|MID +#197|Sungold Barrage|C|MID +#198|Haunted Ridge|R|MID +#199|Shipwreck Marsh|R|MID +#200|Rockfall Vale|R|MID +#201|Overgrown Farmland|R|MID +#202|Deserted Beach|R|MID +#203|Arrogant Outlaw|C|MID +#204|Brimstone Vandal|C|MID +#205|Famished Foragers|C|MID +#206|Siege Zombie|C|MID +#207|Unruly Mob|C|MID +#208|Pestilent Wolf|C|MID +#209|Snarling Wolf|C|MID +#210|Lunarch Veteran|C|MID +#211|Tavern Ruffian|C|MID +#212|Baithook Angler|C|MID +#213|Soul-Guide Gryff|C|MID +#214|Component Collector|C|MID +#215|Drownyard Amalgam|C|MID +#216|Flip the Switch|C|MID +#217|Shipwreck Sifters|C|MID +#218|Blood Pact|C|MID +#219|Crawl from the Cellar|C|MID +#220|Novice Occultist|C|MID +#221|Electric Revelation|C|MID +#222|Mounted Dreadknight|C|MID +#223|Raze the Effigy|C|MID +#224|Bounding Wolf|C|MID +#225|Bramble Armor|C|MID +#226|Eccentric Farmer|C|MID +#227|Path to the Festival|C|MID +#228|Tapping at the Window|C|MID +#229|Crossroads Candleguide|C|MID +#230|Jack-o'-Lantern|C|MID +#231|Diregraf Horde|C|MID +#232|Stuffed Bear|C|MID +#233|Turn the Earth|U|MID +#234|Ritual Guardian|C|MID +#235|Candlelit Cavalry|C|MID +#236|Secrets of the Key|C|MID +#237|Stormrider Spirit|C|MID +#238|Howl of the Hunt|C|MID +#239|Ecstatic Awakener|C|MID +#240|Blessed Defiance|C|MID +#241|No Way Out|C|MID +#242|Abandon the Post|C|MID +#243|Stolen Vitality|C|MID +#244|Voldaren Stinger|C|MID +#245|Return to Nature|C|MID +#246|Bladebrand|C|MID +#247|Moonsilver Key|U|MID +#248|Larder Zombie|C|MID +#249|Hedgewitch's Mask|C|MID +#250|Might of the Old Ways|C|MID +#251|Consider|C|MID +#252|Dire-Strain Rampage|R|MID +#253|Necrosynthesis|U|MID +#254|Flare of Faith|C|MID +#255|Devious Cover-Up|C|MID +#256|Rotten Reunion|C|MID +#257|Neonate's Rush|C|MID +#258|Pack's Betrayal|C|MID +#259|Sigardian Savior|M|MID +#260|Thraben Exorcism|C|MID +#261|Otherworldly Gaze|C|MID +#262|Duress|C|MID +#263|Plummet|C|MID +#264|Curse of Shaken Faith|R|MID +#265|Field of Ruin|U|MID +#266|Curse of Silence|R|MID +#267|Pithing Needle|R|MID From 9cfc6897c5eed239c3285a5e095a25f64ef5951d Mon Sep 17 00:00:00 2001 From: Stu Bob Date: Mon, 13 Sep 2021 03:28:49 +0000 Subject: [PATCH 48/58] More cards: clarion_cathars gavony_silversmith soul_guide_gryff homestead_courage Something is wrong with Hedgewitch's Mask the can't be blocked clause doesn't seem to be working. --- forge-gui/res/cardsfolder/a/akoum_boulderfoot.txt | 2 +- forge-gui/res/cardsfolder/upcoming/clarion_cathars.txt | 8 ++++++++ forge-gui/res/cardsfolder/upcoming/gavony_silversmith.txt | 8 ++++++++ forge-gui/res/cardsfolder/upcoming/hedgewitchs_mask.txt | 7 +++++++ forge-gui/res/cardsfolder/upcoming/homestead_courage.txt | 8 ++++++++ forge-gui/res/cardsfolder/upcoming/soul_guide_gryff.txt | 8 ++++++++ 6 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 forge-gui/res/cardsfolder/upcoming/clarion_cathars.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/gavony_silversmith.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/hedgewitchs_mask.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/homestead_courage.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/soul_guide_gryff.txt diff --git a/forge-gui/res/cardsfolder/a/akoum_boulderfoot.txt b/forge-gui/res/cardsfolder/a/akoum_boulderfoot.txt index c9df933082a..0e9ea9ad3db 100644 --- a/forge-gui/res/cardsfolder/a/akoum_boulderfoot.txt +++ b/forge-gui/res/cardsfolder/a/akoum_boulderfoot.txt @@ -3,6 +3,6 @@ ManaCost:4 R R Types:Creature Giant Warrior PT:4/5 T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigDealDamage | TriggerDescription$ When CARDNAME enters the battlefield, it deals 1 damage to any target. -SVar:TrigDealDamage:DB$DealDamage | ValidTgts$ Creature,Player,Planeswalker | TgtPrompt$ Select any target | NumDmg$ 1 +SVar:TrigDealDamage:DB$ DealDamage | ValidTgts$ Creature,Player,Planeswalker | TgtPrompt$ Select any target | NumDmg$ 1 SVar:Picture:http://www.wizards.com/global/images/magic/general/akoum_boulderfoot.jpg Oracle:When Akoum Boulderfoot enters the battlefield, it deals 1 damage to any target. diff --git a/forge-gui/res/cardsfolder/upcoming/clarion_cathars.txt b/forge-gui/res/cardsfolder/upcoming/clarion_cathars.txt new file mode 100644 index 00000000000..ce380447bfe --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/clarion_cathars.txt @@ -0,0 +1,8 @@ +Name:Clarion Cathars +ManaCost:3 W +Types:Creature Human Knight +PT:3/3 +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigToken | TriggerDescription$ When CARDNAME enters the battlefield, create a 1/1 white Human creature token. +SVar:TrigToken:DB$ Token | TokenAmount$ 1 | TokenScript$ w_1_1_human | TokenOwner$ You +DeckHas:Ability$Token +Oracle:When Clarion Cathars enters the battlefield, create a 1/1 white Human creature token. diff --git a/forge-gui/res/cardsfolder/upcoming/gavony_silversmith.txt b/forge-gui/res/cardsfolder/upcoming/gavony_silversmith.txt new file mode 100644 index 00000000000..a289ce6a16f --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/gavony_silversmith.txt @@ -0,0 +1,8 @@ +Name:Gavony Silversmith +ManaCost:3 W +Types:Creature Human Soldier +PT:3/3 +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigPut | TriggerDescription$ When CARDNAME enters the battlefield, put a +1/+1 counter on each of up to two target creatures. +SVar:TrigPut:DB$ PutCounter | TargetMin$ 0 | TargetMax$ 2 | ValidTgts$ Creature | TgtPrompt$ Select up to two target creatures | CounterType$ P1P1 | CounterNum$ 1 +DeckHas:Ability$Counters +Oracle:When Gavony Silversmith enters the battlefield, put a +1/+1 counter on each of up to two target creatures. diff --git a/forge-gui/res/cardsfolder/upcoming/hedgewitchs_mask.txt b/forge-gui/res/cardsfolder/upcoming/hedgewitchs_mask.txt new file mode 100644 index 00000000000..a3206a5283d --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/hedgewitchs_mask.txt @@ -0,0 +1,7 @@ +Name:Hedgewitch's Mask +ManaCost:W +Types:Artifact Equipment +K:Equip:2 +S:Mode$ CantBlockBy | ValidAttacker$ Creature.EquippedBy | ValidBlocker$ Creature.powerGE4 | Description$ Equipped creature can't be blocked by creatures with power 4 or greater. +S:Mode$ Continuous | Affected$ Creature.EquippedBy | AddPower$ 1 | AddToughness$ 1 | Description$ Equipped creature gets +1/+1. +Oracle:Equipped creature gets +1/+1.\nEquipped creature can't be blocked by creatures with power 4 or greater.\nEquip {2} diff --git a/forge-gui/res/cardsfolder/upcoming/homestead_courage.txt b/forge-gui/res/cardsfolder/upcoming/homestead_courage.txt new file mode 100644 index 00000000000..afa9be8dff0 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/homestead_courage.txt @@ -0,0 +1,8 @@ +Name:Homestead Courage +ManaCost:W +Types:Sorcery +K:Flashback:W +A:SP$ PutCounter | Cost$ W | ValidTgts$ Creature.YouCtrl | TgtPrompt$ Select target creature | CounterType$ P1P1 | CounterNum$ 1 | SubAbility$ DBPump | SpellDescription$ Put a +1/+1 counter on target creature you control. +SVar:DBPump:DB$ Pump | Defined$ Targeted | KW$ Vigilance +DeckHas:Ability$Counters +Oracle:Put a +1/+1 counter on target creature you control. That creature gains vigilance until end of turn.\nFlashback {W} (You may cast this card from your graveyard for its flashback cost. Then exile it.) diff --git a/forge-gui/res/cardsfolder/upcoming/soul_guide_gryff.txt b/forge-gui/res/cardsfolder/upcoming/soul_guide_gryff.txt new file mode 100644 index 00000000000..c1bafa30c18 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/soul_guide_gryff.txt @@ -0,0 +1,8 @@ +Name:Soul-Guide Gryff +ManaCost:4 W +Types:Creature Hippogriff Spirit +PT:3/4 +K:Flying +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigChangeZone | TriggerDescription$ When CARDNAME enters the battlefield, exile up to one target card from a graveyard. +SVar:TrigChangeZone:DB$ChangeZone | Origin$ Graveyard | Destination$ Exile | ValidTgts$ Card | TargetMin$ 0 | TargetMax$ 1 | TgtPrompt$ Select target card in a graveyard to exile +Oracle:Flying\nWhen Soul-Guide Gryff enters the battlefield, exile up to one target card from a graveyard. \ No newline at end of file From e97cdbb293c8456fb26749d0e1a7f0c876f0ac75 Mon Sep 17 00:00:00 2001 From: Anthony Calosa Date: Mon, 13 Sep 2021 08:04:31 +0000 Subject: [PATCH 49/58] Update Commander Collection Black.txt --- .../res/editions/Commander Collection Black.txt | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/forge-gui/res/editions/Commander Collection Black.txt b/forge-gui/res/editions/Commander Collection Black.txt index eee0f93ed27..f8a4eca1251 100644 --- a/forge-gui/res/editions/Commander Collection Black.txt +++ b/forge-gui/res/editions/Commander Collection Black.txt @@ -7,13 +7,14 @@ ScryfallCode=CC2 [cards] 1 M Liliana, Heretical Healer @Bastien L. Deharme -2 M Ghoulcaller Gisa @Aaron J. Riley -3 R Ophiomancer @Caroline Gariba -4 R Phyrexian Arena @Vincent Proce -5 R Reanimate @Nils Hamm -6 R Toxic Deluge @Yeong-Hao Han -7 R Sol Ring @Yeong-Hao Han -8 R Command Tower @Julian Kok Joon Wen +2 M Liliana, Defiant Necromancer @Bastien L. Deharme +3 M Ghoulcaller Gisa @Aaron J. Riley +4 R Ophiomancer @Caroline Gariba +5 R Phyrexian Arena @Vincent Proce +6 R Reanimate @Nils Hamm +7 R Toxic Deluge @Yeong-Hao Han +8 R Sol Ring @Yeong-Hao Han +9 R Command Tower @Julian Kok Joon Wen [tokens] b_1_1_snake_deathtouch From 236fd998626ff25ee7a31f32286f2f8238d95467 Mon Sep 17 00:00:00 2001 From: leriomaggio Date: Mon, 13 Sep 2021 10:15:32 +0100 Subject: [PATCH 50/58] FIX cardDb tests in Maven for ImageKeys Mock configuration between subclasses --- .../src/main/java/forge/card/CardDb.java | 8 ++++-- .../test/java/forge/card/CardDbTestCase.java | 16 +++++++----- .../forge/card/CardDbTestWithNoImage.java | 11 +++++--- .../forge/card/ForgeCardMockTestCase.java | 25 +++++++++++-------- 4 files changed, 38 insertions(+), 22 deletions(-) diff --git a/forge-core/src/main/java/forge/card/CardDb.java b/forge-core/src/main/java/forge/card/CardDb.java index 15d4687e32d..36fc22495cf 100644 --- a/forge-core/src/main/java/forge/card/CardDb.java +++ b/forge-core/src/main/java/forge/card/CardDb.java @@ -556,9 +556,11 @@ public final class CardDb implements ICardDatabase, IDeckGenPool { // Before returning make sure that actual candidate has Image. // If not, try to replace current candidate with one having image, // so to align this implementation with old one. - while (!candidate.hasImage() && candidatesIterator.hasNext()) { + // If none will have image, the original candidate will be retained! + PaperCard firstCandidate = candidate; + while (!candidate.hasImage() && candidatesIterator.hasNext()) candidate = candidatesIterator.next(); - } + candidate = candidate.hasImage() ? candidate : firstCandidate; return isFoil ? candidate.getFoiled() : candidate; } @@ -724,10 +726,12 @@ public final class CardDb implements ICardDatabase, IDeckGenPool { final Iterator editionIterator = acceptedEditions.iterator(); CardEdition ed = editionIterator.next(); PaperCard candidate = candidatesCard.get(ed.getCode()); + PaperCard firstCandidate = candidate; while (!candidate.hasImage() && editionIterator.hasNext()) { ed = editionIterator.next(); candidate = candidatesCard.get(ed.getCode()); } + candidate = candidate.hasImage() ? candidate : firstCandidate; //If any, we're sure that at least one candidate is always returned despite it having any image return cr.isFoil ? candidate.getFoiled() : candidate; } diff --git a/forge-gui-desktop/src/test/java/forge/card/CardDbTestCase.java b/forge-gui-desktop/src/test/java/forge/card/CardDbTestCase.java index e11464623ae..9e8060e3ca5 100644 --- a/forge-gui-desktop/src/test/java/forge/card/CardDbTestCase.java +++ b/forge-gui-desktop/src/test/java/forge/card/CardDbTestCase.java @@ -1,20 +1,25 @@ package forge.card; import com.google.common.base.Predicate; +import forge.ImageCache; +import forge.ImageKeys; +import forge.Singletons; import forge.StaticData; import forge.item.IPaperCard; import forge.item.PaperCard; +import forge.localinstance.properties.ForgePreferences; import forge.model.FModel; +import forge.util.Localizer; +import org.mockito.Mockito; +import org.powermock.api.mockito.PowerMockito; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; +import javax.imageio.ImageIO; import java.text.ParseException; import java.text.SimpleDateFormat; import java.time.Instant; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Date; -import java.util.List; +import java.util.*; import static org.testng.Assert.*; @@ -2138,5 +2143,4 @@ public class CardDbTestCase extends ForgeCardMockTestCase { assertTrue(islandCard.isFoil()); } -} - +} \ No newline at end of file diff --git a/forge-gui-desktop/src/test/java/forge/card/CardDbTestWithNoImage.java b/forge-gui-desktop/src/test/java/forge/card/CardDbTestWithNoImage.java index 1545fc8431d..c3ac9a9fa0b 100644 --- a/forge-gui-desktop/src/test/java/forge/card/CardDbTestWithNoImage.java +++ b/forge-gui-desktop/src/test/java/forge/card/CardDbTestWithNoImage.java @@ -1,5 +1,6 @@ package forge.card; +import forge.ImageCache; import forge.ImageKeys; import forge.item.PaperCard; import org.mockito.Mockito; @@ -7,6 +8,8 @@ import org.powermock.api.mockito.PowerMockito; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; +import javax.imageio.ImageIO; + import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertNotNull; @@ -26,11 +29,11 @@ public class CardDbTestWithNoImage extends CardDbTestCase { } @Override - @BeforeMethod - protected void initMocks() throws Exception { + protected void initCardImageMocks() { + PowerMockito.mockStatic(ImageIO.class); + PowerMockito.mockStatic(ImageCache.class); PowerMockito.mockStatic(ImageKeys.class); - PowerMockito.when(ImageKeys.hasImage(Mockito.any(PaperCard.class))).thenReturn(false); - super.initMocks(); + PowerMockito.when(ImageKeys.hasImage(Mockito.any(PaperCard.class), Mockito.anyBoolean())).thenReturn(false); } @Test diff --git a/forge-gui-desktop/src/test/java/forge/card/ForgeCardMockTestCase.java b/forge-gui-desktop/src/test/java/forge/card/ForgeCardMockTestCase.java index 61acc9cd91a..49859e7706d 100644 --- a/forge-gui-desktop/src/test/java/forge/card/ForgeCardMockTestCase.java +++ b/forge-gui-desktop/src/test/java/forge/card/ForgeCardMockTestCase.java @@ -114,7 +114,7 @@ public class ForgeCardMockTestCase extends PowerMockTestCase { fLangDir.set(ForgeConstants.class, langDir); } - private void setMock(Localizer mock) { + protected void setMock(Localizer mock) { try { Field instance = Localizer.class.getDeclaredField("instance"); instance.setAccessible(true); @@ -128,16 +128,14 @@ public class ForgeCardMockTestCase extends PowerMockTestCase { protected void initMocks() throws Exception { //Loading a card also automatically loads the image, which we do not want (even if it wouldn't cause exceptions). //The static initializer block in ImageCache can't fully be mocked (https://code.google.com/p/powermock/issues/detail?id=256), so we also need to mess with ImageIO... - //TODO: make sure that loading images only happens in a GUI environment, so we no longer need to mock this - PowerMockito.mockStatic(ImageIO.class); - PowerMockito.mockStatic(ImageCache.class); - PowerMockito.mockStatic(ImageKeys.class); + initCardImageMocks(); initForgeConstants(); - - // Always Has Image (there is a separated test case to cover the opposite case) - PowerMockito.when(ImageKeys.hasImage(Mockito.any(PaperCard.class))).thenReturn(true); - //Mocking some more static stuff + initForgePreferences(); + initializeStaticData(); + } + + protected void initForgePreferences() throws IllegalAccessException { PowerMockito.mockStatic(Singletons.class); PowerMockito.mockStatic(FModel.class); ForgePreferences forgePreferences = new ForgePreferences(); @@ -161,7 +159,14 @@ public class ForgeCardMockTestCase extends PowerMockTestCase { PowerMockito.field(Localizer.class, "resourceBundle").set(localizerMock, dummyResourceBundle); PowerMockito.when(localizerMock.getMessage(Mockito.anyString())).thenReturn("any string"); PowerMockito.when(FModel.getPreferences()).thenReturn(forgePreferences); - initializeStaticData(); + } + + protected void initCardImageMocks() { + //make sure that loading images only happens in a GUI environment, so we no longer need to mock this + PowerMockito.mockStatic(ImageIO.class); + PowerMockito.mockStatic(ImageCache.class); + PowerMockito.mockStatic(ImageKeys.class); + PowerMockito.when(ImageKeys.hasImage(Mockito.any(PaperCard.class), Mockito.anyBoolean())).thenReturn(true); } protected void initializeStaticData() { From 4e2a6d3c24036bd722c24e0d094ba2ba6055b7b2 Mon Sep 17 00:00:00 2001 From: tool4EvEr Date: Mon, 13 Sep 2021 11:33:44 +0200 Subject: [PATCH 51/58] Remove old code --- forge-gui/src/main/java/forge/player/HumanPlay.java | 8 -------- 1 file changed, 8 deletions(-) diff --git a/forge-gui/src/main/java/forge/player/HumanPlay.java b/forge-gui/src/main/java/forge/player/HumanPlay.java index 0888afa10ac..fc4e8c28fb9 100644 --- a/forge-gui/src/main/java/forge/player/HumanPlay.java +++ b/forge-gui/src/main/java/forge/player/HumanPlay.java @@ -753,14 +753,6 @@ public class HumanPlay { } } - Integer replicate = ability.getSVarInt("Replicate"); - if (replicate != null) { - ManaCost rCost = source.getManaCost(); - for (int i = 0; i < replicate; i++) { - toPay.addManaCost(rCost); - } - } - CardCollection cardsToDelve = new CardCollection(); if (isActivatedSa) { CostAdjustment.adjust(toPay, ability, cardsToDelve, false); From 90e5bef028a73bf421a6e285bf2e9c37aae6a6e5 Mon Sep 17 00:00:00 2001 From: tool4EvEr Date: Mon, 13 Sep 2021 11:33:56 +0200 Subject: [PATCH 52/58] Clean up --- forge-game/src/main/java/forge/game/GameAction.java | 1 - .../src/main/java/forge/game/ability/AbilityUtils.java | 3 +-- .../src/main/java/forge/game/player/PlayerProperty.java | 2 +- forge-gui/res/cardsfolder/a/assassins_trophy.txt | 3 +-- forge-gui/res/cardsfolder/s/sawtusk_demolisher.txt | 5 ++--- 5 files changed, 5 insertions(+), 9 deletions(-) diff --git a/forge-game/src/main/java/forge/game/GameAction.java b/forge-game/src/main/java/forge/game/GameAction.java index 5dec3d908a9..2ddb495f86c 100644 --- a/forge-game/src/main/java/forge/game/GameAction.java +++ b/forge-game/src/main/java/forge/game/GameAction.java @@ -1729,7 +1729,6 @@ public class GameAction { return false; } - if (sa != null) { activator = sa.getActivatingPlayer(); } 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 8e8e64736d4..796bffca76e 100644 --- a/forge-game/src/main/java/forge/game/ability/AbilityUtils.java +++ b/forge-game/src/main/java/forge/game/ability/AbilityUtils.java @@ -2119,7 +2119,7 @@ public class AbilityUtils { // Count$IfCastInOwnMainPhase.. // 7/10 if (sq[0].contains("IfCastInOwnMainPhase")) { - final PhaseHandler cPhase = player.getGame().getPhaseHandler(); + final PhaseHandler cPhase = game.getPhaseHandler(); final boolean isMyMain = cPhase.getPhase().isMain() && cPhase.isPlayerTurn(player) && c.getCastFrom() != null; return doXMath(Integer.parseInt(sq[isMyMain ? 1 : 2]), expr, c, ctb); } @@ -3584,7 +3584,6 @@ public class AbilityUtils { return tot; } - private static CardCollectionView getCardListForXCount(final Card c, final Player cc, final String[] sq, CardTraitBase ctb) { final List opps = cc.getOpponents(); CardCollection someCards = new CardCollection(); diff --git a/forge-game/src/main/java/forge/game/player/PlayerProperty.java b/forge-game/src/main/java/forge/game/player/PlayerProperty.java index 2d56c9676e1..000d1ee46c4 100644 --- a/forge-game/src/main/java/forge/game/player/PlayerProperty.java +++ b/forge-game/src/main/java/forge/game/player/PlayerProperty.java @@ -169,7 +169,7 @@ public class PlayerProperty { return false; } } else if (property.equals("Defending")) { - if (!player.getGame().getCombat().getAttackersAndDefenders().values().contains(player)) { + if (!game.getCombat().getAttackersAndDefenders().values().contains(player)) { return false; } } else if (property.equals("wasDealtCombatDamageThisTurn")) { diff --git a/forge-gui/res/cardsfolder/a/assassins_trophy.txt b/forge-gui/res/cardsfolder/a/assassins_trophy.txt index c41401cbd95..a40b1fe90e9 100644 --- a/forge-gui/res/cardsfolder/a/assassins_trophy.txt +++ b/forge-gui/res/cardsfolder/a/assassins_trophy.txt @@ -2,6 +2,5 @@ Name:Assassin's Trophy ManaCost:B G Types:Instant A:SP$ Destroy | Cost$ B G | ValidTgts$ Permanent.OppCtrl | AITgts$ Permanent.nonLand,Land.nonBasic | TgtPrompt$ Select target permanent an opponent controls | SubAbility$ DBChange | SpellDescription$ Destroy target permanent an opponent controls. Its controller may search their library for a basic land card, put it onto the battlefield, then shuffle. -SVar:DBChange:DB$ ChangeZone | Optional$ True | Origin$ Library | Destination$ Battlefield | DefinedPlayer$ TargetedController | ChangeType$ Land.Basic | ChangeNum$ 1 | DefinedPlayer$ TargetedController | ShuffleNonMandatory$ True | SubAbility$ DBCleanup -SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True +SVar:DBChange:DB$ ChangeZone | Optional$ True | Origin$ Library | Destination$ Battlefield | DefinedPlayer$ TargetedController | ChangeType$ Land.Basic | ChangeNum$ 1 | DefinedPlayer$ TargetedController | ShuffleNonMandatory$ True Oracle:Destroy target permanent an opponent controls. Its controller may search their library for a basic land card, put it onto the battlefield, then shuffle. diff --git a/forge-gui/res/cardsfolder/s/sawtusk_demolisher.txt b/forge-gui/res/cardsfolder/s/sawtusk_demolisher.txt index 647711a2cfb..c9ba959430a 100644 --- a/forge-gui/res/cardsfolder/s/sawtusk_demolisher.txt +++ b/forge-gui/res/cardsfolder/s/sawtusk_demolisher.txt @@ -5,8 +5,7 @@ PT:6/6 K:Mutate:3 G K:Trample T:Mode$ Mutates | ValidCard$ Card.Self | TriggerZones$ Battlefield | Execute$ TrigDestroy | TriggerDescription$ Whenever this creature mutates, destroy target noncreature permanent. Its controller creates a 3/3 green Beast creature token. -SVar:TrigDestroy:DB$ Destroy | ValidTgts$ Permanent.nonCreature | TgtPrompt$ Select target noncreature permanent | AITgts$ Card.cmcGE4 | RememberLKI$ True | SubAbility$ DBToken -SVar:DBToken:DB$ Token | TokenAmount$ 1 | TokenScript$ g_3_3_beast | TokenOwner$ RememberedController | SubAbility$ DBCleanup -SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True +SVar:TrigDestroy:DB$ Destroy | ValidTgts$ Permanent.nonCreature | TgtPrompt$ Select target noncreature permanent | AITgts$ Card.cmcGE4 | SubAbility$ DBToken +SVar:DBToken:DB$ Token | TokenAmount$ 1 | TokenScript$ g_3_3_beast | TokenOwner$ TargetedController DeckHas:Ability$Token Oracle:Mutate {3}{G} (If you cast this spell for its mutate cost, put it over or under target non-Human creature you own. They mutate into the creature on top plus all abilities from under it.)\nTrample\nWhenever this creature mutates, destroy target noncreature permanent. Its controller creates a 3/3 green Beast creature token. From adf44be7329a05e8104c984dafab73d43abbedd1 Mon Sep 17 00:00:00 2001 From: leriomaggio Date: Mon, 13 Sep 2021 11:26:05 +0100 Subject: [PATCH 53/58] removed unused imports --- .../src/test/java/forge/card/CardDbTestCase.java | 9 --------- 1 file changed, 9 deletions(-) diff --git a/forge-gui-desktop/src/test/java/forge/card/CardDbTestCase.java b/forge-gui-desktop/src/test/java/forge/card/CardDbTestCase.java index 9e8060e3ca5..46e8bf93c88 100644 --- a/forge-gui-desktop/src/test/java/forge/card/CardDbTestCase.java +++ b/forge-gui-desktop/src/test/java/forge/card/CardDbTestCase.java @@ -1,21 +1,12 @@ package forge.card; import com.google.common.base.Predicate; -import forge.ImageCache; -import forge.ImageKeys; -import forge.Singletons; import forge.StaticData; import forge.item.IPaperCard; import forge.item.PaperCard; -import forge.localinstance.properties.ForgePreferences; import forge.model.FModel; -import forge.util.Localizer; -import org.mockito.Mockito; -import org.powermock.api.mockito.PowerMockito; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; - -import javax.imageio.ImageIO; import java.text.ParseException; import java.text.SimpleDateFormat; import java.time.Instant; From 4f1eec23e683e6b0696ab1e34e86af2b5f82281d Mon Sep 17 00:00:00 2001 From: Stu Bob Date: Mon, 13 Sep 2021 14:10:14 +0000 Subject: [PATCH 54/58] Bounding Wolf Raze the Effigy Novice Occultist Shadowbeast Sighting Gavony Trapper Defend the Celestus As well as fixing typos. --- forge-gui/res/cardsfolder/a/amphin_mutineer.txt | 2 +- forge-gui/res/cardsfolder/c/coastline_marauders.txt | 2 +- forge-gui/res/cardsfolder/i/impulsive_pilferer.txt | 2 +- forge-gui/res/cardsfolder/p/profane_transfusion.txt | 2 +- forge-gui/res/cardsfolder/s/szadek_lord_of_secrets.txt | 2 +- forge-gui/res/cardsfolder/upcoming/bounding_wolf.txt | 7 +++++++ .../res/cardsfolder/upcoming/defend_the_celestus.txt | 6 ++++++ forge-gui/res/cardsfolder/upcoming/gavony_trapper.txt | 6 ++++++ forge-gui/res/cardsfolder/upcoming/novice_occultist.txt | 8 ++++++++ forge-gui/res/cardsfolder/upcoming/raze_the_effigy.txt | 7 +++++++ .../res/cardsfolder/upcoming/shadowbeast_sighting.txt | 7 +++++++ 11 files changed, 46 insertions(+), 5 deletions(-) create mode 100644 forge-gui/res/cardsfolder/upcoming/bounding_wolf.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/defend_the_celestus.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/gavony_trapper.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/novice_occultist.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/raze_the_effigy.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/shadowbeast_sighting.txt diff --git a/forge-gui/res/cardsfolder/a/amphin_mutineer.txt b/forge-gui/res/cardsfolder/a/amphin_mutineer.txt index fad351f6004..28dadf024e9 100755 --- a/forge-gui/res/cardsfolder/a/amphin_mutineer.txt +++ b/forge-gui/res/cardsfolder/a/amphin_mutineer.txt @@ -6,5 +6,5 @@ T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.S SVar:TrigExile:DB$ ChangeZone | TargetMin$ 0 | TargetMax$ 1 | ValidTgts$ Creature.nonSalamander | TgtPrompt$ Select up to one target non-Salamander creature | Origin$ Battlefield | Destination$ Exile | SubAbility$ DBToken SVar:DBToken:DB$ Token | TokenAmount$ 1 | TokenScript$ u_4_3_salamander_warrior | TokenOwner$ TargetedController K:Encore:4 U U -DeckHas:Ablity$Token +DeckHas:Ability$Token Oracle:When Amphin Mutineer enters the battlefield, exile up to one target non-Salamander creature. That creature's controller creates a 4/3 blue Salamander Warrior creature token.\nEncore {4}{U}{U} ({4}{U}{U}, Exile this card from your graveyard: For each opponent, create a token copy that attacks that opponent this turn if able. They gain haste. Sacrifice them at the beginning of the next end step. Activate only as a sorcery.) diff --git a/forge-gui/res/cardsfolder/c/coastline_marauders.txt b/forge-gui/res/cardsfolder/c/coastline_marauders.txt index c8e843d5e15..18a755678b6 100644 --- a/forge-gui/res/cardsfolder/c/coastline_marauders.txt +++ b/forge-gui/res/cardsfolder/c/coastline_marauders.txt @@ -8,5 +8,5 @@ T:Mode$ Attacks | ValidCard$ Card.Self | Execute$ TrigPump | TriggerDescription$ SVar:TrigPump:DB$ Pump | Defined$ Self | NumAtt$ X SVar:X:Count$Valid Land.DefenderCtrl SVar:HasAttackEffect:TRUE -DeckHas:Ablity$Token +DeckHas:Ability$Token Oracle:Trample\nWhenever Coastline Marauders attacks, it gets +1/+0 until end of turn for each land defending player controls.\nEncore {4}{R}{R} ({4}{R}{R}, Exile this card from your graveyard: For each opponent, create a token copy that attacks that opponent this turn if able. They gain haste. Sacrifice them at the beginning of the next end step. Activate only as a sorcery.) diff --git a/forge-gui/res/cardsfolder/i/impulsive_pilferer.txt b/forge-gui/res/cardsfolder/i/impulsive_pilferer.txt index 18fc9fe67b6..edcb05c15c1 100644 --- a/forge-gui/res/cardsfolder/i/impulsive_pilferer.txt +++ b/forge-gui/res/cardsfolder/i/impulsive_pilferer.txt @@ -5,7 +5,7 @@ PT:1/1 K:Encore:3 R T:Mode$ ChangesZone | ValidCard$ Card.Self | Origin$ Battlefield | Destination$ Graveyard | Execute$ TrigToken | TriggerDescription$ When CARDNAME dies, create a Treasure token. (It's an artifact with "{T}, Sacrifice this artifact: Add one mana of any color.") SVar:TrigToken:DB$ Token | TokenScript$ c_a_treasure_sac | TokenAmount$ 1 -DeckHas:Ablity$Token +DeckHas:Ability$Token DeckHints:Ability$Sacrifice SVar:SacMe:1 Oracle:When Impulsive Pilferer dies, create a Treasure token. (It's an artifact with "{T}, Sacrifice this artifact: Add one mana of any color.")\nEncore {3}{R} ({3}{R}, Exile this card from your graveyard: For each opponent, create a token copy that attacks that opponent this turn if able. They gain haste. Sacrifice them at the beginning of the next end step. Activate only as a sorcery.) diff --git a/forge-gui/res/cardsfolder/p/profane_transfusion.txt b/forge-gui/res/cardsfolder/p/profane_transfusion.txt index d48ca1b371e..f2e4fa895e0 100755 --- a/forge-gui/res/cardsfolder/p/profane_transfusion.txt +++ b/forge-gui/res/cardsfolder/p/profane_transfusion.txt @@ -5,6 +5,6 @@ A:SP$ ExchangeLife | Cost$ 6 B B B | TargetMin$ 2 | TargetMax$ 2 | ValidTgts$ Pl SVar:DBToken:DB$ Token | TokenAmount$ 1 | TokenScript$ c_x_x_a_horror | TokenOwner$ You | TokenPower$ X | TokenToughness$ X | SubAbility$ DBCleanup | StackDescription$ {p:You} creates an X/X colorless Horror artifact creature token, where X is the difference between those players' life totals. SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True SVar:X:Count$RememberedNumber/Abs -DeckHas:Ablity$Token +DeckHas:Ability$Token AI:RemoveDeck:All Oracle:Two target players exchange life totals. You create an X/X colorless Horror artifact creature token, where X is the difference between those players' life totals. diff --git a/forge-gui/res/cardsfolder/s/szadek_lord_of_secrets.txt b/forge-gui/res/cardsfolder/s/szadek_lord_of_secrets.txt index 27ba3d7c547..768ba8a3a8a 100644 --- a/forge-gui/res/cardsfolder/s/szadek_lord_of_secrets.txt +++ b/forge-gui/res/cardsfolder/s/szadek_lord_of_secrets.txt @@ -7,5 +7,5 @@ R:Event$ DamageDone | ActiveZones$ Battlefield | IsCombat$ True | ValidSource$ C SVar:X:ReplaceCount$DamageAmount SVar:CountersAndMill:DB$ PutCounter | Defined$ Self | CounterNum$ X | CounterType$ P1P1 | SubAbility$ Mill SVar:Mill:DB$ Mill | Defined$ ReplacedTarget | NumCards$ X -DeckHas:Ablity$Counters +DeckHas:Ability$Counters Oracle:Flying\nIf Szadek, Lord of Secrets would deal combat damage to a player, instead put that many +1/+1 counters on Szadek and that player mills that many cards. diff --git a/forge-gui/res/cardsfolder/upcoming/bounding_wolf.txt b/forge-gui/res/cardsfolder/upcoming/bounding_wolf.txt new file mode 100644 index 00000000000..f4004c4dc8c --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/bounding_wolf.txt @@ -0,0 +1,7 @@ +Name:Bounding Wolf +ManaCost:2 G +Types:Creature wolf +PT:3/2 +K:Flash +K:Reach +Oracle:Flash\nReach diff --git a/forge-gui/res/cardsfolder/upcoming/defend_the_celestus.txt b/forge-gui/res/cardsfolder/upcoming/defend_the_celestus.txt new file mode 100644 index 00000000000..ad022735c2b --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/defend_the_celestus.txt @@ -0,0 +1,6 @@ +Name:Defend the Celestus +ManaCost:2 G G +Types:Instant +A:SP$ PutCounter | Cost$ 2 G G | ValidTgts$ Creature | TgtPrompt$ Select target creature to distribute counters to | CounterType$ P1P1 | CounterNum$ 3 | TargetMin$ 1 | TargetMax$ 3 | DividedAsYouChoose$ 3 | SpellDescription$ Distribute three +1/+1 counters among one, two, or three target creatures. +DeckHas:Ability$Counters +Oracle:Distribute three +1/+1 counters among one, two or three target creatures you control. diff --git a/forge-gui/res/cardsfolder/upcoming/gavony_trapper.txt b/forge-gui/res/cardsfolder/upcoming/gavony_trapper.txt new file mode 100644 index 00000000000..d378259cc55 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/gavony_trapper.txt @@ -0,0 +1,6 @@ +Name:Gavony Trapper +ManaCost:W +Types:Creature Human Soldier +PT:0/2 +A:AB$ Tap | Cost$ 2 T | ValidTgts$ Creature | TgtPrompt$ Select target creature | SpellDescription$ Tap target creature. +Oracle:{2}{T}: Tap target creature. diff --git a/forge-gui/res/cardsfolder/upcoming/novice_occultist.txt b/forge-gui/res/cardsfolder/upcoming/novice_occultist.txt new file mode 100644 index 00000000000..d44477cb97e --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/novice_occultist.txt @@ -0,0 +1,8 @@ +Name:Novice Occultist +ManaCost:1 B +Types:Creature Human Wizard +PT:1/2 +T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Self | Execute$ TrigDraw | TriggerController$ TriggeredCardController | TriggerDescription$ When CARDNAME dies, you draw a card and you lose 1 life. +SVar:TrigDraw:DB$ Draw | Defined$ You | NumCards$ 1 | SubAbility$ DBLoseLife +SVar:DBLoseLife:DB$ LoseLife | LifeAmount$ 1 +Oracle:When Novice Occultist dies, you draw a card and you lose 1 life. diff --git a/forge-gui/res/cardsfolder/upcoming/raze_the_effigy.txt b/forge-gui/res/cardsfolder/upcoming/raze_the_effigy.txt new file mode 100644 index 00000000000..07999841d75 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/raze_the_effigy.txt @@ -0,0 +1,7 @@ +Name:Raze the Effigy +ManaCost:R +Types:Instant +A:SP$ Charm | Cost$ R | Choices$ DBDestroy,DBPump +SVar:DBDestroy:DB$ Destroy | ValidTgts$ Artifact | TgtPrompt$ Select target artifact | SpellDescription$ Destroy target artifact +SVar:DBPump:DB$ Pump | ValidTgts$ Creature.attacking | TgtPrompt$ Select target attacking creature | NumAtt$ +2 | NumDef$ +2 | SpellDescription$ Target attacking creature gets +2/+2 until end of turn. +Oracle:Choose one —\n•Destroy target artifact\n•Target attacking creature gets +2/+2 until end of turn. diff --git a/forge-gui/res/cardsfolder/upcoming/shadowbeast_sighting.txt b/forge-gui/res/cardsfolder/upcoming/shadowbeast_sighting.txt new file mode 100644 index 00000000000..ce2315aeeac --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/shadowbeast_sighting.txt @@ -0,0 +1,7 @@ +Name:Shadowbeast Sighting +ManaCost:3 G +Types:Sorcery +A:SP$ Token | Cost$ 3 G | TokenScript$ g_4_4_beast | TokenOwner$ You | SpellDescription$ Create a 4/4 green Beast creature token. +K:Flashback:6 G +DeckHas:Ability$Token & Ability$Graveyard +Oracle:Create a 4/4 green Beast creature token.\nFlashback {6}{G} From 2473ace8a10ece1d6a5aa5e6510d28307940417d Mon Sep 17 00:00:00 2001 From: Northmoc Date: Mon, 13 Sep 2021 10:42:22 -0400 Subject: [PATCH 55/58] add AI hints --- .../upcoming/baithook_angler_hook_haunt_drifter.txt | 1 + .../res/cardsfolder/upcoming/beloved_beggar_generous_soul.txt | 1 + .../cardsfolder/upcoming/covert_cutpurse_covetous_geist.txt | 1 + .../upcoming/covetous_castaway_ghostly_castigator.txt | 2 +- forge-gui/res/cardsfolder/upcoming/croaking_counterpart.txt | 1 + .../upcoming/devoted_grafkeeper_departed_soulkeeper.txt | 3 +-- 6 files changed, 6 insertions(+), 3 deletions(-) diff --git a/forge-gui/res/cardsfolder/upcoming/baithook_angler_hook_haunt_drifter.txt b/forge-gui/res/cardsfolder/upcoming/baithook_angler_hook_haunt_drifter.txt index ee761c99313..a850c9831cb 100644 --- a/forge-gui/res/cardsfolder/upcoming/baithook_angler_hook_haunt_drifter.txt +++ b/forge-gui/res/cardsfolder/upcoming/baithook_angler_hook_haunt_drifter.txt @@ -4,6 +4,7 @@ Types:Creature Human Peasant PT:2/1 K:Disturb:1 U AlternateMode:DoubleFaced +DeckHas:Ability$Graveyard Oracle:Disturb {1}{U} (You may cast this card from your graveyard transformed for its disturb cost.) ALTERNATE diff --git a/forge-gui/res/cardsfolder/upcoming/beloved_beggar_generous_soul.txt b/forge-gui/res/cardsfolder/upcoming/beloved_beggar_generous_soul.txt index 8119a4e8cab..b3eacb9cd1f 100644 --- a/forge-gui/res/cardsfolder/upcoming/beloved_beggar_generous_soul.txt +++ b/forge-gui/res/cardsfolder/upcoming/beloved_beggar_generous_soul.txt @@ -4,6 +4,7 @@ Types:Creature Human Peasant PT:0/4 K:Disturb:4 W W AlternateMode:DoubleFaced +DeckHas:Ability$Graveyard Oracle:Disturb {4}{W}{W} (You may cast this card from your graveyard transformed for its disturb cost.) ALTERNATE diff --git a/forge-gui/res/cardsfolder/upcoming/covert_cutpurse_covetous_geist.txt b/forge-gui/res/cardsfolder/upcoming/covert_cutpurse_covetous_geist.txt index f31187ce3bd..d32dcea287c 100644 --- a/forge-gui/res/cardsfolder/upcoming/covert_cutpurse_covetous_geist.txt +++ b/forge-gui/res/cardsfolder/upcoming/covert_cutpurse_covetous_geist.txt @@ -6,6 +6,7 @@ T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.S SVar:TrigDestroy:DB$ Destroy | ValidTgts$ Creature.YouDontCtrl+wasDealtDamageThisTurn | TgtPrompt$ Select target creature you don't control that was dealt damage this turn K:Disturb:4 B AlternateMode:DoubleFaced +DeckHas:Ability$Graveyard Oracle:When Covert Cutpurse enters the battlefield, destroy target creature you don't control that was dealt damage this turn.\nDisturb {4}{B} (You may cast this card from your graveyard transformed for its disturb cost.) ALTERNATE diff --git a/forge-gui/res/cardsfolder/upcoming/covetous_castaway_ghostly_castigator.txt b/forge-gui/res/cardsfolder/upcoming/covetous_castaway_ghostly_castigator.txt index 453a787a0e5..3b564673845 100644 --- a/forge-gui/res/cardsfolder/upcoming/covetous_castaway_ghostly_castigator.txt +++ b/forge-gui/res/cardsfolder/upcoming/covetous_castaway_ghostly_castigator.txt @@ -6,7 +6,7 @@ K:Disturb:3 U U T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Self | Execute$ TrigMill | TriggerDescription$ When CARDNAME dies, mill three cards. SVar:TrigMill:DB$ Mill | NumCards$ 3 | Defined$ You AlternateMode:DoubleFaced -DeckHas:Ability$Mill +DeckHas:Ability$Mill & Ability$Graveyard Oracle:When Covetous Castaway dies, mill three cards.\nDisturb {3}{U}{U} (You may cast this card from your graveyard transformed for its disturb cost.) ALTERNATE diff --git a/forge-gui/res/cardsfolder/upcoming/croaking_counterpart.txt b/forge-gui/res/cardsfolder/upcoming/croaking_counterpart.txt index 15e8f6745c4..f67d2abc1a0 100644 --- a/forge-gui/res/cardsfolder/upcoming/croaking_counterpart.txt +++ b/forge-gui/res/cardsfolder/upcoming/croaking_counterpart.txt @@ -3,4 +3,5 @@ ManaCost:1 G U Types:Sorcery A:SP$ CopyPermanent | ValidTgts$ Creature.nonFrog | TgtPrompt$ Select target non-Frog creature | SetPower$ 1 | SetToughness$ 1 | SetColor$ Green | SetCreatureTypes$ Frog | StackDescription$ Create a token that's a copy of {c:Targeted}, except it's a 1/1 green Frog. | SpellDescription$ Create a token that's a copy of target non-Frog creature, except it's a 1/1 green Frog. K:Flashback:3 G U +DeckHas:Ability$Token & Ability$Graveyard Oracle:Create a token that's a copy of target non-Frog creature, except it's a 1/1 green Frog.\nFlashback {3}{G}{U} (You may cast this card from your graveyard for its flashback cost. Then exile it.) diff --git a/forge-gui/res/cardsfolder/upcoming/devoted_grafkeeper_departed_soulkeeper.txt b/forge-gui/res/cardsfolder/upcoming/devoted_grafkeeper_departed_soulkeeper.txt index 5617e0f3a51..c0f6d71278b 100644 --- a/forge-gui/res/cardsfolder/upcoming/devoted_grafkeeper_departed_soulkeeper.txt +++ b/forge-gui/res/cardsfolder/upcoming/devoted_grafkeeper_departed_soulkeeper.txt @@ -6,8 +6,7 @@ T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.S SVar:TrigMill:DB$ Mill | NumCards$ 2 | Defined$ You T:Mode$ SpellCast | ValidCard$ Card.wasCastFromGraveyard | ValidActivatingPlayer$ You | Execute$ TrigTap | TriggerZones$ Battlefield | TriggerDescription$ Whenever you cast a spell from your graveyard, tap target creature you don't control. SVar:TrigTap:DB$ Tap | ValidTgts$ Creature.YouDontCtrl | TgtPrompt$ Select target creature you don't control -DeckHas:Ability$Mill -DeckHints:Ability$Graveyard +DeckHas:Ability$Mill & Ability$Graveyard K:Disturb:1 W U AlternateMode:DoubleFaced Oracle:When Devoted Grafkeeper enters the battlefield, mill two cards.\nWhenever you cast a spell from your graveyard, tap target creature you don't control.\nDisturb {1}{W}{U} (You may cast this card from your graveyard transformed for its disturb cost.) From 928a1df4e707b65caf8ff5ada6d70a155d731c9b Mon Sep 17 00:00:00 2001 From: Northmoc Date: Mon, 13 Sep 2021 10:42:50 -0400 Subject: [PATCH 56/58] fix typo --- forge-gui/res/cardsfolder/upcoming/patrician_geist.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/forge-gui/res/cardsfolder/upcoming/patrician_geist.txt b/forge-gui/res/cardsfolder/upcoming/patrician_geist.txt index 66a3c27b9b1..520263ea26e 100644 --- a/forge-gui/res/cardsfolder/upcoming/patrician_geist.txt +++ b/forge-gui/res/cardsfolder/upcoming/patrician_geist.txt @@ -5,5 +5,5 @@ PT:2/2 K:Flying S:Mode$ Continuous | Affected$ Spirit.Other+YouCtrl | AddPower$ 1 | AddToughness$ 1 | Description$ Other Spirits you control get +1/+1. S:Mode$ ReduceCost | ValidCard$ Card.wasCastFromGraveyard | Type$ Spell | Activator$ You | Amount$ 1 | Description$ Spells you cast from your graveyard cost {1} less to cast. -DeckHints:Ability$Graveyard & Type$ Spirit +DeckHints:Ability$Graveyard & Type$Spirit Oracle:Flying\nOther Spirits you control get +1/+1.\nSpells you cast from your graveyard cost {1} less to cast. From ac350a0bfb8e56c6325837af0783c283dbd6c094 Mon Sep 17 00:00:00 2001 From: Anthony Calosa Date: Mon, 13 Sep 2021 23:01:13 +0800 Subject: [PATCH 57/58] [Mobile] Fix card image not loading --- forge-core/src/main/java/forge/ImageKeys.java | 3 +++ forge-gui-mobile/src/forge/assets/ImageCache.java | 14 +++++--------- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/forge-core/src/main/java/forge/ImageKeys.java b/forge-core/src/main/java/forge/ImageKeys.java index 9112855b0eb..bee1a0743ec 100644 --- a/forge-core/src/main/java/forge/ImageKeys.java +++ b/forge-core/src/main/java/forge/ImageKeys.java @@ -66,6 +66,9 @@ public final class ImageKeys { } private static final Map cachedCards = new HashMap<>(50000); + public static File getCachedCardsFile(String key) { + return cachedCards.get(key); + } public static File getImageFile(String key) { if (StringUtils.isEmpty(key)) return null; diff --git a/forge-gui-mobile/src/forge/assets/ImageCache.java b/forge-gui-mobile/src/forge/assets/ImageCache.java index 053c6d79da2..c69c523b409 100644 --- a/forge-gui-mobile/src/forge/assets/ImageCache.java +++ b/forge-gui-mobile/src/forge/assets/ImageCache.java @@ -50,7 +50,6 @@ import forge.localinstance.properties.ForgeConstants; import forge.localinstance.properties.ForgePreferences; import forge.model.FModel; import forge.util.ImageUtil; -import forge.util.TextUtil; /** * This class stores ALL card images in a cache with soft values. this means @@ -171,10 +170,7 @@ public class ImageCache { } else { final boolean backFace = imageKey.endsWith(ImageKeys.BACKFACE_POSTFIX); final String cardfilename = backFace ? paperCard.getCardAltImageKey() : paperCard.getCardImageKey(); - if (!new File(ForgeConstants.CACHE_CARD_PICS_DIR + "/" + cardfilename + ".jpg").exists()) - if (!new File(ForgeConstants.CACHE_CARD_PICS_DIR + "/" + cardfilename + ".png").exists()) - if (!new File(ForgeConstants.CACHE_CARD_PICS_DIR + "/" + TextUtil.fastReplace(cardfilename,".full", ".fullborder") + ".jpg").exists()) - return false; + return ImageKeys.getCachedCardsFile(cardfilename) != null; } } else if (prefix.equals(ImageKeys.TOKEN_PREFIX)) { final String tokenfilename = imageKey.substring(2) + ".jpg"; @@ -242,10 +238,10 @@ public class ImageCache { // a default "not available" image and add to cache for given key. if (image == null) { if (useDefaultIfNotFound) { - image = useOtherCache ? defaultImage : null; - if (useOtherCache) - otherCache.put(imageKey, defaultImage); - + image = defaultImage; + /*fix not loading image file since we intentionally not to update the cache in order for the + image fetcher to update automatically after the card image/s are downloaded*/ + imageLoaded = false; if (image != null && imageBorder.get(image.toString()) == null) imageBorder.put(image.toString(), Pair.of(Color.valueOf("#171717").toString(), false)); //black border } From 4f1219b30594636d76322c6cb2222482dfa2e9c3 Mon Sep 17 00:00:00 2001 From: paul_snoops Date: Mon, 13 Sep 2021 18:32:49 +0100 Subject: [PATCH 58/58] Commander: Golas banned; Worldfire unbanned --- forge-gui/res/formats/Casual/Commander.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/forge-gui/res/formats/Casual/Commander.txt b/forge-gui/res/formats/Casual/Commander.txt index 35011925e6b..f98af3000b7 100644 --- a/forge-gui/res/formats/Casual/Commander.txt +++ b/forge-gui/res/formats/Casual/Commander.txt @@ -3,4 +3,4 @@ Name:Commander Type:Casual Subtype:Commander Order:137 -Banned:Adriana's Valor; Advantageous Proclamation; Ashnod's Coupon; Assemble the Rank and Vile; Backup Plan; Brago's Favor; Double Cross; Double Deal; Double Dip; Double Play; Double Stroke; Double Take; Echoing Boon; Emissary's Ploy; Enter the Dungeon; Flash; Hired Heist; Hold the Perimeter; Hullbreacher; Hymn of the Wilds; Immediate Action; Incendiary Dissent; Iterative Analysis; Lutri, the Spellchaser; Magical Hacker; Mox Lotus; Muzzio's Preparations; Natural Unity; Once More with Feeling; Power Play; R&D's Secret Lair; Richard Garfield, Ph.D.; Secret Summoning; Secrets of Paradise; Sentinel Dispatch; Sovereign's Realm; Staying Power; Summoner's Bond; Time Machine; Unexpected Potential; Weight Advantage; Worldknit; Amulet of Quoz; Bronze Tablet; Contract from Below; Darkpact; Demonic Attorney; Jeweled Bird; Rebirth; Tempest Efreet; Timmerian Fiends; Ancestral Recall; Balance; Biorhythm; Black Lotus; Braids, Cabal Minion; Chaos Orb; Coalition Victory; Channel; Emrakul, the Aeons Torn; Erayo, Soratami Ascendant; Falling Star; Fastbond; Gifts Ungiven; Griselbrand; Iona, Shield of Emeria; Karakas; Leovold, Emissary of Trest; Library of Alexandria; Limited Resources; Mox Emerald; Mox Jet; Mox Pearl; Mox Ruby; Mox Sapphire; Panoptic Mirror; Paradox Engine; Primeval Titan; Prophet of Kruphix; Recurring Nightmare; Rofellos, Llanowar Emissary; Shahrazad; Sundering Titan; Sway of the Stars; Sylvan Primordial; Time Vault; Time Walk; Tinker; Tolarian Academy; Trade Secrets; Upheaval; Worldfire; Yawgmoth's Bargain; Cleanse; Crusade; Imprison; Invoke Prejudice; Jihad; Pradesh Gypsies; Stone-Throwing Devils +Banned:Adriana's Valor; Advantageous Proclamation; Ashnod's Coupon; Assemble the Rank and Vile; Backup Plan; Brago's Favor; Double Cross; Double Deal; Double Dip; Double Play; Double Stroke; Double Take; Echoing Boon; Emissary's Ploy; Enter the Dungeon; Flash; Hired Heist; Hold the Perimeter; Hullbreacher; Hymn of the Wilds; Immediate Action; Incendiary Dissent; Iterative Analysis; Lutri, the Spellchaser; Magical Hacker; Mox Lotus; Muzzio's Preparations; Natural Unity; Once More with Feeling; Power Play; R&D's Secret Lair; Richard Garfield, Ph.D.; Secret Summoning; Secrets of Paradise; Sentinel Dispatch; Sovereign's Realm; Staying Power; Summoner's Bond; Time Machine; Unexpected Potential; Weight Advantage; Worldknit; Amulet of Quoz; Bronze Tablet; Contract from Below; Darkpact; Demonic Attorney; Jeweled Bird; Rebirth; Tempest Efreet; Timmerian Fiends; Ancestral Recall; Balance; Biorhythm; Black Lotus; Braids, Cabal Minion; Chaos Orb; Coalition Victory; Channel; Emrakul, the Aeons Torn; Erayo, Soratami Ascendant; Falling Star; Fastbond; Gifts Ungiven; Griselbrand; Iona, Shield of Emeria; Karakas; Leovold, Emissary of Trest; Library of Alexandria; Limited Resources; Mox Emerald; Mox Jet; Mox Pearl; Mox Ruby; Mox Sapphire; Panoptic Mirror; Paradox Engine; Primeval Titan; Prophet of Kruphix; Recurring Nightmare; Rofellos, Llanowar Emissary; Shahrazad; Sundering Titan; Sway of the Stars; Sylvan Primordial; Time Vault; Time Walk; Tinker; Tolarian Academy; Trade Secrets; Upheaval; Yawgmoth's Bargain; Cleanse; Crusade; Imprison; Invoke Prejudice; Jihad; Pradesh Gypsies; Stone-Throwing Devils; Golos, Tireless Pilgrim