Compare commits

...

1052 Commits

Author SHA1 Message Date
Michael Kamensky
6c1fe344e1 [maven-release-plugin] prepare release forge-1.6.51 2022-06-11 12:48:23 +03:00
Agetian
3161033675 Merge pull request #655 from Agetian/master
Added puzzles PS_SNC1 and PS_SNC2
2022-06-11 12:43:03 +03:00
Michael Kamensky
ede000f264 - Different (working) implementation for PS_SNC2. 2022-06-11 12:40:17 +03:00
ZacharyDeganutti
4e99f80f56 Implement Jan Jansen, Chaos Crafter (#651)
* Implement Jan Jansen, Chaos Crafter
2022-06-11 08:56:40 +00:00
Michael Kamensky
5c807f1ee7 - Slight tweak to PS_SNC2. 2022-06-11 09:15:09 +03:00
Agetian
25f60fb237 Merge branch 'Card-Forge:master' into master 2022-06-11 09:07:16 +03:00
Michael Kamensky
8cc48fdfd7 - Added puzzles PS_SNC1 and PS_SNC2. 2022-06-11 09:06:22 +03:00
Agetian
42da203998 Merge pull request #653 from Agetian/master
Ignore changes to Maven local settings when preparing a release
2022-06-11 08:29:10 +03:00
Michael Kamensky
74249924da - Ignore changes to Maven local settings when preparing a release 2022-06-11 08:28:25 +03:00
Agetian
3c0d8660c7 Merge pull request #646 from Northmoc/clbSarevok
CLB: Sarevok, Deathbringer and support
2022-06-11 08:22:59 +03:00
Anthony Calosa
1d3a277485 Merge pull request #650 from Northmoc/clb10
CLB: 10 June
2022-06-11 09:01:56 +08:00
Northmoc
35acb80ee9 feywild_visitor.txt 2022-06-10 20:30:03 -04:00
Northmoc
8ddc803852 renari_merchant_of_marvels.txt 2022-06-10 20:22:03 -04:00
Northmoc
a851fc850c breath_of_the_sleepless.txt fix typo 2022-06-10 20:20:52 -04:00
Northmoc
05fdaf2482 CountersPutAllEffect add Default to CounterNum 2022-06-10 20:20:35 -04:00
Northmoc
a11a3007a1 lulu_loyal_hollyphant.txt 2022-06-10 20:20:02 -04:00
Northmoc
1cf84f69cc scion_of_halaster.txt fix 2022-06-10 20:16:21 -04:00
Northmoc
d6e6f86dcc fang_dragon_forktail_sweep.txt 2022-06-10 20:16:13 -04:00
Chris H
8f0dc73931 Merge pull request #649 from paulsnoops/a-fates_reversal_fix
A-Fates' Reversal cost fix
2022-06-10 17:45:06 -04:00
paul_snoops
0108dc9e1d A-Fates' Reversal cost fix 2022-06-10 22:04:51 +01:00
Paul Hammerton
c128be1816 Merge pull request #647 from FLAREdirector-mse/master
Create arming_gala.txt
2022-06-10 20:01:07 +01:00
FLAREdirector-mse
75a9795a4d Create arming_gala.txt
Arming Gala
I don't know if the SVar:Update and DBUpdate lines are necessary, but Freyalise has them so I figured, hey, can't hurt.
2022-06-10 11:55:32 -05:00
Northmoc
d3ebc9497f CardTraitBase.meetsCommonRequirements support Revolt$ None 2022-06-10 12:18:10 -04:00
Northmoc
637234870c sarevok_deathbringer.txt 2022-06-10 12:17:41 -04:00
Hans Mackowiak
8e8b3eece8 Merge pull request #645 from Northmoc/backgroundFix
CLB: better Background check for commander decks
2022-06-10 16:25:53 +02:00
Northmoc
4e636857c2 better Background check for decks 2022-06-10 09:03:54 -04:00
Paul Hammerton
9f581d0bc1 Merge pull request #642 from paulsnoops/remaining_clb_edition_cards
CLB edition - remaining cards
2022-06-10 09:07:39 +01:00
paul_snoops
fd39200fa6 CLB edition - remaining cards 2022-06-10 09:04:41 +01:00
Anthony Calosa
8c558a28d1 Merge pull request #637 from kevlahnota/master
catch exception
2022-06-10 08:14:22 +08:00
Anthony Calosa
77e1621932 catch exception 2022-06-10 08:09:08 +08:00
Paul Hammerton
712724c29a Merge pull request #636 from paulsnoops/small_fixes
Remove unneeded from Rasaad and fix PPRO edition
2022-06-09 19:30:40 +01:00
paul_snoops
61a483f53f CN_fix 2022-06-09 19:27:51 +01:00
paul_snoops
6d2334e94b Remove unneeded from Rasaad and fix PPRO edition 2022-06-09 19:13:44 +01:00
Paul Hammerton
447fc7b8f3 Merge pull request #502 from Northmoc/clbInitiativeUndercity
CLB: Initiative and Undercity
2022-06-09 13:34:15 +01:00
Paul Hammerton
ebd9969228 Merge pull request #538 from Northmoc/clbInitiative2
CLB: remaining initiative cards (until they add more anyway)
2022-06-09 13:09:53 +01:00
Paul Hammerton
27480926b7 Merge pull request #633 from Card-Forge/a-goldspan_dragon_edit
Update A-Goldspan Dragon
2022-06-09 12:07:08 +01:00
Paul Hammerton
1816bfaed7 Merge pull request #634 from JohnWilliams77/patch-12
Update Media Inserts.txt
2022-06-09 11:16:34 +01:00
JohnWilliams77
17fa6f550f Update Pro Tour Promos.txt 2022-06-09 11:07:34 +01:00
JohnWilliams77
af00bc605e Update Pro Tour Promos.txt 2022-06-09 10:00:55 +01:00
JohnWilliams77
a7a9985629 Update Media Inserts.txt 2022-06-09 09:59:39 +01:00
Paul Hammerton
0243c02537 Update a-goldspan_dragon.txt 2022-06-09 08:24:15 +01:00
Agetian
c9657558dd Merge pull request #632 from Northmoc/clb7
CLB: 5 more "Choose a Background" legends
2022-06-09 07:10:17 +03:00
Northmoc
d04ef252ab karlach_fury_of_avernus.txt 2022-06-08 21:17:46 -04:00
Northmoc
8053e16663 gut_true_soul_zealot.txt 2022-06-08 21:17:39 -04:00
Northmoc
bd78906c6e anje_maid_of_dishonor.txt remove unneeded ';' 2022-06-08 21:14:38 -04:00
Northmoc
9e7a202e5c goldspan_dragon.txt fix cost parsing and add AI hints 2022-06-08 21:14:06 -04:00
Northmoc
2ddddd6e97 citanul_hierophants.txt style fix 2022-06-08 21:13:45 -04:00
Northmoc
2e312cf27a b_4_1_skeleton_menace.txt 2022-06-08 21:13:34 -04:00
Northmoc
ebc2708af6 TypeLists.txt add Gith to CreatureTypes 2022-06-08 21:13:21 -04:00
Northmoc
3c2216481e laezel_vlaakiths_champion.txt 2022-06-08 21:13:06 -04:00
Northmoc
db6b524c5a Merge pull request #615 from paulsnoops/edition_updates
CLB, SLD edition updates and new YSNC edition
2022-06-08 21:04:04 -04:00
Northmoc
e6240a0ea8 jaheira_friend_of_the_forest.txt 2022-06-08 20:54:39 -04:00
Northmoc
da4e2bd03b undermountain_adventurer.txt remove unneeded 2022-06-08 19:20:34 -04:00
Northmoc
8134908a81 Merge pull request #305 from AvacynAngel/master
Create cliffgate.txt
2022-06-08 19:08:48 -04:00
Northmoc
ecaaf3a951 Player.createInitiativeEffect correct ValidSource 2022-06-08 19:06:55 -04:00
Northmoc
23a2fc2fd4 Player.createInitiativeEffect - use new TriggerDamageDoneOnceByController 2022-06-08 19:06:54 -04:00
Northmoc
55e58f29e8 RestartGameEffect.resolve add setHasInitiative(null) 2022-06-08 19:06:53 -04:00
Northmoc
b689a960b4 passageway_seer.txt better check 2022-06-08 19:06:53 -04:00
Northmoc
dd220ec270 GameAction.takeInitiative fix indent 2022-06-08 19:06:52 -04:00
Northmoc
4771088676 undercellar_sweep.txt 2022-06-08 19:06:52 -04:00
Northmoc
096d0e6a99 AbilityUtils.calculateAmount "PlayerCountDefined" support 2022-06-08 19:06:51 -04:00
Northmoc
ac4e03082f Player.createInitiativeEffect DamageDoneOnce not DamageDone 2022-06-08 19:06:51 -04:00
Northmoc
b04461ac10 TriggerDamageDoneOnce.setTriggeringObjects AttackingPlayer 2022-06-08 19:06:50 -04:00
Northmoc
dce11aa70d PlayerProperty.playerHasProperty "hasInitiative" 2022-06-08 19:06:50 -04:00
Northmoc
9f64006739 passageway_seer.txt 2022-06-08 19:06:49 -04:00
Northmoc
dfbbf49d2e AbilityUtils > xCount add "Initiative" 2022-06-08 19:06:49 -04:00
Northmoc
0e7d730b1f dungeoneers_pack.txt 2022-06-08 19:06:49 -04:00
Northmoc
c6d779ff08 rilsa_rael_kingpin.txt 2022-06-08 19:06:48 -04:00
Northmoc
fe25c7d2c4 BecomeMonarchEffect improve getStackDescription 2022-06-08 19:06:47 -04:00
Northmoc
ffc24faa5a TakeInitiativeEffect improve stackDesc and Resolve 2022-06-08 19:06:47 -04:00
Northmoc
f34f7810ce undercity.txt fix/improve Throne 2022-06-08 19:06:46 -04:00
Northmoc
b19806eea2 make Initiative trigger able to work right away 2022-06-08 19:06:46 -04:00
Northmoc
e527c2f9a6 TakeInitiativeEffect remove unneeded imports 2022-06-08 19:06:46 -04:00
Northmoc
ae345b6468 aarakocra_sneak.txt 2022-06-08 19:06:45 -04:00
Northmoc
929de7181c ApiType add TakeInitiative 2022-06-08 19:06:45 -04:00
Northmoc
d4eaba43f6 Undercity Catacombs skeleton token 2022-06-08 19:06:44 -04:00
Northmoc
180a90e0a1 CardRules isEnterableDungeon boolean 2022-06-08 19:06:44 -04:00
Northmoc
faea325473 DigEffect WithCounterNum for Throne of the Dead Three (Undercity) 2022-06-08 19:06:43 -04:00
Northmoc
2fa38e2ecf Game track who has initiative, handle that player losing 2022-06-08 19:06:43 -04:00
Northmoc
0d030976bc GameAction takeInitiative 2022-06-08 19:06:42 -04:00
Northmoc
b66d73162a Player - create, has, remove for InitiativeEffect 2022-06-08 19:06:42 -04:00
Northmoc
ca9f20281a SpellApiToAi - add TakeInitiative 2022-06-08 19:06:41 -04:00
Northmoc
dd5a2f16b4 TakeInitiativeEffect.java 2022-06-08 19:06:41 -04:00
Northmoc
38ea1fad46 TriggerTakesInitiative.java 2022-06-08 19:06:40 -04:00
Northmoc
684cb4115d TriggerType add TakesInitiative 2022-06-08 19:06:40 -04:00
Northmoc
b996ce4720 undercity.txt fix Arena RoomName 2022-06-08 19:06:39 -04:00
Northmoc
e3dba72bf2 VentureEffect: support "Dungeon" + check for Enterable boolean 2022-06-08 19:06:39 -04:00
Northmoc
8608a0e438 undercity.txt 2022-06-08 19:06:38 -04:00
Northmoc
5fdd62358d Merge pull request #628 from paulsnoops/gold_token_fix
Rename c_a_gold_draw > c_a_gold_sac
2022-06-08 19:03:07 -04:00
paul_snoops
cdbbb3eb4b unfinity corrected release date 2022-06-08 20:18:53 +01:00
paul_snoops
3a5ec9e846 Rename c_a_gold_draw > c_a_gold_sac 2022-06-08 20:04:25 +01:00
Paul Hammerton
a4591b5254 Update Commander Legends Battle for Baldur's Gate.txt 2022-06-08 10:37:20 +01:00
Paul Hammerton
1c7a3b92ab Update Secret Lair Drop Series.txt 2022-06-08 09:52:34 +01:00
Paul Hammerton
2735f598ff Update Alchemy New Capenna.txt 2022-06-08 09:50:08 +01:00
Anthony Calosa
02ae6282ba Merge pull request #624 from churrufli/master
Net Deck Archive Updates
2022-06-08 07:22:14 +08:00
Churrufli
e585239866 Net Deck Archive Updates 2022-06-07 23:38:12 +02:00
Paul Hammerton
886fcf3d2f Merge pull request #623 from paulsnoops/bans76222
Format updates: Added CLB, YSNC and Pioneer Bans 7 June 2022
2022-06-07 19:23:16 +01:00
paul_snoops
dbcb8b9a71 Format updates: Added CLB, YSNC and Pioneer Bans 7 June 2022 2022-06-07 19:18:26 +01:00
Northmoc
a5e65c9563 gut_true_soul_zealot.txt 2022-06-07 13:57:53 -04:00
Northmoc
9f21108218 ganax_astral_hunter.txt 2022-06-07 13:57:46 -04:00
Agetian
4c98a1a5fa Merge pull request #621 from Card-Forge/dungeonFixes
CardType: add Dungeon SubTypes
2022-06-05 20:59:21 +03:00
Hans Mackowiak
c893a01bb8 CardType: add Dungeon SubTypes 2022-06-05 19:51:46 +02:00
Anthony Calosa
6207db6bd8 Merge pull request #619 from kevlahnota/master
Fix Continue button
2022-06-05 00:35:11 +08:00
Anthony Calosa
dff30a09c6 cleanup 2022-06-05 00:29:01 +08:00
Anthony Calosa
62c9c0e9da Merge branch 'master' into newmaster 2022-06-05 00:25:06 +08:00
Anthony Calosa
5c96a624f2 Fix Continue button
-refactor some collection for FSkinFont & ImageCache
2022-06-05 00:21:58 +08:00
Agetian
1e814caec4 Merge pull request #618 from Card-Forge/triggerDamageByController
TriggerDamageDoneOnceByController
2022-06-04 17:22:27 +03:00
Hans Mackowiak
f556d5a24d TriggerDamageDoneOnceByController 2022-06-04 16:13:39 +02:00
Agetian
fcbd98d3e6 Merge pull request #617 from tool4ever/thefallen
Fix The Fallen ignoring planeswalkers
2022-06-04 15:43:54 +03:00
tool4EvEr
2b3522205d Fix counting when type didn't match 2022-06-04 14:13:28 +02:00
tool4EvEr
185598ab9c Fix NPE 2022-06-04 14:12:45 +02:00
tool4EvEr
aa40b9c64a Card fix 2022-06-04 14:12:15 +02:00
tool4EvEr
0750ae52bb Fix The Fallen ignoring planeswalkers 2022-06-04 14:11:42 +02:00
Agetian
9d0b1128e0 Merge pull request #561 from Northmoc/clbRasaad
CLB: Rasaad yn Bashir and support ("combat damage from toughness" non-keyword keyword -> static)
2022-06-03 23:06:06 +03:00
Anthony Calosa
b54b6e83c2 Merge pull request #616 from Jakob002/evangelyne_fix
Fixed quest deck 'Evangelyne (hard)'
2022-06-03 14:52:56 +08:00
Jakob002
5ee8f949e8 Fixed quest deck 'Evangelyne (hard)' 2022-06-03 06:19:12 +02:00
Agetian
978e6b4568 Merge pull request #614 from tool4ever/tokencreated
Fix TokenCreated counting
2022-06-03 06:24:06 +03:00
paul_snoops
1a20722fdb WIP: CLB, SLD edition updates and new YSNC edition 2022-06-02 09:43:33 +01:00
tool4EvEr
636ad404c7 Fix TokenCreated counting 2022-06-02 09:29:41 +02:00
Anthony Calosa
06f8944d2b Merge pull request #612 from kevlahnota/master
update translation
2022-06-01 06:01:28 +08:00
Anthony Calosa
4645e32ab7 update translation 2022-06-01 05:58:52 +08:00
Anthony Calosa
86be83ff44 Merge pull request #584 from Northmoc/clbBackgroundMostLife
CLB: support for "if no opponent has more life than that player" (Background)
2022-06-01 05:49:29 +08:00
Anthony Calosa
c4b8d52abc Merge pull request #607 from Northmoc/clbEllyn
CLB: Ellyn Harbreeze, Busybody and DigEffect stackDesc update
2022-06-01 05:48:11 +08:00
Anthony Calosa
da9978a063 Merge pull request #603 from henges/add_continue_button
[Adventure] Add continue button to main menu
2022-06-01 05:47:17 +08:00
Agetian
2655b4a626 Merge pull request #611 from tool4ever/tovolar
Fix Tovolar, Dire Overlord counting Changelings twice
2022-05-31 22:02:35 +03:00
tool4EvEr
728191ca1e Fix Tovolar, Dire Overlord counting Changelings twice 2022-05-31 20:57:55 +02:00
Northmoc
f1516060c9 Merge pull request #610 from Northmoc/clb31CaB
CLB: Choose a Background cards (31 May)
2022-05-31 13:35:34 -04:00
Northmoc
6db9e0b7d1 Merge pull request #537 from Northmoc/clbBackground2
CLB: Background type cards (30 May)
2022-05-31 13:35:04 -04:00
Agetian
de063350d6 Merge pull request #604 from CCTV-1/master
update simplified chinese translation
2022-05-31 20:10:59 +03:00
Northmoc
3e7a84759e gale_waterdeep_prodigy.txt 2022-05-31 11:47:12 -04:00
Northmoc
8306da261c erinis_gloom_stalker.txt 2022-05-31 11:29:26 -04:00
Northmoc
18683d713a abdel_adrian_gorions_ward.txt 2022-05-31 11:29:11 -04:00
Northmoc
cb4e0be196 DigEffect.getStackDescription better X handling 2022-05-31 09:57:05 -04:00
Northmoc
118c79c572 ellyn_harbreeze_busybody.txt 2022-05-31 09:55:43 -04:00
Northmoc
91187af7f7 faceless_one.txt 2022-05-31 09:51:58 -04:00
Northmoc
03c9a12826 amber_gristle_omaul.txt 2022-05-31 09:48:17 -04:00
CCTV-1
3583bc1e11 translate new effect text. 2022-05-31 20:07:46 +08:00
Alexander Jones
6d83c439a8 Support portrait mode 2022-05-31 19:34:28 +08:00
Alexander Jones
fae435a29f Remove logging imports 2022-05-31 19:33:42 +08:00
Alexander Jones
139160c32d [Adv] Add a continue button to the main menu 2022-05-31 19:29:23 +08:00
Suthro
e16a80e822 CLB: Cadira, Caller of the Small + 4 cards (#586)
- Archivist of Oghma
- Ascend from Avernus
- Astarion's Thirst
- Baldur's Gate
- Cadira, Caller of the Small
- Token: 1/1 white Rabbit creature
2022-05-31 09:00:04 +02:00
Anthony Calosa
0a79fa4c20 Merge pull request #602 from kevlahnota/master
add Selector Mode option
2022-05-31 11:51:00 +08:00
Anthony Calosa
44d45cef91 add Selector Mode option
- Default enables the selector, Adventure or Classic will open their main screen at startup.
2022-05-31 11:46:13 +08:00
Anthony Calosa
566768bbd8 Merge pull request #601 from Northmoc/conniveFix
ConniveEffect.resolve avoid potential infinite loop
2022-05-31 09:10:19 +08:00
Northmoc
a97c18a16b ConniveEffect.resolve avoid potential infinite loop 2022-05-30 20:07:29 -04:00
Northmoc
4997ddc0e2 far_traveler.txt 2022-05-30 19:06:35 -04:00
Northmoc
e044abf20c hama_pashar_ruin_seeker.txt tidy and AI 2022-05-30 19:06:23 -04:00
Northmoc
296ddf63ed dungeon_delver.txt 2022-05-30 19:06:04 -04:00
Northmoc
c7dd68749d agent_of_the_shadow_thieves.txt revis 2022-05-30 17:50:58 -04:00
Northmoc
9724786d8d guild_artisan.txt 2022-05-30 17:50:15 -04:00
Northmoc
d9dae5be6e hardy_outlander.txt 2022-05-30 17:48:26 -04:00
Northmoc
bfa75b4042 sword_coast_sailor.txt 2022-05-30 17:43:01 -04:00
Northmoc
61763c9bb8 veteran_soldier.txt +AI flag 2022-05-30 17:42:46 -04:00
Northmoc
90d4eb0846 veteran_soldier.txt 2022-05-30 17:41:35 -04:00
Northmoc
23799411e6 Trigger.meetsRequirementsOnTriggeredObjects add "NoOpponentHasMoreLifeThanAttacked" 2022-05-30 17:41:26 -04:00
Northmoc
01b7aa1818 hardy_outlander.txt 2022-05-30 15:22:34 -04:00
Northmoc
d898ca7987 agent_of_the_shadow_thieves.txt 2022-05-30 15:22:33 -04:00
Northmoc
8773ae34fb Merge pull request #585 from Northmoc/myrkul_fix
myrkul_lord_of_bones.txt - copy card as it was in graveyard
2022-05-30 15:06:24 -04:00
Agetian
928f5457d8 Merge pull request #587 from tool4ever/immediatetrig
Fix AI not playing or confirming bad ImmediateTrigger
2022-05-30 21:04:45 +03:00
tool4EvEr
5131d9ed96 Fix AI not playing or confirming bad ImmediateTrigger 2022-05-30 19:42:48 +02:00
Michael Kamensky
888d49a43f [maven-release-plugin] prepare for next development iteration 2022-05-30 20:40:26 +03:00
Michael Kamensky
b9897ecb00 [maven-release-plugin] prepare release forge-1.6.50 2022-05-30 20:40:16 +03:00
Northmoc
93ce8dd188 myrkul_lord_of_bones.txt - copy card as it was in graveyard 2022-05-30 13:02:00 -04:00
Northmoc
eaa84f07ea candlekeep_sage.txt 2022-05-30 11:36:32 -04:00
Northmoc
4db0f4742b acolyte_of_bahamut.txt 2022-05-30 11:36:31 -04:00
Northmoc
15bc4e4d1b agent_of_the_iron_throne.txt 2022-05-30 11:36:31 -04:00
Northmoc
2ba76439e1 scion_of_halaster.txt 2022-05-30 11:36:30 -04:00
Northmoc
51362e8290 CLB: Background mechanic and related cards (#532)
* cloakwood_hermit.txt

* halsin_emerald.archdruid.txt

* master_chef.txt

* Card > keywordsToText() for Choose a Background

* CardRules update for Background

* DeckFormat > get DeckConformanceProblem : update for Background

* TypeLists.txt add Background

* Keyword add Choose a Background

* shameless_charlatan.txt

* raised_by_giants.txt

* alora_merry_thief.txt

* livaan_cultist_of_tiamat.txt
2022-05-30 15:43:35 +02:00
Northmoc
f0414c1ef0 better check for "if you have the initiative" on triggers 2022-05-30 09:24:04 -04:00
Northmoc
1ca5609d1f rasaad_yn_bashir.txt better initiative check 2022-05-30 09:21:17 -04:00
Suthro
f9620ec816 CLB: Brainstealer Dragon, Giant Ankheg + 4 cards (#555)
- Amethyst Dragon // Explosive Crystal
- Ancient Copper Dragon
- Black Market Connections
- Brainstealer Dragon
- Giant Ankheg
- Zhentarim Bandit
2022-05-30 12:33:26 +02:00
Agetian
385fcbe4e8 Merge pull request #577 from tool4ever/manafix
Non-mana abilities incorrectly triggering/replaced
2022-05-30 12:54:52 +03:00
TRT
34e3bd2dc1 Non-mana abilities incorrectly triggering/replaced 2022-05-30 10:38:31 +02:00
Hans Mackowiak
58c738e414 Merge pull request #518 from tool4ever/triggerzones
Add some missing TriggerZones
2022-05-30 10:31:18 +02:00
Paul Hammerton
7e6496551c Merge pull request #576 from paulsnoops/clb_edition_update
CLB edition update
2022-05-30 08:31:18 +01:00
paul_snoops
f8e3032671 CLB edition update 2022-05-30 08:22:16 +01:00
Agetian
103bffa681 Merge pull request #575 from Agetian/master
Further update references to the git repo in pom.xml
2022-05-30 08:42:13 +03:00
Michael Kamensky
e3ba05b612 - Update references to the git repo in pom.xml 2022-05-30 08:41:28 +03:00
Anthony Calosa
f8ef3ef186 Merge pull request #574 from Northmoc/typo
pygmy_troll.txt typo
2022-05-30 09:08:17 +08:00
Anthony Calosa
06c742ca2d fix settings label 2022-05-30 07:39:34 +08:00
Northmoc
4787a4c6df old cards use CombatDamageToughness Mode 2022-05-29 17:56:50 -04:00
Northmoc
defc9f41f4 Card.java toughnessAssignsDamage() boolean refactor 2022-05-29 17:56:49 -04:00
Northmoc
35024b49ea GlobalRuleChange remove toughnessAssignsDamage 2022-05-29 17:56:49 -04:00
Northmoc
af22f0e04f StaticAbilityCombatDamageToughness.java 2022-05-29 17:56:49 -04:00
Northmoc
7e31ee6d25 rasaad_yn_bashir.txt 2022-05-29 17:56:48 -04:00
Northmoc
40e62f4665 vicious_battlerager.txt 2022-05-29 17:53:15 -04:00
Northmoc
b68e425d84 underdark_explorer.txt 2022-05-29 17:53:15 -04:00
Northmoc
4f9f755e05 stirring_bard.txt 2022-05-29 17:53:14 -04:00
Northmoc
4bd141d3c0 update AI hints on hit counter cards 2022-05-29 17:53:14 -04:00
Northmoc
0f4a0c58b8 imoen_mystic_trickster.txt 2022-05-29 17:53:13 -04:00
Northmoc
8349cb6df1 trailblazers_torch.txt 2022-05-29 17:53:13 -04:00
Northmoc
6c32809b1a ravenloft_adventurer.txt 2022-05-29 17:53:12 -04:00
Northmoc
029142fbd3 white_plume_adventurer.txt 2022-05-29 17:53:12 -04:00
Northmoc
39394f77fb feywild_caretaker.txt 2022-05-29 17:53:11 -04:00
Northmoc
e76626427d explore_the_underdark.txt 2022-05-29 17:53:11 -04:00
Northmoc
c4223280dd tomb_of_horrors_adventurer.txt 2022-05-29 17:53:10 -04:00
Northmoc
858b911d4b caves_of_chaos_adventurer.txt 2022-05-29 17:53:10 -04:00
Northmoc
da55f6acfe undermountain_adventurer.txt 2022-05-29 17:53:09 -04:00
Northmoc
5e1624a41a goliath_paladin.txt 2022-05-29 17:53:09 -04:00
Northmoc
0b5048cf60 safana_calimport.cutthroat.txt fixup TrigDesc 2022-05-29 17:53:08 -04:00
Northmoc
22aca4e448 bloodboil_sorceror.txt fixup 2022-05-29 17:53:08 -04:00
Northmoc
eba20e3c62 bloodboil_sorceror.txt 2022-05-29 17:53:07 -04:00
Northmoc
6a1ed1de8d safana_calimport.cutthroat.txt 2022-05-29 17:53:07 -04:00
Northmoc
6bee902800 avenging_hunter.txt 2022-05-29 17:53:06 -04:00
Northmoc
29c13ccfc6 pygmy_troll.txt typo 2022-05-29 17:49:14 -04:00
Anthony Calosa
e583ebf60e Merge pull request #513 from Northmoc/clbDynaheir
CLB: Dynaheir, Inovker Adept and support (kills another non-keyword keyword)
2022-05-30 00:43:37 +08:00
Agetian
d4aaa0751d Merge pull request #564 from tool4ever/aihints
AiHints upgrade
2022-05-29 18:40:24 +03:00
tool4EvEr
76f95bd0a9 AiHints upgrade 2022-05-29 17:35:14 +02:00
Anthony Calosa
149f9f083c Merge pull request #563 from tool4ever/timestampsscripts
Update more cards with timestamps
2022-05-29 19:11:50 +08:00
tool4EvEr
4c172ddb06 Update more cards with timestamps 2022-05-29 11:29:49 +02:00
tool4ever
dfc53cd315 wasAttackedThisCombat restriction for multiplayer (#467)
* wasAttackedThisCombat restriction for multiplayer
2022-05-29 07:56:38 +00:00
tool4ever
5f4e95e392 Update a few damage replacements from Pump to Effect (#466)
* Update a few damage replacements from Pump to Effect
2022-05-29 07:55:53 +00:00
Anthony Calosa
b6c2bf5630 Merge pull request #562 from kevlahnota/master
update ProtocolMethod & access options
2022-05-29 14:38:25 +08:00
Anthony Calosa
be997820a3 update ProtocolMethod & access options
- added access options for Java 11 and up
- fix Netplay Crash on assignGenericAmount
2022-05-29 14:31:15 +08:00
Chris H
e1ec1aa2fc Merge pull request #542 from Agetian/master
Update references to the git repo in pom.xml
2022-05-28 19:25:44 -04:00
Creston Davison
568f026440 Baba Lysaga, Night Witch and Oji, the Exquisite Blade (#497)
* Added Baba Lysaga

* Added Oji
2022-05-28 19:34:00 +00:00
Michael Kamensky
262dd1fb85 - Update references to the git repo in pom.xml 2022-05-28 20:58:27 +03:00
Agetian
216e7286ea Merge pull request #541 from Agetian/master
- Update ANNOUNCEMENTS.txt
2022-05-28 19:38:28 +03:00
Michael Kamensky
e43a1d1726 - Update ANNOUNCEMENTS.txt 2022-05-28 19:37:18 +03:00
Anthony Calosa
6bc2e035b3 Merge pull request #536 from Northmoc/tyvar
Tyvar update
2022-05-28 09:54:39 +08:00
Anthony Calosa
906c66d1a4 Merge pull request #520 from Northmoc/tweak
Housekeeping
2022-05-28 09:53:49 +08:00
Anthony Calosa
07d0e20bc1 Rename forge-gui/res/cardsfolder/upcoming/tymoras_invoker.txt to forge-gui/res/cardsfolder/t/tymoras_invoker.txt 2022-05-28 09:51:36 +08:00
Anthony Calosa
f4a8e1f8b1 Rename forge-gui/res/cardsfolder/upcoming/summon_undead.txt to forge-gui/res/cardsfolder/s/summon_undead.txt 2022-05-28 09:51:16 +08:00
Anthony Calosa
447cfdbdb1 Rename forge-gui/res/cardsfolder/upcoming/silvanuss_invoker.txt to forge-gui/res/cardsfolder/s/silvanuss_invoker.txt 2022-05-28 09:50:57 +08:00
Anthony Calosa
ec1dff4bab Rename forge-gui/res/cardsfolder/upcoming/eldritch_pact.txt to forge-gui/res/cardsfolder/e/eldritch_pact.txt 2022-05-28 09:50:32 +08:00
Anthony Calosa
61747fcd75 Rename forge-gui/res/cardsfolder/upcoming/citadel_gate.txt to forge-gui/res/cardsfolder/c/citadel_gate.txt 2022-05-28 09:50:11 +08:00
Anthony Calosa
186d71e452 Rename forge-gui/res/cardsfolder/upcoming/bhaals_invoker.txt to forge-gui/res/cardsfolder/b/bhaals_invoker.txt 2022-05-28 09:49:49 +08:00
Anthony Calosa
9e6125d649 Rename forge-gui/res/cardsfolder/upcoming/banes_invoker.txt to forge-gui/res/cardsfolder/b/banes_invoker.txt 2022-05-28 09:48:37 +08:00
Northmoc
e0464d8b87 Tyvar update 2022-05-27 16:15:04 -04:00
Northmoc
36ad56e6d7 dynaheir_invoker_adept.txt remove Duration + fix TrigDesc 2022-05-27 08:29:07 -04:00
Northmoc
9c4e44dc37 AI hints Type$ not Types$ 2022-05-27 08:21:06 -04:00
Northmoc
d733291b78 move CLB cards back to upcoming and add .txt to one 2022-05-27 08:19:09 -04:00
Agetian
ae9b1745c6 Merge pull request #524 from tool4ever/tappingai
Fix Tap trigger decisions
2022-05-27 13:09:38 +03:00
Agetian
7701ba4990 Merge pull request #525 from tool4ever/aicheat
Fix AI cheating when chaining dmg spells with DividedAsYouChoose
2022-05-27 13:08:45 +03:00
tool4EvEr
5e5ce90fa9 Fix AI cheating when chaining dmg spells with DividedAsYouChoose 2022-05-27 11:35:04 +02:00
tool4EvEr
496ab01599 Fix Tap trigger decisions 2022-05-27 10:26:43 +02:00
Chris H
9a14ec945a Merge pull request #523 from Card-Forge/migrate-capenna
Migrate cards for release
2022-05-26 21:35:07 -04:00
friarsol
07cacc406b Migrate cards for release 2022-05-26 21:30:24 -04:00
Anthony Calosa
39d74f5df2 Merge pull request #509 from Northmoc/clbNemesis
CLB: Nemesis Phoenix and support
2022-05-27 08:33:01 +08:00
Anthony Calosa
edbce270ae Merge pull request #519 from kevlahnota/master
update Adventure Chaos Mode
2022-05-27 08:32:09 +08:00
Anthony Calosa
63406fbca5 Merge branch 'master' into newmaster 2022-05-27 08:24:47 +08:00
Anthony Calosa
846e1e95bc update Adventure Chaos Mode
- Genetic AI Decks is available on some enemies on Hard Difficulty on Adventure Chaos Mode
2022-05-27 08:21:03 +08:00
Northmoc
e215eceafc Merge pull request #517 from tool4ever/controlfix
ControlGain: Fix NPE
2022-05-26 20:03:04 -04:00
Northmoc
36401b05ee "Affected" -> "ValidCard" and zone restriction 2022-05-26 19:55:36 -04:00
Paul Hammerton
a9715e4489 Merge pull request #514 from Northmoc/various
various random fixes
2022-05-26 20:45:18 +01:00
tool4EvEr
4c805f5e55 Add some missing TriggerZones 2022-05-26 21:16:28 +02:00
tool4EvEr
aee9a3a41d Fix NPE 2022-05-26 21:15:53 +02:00
Northmoc
830e1d0469 various random fixes 2022-05-26 14:02:23 -04:00
Northmoc
4a1ac0e90c thousand_year_elixir.txt change static to ActivateAbilityAsIfHaste 2022-05-26 14:01:35 -04:00
Northmoc
01a2dfe5fc StaticAbilityActivateAbilityAsIfHaste.java 2022-05-26 14:01:07 -04:00
Agetian
628993f86d Merge pull request #512 from Northmoc/crashFix
ControlGainEffect fix crash
2022-05-26 21:00:59 +03:00
Northmoc
742395d1a1 ForgeScript > spellAbilityHasProperty add "ManaSpent" 2022-05-26 14:00:04 -04:00
Northmoc
a1dcb3617c CostUntap change keyword check to static check 2022-05-26 13:59:19 -04:00
Northmoc
a10e906ddb CostTap change keyword check to static check 2022-05-26 13:58:59 -04:00
Northmoc
6be30ee2e4 ControlGainEffect fix crash 2022-05-26 13:56:31 -04:00
Northmoc
a87ca115f1 dynaheir_invoker_adept.txt 2022-05-26 13:53:09 -04:00
Northmoc
dbd9e7f61d Combat getAttackedOpponents streamline a bit more 2022-05-26 12:48:42 -04:00
Northmoc
942998585c Combat getAttackedOpponents streamline 2022-05-26 12:44:40 -04:00
Northmoc
167a774c2a remove rogue } 2022-05-26 12:17:09 -04:00
Northmoc
fea06524bd AbilityUtils > playerXProperty add "OpponentsAttackedThisCombat" 2022-05-26 11:50:03 -04:00
Northmoc
38b4fc2087 ChangeZoneEffect stackDescs reflect Attacking$ 2022-05-26 11:49:23 -04:00
Northmoc
40b140a580 Combat add getAttackedOpponents 2022-05-26 11:48:47 -04:00
Northmoc
f10537c1e7 nemesis_phoenix.txt 2022-05-26 11:42:29 -04:00
Northmoc
c3715fd4eb Merge pull request #490 from Northmoc/clb24
CLB: 24 May
2022-05-26 09:48:58 -04:00
Northmoc
b2fcf62d1c commander_liara_portyr.txt +DeckHints 2022-05-26 09:46:03 -04:00
Northmoc
edbc6553f4 raphael_fiendish_savior.txt: don't trigger for tokens 2022-05-26 07:18:39 -04:00
Northmoc
d71213f8f8 lozhan_dragons_legacy.txt +DeckHints 2022-05-26 07:16:57 -04:00
Northmoc
966b9a0121 laurine_the_diversion.txt missing | 2022-05-26 07:16:44 -04:00
Northmoc
f237acdeb8 gorion_wise_mentor.txt AI 2022-05-26 07:16:28 -04:00
Anthony Calosa
a95573fbb7 Merge pull request #506 from kevlahnota/master
update transition screen
2022-05-26 19:13:56 +08:00
Northmoc
2a4311e24b Merge pull request #496 from paulsnoops/clb_raggadragga
CLB: Raggadragga, Goreguts Boss and support
2022-05-26 07:05:44 -04:00
Anthony Calosa
3722fe98b1 update transition screen 2022-05-26 18:49:16 +08:00
Anthony Calosa
cbf0341a2e Merge pull request #504 from kevlahnota/master
Add delay to see attack animation on stage
2022-05-26 17:45:49 +08:00
Anthony Calosa
a264d4a6a0 Merge branch 'master' into newmaster 2022-05-26 13:14:20 +08:00
Anthony Calosa
644b1c051a add delay to see attack animation
- add short delay to see player and mob attack animation before the duel starts
2022-05-26 13:11:15 +08:00
Agetian
f5792e3755 Merge pull request #498 from tool4ever/tempcontroller
Fix removeTempController
2022-05-26 06:18:38 +03:00
Northmoc
757e4b2c92 Merge pull request #486 from Suthro/master
CBL: Astarion, the Decadent + 3 cards
2022-05-25 21:26:03 -04:00
Northmoc
8c5e549a66 Merge pull request #499 from t-w-o-s-a-t/master
Update de-DE.properties
2022-05-25 20:01:47 -04:00
t-w-o-s-a-t
f4db580f6a Update de-DE.properties
translated lblChooseConniver
2022-05-25 23:09:39 +02:00
tool4EvEr
3ade673e74 Fix removeTempController 2022-05-25 22:55:37 +02:00
paul_snoops
6a723b7d13 CLB: Raggadragga, Goreguts Boss and support 2022-05-25 15:43:50 +01:00
Anthony Calosa
6269a07632 Merge pull request #495 from kevlahnota/master
fix clearing screen
2022-05-25 22:08:48 +08:00
Anthony Calosa
e0a16981ff update check 2022-05-25 21:58:45 +08:00
Anthony Calosa
52e5ac04dc revert removed lines
-fix clearing match on adventure
2022-05-25 21:42:58 +08:00
Paul Hammerton
9925610cd8 Merge pull request #493 from paulsnoops/clb_edition_update
CLB edition update
2022-05-25 08:52:04 +01:00
paul_snoops
4bad103bd1 clb_edition_update 2022-05-25 08:46:30 +01:00
Northmoc
62773ab38d CLB: Zevlor, Elturel Exile and support (#462) 2022-05-25 09:42:53 +02:00
Anthony Calosa
f5747193d5 Merge pull request #492 from kevlahnota/master
update chaos battle dialogue
2022-05-25 12:40:01 +08:00
Anthony Calosa
d189212786 unused imports 2022-05-25 12:18:17 +08:00
Anthony Calosa
6f08b0a558 update chaos battle dialogue 2022-05-25 12:04:55 +08:00
Suthro
a436c474c8 Update wizards_of_thay.txt
- Removed redundant Wizard creature type deck hint
2022-05-24 22:08:25 -05:00
Suthro
9656a494ad Update gales_redirection.txt
- Added spell description strings to both effects
- Moved `number VERT` prefixes from static abilities to both effects
2022-05-24 22:07:32 -05:00
Suthro
39747d2be3 Update astarion_the_decadent.txt
- Added Feed/Friends prefix to spell description strings
- Removed redundant creature type deck hint line
2022-05-24 22:01:02 -05:00
Northmoc
f774c7d891 commander_liara_portyr.txt 2022-05-24 22:46:43 -04:00
Northmoc
73a8711108 mazzy_truesword_paladin.txt 2022-05-24 22:20:30 -04:00
Anthony Calosa
d7dff9b06c Merge pull request #377 from Northmoc/nccLethalScheme
NCC: Lethal Scheme and ConniveEffect improvements
2022-05-25 06:33:27 +08:00
Suthro
9e6accc441 Merge branch 'master' of https://github.com/Suthro/forge_scripttesting 2022-05-24 15:47:02 -05:00
Suthro
af331e0f07 CBL: Astarion, the Decadent + 3 cards
Add scripts for CBL cards:
- Astarion, the Decadent
- Gale's Redirection
- Legion Loyalty
- Wizards of Thay
2022-05-24 15:46:45 -05:00
Northmoc
9c9cbf5bd3 TriggeredNewCardLKICopy -> Remembered (#479)
* TriggeredNewCardLKICopy -> Remembered for ConditionDefined
2022-05-24 17:51:17 +02:00
Northmoc
8649ccdbf5 lozhan_dragons_legacy.txt 2022-05-24 11:23:40 -04:00
Northmoc
656643c772 raphael_fiendish_savior.txt 2022-05-24 10:57:14 -04:00
Northmoc
467c836476 ConniveEffect eliminate cast 2022-05-24 08:51:01 -04:00
Northmoc
c4f0d4454f ConniveEffect - better name and crash fix 2022-05-24 07:01:10 -04:00
Northmoc
c7fdab7996 ConniveEffect - tidy up 2022-05-24 06:51:15 -04:00
Anthony Calosa
a35bc9c7b6 Merge pull request #469 from Northmoc/clbWyllsReversal
CLB: Wyll's Reversal and support/refactor
2022-05-24 16:42:23 +08:00
Anthony Calosa
066361c9b7 Merge pull request #478 from jyockey/master
Stop unnecessarily directory scanning 1000s of times when downloading images
2022-05-24 13:37:08 +08:00
jyockey
ff5dc62445 Don't need the cache file reference either (wasn't used) 2022-05-23 20:36:45 -07:00
jyockey
49d475a418 Merge branch 'master' of https://github.com/jyockey/forge 2022-05-23 20:21:04 -07:00
jyockey
ba6c09fd63 Make looking up card image files much more efficient 2022-05-23 20:17:09 -07:00
Agetian
cbe816ac7a Merge pull request #477 from Northmoc/clb23
CLB: 23 May
2022-05-24 05:57:11 +03:00
Agetian
a6feacecc3 Merge pull request #434 from Northmoc/nccSeizeTheSpotlight
NCC: Seize the Spotlight and support
2022-05-24 05:56:38 +03:00
Northmoc
34c19db4c2 ConniveEffect according to the latest tweets 2022-05-23 22:13:50 -04:00
Northmoc
691465120a aerie_worshippers.txt tidy + AI 2022-05-23 21:51:19 -04:00
Northmoc
9bb6cf8535 the_hippodrome.txt fix bad restriction 2022-05-23 21:50:50 -04:00
Northmoc
2f923109d1 elminsters_simulacrum.txt 2022-05-23 21:50:17 -04:00
Northmoc
4abf6ed5f8 jeleva_nephalias_scourge.txt NICKNAME and oracle matching 2022-05-23 20:53:38 -04:00
Northmoc
1a9a6042f1 myrkul_lord_of_bones.txt 2022-05-23 20:51:56 -04:00
Anthony Calosa
15d5f9f560 Merge pull request #474 from kevlahnota/master
add Chaos Battle intro messages..
2022-05-24 08:44:59 +08:00
Anthony Calosa
faf42bc29f Merge branch 'master' into newmaster 2022-05-24 08:39:38 +08:00
Anthony Calosa
8bb4fdd110 add Chaos Battle intro messages.. 2022-05-24 08:39:13 +08:00
Anthony Calosa
1fcf6a7c24 Merge pull request #470 from Northmoc/teferi
Teferi's Time Twist tweak
2022-05-24 05:49:49 +08:00
Anthony Calosa
cd441384fe Merge pull request #473 from Northmoc/npeFix
CountersRemoveEffect fix possible NPE at prompt
2022-05-24 05:49:11 +08:00
Northmoc
cbff04ca46 CountersRemoveEffect fix possible NPE at prompt 2022-05-23 17:06:37 -04:00
Northmoc
ef90f497a6 teferis_time_twist.txt revis 2022-05-23 14:20:08 -04:00
Northmoc
b517a9005b remove unused imports 2022-05-23 14:11:34 -04:00
Anthony Calosa
d290566820 Merge pull request #468 from kevlahnota/master
update adventure dialog
2022-05-24 02:05:22 +08:00
Anthony Calosa
c138135376 Merge branch 'master' into newmaster 2022-05-24 02:04:54 +08:00
Northmoc
6788ccd776 old cards refactor targeting and prompts 2022-05-23 13:47:36 -04:00
Northmoc
4898ce557c wylls_reversal.txt 2022-05-23 13:46:44 -04:00
Northmoc
18733e046f TargetSelection > chooseCardFromList do some CARDNAME replacements 2022-05-23 13:46:26 -04:00
Northmoc
e5773a5e24 TargetRestrictions remove singleTarget stuff 2022-05-23 13:45:52 -04:00
Northmoc
910065e5a0 SpellAbility > canTargetSpellAbility() remove singleTarget stuff 2022-05-23 13:45:28 -04:00
Northmoc
5918030022 ForgeScript > spellAbilityHasProperty() add "numTargets" 2022-05-23 13:44:49 -04:00
Anthony Calosa
7f5bc60208 update adventure dialog
- show deck color identity for awarded deck ie chaos mode precons, etc
2022-05-24 01:43:36 +08:00
Northmoc
9f17a67a63 AbilityFactory remove "TargetsSingleTarget" 2022-05-23 13:41:54 -04:00
Anthony Calosa
875f6c78c0 Merge pull request #457 from tool4ever/smuggler
Smuggler's Share fix and some others
2022-05-23 20:49:23 +08:00
TRT
a20e6e6a2c Fix RememberObjects$ Valid remembering players 2022-05-23 13:16:01 +02:00
TRT
ebee360cda Don't grant abilities (PumpAll -> Effect) 2022-05-23 13:11:19 +02:00
TRT
25e47f2836 Fix Maddening Imp 2022-05-23 11:58:03 +02:00
TRT
ca9e80ddb7 Clean up 2022-05-23 10:15:09 +02:00
TRT
c9a37c01bf Merge branch 'smuggler' of https://github.com/tool4ever/forge.git into smuggler 2022-05-23 10:04:52 +02:00
TRT
b732e14012 Timing fixes 2022-05-23 10:04:28 +02:00
tool4ever
8b9caeb2a6 Merge branch 'Card-Forge:master' into smuggler 2022-05-23 10:03:30 +02:00
Anthony Calosa
21bfcf92c6 Update ravenous_trap.txt 2022-05-23 12:51:25 +08:00
Anthony Calosa
7c66787a32 Merge pull request #465 from kevlahnota/master
fix oathbreaker signature spell not recognized
2022-05-23 12:45:29 +08:00
Anthony Calosa
6721fc5970 disable some test since it doesn't check the game modes for oathbreaker
- todo make the test relevant for the actual game mode
2022-05-23 12:42:06 +08:00
Northmoc
72ee0066ce NCC: Wave of Rats and support (#458)
* wave_of_rats.txt

* CardProperty > cardHasProperty() expand to GameObject
2022-05-23 06:38:39 +02:00
Agetian
2880a704e3 Merge pull request #443 from cfdavison/master
Dread Linnorm // Scale Deflection, Monster Manual // Zoological Study, Stirge
2022-05-23 07:11:45 +03:00
Agetian
ca28ab16a0 Merge pull request #459 from Northmoc/various
Various little fixes
2022-05-23 07:10:02 +03:00
Agetian
4b710354a6 Merge pull request #460 from Northmoc/ncc22
NCC: 22 April
2022-05-23 07:09:40 +03:00
Agetian
122629477b Merge pull request #448 from Northmoc/nccSkywayRobber
NCC: Skyway Robber and support
2022-05-23 07:08:42 +03:00
Anthony Calosa
ff7e0f4870 fix oathbreaker signature spell not recognized 2022-05-23 11:47:58 +08:00
Creston Davison
404e2912cb DeckHas:Ability$Mill|Graveyard 2022-05-22 18:38:28 -05:00
Creston Davison
1a1f36a759 Removed trailing whitespace 2022-05-22 18:27:29 -05:00
Northmoc
e403720ab9 turf_war.txt and contested counter 2022-05-22 18:13:52 -04:00
Northmoc
672b06fb4c xanders_pact.txt 2022-05-22 18:09:36 -04:00
Northmoc
b9ba160117 writ_of_return.txt 2022-05-22 18:09:36 -04:00
Northmoc
4506bb57d8 viviens_stampede.txt 2022-05-22 18:09:35 -04:00
Northmoc
0ba7238360 DiscardEffect.java > getStackDescription() improve 2022-05-22 18:07:23 -04:00
Northmoc
7420f9a6b0 gravewaker.txt improve 2022-05-22 18:06:58 -04:00
tool4EvEr
26a335e115 Smuggler's Share fix 2022-05-22 20:30:40 +02:00
Anthony Calosa
4f2f54207e Merge pull request #456 from kevlahnota/master
added chaos battle on adventure
2022-05-22 21:06:28 +08:00
Anthony Calosa
77562edd43 Merge branch 'master' into newmaster 2022-05-22 20:59:32 +08:00
Anthony Calosa
ab622322b9 added chaos battle on adventure
- mimic (Doppelganger) will use random quest challenge decks on chaos mode. winning the challenge grants 1 life, random amount of gold, 2 rares, 2 uncommons and 3 common cards.
2022-05-22 20:58:07 +08:00
Anthony Calosa
0c23cd661f Merge pull request #451 from kevlahnota/master
minor fixes for adventure mode
2022-05-22 12:23:24 +08:00
Anthony Calosa
3b0f6fc11a Merge branch 'master' into newmaster 2022-05-22 12:19:57 +08:00
Anthony Calosa
e94a0511b6 minor fixes for adventure mode
- fix save dialog now fading out when saving
- fix deck selector for chaos mode as check box
- fix button opacity on tiled map stage
2022-05-22 12:17:55 +08:00
Anthony Calosa
ebbe62b76c Merge pull request #437 from Northmoc/nccSmugglersShare
NCC: Smuggler's Share and support
2022-05-22 10:04:12 +08:00
Anthony Calosa
aad08bb6d9 Merge pull request #428 from Northmoc/nccBeamtown
NCC: The Beamtown Bullies and some related ChangeZoneEffect cleanup
2022-05-22 10:02:29 +08:00
Northmoc
48bc0737ec AbilityUtils > handleRemembering - "RememberCostCards" look at paidHash instead of referencing Cost 2022-05-21 20:25:21 -04:00
Northmoc
ee2ad22967 skyway_robber.txt 2022-05-21 20:23:55 -04:00
Creston Davison
e5bda7b88a Removed unnecessary default values 2022-05-21 16:00:05 -05:00
Creston Davison
9aa93021f8 Removed unnecessary default values
Added Mandatory
2022-05-21 15:58:11 -05:00
Creston Davison
58470cef63 Removed unnecessary default values
Changed ’ to '
Added DeckHas
2022-05-21 15:46:39 -05:00
Creston Davison
997b1462d3 Changed RemeberCards to Targeted
Added DeckHas
2022-05-21 15:44:01 -05:00
Northmoc
578b35107c AbilityUtils > playerXCount() support "Condition" 2022-05-21 15:57:13 -04:00
Northmoc
b373f1c941 smugglers_share.txt 2022-05-21 15:56:58 -04:00
Creston Davison
4737bbb738 Added Bhaal, Lord of Murder 2022-05-21 13:12:47 -05:00
Creston Davison
4567393e75 Added Dread Linnorm // Scale Deflection
Added Monster Manual // Zoological Study
Added Stirge
2022-05-21 11:34:38 -05:00
Northmoc
aa4fad07cb Merge pull request #442 from tool4ever/changeszoneall
PlayEffect: add TriggerChangesZoneAll
2022-05-21 08:25:46 -04:00
tool4EvEr
209488f024 Cause=sa 2022-05-21 11:44:58 +02:00
Agetian
193c58c7fb Merge pull request #438 from tool4ever/cleanupnew
Remove obsolete Game fields
2022-05-21 12:25:21 +03:00
Paul Hammerton
691aaf26ce Merge pull request #441 from paulsnoops/clb_edition_update
clb_edition_update
2022-05-21 10:01:53 +01:00
tool4EvEr
f68e427a18 PlayEffect: add TriggerChangesZoneAll 2022-05-21 10:55:06 +02:00
paul_snoops
5081bf29fb clb_edition_update 2022-05-21 09:48:05 +01:00
Anthony Calosa
031e64827e Merge pull request #440 from kevlahnota/master
refactor deckgen for adventure
2022-05-21 16:42:54 +08:00
Anthony Calosa
bd5a33edff refactor deckgen for adventure
-some phones don't like streams
2022-05-21 16:39:05 +08:00
Anthony Calosa
2ca40f936b Merge pull request #439 from paulsnoops/riveteers_confluence_fix
riveteers_confluence fix
2022-05-21 15:54:20 +08:00
paul_snoops
2b12d0e11a riveteers_confluence fix 2022-05-21 08:52:54 +01:00
tool4EvEr
09f2ecba27 Remove obsolete Game fields 2022-05-21 09:45:48 +02:00
Agetian
876e2749b9 Merge pull request #409 from tool4ever/card_fixes
Card fixes
2022-05-21 07:17:22 +03:00
Agetian
2da6223526 Merge pull request #429 from Northmoc/cleanup
Various card cleanups
2022-05-21 07:16:17 +03:00
Agetian
4f2036270f Merge pull request #421 from Northmoc/nccRainOfRiches
NCC: Rain of Riches and support
2022-05-21 07:15:48 +03:00
Agetian
b45191e6b5 Merge pull request #432 from Northmoc/ncc20
NCC: 20 May
2022-05-21 07:14:59 +03:00
Northmoc
bf80721f13 guess some of those were needed 2022-05-20 23:29:51 -04:00
Northmoc
c3bcee0f8c remove more unused imports 2022-05-20 23:23:58 -04:00
Northmoc
923159bebb remove unused imports 2022-05-20 23:21:41 -04:00
Northmoc
0cf08249ef ControlGainEffect add "Choices" 2022-05-20 23:06:19 -04:00
Northmoc
1716da35d3 seize_the_spotlight.txt 2022-05-20 23:05:22 -04:00
Northmoc
5ae456b3fb scepter_of_celebration.txt 2022-05-20 22:14:54 -04:00
Northmoc
95c72210ab rose_room_treasurer.txt 2022-05-20 21:26:41 -04:00
Anthony Calosa
476a85c658 Update pom.xml
fix warning Deserialization of Untrusted Data in Gson
2022-05-21 08:21:53 +08:00
Northmoc
2a0b53f71b persist.txt tidy 2022-05-20 20:14:07 -04:00
Northmoc
8caa76c0c6 remove weird unused RemMultiplayer 2022-05-20 20:13:36 -04:00
Northmoc
14958c0203 obzedat NICKNAME 2022-05-20 20:12:51 -04:00
Northmoc
a499054865 omnath BuffedBy 2022-05-20 20:12:38 -04:00
Northmoc
1ff3637443 one more – fold "NewController" into "GainControl" 2022-05-20 20:06:43 -04:00
Northmoc
bf44fd78b4 old card – fold "NewController" into "GainControl" 2022-05-20 20:06:08 -04:00
Northmoc
8c95e15b74 ChangeZoneEffect: fold "NewController" into "GainControl" 2022-05-20 20:04:08 -04:00
Northmoc
eebb1b1d37 the_beamtown_bullies.txt 2022-05-20 20:02:59 -04:00
Northmoc
c4561a4fb8 insulate ConniveEffect against case of multiple controllers 2022-05-20 17:45:43 -04:00
Northmoc
d6892f2e77 rain_of_riches.txt use CastSa 2022-05-20 15:08:06 -04:00
Northmoc
685e94233a ForgeScript > spellAbilityHasProperty support "ManaFrom" 2022-05-20 15:08:05 -04:00
Anthony Calosa
8220857193 Merge pull request #427 from kevlahnota/master
add questevent decks for chaos mode
2022-05-21 02:46:54 +08:00
Anthony Calosa
f9ebfddaab add questevent decks for chaos mode
- add dumpEnemyDeckList command
2022-05-21 02:40:09 +08:00
Northmoc
36c7270dbd rain_of_riches.txt 2022-05-20 10:30:51 -04:00
Northmoc
9078724546 riveteers_confluence.txt 2022-05-20 10:29:16 -04:00
Northmoc
ace995973e add Connive prompt 2022-05-20 09:52:41 -04:00
Northmoc
8d87b2b6db restructure multiple connivers to match rules tweet 2022-05-20 09:52:20 -04:00
tool4EvEr
ea57f316c5 Clean up 2022-05-20 13:28:43 +02:00
Agetian
27603a78e2 Merge pull request #406 from Northmoc/canAttackDefender
NCC: Weathered Sentinels and support (Refactor "CARDNAME can attack as though it didn't have defender" to static)
2022-05-20 12:20:05 +03:00
Agetian
347842c7bc Merge pull request #423 from Northmoc/ncc19
NCC: 19 May
2022-05-20 12:19:47 +03:00
Anthony Calosa
0eb04df4b6 Fix tasha missing tokenscript 2022-05-20 15:43:35 +08:00
Anthony Calosa
6537696eef Merge pull request #424 from kevlahnota/master
update deckgen for adventure
2022-05-20 15:05:23 +08:00
Anthony Calosa
b9a01753ed update deckgen for adventure
- merge theme and precons for more random deck on chaos mode
2022-05-20 12:49:42 +08:00
Anthony Calosa
853e8b64f0 Merge pull request #420 from churrufli/master
Net Decks Archive Updates
2022-05-20 08:09:27 +08:00
Churrufli
4a4bd5588a Net Deck Archive Updates 2022-05-19 23:10:34 +02:00
Northmoc
3b43f78e0c protection_racket.txt 2022-05-19 14:16:29 -04:00
Northmoc
b48e72b607 prosperou 2022-05-19 14:12:34 -04:00
Agetian
25bc52f9fe Merge pull request #399 from Suthro/master
CBL: Captain N'ghathrod and Tasha, the Witch Queen
2022-05-19 21:07:41 +03:00
Suthro
a6c29c9cfd Update nobles_purse.txt 2022-05-19 12:54:32 -05:00
Suthro
b4120e0211 Update sea_hag_aquatic_ingress.txt 2022-05-19 12:44:41 -05:00
Suthro
d490a93f53 Update irenicuss_vile_duplication.txt 2022-05-19 12:40:34 -05:00
Suthro
a0e890fc50 Update tasha_the_witch_queen.txt 2022-05-19 12:39:33 -05:00
Northmoc
f3d52fa36f avoid NoSuchElementException in case toConnive is empty 2022-05-19 13:16:06 -04:00
Northmoc
f0e0ff1fa4 Merge pull request #382 from Northmoc/nccParty
NCC: Life of the Party and support
2022-05-19 13:11:43 -04:00
Northmoc
76cc5fa90e rest of the refactored cards 2022-05-19 13:09:09 -04:00
Northmoc
f7b6b99032 nuke it! 2022-05-19 12:37:37 -04:00
Northmoc
0cca0ba206 remove keyword AI references 2022-05-19 12:35:46 -04:00
Northmoc
fe92c79a64 o_kagachi_vengeful_kami.txt refactor 2022-05-19 12:31:52 -04:00
Northmoc
2bcdfcd06b remove "AttackedPlayerWhoAttackedYouLastTurn" condition 2022-05-19 12:31:32 -04:00
Northmoc
90e99f3f66 more old cards 2022-05-19 12:05:46 -04:00
Northmoc
dc796c14f2 remove KW from list 2022-05-19 11:49:39 -04:00
Northmoc
9f297694c9 use StaticAbilityCanAttackDefender canAttack check 2022-05-19 11:33:26 -04:00
Northmoc
b057dcff06 add canAttack boolean and MODE string 2022-05-19 11:32:56 -04:00
Northmoc
960324efca move this to StaticAbilityCanAttackDefender 2022-05-19 11:32:05 -04:00
Northmoc
159682561a use Effect instead of Animate 2022-05-19 11:14:37 -04:00
Northmoc
94a5560db2 don't addAttackedPlayersMyTurn if attacked planeswalker 2022-05-19 11:04:48 -04:00
Northmoc
e6101ba1dd NCC: 18 April (#407) 2022-05-19 12:08:48 +00:00
tool4EvEr
340878f6fb Clean up scripts 2022-05-19 12:41:14 +02:00
tool4EvEr
41b42cd610 Fix some corner cases 2022-05-19 12:41:05 +02:00
tool4EvEr
f1a38d35bf Timing fix 2022-05-19 12:39:46 +02:00
tool4EvEr
969f8d2b17 Clean up 2022-05-19 12:37:37 +02:00
Anthony Calosa
44fd509809 Merge pull request #408 from kevlahnota/master
add Console toggle for android
2022-05-19 17:25:10 +08:00
Anthony Calosa
5cdf780fb4 Merge branch 'master' into newmaster 2022-05-19 15:39:21 +08:00
Anthony Calosa
b17b7a5067 add Console toggle for android
- longpress the GameHud or the avatar to open the console.
2022-05-19 15:32:05 +08:00
tool4ever
bcea70e32c Jabari's Influence: multiplayer fix (#375)
* Jabari's Influence: multiplayer fix
2022-05-19 07:34:13 +02:00
Suthro
78c274a6b5 Update irenicuss_vile_duplication.txt 2022-05-19 00:16:52 -05:00
Suthro
613763b77f Update irenicuss_vile_duplication.txt 2022-05-19 00:16:04 -05:00
Northmoc
04d3d8ded2 refactor some old cards 2022-05-18 22:32:08 -04:00
Northmoc
ebd4870a59 checkDeclaredAttacker support addAttackedPlayersMyTurn 2022-05-18 22:29:50 -04:00
Northmoc
07d9f9e105 track Players attacked 2022-05-18 22:17:18 -04:00
Northmoc
4e83623e69 playerHasProperty() add "attackedYouTheirLastTurn" 2022-05-18 22:14:53 -04:00
Northmoc
d8cc879a1e modify applyCantAttackAbility() "DefenderKeyword" 2022-05-18 22:13:43 -04:00
Northmoc
c9bd1644ba applyAbility() add "CanAttackDefender" mode 2022-05-18 22:11:40 -04:00
Northmoc
c7eba8400d StaticAbilityCanAttackDefender 2022-05-18 22:10:25 -04:00
Northmoc
d13908ed63 weathered_sentinels.txt 2022-05-18 22:09:21 -04:00
Anthony Calosa
bcc15c5857 Merge pull request #405 from kevlahnota/master
GameHUD buttons update
2022-05-19 07:31:28 +08:00
Anthony Calosa
2b15ddae90 GameHUD buttons update 2022-05-19 07:28:25 +08:00
Suthro
ea24f42b26 Merge branch 'master' of https://github.com/Suthro/forge_scripttesting 2022-05-18 15:17:00 -05:00
Suthro
b2e1bb4ef5 CBL: Gorion, Wise Mentor + Uncommons
CBL Scripts for Gorion, Wise Mentor and a handful of lower rarity cards
2022-05-18 15:14:34 -05:00
Suthro
24027156f6 Update: Fix missing commander clause 2022-05-18 14:33:11 -05:00
Suthro
13c3dbe8d0 Update tasha_the_witch_queen.txt 2022-05-18 14:31:38 -05:00
Suthro
3dda45713f Update: Fix file name typo, add rec. changes 2022-05-18 14:29:36 -05:00
Northmoc
ee03f26e04 remove unused import 2022-05-18 14:44:14 -04:00
Agetian
d067e55c23 Merge pull request #380 from Northmoc/ncc17
NCC: 17 April
2022-05-18 21:42:22 +03:00
Northmoc
3501a546e5 make_an_example.txt sacrifice all at once 2022-05-18 14:38:48 -04:00
Northmoc
b1de935c0b Merge pull request #398 from FLAREdirector-mse/master
40K: Abaddon + Blood for the Blood God!
2022-05-18 14:35:59 -04:00
Suthro
e5d73b6d3a Update Tasha: add missing Commander clause
Add after line 4:
"Text:CARDNAME can be your commander."
2022-05-18 13:30:06 -05:00
FLAREdirector-mse
d78108bc54 40K Update 1 2022-05-18 13:13:40 -05:00
Suthro
64063f7388 CBL: Captain N'ghathrod and Tasha, the Witch Queen
Scripts for the CBL cards Captain N'ghathrod and Tasha, the Witch Queen, awaiting review
2022-05-18 12:43:42 -05:00
FLAREdirector-mse
f3e4819a2b 40K: Abaddon + Blood for the Blood God! 2022-05-18 12:37:52 -05:00
Northmoc
59aee823ad support list for Controller$ instead of using only first 2022-05-18 13:28:19 -04:00
Northmoc
4f56632d42 life_of_the_party.txt with Controller$ Opponent 2022-05-18 13:27:43 -04:00
Northmoc
cfacb6be7f tidy up resolve() more 2022-05-18 13:03:41 -04:00
Northmoc
143e353032 remove extra space 2022-05-18 13:03:18 -04:00
Agetian
ef2d5c4d7c Merge pull request #391 from Card-Forge/libgdxUpdate
Update LibGDX 1.11.0
2022-05-18 17:07:25 +03:00
Northmoc
301cf14d7f mezzio_mugger.txt ignore color not type 2022-05-18 09:11:11 -04:00
Northmoc
28858205c6 misfortune_teller.txt typo fix 2022-05-18 09:10:17 -04:00
Northmoc
40ac36a9b5 support "EachCreates" 2022-05-18 09:02:15 -04:00
Northmoc
81f292a194 life_of_the_party.txt without RepeatEach 2022-05-18 09:01:44 -04:00
Anthony Calosa
6afdda9a0f Update GameHUD.java
fix inverted action, hide when buttons are visible
2022-05-18 20:59:39 +08:00
Anthony Calosa
4c3fbb9810 Merge pull request #397 from kevlahnota/master
[Android] GameHUD buttons action for adventure
2022-05-18 20:42:02 +08:00
Anthony Calosa
89b212e9d0 [Android] GameHUD buttons action for adventure
- add move action for GameHUD buttons
2022-05-18 20:37:30 +08:00
Paul Hammerton
ef3f3d44a9 Merge pull request #393 from paulsnoops/clb_edition
CLB edition update
2022-05-18 08:14:07 +01:00
paul_snoops
51c5bbe010 CLB edition update 2022-05-18 08:10:58 +01:00
Anthony Calosa
786c89c185 Merge pull request #392 from kevlahnota/master
refactor generated deck selection for adventure
2022-05-18 14:26:47 +08:00
Anthony Calosa
a664ff6a52 refactor generated deck selection for adventure 2022-05-18 14:24:56 +08:00
Anthony Calosa
8f06aaeb01 Update LibGDX 1.11.0
- see https://libgdx.com/news/2022/05/gdx-1-11
2022-05-18 11:34:11 +08:00
Northmoc
60d8cda45f add "Card" to commonStuff 2022-05-17 20:47:44 -04:00
Northmoc
8cf05af3c5 misfortune_teller.txt 2022-05-17 20:47:19 -04:00
Northmoc
4cc092005a mezzio_mugger.txt 2022-05-17 20:35:16 -04:00
Northmoc
027ca95b7f master_of_ceremonies.txt 2022-05-17 19:59:03 -04:00
Northmoc
33da935a5f mask_of_the_schemer.txt 2022-05-17 19:58:39 -04:00
Northmoc
95a404c5c5 repository_skaab.txt typo 2022-05-17 19:13:23 -04:00
Northmoc
b7b29510c6 make_an_example.txt 2022-05-17 19:13:01 -04:00
Northmoc
2ea006961a maestros_confluence.txt 2022-05-17 19:12:29 -04:00
Northmoc
d9574f9208 support Duration$ Permanent 2022-05-17 18:47:09 -04:00
Northmoc
15fb11eca1 small typo 2022-05-17 18:46:43 -04:00
Northmoc
48b26050d7 life_of_the_party.txt 2022-05-17 18:42:49 -04:00
Northmoc
f31b7f2686 life_insurance.txt 2022-05-17 15:57:23 -04:00
Anthony Calosa
66c6b05397 Merge pull request #378 from kevlahnota/master
[Adventure] Update Deck selector to Chaos Mode
2022-05-18 02:37:30 +08:00
Anthony Calosa
21dce32791 [Adventure] Update Deck selector to Chaos Mode
- Precons, Theme or Random Generated color decks for Enemies, Precons for Player deck on New Game
2022-05-18 02:30:56 +08:00
Northmoc
16f3138e8e missed a Player p :( 2022-05-17 13:03:05 -04:00
Northmoc
f9c8af3296 remove Player p and streamline discarding 2022-05-17 13:00:47 -04:00
Northmoc
a4186102b2 return "" for stackDesc if nothing is conniving 2022-05-17 13:00:06 -04:00
Northmoc
2a89690b4f spacing after keyword for Card Detail 2022-05-17 10:29:57 -04:00
Northmoc
9af710e48d allow ordering of connivers and don't allow discarding the same card multiple times 2022-05-17 10:24:56 -04:00
Northmoc
a37ad6b923 lethal_scheme.txt 2022-05-17 10:21:26 -04:00
Northmoc
73fd071935 killer_service.txt 2022-05-17 09:11:29 -04:00
Anthony Calosa
70bfc13569 Merge pull request #374 from kevlahnota/master
[Adventure] Add Fantasy Mode on Deck Selector
2022-05-17 16:15:33 +08:00
Anthony Calosa
605dfb351f [Adventure] Add Fantasy Mode on Deck Selector
- Fantasy Mode will get Random Preconstructed Decks for Enemies and Player.
2022-05-17 16:03:42 +08:00
Agetian
3e5b4fcf3f Merge pull request #371 from Northmoc/ncc16
NCC: 16 April
2022-05-17 07:38:13 +03:00
tool4ever
a15cfb716c Spy Kit Fix (#368)
Co-authored-by: tool4EvEr <tool4EvEr@192.168.0.59>
2022-05-17 05:25:29 +02:00
Northmoc
45c7737624 crovax.txt fix ValidTargets 2022-05-16 20:51:44 -04:00
Northmoc
0ff01b9fa6 silhana_wayfinder.txt remove duplicate Param 2022-05-16 20:48:13 -04:00
Northmoc
b8aef0298c crovax.txt typo 2022-05-16 20:47:49 -04:00
Northmoc
7206b42fec jailbreak.txt 2022-05-16 20:46:46 -04:00
Northmoc
f476495958 in_too_deep.txt 2022-05-16 20:46:37 -04:00
Northmoc
fc4b9ffc61 industrial_advancement.txt 2022-05-16 16:41:43 -04:00
Northmoc
ab2e293a29 Merge pull request #363 from t-w-o-s-a-t/master
Update de-DE.properties
2022-05-16 10:30:30 -04:00
t-w-o-s-a-t
0f61b17482 Update de-DE.properties
translated lblWithKindCounter, lblEachOther, lblChooseAnyNumberToPhase
streamlined some translations
2022-05-16 08:41:45 +02:00
Agetian
b47c9f0fec Merge pull request #361 from tool4ever/tezz
Fix Tezzeret, Betrayer of Flesh
2022-05-16 07:19:57 +03:00
tool4EvEr
5a1d740243 Fix Tezzeret, Betrayer of Flesh 2022-05-15 23:36:19 +02:00
Hans Mackowiak
24b2186e0d Card: ignoreLegendRule as StaticAbility (#360)
* Card: ignoreLegendRule as StaticAbility
2022-05-15 21:32:55 +00:00
tool4ever
573e7dfb23 Run AbilityCast triggers for mana abilities (#345)
* Run AbilityCast triggers for mana abilities

* Fix for Vazi

* Update cards

* Make mana abilities undoable if they cause a trigger

* Reset undoable

* Cleanup related to Winter's Night
2022-05-15 21:24:40 +00:00
Agetian
aaee31e1c6 Merge pull request #355 from Northmoc/nccFirstResponder
Fix NCC First Responder
2022-05-14 18:04:46 +03:00
Paul Hammerton
f7556f90a8 Merge pull request #359 from JohnWilliams77/patch-11
Update Secret Lair Drop Series.txt
2022-05-14 10:30:03 +01:00
JohnWilliams77
14460a97bc Update Secret Lair Drop Series.txt 2022-05-14 07:44:11 +01:00
Agetian
9e1c06301f Merge pull request #352 from Northmoc/ncc13
NCC: 13 April
2022-05-14 07:25:18 +03:00
Northmoc
9bde0f210f grime_gorger.txt 2022-05-13 18:32:09 -04:00
Northmoc
defe53894e flawless_forgery.txt 2022-05-13 18:31:58 -04:00
Agetian
ac10d6f499 Merge pull request #354 from Northmoc/nccCurrency
NCC Currency Converter and stackDesc tweaks
2022-05-13 23:04:30 +03:00
Northmoc
73f747b9ea grand_crescendo.txt 2022-05-13 15:13:28 -04:00
Northmoc
2f75053478 dodgy_jalopy.txt 2022-05-13 12:55:41 -04:00
Northmoc
8a86b32655 add RememberLKI to playerinvariant 2022-05-13 12:45:54 -04:00
Northmoc
8996675c91 first_responder.txt without cost 2022-05-13 12:45:19 -04:00
Agetian
1260ae32df Merge pull request #353 from Northmoc/sdFix
CountersPutEffect fix backward stackDesc logic
2022-05-13 19:43:58 +03:00
Northmoc
6dda7422ea better parse wacky stackDesc constructions 2022-05-13 12:14:18 -04:00
Northmoc
19ab071643 changeHiddenOriginStackDesc begin to support Exile origin 2022-05-13 12:13:45 -04:00
Northmoc
84c156f389 currency_converter.txt 2022-05-13 12:10:58 -04:00
Northmoc
9055556d65 CountersPutEffect fix backward logic 2022-05-13 12:06:30 -04:00
Agetian
488cabbde4 Merge pull request #351 from Northmoc/nccFirstResponder
NCC: First Responder and support
2022-05-13 18:36:14 +03:00
Northmoc
1493331238 false_floor.txt 2022-05-13 11:27:00 -04:00
Northmoc
50ff19b442 determined_iteration.txt 2022-05-13 11:25:44 -04:00
Northmoc
ea8ae179a8 calculateAmount support using "Returned" paid list 2022-05-13 11:20:31 -04:00
Northmoc
e3293b8cc0 first_responder.txt 2022-05-13 11:18:36 -04:00
Paul Hammerton
1754459459 Merge pull request #340 from Northmoc/ncc12
NCC: 12 April
2022-05-13 16:02:02 +01:00
Northmoc
c2f0b3cc14 dogged_detective.txt 2022-05-13 10:52:26 -04:00
Northmoc
892f21e8da crash_the_party.txt they're drunk 2022-05-13 07:58:36 -04:00
Northmoc
4ccf2ff0df cryptic_pursuit.txt cast not play 2022-05-13 07:57:52 -04:00
tool4ever
9e5573560c Fix missing check for mana abilities at instant speed (#336)
Co-authored-by: tool4EvEr <tool4EvEr@192.168.0.59>
2022-05-13 09:58:00 +02:00
Paul Hammerton
b40e971cdd Merge pull request #342 from paulsnoops/new_editions
New edition files: 2X2, 40K, PLG22, SCH
2022-05-13 08:36:51 +01:00
paul_snoops
2c844284a7 New edition files: 2X2, 40K, PLG22, SCH 2022-05-13 08:29:28 +01:00
Agetian
cc266d675b Merge pull request #338 from Northmoc/nccChangeOfPlans
NCC: Change of Plans and support
2022-05-13 06:51:30 +03:00
Northmoc
d307b78dc9 cryptic_pursuit.txt 2022-05-12 20:29:55 -04:00
Northmoc
6380950831 crash_the_party.txt 2022-05-12 19:40:45 -04:00
Northmoc
e78f65558a PhasesEffect choose any number prompt 2022-05-12 19:25:23 -04:00
Northmoc
d8cf7baba1 PhasesEffect support "AnyNumber" 2022-05-12 19:24:52 -04:00
Northmoc
c28e2fbf78 change_of_plans.txt 2022-05-12 19:18:53 -04:00
Northmoc
4902ed4242 cephalid_facetaker.txt 2022-05-12 13:24:19 -04:00
Northmoc
7d6fe5bc5b caldaia_guardian.txt 2022-05-12 13:21:59 -04:00
Northmoc
6fa6ce8680 cabaretti_confluence.txt 2022-05-12 12:43:40 -04:00
Northmoc
64bb51fad4 bosss_chauffeur.txt 2022-05-12 12:43:27 -04:00
Paul Hammerton
b18c086c07 Removal of 3 un-needed "digital" formats (#315) 2022-05-12 17:41:45 +02:00
Northmoc
c55afd50de NCC: Bess, Soul Nourisher and support (#327) 2022-05-12 14:55:30 +02:00
tool4ever
a3b6d34887 Update demonfire.txt (#330) 2022-05-12 14:52:53 +02:00
Agetian
aa6830c454 Merge pull request #329 from Northmoc/ncc11
NCC: 11 April
2022-05-12 07:50:11 +03:00
Anthony Calosa
73690dd461 Merge pull request #324 from magpie514/master
Adventure mode - Small map updates
2022-05-12 08:00:40 +08:00
Northmoc
53a7c0736e audacious_swap.txt add DefinedDesc 2022-05-11 16:42:45 -04:00
Northmoc
7789aeb3be audacious_swap.txt 2022-05-11 16:39:13 -04:00
Northmoc
f2fc19f352 lilianas_standard_bearer.txt simple AI hint 2022-05-11 16:30:49 -04:00
Northmoc
e58e7825bc body_count.txt 2022-05-11 16:30:30 -04:00
Northmoc
496dcd3838 the_grand_tour.txt little typo 2022-05-11 16:23:27 -04:00
Northmoc
2b9ce7df5d bellowing_mauler.txt 2022-05-11 16:23:05 -04:00
Northmoc
49ec28ac24 aerial_extortionist.txt 2022-05-11 15:30:14 -04:00
Magpie
6455a3332c Adventure mode - Small map updates
* Castle gates do not consume keys anymore.
* Prototype waste town update.
2022-05-11 21:22:54 +02:00
tool4ever
57eeb5e0bf Fix NPE with Crystalline Giant (#321)
* Fix Crystalline Giant NPE

* Clean up

Co-authored-by: tool4EvEr <tool4EvEr@192.168.0.59>
2022-05-11 16:45:36 +00:00
Agetian
284fbb9d53 Merge pull request #319 from Northmoc/ncc10
NCC: 10 April
2022-05-11 09:15:46 +03:00
Northmoc
b7a9bb9742 sinister_concierge.txt 2022-05-10 22:13:42 -04:00
Northmoc
27af5756e1 waste_management.txt add AI hint 2022-05-10 11:38:17 -04:00
Agetian
0b3e9745f8 Merge pull request #317 from Northmoc/nccSmugBug
NCC: Smuggler's Buggy and a little support
2022-05-10 18:19:02 +03:00
Agetian
4a3eabd690 Merge pull request #318 from paulsnoops/update_snc_draft_rank
Update SNC Draft Ranking
2022-05-10 18:18:52 +03:00
Northmoc
7a7dff9088 Card.java Kicker for non-permanents same spacing in card detail 2022-05-10 11:01:36 -04:00
Northmoc
3dd2a2c076 waste_management.txt 2022-05-10 11:00:53 -04:00
paul_snoops
bac850072a Update SNC Draft Ranking 2022-05-10 15:57:45 +01:00
Northmoc
2e85504747 smugglers_buggy.txt "cast" means ValidSA$ Spell is needed 2022-05-10 10:51:55 -04:00
Northmoc
08c7a3d5b1 PlayEffect optional is not final 2022-05-10 10:27:38 -04:00
Northmoc
d95dd98fce PlayEffect add option to Imprint 2022-05-10 09:51:04 -04:00
Northmoc
771092a2b5 smugglers_buggy.txt 2022-05-10 09:44:25 -04:00
Northmoc
6c9ce36c17 NCC: Gavel of the Righteous and output support (#298) 2022-05-10 14:53:49 +02:00
Northmoc
a49015316e extravagant_replication.txt AI hint 2022-05-10 08:20:38 -04:00
Northmoc
24970bff01 spellbinding_soprano.txt 2022-05-10 08:20:19 -04:00
Hans Mackowiak
6c4367efa4 Merge pull request #274 from tool4ever/slogurk
Fix Slogurk not triggering when dying from SBA legendary
2022-05-10 10:32:01 +02:00
Northmoc
8b84ec9998 NCC: Agent's Toolkit and support (#300)
* agents_toolkit.txt

* CardFactoryUtil support multiple counter kinds for etbCounter

* CountersPutEffect support ETBcounters for CounterTypes

* add " " to syntax
2022-05-10 08:09:37 +02:00
Agetian
083ea2fc69 Merge pull request #310 from tool4ever/cleanstuff
Cleanup some scripts
2022-05-10 08:42:31 +03:00
Agetian
cdd7089534 Merge pull request #311 from Northmoc/ncc9
NCC: 9 April
2022-05-10 08:40:16 +03:00
Agetian
b8334c8891 Merge pull request #314 from CCTV-1/master
update simplified chinese translation
2022-05-10 08:39:32 +03:00
CCTV-1
87bad87d54 translate new strings. 2022-05-10 10:15:35 +08:00
Northmoc
99e00468bc syrix_carrier_of_the_flame.txt remove comment 2022-05-09 19:16:38 -04:00
Northmoc
e931169390 parnesse_the_subtle_brush.txt 2022-05-09 19:13:57 -04:00
Northmoc
f2cefcff5d syrix_carrier_of_the_flame.txt 2022-05-09 18:40:57 -04:00
tool4EvEr
3fe10a24fa Tweak declareAttackers 2022-05-09 22:25:29 +02:00
tool4EvEr
6b84716f53 Cleanup cards 2022-05-09 22:24:59 +02:00
tool4EvEr
e5f3488d5d Clean up 2022-05-09 22:24:41 +02:00
TheBlackMarvel
5e9a76043a NCC Extravagant Replication (#307)
* NCC Extravagant Replication
2022-05-09 13:10:06 +02:00
Northmoc
6b13169fd5 NCC: 8 April (#299)
* familys_favor.txt

* brokers_confluence.txt

* denry_klin_editor_in_chief.txt

* skyboon_evangelist.txt fix Oracle
2022-05-09 06:22:37 +00:00
AvacynAngel
44b839e4f6 Update cliffgate.txt 2022-05-08 20:39:34 -04:00
AvacynAngel
9a0a9ab7fc Create citadel_gate.txt 2022-05-08 20:29:56 -04:00
Anthony Calosa
5427e51a04 Merge pull request #296 from magpie514/master
Adventure mode - Small fixes
2022-05-09 08:10:56 +08:00
Anthony Calosa
f16825acdc Merge pull request #297 from tool4ever/manafix
ComputerUtilMana: Fix payment fails with TapsForMana triggers
2022-05-09 08:09:46 +08:00
Agetian
f481d5d78c Merge pull request #295 from tool4ever/scriptparser
Small improvement to CardScriptParser
2022-05-08 22:37:50 +03:00
tool4EvEr
93e1a58a13 Fix for Reflecting Pool + Wastes 2022-05-08 21:16:14 +02:00
Hans Mackowiak
a028986abf SNC: Lagrella, the Magpie (#293)
* SNC: add Special ReturnAbility for Magpie

* StaticAbilityPanharmonicon: no for delayed trigger
2022-05-08 17:37:54 +02:00
Northmoc
66bcff8eea Merge branch 'master' of https://github.com/Card-Forge/forge 2022-05-08 09:07:14 -04:00
tool4EvEr
43ca318931 Fix payment fails with TapsForMana triggers 2022-05-08 14:52:50 +02:00
Magpie
522ccc2f2d Adventure mode - Small fixes
* Fixed hedge walls not having collisions.
* Variable timer added to reward scenes, it'll wait longer the more rewards are given. It can also be exited by activating the button again after 0.2 seconds (to prevent accidental double clicks).
2022-05-08 14:40:31 +02:00
tool4EvEr
ebe1642bec Small improvement to CardScriptParser 2022-05-08 14:12:38 +02:00
Agetian
e14ae15c70 Merge pull request #294 from Agetian/ai-witnessthefuture
Make Witness the Future AI playable.
2022-05-08 12:41:29 +03:00
Michael Kamensky
75a974165d - Make Witness the Future AI playable. 2022-05-08 12:31:32 +03:00
Anthony Calosa
21fca37c7d Merge pull request #292 from kevlahnota/master
[Adventure] Fix Layout and add Preview Date Time
2022-05-08 14:14:32 +08:00
Anthony Calosa
e88c25ce7c Merge branch 'master' into newmaster 2022-05-08 14:08:19 +08:00
Anthony Calosa
8708b6300c [Adventure] Fix Layout and add Preview Date Time 2022-05-08 14:06:09 +08:00
Anthony Calosa
b4a0f673f6 Merge pull request #289 from kevlahnota/master
[Adventure] Refactor GameHud
2022-05-08 06:50:56 +08:00
Anthony Calosa
ace4ba4aea Merge branch 'master' into newmaster 2022-05-08 05:58:42 +08:00
Anthony Calosa
3c9cabfb7b [Adventure] Refactor GameHud
Make GameHud controls/layout for Android landscape mode identical to Android portrait mode. Desktop Adventure mode retains previous layout and controls.
2022-05-08 05:57:36 +08:00
Anthony Calosa
c039f238d0 Merge pull request #283 from tool4ever/caststuff
Fix casting card copies not being added to stack correctly
2022-05-08 05:53:50 +08:00
Anthony Calosa
864d2caa12 Merge pull request #287 from JohnWilliams77/patch-5
Update Secret Lair Drop Series.txt
2022-05-08 05:53:01 +08:00
Anthony Calosa
dce75c9578 Merge pull request #282 from Northmoc/nccBribeTaker
NCC: Bribe Taker and support
2022-05-08 05:52:33 +08:00
Anthony Calosa
8828003333 Merge pull request #288 from kevlahnota/master
[Mobile] Add Auto AI Deck option
2022-05-08 05:43:08 +08:00
Northmoc
5e8587d786 sewer_crocodile.txt remove bad NonStackingEffect line (#286) 2022-05-07 20:02:49 +00:00
Anthony Calosa
409d18aa06 fix translation 2022-05-08 02:19:46 +08:00
Anthony Calosa
a318749a78 [Mobile] Add Auto AI Deck option 2022-05-08 02:16:36 +08:00
JohnWilliams77
8a05179a74 Update Secret Lair Drop Series.txt 2022-05-07 18:15:43 +01:00
Agetian
1f0c2d4a82 Merge pull request #268 from Northmoc/nccContractual
NCC: Contractual Safeguard, Aven Courier and support
2022-05-07 17:15:54 +03:00
Northmoc
e60b6d00c7 Merge pull request #281 from AvacynAngel/master
Create citadel_gate.txt
2022-05-07 09:56:24 -04:00
AvacynAngel
eb0e97e0d7 Create summon_undead.txt (#255)
* Create summon_undead
2022-05-07 13:55:51 +00:00
Northmoc
e4e0edc217 sewer_crocodile.txt remove bad NonStackingEffect line 2022-05-07 09:50:57 -04:00
TheBlackMarvel
01e8f8e2f2 NCC Park Heights Maverick (#221) 2022-05-07 13:47:46 +00:00
Northmoc
ab2ea6554f bribe_taker.txt fix typo 2022-05-07 09:43:16 -04:00
tool4EvEr
9db791d875 Fix casting card copies not being added to stack correctly 2022-05-07 10:18:19 +02:00
Anthony Calosa
721b284b1b revert 2022-05-07 13:02:20 +08:00
Agetian
e576656d7c Merge pull request #276 from Northmoc/ncc6a
Shield Broker (NCC) and AI for Sewer Crocodile
2022-05-07 07:30:45 +03:00
Northmoc
efb713405f CountersPutEffect support "AltChoiceForEach" 2022-05-06 23:20:13 -04:00
Northmoc
b5267d5a64 bribe_taker.txt 2022-05-06 23:19:35 -04:00
Northmoc
0e203c3327 storm_of_forms.txt 2022-05-06 23:18:20 -04:00
Anthony Calosa
1536c0e589 remove extra code 2022-05-07 11:17:54 +08:00
Anthony Calosa
268b1ea823 prevent NPE 2022-05-07 11:15:26 +08:00
AvacynAngel
774c4ff62d Create citadel_gate.txt 2022-05-06 22:07:04 -04:00
Anthony Calosa
a16fbf15bc add check on selectedDeckType 2022-05-07 10:00:18 +08:00
Anthony Calosa
88adf6a99e Merge pull request #279 from Card-Forge/kevlahnota-patch-1
cleanup
2022-05-07 07:29:34 +08:00
Anthony Calosa
f2fb22328a unused import 2022-05-07 07:22:20 +08:00
Anthony Calosa
472b0b8329 Update RandomDeckGenerator.java 2022-05-07 07:20:19 +08:00
Anthony Calosa
2ecc88cada cleanup 2022-05-07 07:18:17 +08:00
Anthony Calosa
4972823b27 Merge pull request #267 from kevlahnota/diffPlus
[Adventure] difficulty selector on NewGame Plus
2022-05-07 06:49:57 +08:00
Anthony Calosa
af51f1f5a5 Merge pull request #263 from Northmoc/hiddenCZ
various getStackDescription improvements
2022-05-07 06:48:46 +08:00
Anthony Calosa
64cbb0badd Merge pull request #265 from Northmoc/stackDesc
TokenEffect > getStackDescription() try to catch more possibilities
2022-05-07 06:39:31 +08:00
Northmoc
8af0a03ebb sewer_crocodile.txt with NonStackingEffect 2022-05-06 18:37:49 -04:00
Northmoc
2b886345b2 shield_broker.txt 2022-05-06 18:37:31 -04:00
Anthony Calosa
4aca310370 update button text 2022-05-07 06:34:48 +08:00
Anthony Calosa
9cd23e6639 Merge pull request #273 from Northmoc/ncc6
NCC: 6 April
2022-05-07 06:23:13 +08:00
Anthony Calosa
1ceed9e52e Merge pull request #264 from kevlahnota/master
refactor aideck selection
2022-05-07 06:07:00 +08:00
tool4EvEr
d5d7c58bdd Fix Slogurk not triggering when dying from SBA legendary 2022-05-06 22:52:33 +02:00
Northmoc
470e3d4a64 CountersPutEffect support "PutOnDefined" and "OnlyNewKind" 2022-05-06 16:35:35 -04:00
Northmoc
2cdb17f437 aven_courier.txt 2022-05-06 16:33:33 -04:00
Northmoc
c3e6da0d35 NCC AI updates 2022-05-06 15:09:15 -04:00
Northmoc
e7a4dcf394 skyboon_evangelist.txt 2022-05-06 15:08:40 -04:00
Northmoc
3698472ca7 damning_verdict.txt 2022-05-06 15:07:01 -04:00
Paul Hammerton
64a1bff911 Merge pull request #271 from paulsnoops/plist_update
PLIST edition update
2022-05-06 19:45:47 +01:00
paul_snoops
f2de64b1f2 PLIST edition update 2022-05-06 19:41:35 +01:00
paul_snoops
3145fedee4 Revert "PLIST edition update"
This reverts commit ac88f0b437.
2022-05-06 19:40:17 +01:00
paul_snoops
ac88f0b437 PLIST edition update 2022-05-06 19:37:49 +01:00
Northmoc
14608f34c4 CountersPutEffect new Localizer prompts 2022-05-06 13:55:26 -04:00
Northmoc
908be0e653 CountersPutEffect support "PutOnEachOther" 2022-05-06 13:42:12 -04:00
Northmoc
e439e2a975 CountersPutEffect > getStackDescription() updates 2022-05-06 13:40:48 -04:00
Northmoc
ae6f8f8f39 unbreakable_formation.txt minor tweaks 2022-05-06 13:39:16 -04:00
Northmoc
d9fd83bbd3 contractual_safeguard.txt 2022-05-06 13:39:00 -04:00
Northmoc
3f44105d93 NCC: Tenuous Truce (#236)
* tenuous_truce.txt
2022-05-06 17:21:49 +00:00
Anthony Calosa
88914f8fec [Adventure] difficulty selector on NewGame Plus 2022-05-07 00:20:21 +08:00
Anthony Calosa
317623fb34 refactor aideck selection 2022-05-06 21:48:11 +08:00
Northmoc
a5e9e394da Merge pull request #262 from JohnWilliams77/patch-4
Update Secret Lair Drop Series.txt
2022-05-06 09:35:53 -04:00
Northmoc
85c597672a various old card tweaks 2022-05-06 09:29:43 -04:00
Northmoc
1d055682ac MillEffect > getStackDescription() tweaks + support "Optional", "IfDesc" 2022-05-06 09:29:07 -04:00
Northmoc
ff026c59cc ChangeZoneEffect > getHiddenOriginStackDescription() : Origin Graveyard improvements 2022-05-06 09:28:10 -04:00
Northmoc
b6e42301bf AnimateEffect > getStackDescription() tweaks + support "AtEOT" 2022-05-06 09:26:51 -04:00
JohnWilliams77
166c61c7c2 Update Secret Lair Drop Series.txt 2022-05-06 10:54:38 +01:00
Agetian
105f2ce1f3 Merge pull request #244 from tool4ever/lkifix
Update some older cards to use LKI correctly
2022-05-06 10:48:49 +03:00
AvacynAngel
b476797001 Create eldritch_pact.txt (#246)
* Create eldritch_pact.txt
2022-05-06 09:11:23 +02:00
TRT
16c3f4ed14 Card fix 2022-05-06 09:09:41 +02:00
Anthony Calosa
b8288c4e5a Merge pull request #260 from magpie514/master
Adventure mode - Revert RNG
2022-05-06 07:38:41 +08:00
Magpie
78345ade78 Adventure mode - Revert RNG
Better fix for yesterday's revert.
2022-05-06 01:33:51 +02:00
Anthony Calosa
920bb96a57 Merge pull request #257 from tool4ever/chooseCardsToDiscardFrom
Fix chooseCardsToDiscardFrom
2022-05-06 06:34:27 +08:00
tool4EvEr
11195e64d2 Fix chooseCardsToDiscardFrom 2022-05-05 22:13:30 +02:00
Agetian
42f98bd8da Merge pull request #211 from Northmoc/nccVazi
NCC: Vazi, Keen Negotiator and support
2022-05-05 22:39:26 +03:00
Northmoc
b5608929fc Merge pull request #245 from paulsnoops/rebalanced_05_may
Alchemy Rebalancing for May 5, 2022
2022-05-05 14:19:37 -04:00
paul_snoops
7a9e9b0e19 type_addition 2022-05-05 18:48:29 +01:00
Anthony Calosa
84e4cf5bd2 Merge pull request #250 from magpie514/master
Adventure mode - Revert RNG
2022-05-05 20:06:45 +08:00
Magpie
e931da7ce0 Adventure mode - Revert RNG
Fix call that assumed MyRandom.
2022-05-05 14:00:43 +02:00
Anthony Calosa
fe0215673b Merge pull request #249 from CCTV-1/master
update simplified chinese translation
2022-05-05 18:40:24 +08:00
Anthony Calosa
c6e8f1d23a Update RandomDeckGenerator.java 2022-05-05 18:36:28 +08:00
CCTV-1
0fbdd526a6 translate lblSelectCounterType and fix lblWinGameWithOutCasting translation. 2022-05-05 18:32:40 +08:00
Anthony Calosa
10af16aa1d [Mobile] Auto select AI Enabled Decks randomly
- Auto select AI Enabled Decks (non generated decks) when the Player Panel is AI and at least more than 10 AI Decks is available randomly
2022-05-05 18:16:54 +08:00
Anthony Calosa
631ae3f9e5 Merge pull request #248 from magpie514/master
Adventure mode - Revert RNG
2022-05-05 13:51:20 +08:00
Magpie
509ba33d8e Adventure mode - Revert RNG
Unused import.
2022-05-05 06:49:45 +02:00
Magpie
b0c545b039 Adventure mode - Revert RNG
This will revert shops back to being handled consistently. Temporary solution, most changes are code re-arranging to prepare for more drastic changes.
The current design for shops doesn't allow for easy dynamic changes, it's going to need to be rewritten so PointOfInterestChanges saves the type and current contents of a shop. As it is, a world will always generate a limited amount of cards unless that's done.
2022-05-05 06:41:18 +02:00
Anthony Calosa
9fd90a63c3 Merge pull request #247 from magpie514/master
Adventure mode - Support to restrict cards from random pool
2022-05-05 12:03:42 +08:00
Magpie
8251c08af0 Adventure mode - Support to restrict cards from random pool
For now we just restrict the Power Nine. They can still be dropped explicitly.
2022-05-05 05:08:31 +02:00
Anthony Calosa
47168e1de7 Update PeekAndRevealAi.java 2022-05-05 04:42:24 +08:00
paul_snoops
40028b77aa description fixes 2022-05-04 20:29:09 +01:00
paul_snoops
ebc12bed0f Alchemy Rebalancing for May 5, 2022 2022-05-04 19:36:39 +01:00
TRT
84bd068a06 Update some older cards to use LKI correctly 2022-05-04 15:51:08 +02:00
Agetian
50e068e047 Merge pull request #243 from tool4ever/cycle
Fix Cycle of Life
2022-05-04 13:15:40 +03:00
TRT
6096207a85 Fix Cycle of Life 2022-05-04 10:01:10 +02:00
Paul Hammerton
9e95c7d2c0 Merge pull request #242 from churrufli/master
Net Deck Archive Updates
2022-05-04 08:47:21 +01:00
churrufli
abb1127fc9 Update net-decks-archive-pauper.txt 2022-05-04 09:34:01 +02:00
Churrufli
ee13d15067 Net Deck Archive Updates 2022-05-04 09:26:41 +02:00
Anthony Calosa
f01b695805 Merge pull request #233 from AvacynAngel/patch-5
Create silvanuss_invoker.txt
2022-05-04 09:48:06 +08:00
Anthony Calosa
d96798edab Merge pull request #229 from Northmoc/ncc2
NCC: 2 May
2022-05-04 09:46:34 +08:00
Anthony Calosa
60f569682d Merge pull request #198 from Northmoc/refact
refactor some DigEffect + NoMove$ cards to PeekAndReveal
2022-05-04 08:43:06 +08:00
Anthony Calosa
c9a02a52fb Update zh-CN.properties 2022-05-04 08:38:46 +08:00
Anthony Calosa
843c54acec Update pt-BR.properties 2022-05-04 08:37:31 +08:00
Anthony Calosa
92c3e98a5a Update ja-JP.properties 2022-05-04 08:35:18 +08:00
Anthony Calosa
2123a8a4db Update it-IT.properties 2022-05-04 08:34:07 +08:00
Anthony Calosa
2c80d2aba3 Update de-DE.properties 2022-05-04 08:31:43 +08:00
Anthony Calosa
ebd0eb3abe Merge pull request #241 from mousep/update-translation-snc-ncc
Update card translations for SNC and NCC
2022-05-04 08:27:27 +08:00
Alumi
cf4707ead7 Update card translations for SNC and NCC 2022-05-04 08:14:49 +09:00
Anthony Calosa
ee516ae5c6 Merge pull request #239 from Northmoc/npe
ManaEffect > getStackDescription() negative restrictions
2022-05-04 06:40:54 +08:00
Northmoc
2298502eb9 Merge pull request #240 from tool4ever/boxing
Fix Boxing Ring
2022-05-03 17:17:33 -04:00
Northmoc
6ba4074362 refactor more DigEffect + NoMove$ cards to PeekAndReveal 2022-05-03 16:54:03 -04:00
Northmoc
2988499ff4 PS_HOU6.pzl refactor CustomScript 2022-05-03 16:53:33 -04:00
Northmoc
769ac2bf61 PeekAndRevealEffect > getStackDescription() more refinements 2022-05-03 16:53:10 -04:00
tool4EvEr
8b50f365a4 Fix Boxing Ring 2022-05-03 22:03:26 +02:00
Northmoc
aadc9c1693 more refactored cards 2022-05-03 15:44:23 -04:00
Northmoc
2bde63260a zurs_weirding.txt with PeekAndReveal 2022-05-03 15:44:23 -04:00
Northmoc
606926db24 lim_duls_vault.txt with PeekAndReveal 2022-05-03 15:44:23 -04:00
Northmoc
ae0cdcd5c4 PeekAndRevealEffect resolve() tweaks 2022-05-03 15:44:23 -04:00
Northmoc
1b5b05b26c aven_windreader.txt with PeekAndRevealEffect 2022-05-03 15:44:23 -04:00
Northmoc
0f20fe4c1c PumpEffect add "IfDesc" support to getStackDescription 2022-05-03 15:44:22 -04:00
Northmoc
0a66acfcbb lblLookingCardFrom for PeekAndRevealEffect 2022-05-03 15:44:22 -04:00
Northmoc
0612c2a35b PeekAndRevealEffect > getStackDescription() improvements 2022-05-03 15:44:22 -04:00
Northmoc
9d337f60c2 Deceiver cycle 2022-05-03 15:44:22 -04:00
Northmoc
0ea643bffe DrawEffect > getStackDescription$ add "NumCardsDesc" 2022-05-03 15:44:22 -04:00
Northmoc
8ca09b4d7e missing "." 2022-05-03 15:44:21 -04:00
Northmoc
ca66cd9caa druidic_satchel.txt with PeekAndRevealEffect 2022-05-03 15:44:21 -04:00
Northmoc
285fae8b72 interpret_the_signs.txt with PeekAndRevealEffect and NumCardsDesc$ 2022-05-03 15:44:21 -04:00
Northmoc
7e19c32b48 refactor Ripple to PeekAndReveal 2022-05-03 15:44:21 -04:00
Northmoc
5a90005622 refactor some DigEffect + NoMove$ cards to PeekAndReveal 2022-05-03 15:44:21 -04:00
Northmoc
468fc92606 "ManaFrom" check with matchesValidParam 2022-05-03 15:38:41 -04:00
Northmoc
d4fd068955 old cards with YouOwn 2022-05-03 15:38:23 -04:00
Northmoc
8aaaa9767b vazi_keen_negotiator.txt with YouOwn 2022-05-03 15:38:03 -04:00
AvacynAngel
8e409e6e3c Update silvanuss_invoker.txt
*Added ".YouCtrl" to line 5
*Added "you control" to TgtPrompt$
*Added DeckHas:Type$Elemental
^^ "DeckHas" I'm figuring goes above "Oracle" but wasn't 100%
2022-05-03 15:31:59 -04:00
Northmoc
2028306b8e ManaEffect > getStackDescription() negative restrictions 2022-05-03 15:17:56 -04:00
Anthony Calosa
5944ffca50 Merge pull request #238 from Northmoc/npe
ManaEffect > getStackDescription() try to dodge NPE
2022-05-04 03:03:02 +08:00
Northmoc
17d41ef165 ManaEffect > getStackDescription() try to dodge NPE 2022-05-03 14:41:45 -04:00
Paul Hammerton
5d3519e533 Merge pull request #235 from JohnWilliams77/patch-3
Update Secret Lair Drop Series.txt
2022-05-03 19:33:14 +01:00
JohnWilliams77
adc336a25a Update Secret Lair Drop Series.txt 2022-05-03 17:54:47 +01:00
Northmoc
387e2007d7 Merge pull request #215 from AvacynAngel/patch-4
Create tymoras_invoker.txt
2022-05-03 12:03:28 -04:00
Northmoc
ce9f13bbe8 Merge pull request #214 from AvacynAngel/patch-3
Create bhaals_invoker.txt
2022-05-03 12:02:25 -04:00
Northmoc
19c5fa069e Merge pull request #213 from AvacynAngel/patch-2
Create banes_invoker.txt
2022-05-03 12:01:22 -04:00
tool4ever
2198947c53 Fix misc stuff (#234) 2022-05-03 15:54:24 +00:00
AvacynAngel
1ba171d669 Create silvanuss_invoker.txt
*Tested 5/3/22
Untaps target land. Animates into an 8/8 Elemental Creature with Trample and Haste until end of turn
2022-05-03 11:09:44 -04:00
Agetian
cb6f28c741 Merge pull request #224 from Northmoc/nccMari
NCC: Mari, the Killing Quill and support
2022-05-03 16:48:12 +03:00
Northmoc
e28ce4b190 swindlers_scheme.txt with Defined 2022-05-03 09:28:22 -04:00
Agetian
f1a9d542d5 Merge pull request #225 from Northmoc/nccBoxing
NCC: Boxing Ring and support
2022-05-03 10:12:34 +03:00
Anthony Calosa
f5f46ebc9b Merge pull request #222 from tool4ever/fixsac
Sheltering Ancient: fix upkeep cost
2022-05-03 12:06:39 +08:00
Anthony Calosa
56413e2c4b Merge pull request #206 from tool4ever/ascendant
Fix Ascendant Acolyte
2022-05-03 12:05:31 +08:00
Anthony Calosa
a48769fc95 Merge pull request #231 from kevlahnota/master
[Adventure] disables Advanced Search
2022-05-03 11:51:49 +08:00
Anthony Calosa
9a61ac45f4 [Adventure] disables Advanced Search
- needs GUI Layout for Landscape mode
- needs override onclose event on internal EditScreen on AdvancedSearchFilter
2022-05-03 11:50:44 +08:00
AvacynAngel
047995c801 Update tymoras_invoker.txt
*Added "PrecostDesc$ Sleight of Hand - "
2022-05-02 20:44:18 -04:00
AvacynAngel
65f3275419 Update bhaals_invoker.txt
*Removed "ValidTgts$ and "TgtPrompt$"
*Added "Defined$ Opponent"
*Added "PrecostDesc$ Scorching Ray - "
2022-05-02 20:38:14 -04:00
AvacynAngel
42ee46a9e7 Update banes_invoker.txt
*Added space before "KW$ Flying"
*Added "Select up to two target creatures" to TgtPrompt$
*Added "|PrecostDesc$ Wind Walk - |" after TgtPrompt$ and before SpellDescription$
2022-05-02 20:17:46 -04:00
Northmoc
568251df4a threefold_signal.txt 2022-05-02 19:57:37 -04:00
Northmoc
f0aa990aba Replicate TriggerDesc add "." 2022-05-02 19:57:20 -04:00
Northmoc
7d9378f73f swindlers_scheme.txt 2022-05-02 18:59:14 -04:00
Northmoc
967122d829 spiteful_repossession.txt 2022-05-02 18:59:13 -04:00
Northmoc
b36bb7a39f boxing_ring.txt 2022-05-02 17:33:32 -04:00
Northmoc
9dcbc956d8 Card.java add foughtThisTurn and cleanup 2022-05-02 17:33:21 -04:00
Northmoc
9ad1d67539 CardProperty "FoughtThisTurn" 2022-05-02 17:32:55 -04:00
Northmoc
803f3d2d9f FightEffect + setFoughtThisTurn 2022-05-02 17:32:36 -04:00
Northmoc
ae417d0200 etrata_the_silencer.txt tidy up 2022-05-02 17:02:25 -04:00
Northmoc
3595e2bd26 CountersRemoveEffect support "Choices" for Mari 2022-05-02 16:56:25 -04:00
Northmoc
b9fb174457 mari_the_killing_quill.txt 2022-05-02 16:56:01 -04:00
tool4EvEr
f76f466c39 Sheltering Ancient: fix upkeep cost 2022-05-02 22:00:45 +02:00
JohnWilliams77
c2bd4c1a4e Update Secret Lair Drop Series.txt (#220) 2022-05-02 19:05:44 +00:00
Northmoc
7910b23a02 Player remove getNumTokensCreatedThisTurn() 2022-05-02 15:03:03 -04:00
Northmoc
78ba4d9ef6 akim_the_soaring_wind.txt tidy only 2022-05-02 15:02:11 -04:00
Northmoc
1a388f6e6b AbilityUtils remove "TokensCreatedThisTurn" 2022-05-02 15:01:51 -04:00
Northmoc
5e9734ec59 refactor old TokensCreatedThisTurn cards 2022-05-02 14:35:45 -04:00
Northmoc
9646771d4c TriggerSpellAbilityCastOrCopy "ManaFrom" use isValid() check 2022-05-02 14:35:45 -04:00
Northmoc
616324d364 TriggerSpellAbilityCastOrCopy support "ManaFrom" 2022-05-02 14:35:44 -04:00
Northmoc
b7293467aa vazi_keen_negotiator.txt 2022-05-02 14:35:44 -04:00
Northmoc
50d015bbb5 TokenEffect > getStackDescription() try to catch more possibilities 2022-05-02 14:34:06 -04:00
TRT
fca6bf245e Update more cards 2022-05-02 18:20:18 +02:00
AvacynAngel
3bed91cba2 Create tymoras_invoker.txt 2022-05-02 11:49:20 -04:00
AvacynAngel
68a6fafa2b Update banes_invoker.txt 2022-05-02 11:47:15 -04:00
AvacynAngel
402835ee52 Create bhaals_invoker.txt 2022-05-02 11:45:44 -04:00
AvacynAngel
aa6e96284f Create banes_invoker.txt 2022-05-02 11:43:29 -04:00
Agetian
a7f88d97bb Merge pull request #208 from Northmoc/npeTweaks
NPE tweaks
2022-05-02 17:27:28 +03:00
TRT
07e9d46c87 Clean up cards 2022-05-02 16:08:45 +02:00
Northmoc
eb88c12d6d DigEffect remove unneeded () 2022-05-02 09:46:21 -04:00
Northmoc
1f1fdeabe4 CopyPermanentEffect > getStackDescription() "AddTriggers" try to avoid NPE 2022-05-02 09:45:47 -04:00
TRT
d40439b2ec Valid check replacement 2022-05-02 15:18:36 +02:00
Agetian
13dc89c3a2 Merge pull request #186 from tool4ever/questdeckfix
Fix duplicating cards in quest mode
2022-05-02 16:12:55 +03:00
TRT
1988d802f0 Fix Ascendant Acolyte 2022-05-02 14:44:01 +02:00
Anthony Calosa
0a50d4bbde Merge pull request #203 from tool4ever/totalcounters
Cleanup Count$TotalCounters
2022-05-02 19:44:41 +08:00
TRT
0fb56e9bc0 Cleanup Count$TotalCounters 2022-05-02 10:59:59 +02:00
Anthony Calosa
8d85df8b63 Merge pull request #202 from tool4ever/arix
GameAction: Add ETB counters before checking statics
2022-05-02 16:58:11 +08:00
TRT
4c7af60445 Add ETB counters before checking statics 2022-05-02 10:19:59 +02:00
Agetian
ff46dc4520 Merge pull request #195 from Northmoc/nccPerrie
NCC: Perrie and support
2022-05-02 07:38:54 +03:00
Agetian
b03c64b110 Merge pull request #200 from Northmoc/nccKros
NCC: kros_defense_contractor.txt
2022-05-02 07:37:32 +03:00
Anthony Calosa
0b5b3ef756 Merge pull request #201 from magpie514/master
Adventure mode - Add doppelganger enemy
2022-05-02 12:04:26 +08:00
Magpie
640a1029f7 Adventure mode - Add doppelganger enemy
Rare overworld spawn, uses same deck as player and can grant a duplicate.
2022-05-02 05:41:04 +02:00
Northmoc
520306da7e kros_defense_contractor.txt 2022-05-01 21:43:29 -04:00
Anthony Calosa
b722aa7b4c Merge pull request #199 from TrueFuFLeaderG/adventure
Adventure: fixed shopList attribute in towns
2022-05-02 06:30:40 +08:00
Grimm
dc24da9c2e Adventure: fixed shopList attribute in towns 2022-05-02 00:24:22 +02:00
Anthony Calosa
b2164a239a Merge pull request #191 from Northmoc/ncc_1
NCC: 1 May
2022-05-02 06:04:15 +08:00
Anthony Calosa
7b4c73ed07 Merge pull request #188 from Northmoc/snc_1
SNC: 1 May
2022-05-02 06:03:26 +08:00
Anthony Calosa
53655d8621 Merge pull request #185 from Card-Forge/attachSpellAbility
canEquip checks for SpellAbility
2022-05-02 06:01:14 +08:00
Anthony Calosa
32d0f83fbf Merge pull request #190 from Northmoc/stackdesc
DigEffect getStackDescription() improvements + some card tweaks
2022-05-02 05:59:39 +08:00
Northmoc
cfc10681ba AbilityUtils > xCount() support "DifferentCounterKinds_" 2022-05-01 17:20:12 -04:00
Northmoc
477d62ec92 perrie_the_pulverizer.txt 2022-05-01 17:19:24 -04:00
Northmoc
d08e925e9b kitt_kanto_mayhem_diva.txt 2022-05-01 16:51:27 -04:00
Northmoc
f8b6c61b0d phabine_bosss_confidant.txt 2022-05-01 16:51:17 -04:00
Northmoc
8a684f7d43 prismatic_vista.txt + ChangeTypeDesc$, AIhint 2022-05-01 16:24:43 -04:00
Northmoc
e4cb860d3a eye_of_ugin.txt + ChangeTypeDesc$, AIhints 2022-05-01 16:14:29 -04:00
Northmoc
ce4d308610 DigEffect > getStackDescription() improvements 2022-05-01 16:13:49 -04:00
Northmoc
20d0082822 narset_parter_of_veils.txt + ChangeValidDesc$ 2022-05-01 16:13:01 -04:00
Northmoc
3c222937fc once_upon_a_time.txt + ChangeValidDesc$ 2022-05-01 16:06:51 -04:00
Agetian
843c03899b Merge pull request #187 from tool4ever/exalted
Remove duplicate exalted calculation
2022-05-01 19:03:24 +03:00
tool4EvEr
e0f1a47037 Remove duplicate exalted calculation 2022-05-01 17:29:44 +02:00
tool4EvEr
b41d76749c Fix duplicating cards in quest mode 2022-05-01 17:04:52 +02:00
Northmoc
07bae8be9f pyre_sledge_arsonist.txt 2022-05-01 10:57:42 -04:00
Northmoc
266a905515 citizens_crowbar.txt (thanks Snoops) 2022-05-01 10:57:33 -04:00
tool4ever
8f883168fa Always change zone of cards when they move (#176)
* Always change zone of cards when they move

* Update Zone.java

* update check

* Clean up

Co-authored-by: tool4EvEr <tool4EvEr@192.168.0.59>
Co-authored-by: Anthony Calosa <anthonycalosa@gmail.com>
2022-05-01 14:43:00 +00:00
Paul Hammerton
039f9da107 Merge pull request #179 from Northmoc/sncHoldForRansom
SNC: Hold for Ransom and support
2022-05-01 14:39:05 +01:00
Agetian
78cd586245 Merge pull request #182 from Northmoc/snc30
SNC: 30 April
2022-05-01 16:38:14 +03:00
Northmoc
622752a96b structural_assault.txt add Oracle tag 2022-05-01 07:47:11 -04:00
Northmoc
3529e176ff soul_of_emancipation.txt fix AI tag 2022-05-01 07:46:59 -04:00
Hans Mackowiak
99c10761d1 canEquip checks for SpellAbility 2022-05-01 13:29:58 +02:00
Agetian
71464684ff Merge pull request #184 from tool4ever/hideRandom
Random patch for Hideaway and some cards
2022-05-01 11:54:04 +03:00
tool4EvEr
23da591ff2 Fix scripts 2022-05-01 10:39:22 +02:00
tool4EvEr
e0643ac0ec Hideaway RestRandomOrder 2022-05-01 10:38:56 +02:00
Anthony Calosa
8bfbbe4008 Merge pull request #183 from Northmoc/fix2
More card tweaks
2022-05-01 13:14:56 +08:00
Anthony Calosa
9b714c1e12 Update DeckGeneratorBase.java
fix deck gen not getting duals
2022-05-01 13:04:39 +08:00
Northmoc
34e36498e7 CounterEnumType Stash counters 2022-04-30 22:56:52 -04:00
Northmoc
500548fd24 drowsing_tyrannodon.txt with IsPresent 2022-04-30 22:56:30 -04:00
Northmoc
ba2e3be845 mysterious_limousine.txt use ChangeZoneAll 2022-04-30 22:56:14 -04:00
Northmoc
b2ca13d1f9 soul_of_emancipation.txt (thanks Marvel) 2022-04-30 22:49:58 -04:00
Northmoc
8e75620cca glittering_stockpile.txt (thanks Marvel) 2022-04-30 22:45:33 -04:00
Northmoc
3ecee0c381 structural_assault.txt 2022-04-30 22:42:45 -04:00
Northmoc
6e733af98f backstreet_bruiser.txt 2022-04-30 22:16:18 -04:00
Anthony Calosa
655928d93d Merge pull request #181 from Northmoc/fix
Fix some Rhino stuff
2022-05-01 10:03:28 +08:00
Northmoc
4601e7a7ee SacrificeAllEffect expand getStackDesc 2022-04-30 21:58:10 -04:00
Northmoc
da53a114e7 hold_for_ransom.txt with SacrificeAll 2022-04-30 21:58:10 -04:00
Northmoc
7554d6e9cf rhox_pummeler.txt fix cost and power 2022-04-30 21:56:03 -04:00
Northmoc
117a294539 crashing_footfalls.txt add Colors 2022-04-30 21:55:15 -04:00
Anthony Calosa
fb1ca3461a Update crashing_footfalls.txt 2022-05-01 09:41:58 +08:00
Anthony Calosa
1d58dafa9a Merge pull request #180 from Card-Forge/kevlahnota-patch-revert
revert to prevent crash
2022-05-01 08:31:21 +08:00
Anthony Calosa
dcf820e18d Update sanctum_guardian.txt 2022-05-01 08:28:57 +08:00
Anthony Calosa
76cdd02f7e Update martyrs_cause.txt 2022-05-01 08:27:30 +08:00
Agetian
6bc82564c6 Merge pull request #172 from Northmoc/snc_29
SNC: 29 Apr
2022-04-30 16:38:28 +03:00
Northmoc
3004b0c6ac corpse_explosion.txt add AI 2022-04-30 08:57:04 -04:00
Northmoc
e972b6de47 rogues_gallery.txt +LockInText 2022-04-30 08:57:04 -04:00
Northmoc
1c214e733a structural_assault.txt untested 2022-04-30 08:56:03 -04:00
Northmoc
7afb67a7da hold_for_ransom.txt 2022-04-30 08:40:26 -04:00
Northmoc
ef1781d261 AbilityUtils getDefinedPlayers() support of "OriginalHost" stuff 2022-04-30 08:39:52 -04:00
Northmoc
ef6afbeba1 hold_for_ransom.txt 2022-04-30 08:27:55 -04:00
Agetian
d4d8dc2aac Merge pull request #174 from Northmoc/clean
all_suns_dawn.txt tidy up
2022-04-30 15:14:18 +03:00
Northmoc
e68b4050ed all_suns_dawn.txt add LockInText$ True 2022-04-30 08:11:52 -04:00
tool4ever
040a7e5d03 Merge pull request #177 from paulsnoops/card_fix
SNC card fixes
2022-04-30 10:14:42 +00:00
Agetian
6ddb650e49 Merge pull request #178 from tool4ever/aiattack
Improve AI attack requirements
2022-04-30 13:11:27 +03:00
tool4EvEr
b2f05b51c9 NPE fix 2022-04-30 12:05:59 +02:00
tool4EvEr
1e67365ca0 Improve AI attack requirements 2022-04-30 12:00:06 +02:00
paul_snoops
423c55a3e3 sparas_adjudicators 2022-04-30 10:14:29 +01:00
paul_snoops
81584e4dc9 SNC card fixes 2022-04-30 09:52:44 +01:00
Agetian
b1f405332f Merge pull request #171 from paulsnoops/snc_29_april
SNC - 29 April
2022-04-30 08:26:29 +03:00
Agetian
b6e5c74c1a Merge pull request #173 from Northmoc/costexile
CostExile > toString() improve
2022-04-30 08:25:15 +03:00
Agetian
aad6ffc897 Merge pull request #175 from tool4ever/unlessCost
willPayUnlessCost: teach AI to refuse if Uncounterable already prevents it
2022-04-29 20:52:52 +03:00
paul_snoops
e04085b859 fake_your_own_death fix 2022-04-29 17:40:22 +01:00
tool4EvEr
30dda88adb Clean up 2022-04-29 18:13:14 +02:00
tool4EvEr
52f1dac9c9 willPayUnlessCost: teach AI to refuse if Uncounterable prevents it 2022-04-29 18:09:49 +02:00
paul_snoops
0cca50c934 fake_your_own_death and sewer_crocodile 2022-04-29 16:37:50 +01:00
Northmoc
4877c24e8e restore import IntelliJ said was unneeded :( 2022-04-29 11:00:01 -04:00
Northmoc
a1dd01356c all_suns_dawn.txt tidy up 2022-04-29 10:56:03 -04:00
paul_snoops
c550c4f6c4 fixes 2022-04-29 15:54:15 +01:00
Northmoc
62ff002ffb CostExile > toString() improve 2022-04-29 10:50:27 -04:00
paul_snoops
31fe583ef9 sticky_fingers fix 2022-04-29 14:43:18 +01:00
Hans Mackowiak
8aa798d57d Rename brokers_ascendacy.txt to brokers_ascendancy.txt 2022-04-29 15:08:51 +02:00
paul_snoops
4fe2b9f397 sticky_fingers and fixes 2022-04-29 13:27:16 +01:00
Northmoc
6d9b59074f rogues_gallery.txt 2022-04-29 08:24:48 -04:00
Northmoc
74256f6b25 corpse_explosion.txt 2022-04-29 08:17:17 -04:00
paul_snoops
f54c52e142 WIP: SNC - 29 April 2022-04-29 12:48:46 +01:00
Anthony Calosa
12f6461906 Merge pull request #168 from kevlahnota/master
prevent Crash of FatPack generation
2022-04-29 08:05:44 +08:00
Anthony Calosa
05c8cf112a update check 2022-04-29 07:55:20 +08:00
Anthony Calosa
38027e2441 additional FatPack check 2022-04-29 07:48:42 +08:00
Anthony Calosa
a3172d3275 Merge pull request #166 from tool4ever/esix2
Fix Esix replacing too much from other effects
2022-04-29 05:41:58 +08:00
Anthony Calosa
35940cd409 Merge pull request #167 from kevlahnota/master
fix stackoverflow
2022-04-29 05:38:19 +08:00
Anthony Calosa
2cd6d7a047 fix stackoverflow 2022-04-29 05:33:53 +08:00
Anthony Calosa
6429c94fa9 fix stackoverflow 2022-04-29 05:31:20 +08:00
tool4EvEr
7d0f5a1944 Fix Esix replacing too much from other effects 2022-04-28 20:28:35 +02:00
Anthony Calosa
d118be7ee4 Merge pull request #74 from Northmoc/shieldscripts
SNC: Card scripts using new shield counters
2022-04-29 00:00:08 +08:00
Anthony Calosa
b3aa288687 Merge pull request #158 from Northmoc/sncDuskMangler
SNC: Dusk Mangler and support
2022-04-28 23:57:58 +08:00
Anthony Calosa
09aeae1558 Merge pull request #120 from tool4ever/encore
Refactor Encore & Myriad
2022-04-28 23:56:12 +08:00
Anthony Calosa
f2d6c19d1d Update CONTRIBUTORS.txt 2022-04-28 23:49:55 +08:00
tool4ever
b290ca03fd Merge pull request #161 from Northmoc/snc_27
SNC: 27 April
2022-04-28 14:21:56 +00:00
Northmoc
72afbe8b85 Merge pull request #163 from Northmoc/typo
Crew StackDesc remove random )
2022-04-28 10:16:47 -04:00
Northmoc
6a7f46a102 balan_wandering_knight.txt with IsPresent 2022-04-28 10:14:41 -04:00
Northmoc
c06c39ee6f celestial_regulator.txt 2022-04-28 10:14:41 -04:00
Northmoc
35f003f759 endless_detour.txt (thanks medusa) 2022-04-28 10:14:39 -04:00
Northmoc
92ea874fe4 even_the_score.txt (thanks Marvel) 2022-04-28 10:14:38 -04:00
Northmoc
b0aee6a5ca patch_up.txt 2022-04-28 10:14:38 -04:00
Northmoc
85fbede135 brass_knuckles.txt 2022-04-28 10:14:37 -04:00
Northmoc
392c37cfdf riveteers_ascendancy.txt 2022-04-28 10:14:37 -04:00
Northmoc
72ed9896f7 sleep_with_the_fishes.txt 2022-04-28 10:14:36 -04:00
Northmoc
0e98462096 reservoir_kraken.txt 2022-04-28 10:14:36 -04:00
Northmoc
8ec27032e6 shadow_of_mortality.txt 2022-04-28 10:14:35 -04:00
Northmoc
481e701387 cabaretti_ascendancy.txt 2022-04-28 10:14:34 -04:00
Northmoc
d6d3da2c60 hoard_hauler.txt 2022-04-28 10:14:33 -04:00
Northmoc
b53ed7ade5 freelance_muscle.txt 2022-04-28 10:14:33 -04:00
Northmoc
9275f53d26 syndicate_infiltrator.txt 2022-04-28 10:14:32 -04:00
Northmoc
b77dcaf3a8 call_in_a_professional.txt 2022-04-28 10:14:32 -04:00
Northmoc
d2e6281222 fatal_grudge.txt 2022-04-28 10:14:31 -04:00
Northmoc
648b051a3b sanguine_spy.txt 2022-04-28 10:14:30 -04:00
Northmoc
ce28f04bc6 Card.java remove unneeded () 2022-04-28 10:06:59 -04:00
Northmoc
1ce726b8ef GameActionUtil streamline more 2022-04-28 10:06:38 -04:00
Northmoc
7e61af3dca dusk_mangler.txt 2022-04-28 10:00:05 -04:00
Northmoc
b6f15cf1d7 GameActionUtil > getAdditionalCostSpell() "AlternativeAdditionalCost" refactor to support more than two possible costs 2022-04-28 10:00:03 -04:00
Northmoc
86d1f54830 Cost > IsOnlyManaCost() clarify comment 2022-04-28 10:00:02 -04:00
Northmoc
f859bd951a Card > keywordsToText "AlternativeAdditionalCost" refactor to support more than two possible costs 2022-04-28 10:00:01 -04:00
Chris H
8aa7f958de Merge pull request #164 from tool4ever/brilliant
Brilliant Ultimatum without RepeatEach
2022-04-28 09:04:12 -04:00
tool4ever
f21b202237 Merge pull request #162 from Northmoc/sncZiatorasEnvoy
SNC: Ziatora's Envoy and PlayEffect Support
2022-04-28 12:29:10 +00:00
Northmoc
aa0fd6799c ziatoras_envoy.txt streamline 2022-04-28 08:22:07 -04:00
Northmoc
3d3e8d3a0b Crew remove RemoveCardTypes$ 2022-04-28 07:41:28 -04:00
Northmoc
dc309f57bf Crew remove StackDescription$ 2022-04-28 07:38:42 -04:00
TRT
c426a6b270 Brilliant Ultimatum without RepeatEach 2022-04-28 09:39:00 +02:00
Northmoc
cac6321539 Crew StackDesc remove random ) 2022-04-28 00:21:09 -04:00
Northmoc
66a23715b9 old cards "ForgetTargetRemembered" -> "ForgetPlayed" 2022-04-28 00:09:56 -04:00
Northmoc
0e24e1cd63 PlayEffect: "ForgetTargetRemember" -> "ForgetPlayed", add this to land plays as well 2022-04-28 00:09:21 -04:00
Northmoc
a4f3d44ac7 ziatoras_envoy.txt 2022-04-28 00:08:20 -04:00
Northmoc
68e54eec31 Merge pull request #159 from TheBlackMarvel/master
NCC Bennie Bracks, Zoologist
2022-04-27 23:04:23 -04:00
Northmoc
47859a9119 Merge branch 'master' into shieldscripts 2022-04-27 16:36:31 -04:00
TheBlackMarvel
9ecc5f6f3c Update bennie_bracks_zoologist.txt 2022-04-27 21:36:12 +01:00
Northmoc
4ace4b89f6 Shield -> SHIELD 2022-04-27 15:59:06 -04:00
TheBlackMarvel
9af60bcccf Merge branch 'Card-Forge:master' into master 2022-04-27 20:02:35 +01:00
Agetian
6bc28b894f Merge pull request #148 from Northmoc/sncSchemingFence
SNC: Scheming Fence and support
2022-04-27 20:54:35 +03:00
Northmoc
30f2df407a scheming_fence.txt add static trigger 2022-04-27 11:58:13 -04:00
Northmoc
f99aae23f0 Merge pull request #154 from Agetian/shadow-stinger
Fix Shadow Stinger tap cost restriction.
2022-04-27 10:26:56 -04:00
Northmoc
c0ed6f9f4e Merge pull request #131 from TheBlackMarvel/TheBlackMarvel-patch-3
NCC Resourceful Defense
2022-04-27 10:21:11 -04:00
Northmoc
aaa35aefb6 streamline if clause 2022-04-27 10:19:02 -04:00
Agetian
f902057c2f Merge pull request #153 from Northmoc/snc26
park_heights_pegasus.txt
2022-04-27 15:55:07 +03:00
Northmoc
4a8b4c6e32 park_heights_pegasus.txt fix Oracle 2022-04-27 08:50:46 -04:00
TheBlackMarvel
9b165377ce Merge pull request #4 from TheBlackMarvel/TheBlackMarvel-patch-6
NCC Bennie Bracks, Zoologist
2022-04-27 13:14:10 +01:00
TheBlackMarvel
65d6aed6fa Add files via upload 2022-04-27 13:09:14 +01:00
TheBlackMarvel
166903b156 Merge pull request #2 from TheBlackMarvel/TheBlackMarvel-patch-3
NCC Bennie Bracks, Zoologist
2022-04-27 11:50:52 +01:00
TRT
ee3f3b1844 Clean up 2022-04-27 12:20:59 +02:00
TRT
82c54290ff ReplaceToken: don't merge amounts 2022-04-27 10:47:06 +02:00
TheBlackMarvel
a6f3fadc2c Update resourceful_defense.txt 2022-04-27 08:37:28 +01:00
Agetian
62dfd23d75 Merge pull request #156 from Agetian/master
Added puzzle PS_NEO5.
2022-04-27 09:37:43 +03:00
Michael Kamensky
9e5f2a8577 - Added puzzle PS_NEO5. 2022-04-27 08:16:17 +03:00
Agetian
5d2f5286c3 Merge pull request #150 from Card-Forge/shieldCounterEffect
Card: add Shield counter Replacement Effects
2022-04-27 07:56:07 +03:00
Michael Kamensky
cc78a8623d - Fix Shadow Stinger tap cost restriction. 2022-04-27 07:44:53 +03:00
Agetian
31f5621fee Merge pull request #152 from Northmoc/sncJinnieFay
SNC: Jinnie Fay, Jetmir's Second and support
2022-04-27 06:42:29 +03:00
Northmoc
905c6a1dc2 park_heights_pegasus.txt 2022-04-26 21:25:32 -04:00
Northmoc
5813942580 ReplaceTokenEffect allow looking back for RootAbility 2022-04-26 21:15:23 -04:00
Northmoc
61bad0c6c0 jinnie_fay_jetmirs_second.txt and tokens 2022-04-26 21:14:14 -04:00
Northmoc
22167df44a scheming_fence.txt "GainsLoyaltyAbilities" -> "GainsValidAbilities" 2022-04-26 17:54:40 -04:00
Northmoc
06ac9114fc old cards "GainsLoyaltyAbilities" -> "GainsValidAbilities" 2022-04-26 17:54:11 -04:00
Northmoc
818afe5058 StaticAbilityContinuous: "GainsLoyaltyAbilities" -> "GainsValidAbilities" 2022-04-26 17:53:50 -04:00
Paul Hammerton
1bb8dcd32e Merge pull request #151 from paulsnoops/various_fixes
Update SNC draft ranking and various edition/format fixes
2022-04-26 22:29:59 +01:00
paul_snoops
4eb64b919d Update SNC draft ranking and various edition/format fixes 2022-04-26 22:26:07 +01:00
Hans Mackowiak
dbecd2dabd Card: add shield counter replacement Effects, but kinda hidden 2022-04-26 21:20:33 +02:00
tool4EvEr
6afc18ff5f Encore: switch to effect 2022-04-26 19:18:53 +02:00
Agetian
379869c817 Merge pull request #149 from Agetian/master
Added SNC/NCC achievements by Marek14.
2022-04-26 18:57:37 +03:00
Michael Kamensky
5a46e95699 - Added SNC/NCC achievements by Marek14. 2022-04-26 18:48:55 +03:00
Northmoc
ee071ed703 scheming_fence.txt AI and Desc 2022-04-26 11:35:22 -04:00
tool4EvEr
451fdf47d0 Fix imports 2022-04-26 17:30:04 +02:00
tool4EvEr
9249ac7854 Clean up 2022-04-26 17:23:51 +02:00
TheBlackMarvel
34ccb34b1e Update resourceful_defense.txt 2022-04-26 15:58:13 +01:00
Northmoc
970d88d2f1 ComputerUtilMana support "ActivateIgnoreColor" 2022-04-26 10:56:30 -04:00
Northmoc
b4b607bf49 HumanPlaySpellAbility support "ActivateIgnoreColor" 2022-04-26 10:56:08 -04:00
Northmoc
67158c56b4 StaticAbilityContinuous support "GainsAbilitiesActivateIgnoreColor" 2022-04-26 10:55:27 -04:00
Northmoc
9dd33cadfc scheming_fence.txt 2022-04-26 10:54:50 -04:00
Agetian
1e901fb632 Merge pull request #141 from Northmoc/snc_25
SNC: 25 April
2022-04-26 15:53:57 +03:00
Northmoc
37648b46db Merge pull request #138 from Northmoc/snc_ab_ql
Arcane Bombardment - quality of life
2022-04-26 08:34:19 -04:00
Northmoc
114e2f80ce unlicensed_hearse.txt fix 2022-04-26 08:25:47 -04:00
Northmoc
ab3e15f5d0 rocco_cabaretti_caterer.txt wasCastByYou 2022-04-26 08:25:04 -04:00
tool4EvEr
b3cc66422c Remove unneeded 2022-04-26 12:53:41 +02:00
tool4EvEr
65ec73c803 Rebase Encore 2022-04-26 12:39:14 +02:00
tool4ever
1d6757e4c9 Merge branch 'Card-Forge:master' into encore 2022-04-26 10:21:51 +00:00
tool4ever
be4fa07d70 Merge pull request #127 from tool4ever/controllerduration
Improve DelayedTrigger for effects with "during its controller's next turn"
2022-04-26 10:03:31 +00:00
tool4EvEr
771d9c5ade Fix effects 2022-04-26 12:03:49 +02:00
tool4EvEr
95cec4e49b Duration fix 2022-04-26 11:06:49 +02:00
tool4EvEr
a12041ee8b Fix unplayable AI info not displaying card 2022-04-26 11:06:48 +02:00
tool4EvEr
6c0b1c8ba8 Fix another crash (Lukka, Wayward Bonder) 2022-04-26 11:06:47 +02:00
tool4EvEr
1adc9a9129 Improve DelayedTrigger for effects "during its controller's next turn" 2022-04-26 11:06:46 +02:00
tool4EvEr
7512986748 Fix crash with transformed PW 2022-04-26 11:06:45 +02:00
tool4EvEr
3faf43c65a Fix AI error 2022-04-26 11:05:00 +02:00
tool4EvEr
cf22f5c55c Daghatar without Pump 2022-04-26 11:04:45 +02:00
Agetian
4b3de7d9d8 Merge pull request #146 from tool4ever/leech
Cleanup Leech Bonder
2022-04-26 12:00:02 +03:00
Paul Hammerton
37dda460a8 Merge pull request #147 from paulsnoops/explorer_net_decks
Support for Explorer Net Decks
2022-04-26 09:55:09 +01:00
tool4EvEr
8cdb379489 Improve TgtPrompt 2022-04-26 10:52:12 +02:00
paul_snoops
1517b392ac Support for Explorer Net Decks 2022-04-26 09:49:33 +01:00
TheBlackMarvel
dde867b19d Update resourceful_defense.txt 2022-04-26 09:29:25 +01:00
TheBlackMarvel
f17f7ec8d1 Update resourceful_defense.txt 2022-04-26 09:25:09 +01:00
tool4EvEr
c449d55327 Cleanup Leech Bonder 2022-04-26 09:20:45 +02:00
TheBlackMarvel
16762d9f71 Update resourceful_defense.txt 2022-04-26 08:19:46 +01:00
tool4ever
7babc8709d Merge pull request #140 from Northmoc/tweaks
Old card tweaks
2022-04-26 07:16:59 +00:00
tool4ever
9d23620486 Merge pull request #145 from tool4ever/fixattack
Fix StaticAbilityMustAttack
2022-04-26 06:41:08 +00:00
tool4EvEr
e54ef18f00 Fix StaticAbilityMustAttack 2022-04-26 08:41:15 +02:00
Northmoc
42a4d9caba unlicensed_hearse.txt (thanks medusa) 2022-04-25 21:57:10 -04:00
Northmoc
b4ab85b7f7 rocco_cabaretti_caterer.txt 2022-04-25 21:56:51 -04:00
Northmoc
0ca0ee7d1f obscura_ascendancy.txt and token 2022-04-25 21:56:51 -04:00
Northmoc
68d69640a7 alliance_of_arms.txt remove unused SVar 2022-04-25 21:54:10 -04:00
Northmoc
94ea6c9a11 tidal_influence.txt 2022-04-25 21:53:48 -04:00
tool4ever
d8ecce8760 Merge branch 'master' into encore 2022-04-25 20:45:52 +00:00
Northmoc
6b6636deb3 PlayerControllerHuman: support "OrderDuplicates" on sa 2022-04-25 11:23:29 -04:00
Northmoc
b76061f6d8 arcane_bombardment.txt add "OrderDuplicates" to Trigger 2022-04-25 11:22:59 -04:00
Northmoc
7eb8a437b5 HumanPlay addExiledCard for Delve 2022-04-25 11:22:14 -04:00
Northmoc
05df77cdbe GameState addExiledCard for Exiled with X 2022-04-25 11:21:54 -04:00
Northmoc
8d42c07608 add Exiled list to Card Detail 2022-04-25 11:20:20 -04:00
Northmoc
df67f8c2c6 CardView add getExiledCards() 2022-04-25 11:19:51 -04:00
Northmoc
b895e283a8 include addExiledCard command in Costs that exile cards 2022-04-25 11:19:14 -04:00
Northmoc
fa3f7715a3 Card.java: add exiledCards CardCollection 2022-04-25 11:13:19 -04:00
Northmoc
69377622cc include addExiledCard command in Effects that exile cards 2022-04-25 11:12:16 -04:00
Northmoc
6f7dc38f2a TrackableProperty.java add ExiledCards 2022-04-25 11:10:56 -04:00
TheBlackMarvel
7a56870184 Update resourceful_defense.txt 2022-04-25 08:24:23 +01:00
Northmoc
cec02f3579 titan_of_industry.txt 2022-04-22 19:17:58 -04:00
Northmoc
b925effd22 rigo_streetwise_mentor.txt 2022-04-22 19:17:58 -04:00
Northmoc
59dcc44ea6 undercover_operative.txt 2022-04-22 19:17:57 -04:00
Northmoc
03a231ec54 easy shield counter cards 2022-04-22 19:17:57 -04:00
Northmoc
9d695615c4 disciplined_duelist.txt 2022-04-22 19:17:56 -04:00
Northmoc
85ac95caf3 sanctuary_warden.txt (thanks Marvel) 2022-04-22 19:17:55 -04:00
Northmoc
c96f145246 falco_spara_pactweaver.txt 2022-04-22 19:17:55 -04:00
TheBlackMarvel
9036d7ae8a Add files via upload 2022-04-22 14:51:45 +01:00
TRT
a4504d5d38 Refine comment 2022-02-01 10:50:52 +01:00
TRT
87556f8e9e CloneStates: skip set traits when replacing 2022-02-01 10:50:51 +01:00
TRT
eaaa513096 Refactor Myriad 2022-02-01 10:50:49 +01:00
TRT
a2e64e2abd ReplaceToken: don't merge amounts with ForEach 2022-02-01 10:50:47 +01:00
tool4EvEr
4fd4fa37b3 Fix replaced tokens not gaining additional traits 2022-02-01 10:50:45 +01:00
tool4EvEr
052c82545f Fix ReplaceToken 2022-02-01 10:50:43 +01:00
tool4EvEr
6f9de43e51 Clean up 2022-02-01 10:50:41 +01:00
tool4EvEr
5d9f17f71c Refactor Encore & Myriad 2022-02-01 10:50:38 +01:00
1422 changed files with 15669 additions and 3568 deletions

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>forge</artifactId>
<groupId>forge</groupId>
<version>1.6.50-SNAPSHOT</version>
<version>1.6.51</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -119,10 +119,26 @@
<opts>
<opt>-Dfile.encoding=UTF-8</opt>
<opt>--add-opens java.base/java.lang=ALL-UNNAMED</opt>
<opt>--add-opens java.base/java.math=ALL-UNNAMED</opt>
<opt>--add-opens java.base/jdk.internal.misc=ALL-UNNAMED</opt>
<opt>--add-opens java.base/java.nio=ALL-UNNAMED</opt>
<opt>--add-opens=java.base/sun.nio.ch=ALL-UNNAMED</opt>
<opt>--add-opens java.base/java.util=ALL-UNNAMED</opt>
<opt>--add-opens java.base/java.lang.reflect=ALL-UNNAMED</opt>
<opt>--add-opens java.base/java.text=ALL-UNNAMED</opt>
<opt>--add-opens java.desktop/java.awt=ALL-UNNAMED</opt>
<opt>--add-opens java.desktop/java.awt.font=ALL-UNNAMED</opt>
<opt>--add-opens java.desktop/java.awt.image=ALL-UNNAMED</opt>
<opt>--add-opens java.desktop/java.awt.color=ALL-UNNAMED</opt>
<opt>--add-opens java.desktop/sun.awt.image=ALL-UNNAMED</opt>
<opt>--add-opens java.desktop/javax.swing=ALL-UNNAMED</opt>
<opt>--add-opens java.desktop/javax.swing.border=ALL-UNNAMED</opt>
<opt>--add-opens java.desktop/javax.swing.event=ALL-UNNAMED</opt>
<opt>--add-opens java.desktop/sun.swing=ALL-UNNAMED</opt>
<opt>--add-opens java.desktop/java.beans=ALL-UNNAMED</opt>
<opt>--add-opens java.base/java.util.concurrent=ALL-UNNAMED</opt>
<opt>--add-opens java.base/java.net=ALL-UNNAMED</opt>
<opt>-Dio.netty.tryReflectionSetAccessible=true</opt>
</opts>
</jre>
<versionInfo>
@@ -214,30 +230,30 @@
<dependency>
<groupId>com.badlogicgames.gdx</groupId>
<artifactId>gdx</artifactId>
<version>1.10.0</version>
<version>1.11.0</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.badlogicgames.gdx</groupId>
<artifactId>gdx-platform</artifactId>
<version>1.10.0</version>
<version>1.11.0</version>
<classifier>natives-desktop</classifier>
</dependency>
<dependency>
<groupId>com.badlogicgames.gdx</groupId>
<artifactId>gdx-freetype</artifactId>
<version>1.10.0</version>
<version>1.11.0</version>
</dependency>
<dependency>
<groupId>com.badlogicgames.gdx</groupId>
<artifactId>gdx-backend-lwjgl3</artifactId>
<version>1.10.0</version>
<version>1.11.0</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.badlogicgames.gdx</groupId>
<artifactId>gdx-freetype-platform</artifactId>
<version>1.10.0</version>
<version>1.11.0</version>
<classifier>natives-desktop</classifier>
</dependency>
<dependency>
@@ -274,7 +290,7 @@
<dependency>
<groupId>forge</groupId>
<artifactId>forge-gui-mobile</artifactId>
<version>1.6.50-SNAPSHOT</version>
<version>1.6.51</version>
<scope>compile</scope>
</dependency>
</dependencies>

View File

@@ -10,7 +10,7 @@ java -version 1>nul 2>nul || (
for /f tokens^=2^ delims^=.-_^+^" %%j in ('java -fullversion 2^>^&1') do set "jver=%%j"
if %jver% GEQ 17 (
java --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.lang.reflect=ALL-UNNAMED --add-opens java.base/java.text=ALL-UNNAMED --add-opens java.desktop/java.awt.font=ALL-UNNAMED -Xmx4096m -Dfile.encoding=UTF-8 -jar $project.build.finalName$
java --add-opens java.desktop/java.beans=ALL-UNNAMED --add-opens java.desktop/javax.swing.border=ALL-UNNAMED --add-opens java.desktop/javax.swing.event=ALL-UNNAMED --add-opens java.desktop/sun.swing=ALL-UNNAMED --add-opens java.desktop/java.awt.image=ALL-UNNAMED --add-opens java.desktop/java.awt.color=ALL-UNNAMED --add-opens java.desktop/sun.awt.image=ALL-UNNAMED --add-opens java.desktop/javax.swing=ALL-UNNAMED --add-opens java.desktop/java.awt=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.lang.reflect=ALL-UNNAMED --add-opens java.base/java.text=ALL-UNNAMED --add-opens java.desktop/java.awt.font=ALL-UNNAMED --add-opens java.base/jdk.internal.misc=ALL-UNNAMED --add-opens java.base/sun.nio.ch=ALL-UNNAMED --add-opens java.base/java.nio=ALL-UNNAMED --add-opens java.base/java.math=ALL-UNNAMED --add-opens java.base/java.util.concurrent=ALL-UNNAMED --add-opens java.base/java.net=ALL-UNNAMED -Dio.netty.tryReflectionSetAccessible=true -Xmx4096m -Dfile.encoding=UTF-8 -jar $project.build.finalName$
popd
exit /b 0
)

View File

@@ -6,7 +6,7 @@
<parent>
<artifactId>forge</artifactId>
<groupId>forge</groupId>
<version>1.6.50-SNAPSHOT</version>
<version>1.6.51</version>
</parent>
<artifactId>forge-ai</artifactId>

View File

@@ -20,7 +20,6 @@ package forge.ai;
import java.util.ArrayList;
import java.util.List;
import forge.game.staticability.StaticAbilityMustAttack;
import org.apache.commons.lang3.tuple.Pair;
import com.google.common.base.Predicate;
@@ -47,6 +46,7 @@ import forge.game.combat.GlobalAttackRestrictions;
import forge.game.cost.Cost;
import forge.game.keyword.Keyword;
import forge.game.player.Player;
import forge.game.player.PlayerCollection;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.SpellAbilityPredicates;
import forge.game.trigger.Trigger;
@@ -58,9 +58,6 @@ import forge.util.Expressions;
import forge.util.MyRandom;
import forge.util.collect.FCollection;
import forge.util.collect.FCollectionView;
import forge.util.maps.LinkedHashMapToAmount;
import forge.util.maps.MapToAmount;
import forge.util.maps.MapToAmountUtil;
/**
@@ -75,7 +72,7 @@ public class AiAttackController {
// possible attackers and blockers
private List<Card> attackers;
private final List<Card> blockers;
private List<Card> blockers;
private List<Card> oppList; // holds human player creatures
private List<Card> myList; // holds computer creatures
@@ -99,11 +96,9 @@ public class AiAttackController {
public AiAttackController(final Player ai, boolean nextTurn) {
this.ai = ai;
defendingOpponent = choosePreferredDefenderPlayer(ai);
this.oppList = getOpponentCreatures(defendingOpponent);
myList = ai.getCreaturesInPlay();
this.nextTurn = nextTurn;
refreshAttackers(defendingOpponent);
this.blockers = getPossibleBlockers(oppList, this.attackers, this.nextTurn);
refreshCombatants(defendingOpponent);
} // overloaded constructor to evaluate attackers that should attack next turn
public AiAttackController(final Player ai, Card attacker) {
@@ -119,13 +114,15 @@ public class AiAttackController {
this.blockers = getPossibleBlockers(oppList, this.attackers, this.nextTurn);
} // overloaded constructor to evaluate single specified attacker
private void refreshAttackers(GameEntity defender) {
private void refreshCombatants(GameEntity defender) {
this.oppList = getOpponentCreatures(defendingOpponent);
this.attackers = new ArrayList<>();
for (Card c : myList) {
if (canAttackWrapper(c, defender)) {
attackers.add(c);
}
}
this.blockers = getPossibleBlockers(oppList, this.attackers, this.nextTurn);
}
public static List<Card> getOpponentCreatures(final Player defender) {
@@ -153,6 +150,7 @@ public class AiAttackController {
public void removeBlocker(Card blocker) {
this.oppList.remove(blocker);
this.blockers.remove(blocker);
}
private boolean canAttackWrapper(final Card attacker, final GameEntity defender) {
@@ -170,10 +168,14 @@ public class AiAttackController {
public static Player choosePreferredDefenderPlayer(Player ai) {
Player defender = ai.getWeakestOpponent(); //Concentrate on opponent within easy kill range
// TODO for multiplayer combat avoid players with cantLose or (if not playing infect) cantLoseForZeroOrLessLife and !canLoseLife
if (defender.getLife() > 8) {
// TODO connect with evaluateBoardPosition and only fall back to random when no player is the biggest threat by a fair margin
if (defender.getLife() > 8) { //Otherwise choose a random opponent to ensure no ganging up on players
// TODO should we cache the random for each turn? some functions like shouldPumpCard base their decisions on the assumption who will be attacked
//Otherwise choose a random opponent to ensure no ganging up on players
return Aggregates.random(ai.getOpponents());
}
return defender;
@@ -572,8 +574,7 @@ public class AiAttackController {
if (numExtraBlocks > 0) {
// TODO should be limited to how much getBlockCost the opp can pay
while (numExtraBlocks-- > 0 && !remainingAttackers.isEmpty()) {
blockedAttackers.add(remainingAttackers.get(0));
remainingAttackers.remove(0);
blockedAttackers.add(remainingAttackers.remove(0));
maxBlockersAfterCrew--;
}
}
@@ -581,8 +582,7 @@ public class AiAttackController {
if (remainingAttackers.isEmpty()) {
break;
}
blockedAttackers.add(remainingAttackers.get(0));
remainingAttackers.remove(0);
blockedAttackers.add(remainingAttackers.remove(0));
maxBlockersAfterCrew--;
}
unblockedAttackers.addAll(remainingAttackers);
@@ -605,8 +605,8 @@ public class AiAttackController {
final CardCollection unblockableCantPayFor = new CardCollection();
final CardCollection unblockableWithoutCost = new CardCollection();
// TODO also check poison
for (Card attacker : CardLists.getKeyword(unblockedAttackers, Keyword.TRAMPLE)) {
Cost tax = CombatUtil.getAttackCost(attacker.getGame(), attacker, defendingOpponent);
for (Card attacker : unblockedAttackers) {
Cost tax = CombatUtil.getAttackCost(ai.getGame(), attacker, defendingOpponent);
if (tax == null) {
unblockableWithoutCost.add(attacker);
} else {
@@ -620,19 +620,17 @@ public class AiAttackController {
}
}
int dmgUnblockableAfterPaying = ComputerUtilCombat.sumDamageIfUnblocked(unblockableWithPaying, defendingOpponent);
unblockedAttackers = unblockableWithoutCost;
if (dmgUnblockableAfterPaying > trampleDamage) {
unblockedAttackers.removeAll(unblockableCantPayFor);
unblockedAttackers.removeAll(unblockableWithPaying);
myFreeMana -= unblockableAttackTax;
totalCombatDamage = dmgUnblockableAfterPaying;
// recalculate the trampler damage with the reduced mana available now
myFreeMana -= unblockableAttackTax;
trampleDamage = getDamageFromBlockingTramplers(blockedAttackers, remainingBlockers, myFreeMana).getLeft();
} else {
unblockedAttackers = unblockableWithoutCost;
myFreeMana -= tramplerTaxPaid;
// find out if we can still pay for some left
for (Card attacker : unblockableWithPaying) {
Cost tax = CombatUtil.getAttackCost(attacker.getGame(), attacker, defendingOpponent);
Cost tax = CombatUtil.getAttackCost(ai.getGame(), attacker, defendingOpponent);
int taxCMC = tax.getCostMana().getMana().getCMC();
if (myFreeMana < unblockableAttackTax + taxCMC) {
continue;
@@ -664,7 +662,7 @@ public class AiAttackController {
CardCollection remainingBlockers = new CardCollection(blockers);
for (Card attacker : CardLists.getKeyword(blockedAttackers, Keyword.TRAMPLE)) {
// TODO might sort by quotient of dmg/cost for best combination
Cost tax = CombatUtil.getAttackCost(attacker.getGame(), attacker, defendingOpponent);
Cost tax = CombatUtil.getAttackCost(ai.getGame(), attacker, defendingOpponent);
int taxCMC = tax != null ? tax.getCostMana().getMana().getCMC() : 0;
if (myFreeMana < currentAttackTax + taxCMC) {
continue;
@@ -716,12 +714,24 @@ public class AiAttackController {
* @return a {@link forge.game.combat.Combat} object.
*/
public final int declareAttackers(final Combat combat) {
// something prevents attacking, try another
if (this.attackers.isEmpty() && ai.getOpponents().size() > 1) {
final PlayerCollection opps = ai.getOpponents();
opps.remove(defendingOpponent);
defendingOpponent = Aggregates.random(opps);
refreshCombatants(defendingOpponent);
}
final boolean bAssault = doAssault();
// Determine who will be attacked
GameEntity defender = chooseDefender(combat, bAssault);
// decided to attack another defender so related lists need to be updated
// (though usually rather try to avoid this situation for performance reasons)
if (defender != defendingOpponent) {
refreshAttackers(defender);
defendingOpponent = defender instanceof Player ? (Player) defender : ((Card)defender).getController();
refreshCombatants(defender);
}
if (this.attackers.isEmpty()) {
return aiAggression;
@@ -775,10 +785,7 @@ public class AiAttackController {
if (!nextTurn) {
for (final Card attacker : this.attackers) {
GameEntity mustAttackDef = null;
if (attacker.isGoaded()) {
// TODO this might result into trying to attack the wrong player
mustAttackDef = defender;
} else if (attacker.getSVar("MustAttack").equals("True")) {
if (attacker.getSVar("MustAttack").equals("True")) {
mustAttackDef = defender;
} else if (attacker.hasSVar("EndOfTurnLeavePlay")
&& isEffectiveAttacker(ai, attacker, combat, defender)) {
@@ -787,24 +794,19 @@ public class AiAttackController {
//TODO: if there are other ways to tap this creature (like mana creature), then don't need to attack
mustAttackDef = defender;
} else {
final List<GameEntity> attackRequirements = StaticAbilityMustAttack.entitiesMustAttack(attacker);
if (attackRequirements.contains(attacker)) {
// TODO add cost check here and switch defender if there's one without a cost
// must attack anything
mustAttackDef = defender;
// next check if there's also a specific defender to attack, so don't count them
attackRequirements.removeAll(new CardCollection(attacker));
}
final MapToAmount<GameEntity> amounts = new LinkedHashMapToAmount<>();
amounts.addAll(attackRequirements);
while (!amounts.isEmpty()) {
if (combat.getAttackConstraints().getRequirements().get(attacker) == null) continue;
// check defenders in order of maximum requirements
GameEntity mustAttackDefMaybe = MapToAmountUtil.max(amounts).getKey();
for (Pair<GameEntity, Integer> e : combat.getAttackConstraints().getRequirements().get(attacker).getSortedRequirements()) {
if (e.getRight() == 0) continue;
GameEntity mustAttackDefMaybe = e.getLeft();
// Gideon Jura returns LKI
if (mustAttackDefMaybe instanceof Card) {
mustAttackDefMaybe = ai.getGame().getCardState((Card) mustAttackDefMaybe);
}
if (canAttackWrapper(attacker, mustAttackDefMaybe) && CombatUtil.getAttackCost(ai.getGame(), attacker, mustAttackDefMaybe) == null) {
mustAttackDef = mustAttackDefMaybe;
break;
}
amounts.remove(mustAttackDefMaybe);
}
}
if (mustAttackDef != null) {
@@ -926,8 +928,6 @@ public class AiAttackController {
final List<Card> nextTurnAttackers = new ArrayList<>();
int candidateCounterAttackDamage = 0;
final Player opp = defender instanceof Player ? (Player) defender : ((Card)defender).getController();
this.oppList = getOpponentCreatures(opp);
// get the potential damage and strength of the AI forces
final List<Card> candidateAttackers = new ArrayList<>();
int candidateUnblockedDamage = 0;
@@ -936,7 +936,7 @@ public class AiAttackController {
// turn, assume summoning sickness creatures will be able to
if (ComputerUtilCombat.canAttackNextTurn(pCard) && pCard.getNetCombatDamage() > 0) {
candidateAttackers.add(pCard);
candidateUnblockedDamage += ComputerUtilCombat.damageIfUnblocked(pCard, opp, null, false);
candidateUnblockedDamage += ComputerUtilCombat.damageIfUnblocked(pCard, defendingOpponent, null, false);
computerForces++;
}
}
@@ -978,7 +978,7 @@ public class AiAttackController {
// find the potential damage ratio the AI can cause
double humanLifeToDamageRatio = 1000000;
if (candidateUnblockedDamage > 0) {
humanLifeToDamageRatio = (double) (opp.getLife() - ComputerUtil.possibleNonCombatDamage(ai, opp)) / candidateUnblockedDamage;
humanLifeToDamageRatio = (double) (defendingOpponent.getLife() - ComputerUtil.possibleNonCombatDamage(ai, defendingOpponent)) / candidateUnblockedDamage;
}
// determine if the ai outnumbers the player
@@ -1003,7 +1003,7 @@ public class AiAttackController {
// get list of attackers ordered from low power to high
CardLists.sortByPowerAsc(this.attackers);
// get player life total
int humanLife = opp.getLife();
int humanLife = defendingOpponent.getLife();
// get the list of attackers up to the first blocked one
final List<Card> attritionalAttackers = new ArrayList<>();
for (int x = 0; x < (this.attackers.size() - humanForces); x++) {
@@ -1053,7 +1053,7 @@ public class AiAttackController {
}
}
if (isUnblockableCreature) {
unblockableDamage += ComputerUtilCombat.damageIfUnblocked(attacker, opp, combat, false);
unblockableDamage += ComputerUtilCombat.damageIfUnblocked(attacker, defendingOpponent, combat, false);
}
}
for (final Card attacker : nextTurnAttackers) {
@@ -1067,13 +1067,13 @@ public class AiAttackController {
}
}
if (isUnblockableCreature) {
nextUnblockableDamage += ComputerUtilCombat.damageIfUnblocked(attacker, opp, null, false);
nextUnblockableDamage += ComputerUtilCombat.damageIfUnblocked(attacker, defendingOpponent, null, false);
}
}
if (unblockableDamage > 0 && !opp.cantLoseForZeroOrLessLife() && opp.canLoseLife()) {
turnsUntilDeathByUnblockable = 1 + (opp.getLife() - unblockableDamage) / nextUnblockableDamage;
if (unblockableDamage > 0 && !defendingOpponent.cantLoseForZeroOrLessLife() && defendingOpponent.canLoseLife()) {
turnsUntilDeathByUnblockable = 1 + (defendingOpponent.getLife() - unblockableDamage) / nextUnblockableDamage;
}
if (opp.canLoseLife()) {
if (defendingOpponent.canLoseLife()) {
doUnblockableAttack = true;
}
// *****************
@@ -1130,8 +1130,8 @@ public class AiAttackController {
if ( LOG_AI_ATTACKS )
System.out.println("attackersLeft = " + attackersLeft);
FCollection<GameEntity> possibleDefenders = new FCollection<>(opp);
possibleDefenders.addAll(opp.getPlaneswalkersInPlay());
FCollection<GameEntity> possibleDefenders = new FCollection<>(defendingOpponent);
possibleDefenders.addAll(defendingOpponent.getPlaneswalkersInPlay());
while (!attackersLeft.isEmpty()) {
CardCollection attackersAssigned = new CardCollection();
@@ -1180,7 +1180,7 @@ public class AiAttackController {
if (pwDefending.isEmpty()) {
// TODO for now only looks at same player as we'd have to check the others from start too
//defender = new PlayerCollection(Iterables.filter(possibleDefenders, Player.class)).min(PlayerPredicates.compareByLife());
defender = opp;
defender = defendingOpponent;
} else {
final Card pwNearUlti = ComputerUtilCard.getBestPlaneswalkerToDamage(pwDefending);
defender = pwNearUlti != null ? pwNearUlti : ComputerUtilCard.getBestPlaneswalkerAI(pwDefending);

View File

@@ -945,6 +945,39 @@ public class AiBlockController {
}
}
private void makeRequiredBlocks(Combat combat) {
// assign blockers that have to block
final CardCollection 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)) {
chumpBlockers.add(blocker);
}
}
if (!chumpBlockers.isEmpty()) {
for (final Card attacker : attackers) {
List<Card> blockers = getPossibleBlockers(combat, attacker, chumpBlockers, false);
for (final Card blocker : blockers) {
if (CombatUtil.canBlock(attacker, blocker, combat) && blockersLeft.contains(blocker)
&& (CombatUtil.mustBlockAnAttacker(blocker, combat, null)
|| blocker.hasKeyword("CARDNAME blocks each combat if able."))) {
combat.addBlocker(attacker, blocker);
if (!blocker.getMustBlockCards().isEmpty()) {
int mustBlockAmt = blocker.getMustBlockCards().size();
final CardCollectionView blockedSoFar = combat.getAttackersBlockedBy(blocker);
boolean canBlockAnother = CombatUtil.canBlockMoreCreatures(blocker, blockedSoFar);
if (!canBlockAnother || mustBlockAmt == blockedSoFar.size()) {
blockersLeft.remove(blocker);
}
} else {
blockersLeft.remove(blocker);
}
}
}
}
}
}
private void clearBlockers(final Combat combat, final List<Card> possibleBlockers) {
for (final Card blocker : CardLists.filterControlledBy(combat.getAllBlockers(), ai)) {
// don't touch other player's blockers
@@ -1008,9 +1041,6 @@ public class AiBlockController {
clearBlockers(combat, possibleBlockers);
List<Card> blockers;
List<Card> chumpBlockers;
diff = (ai.getLife() * 2) - 5; // This is the minimal gain for an unnecessary trade
if (ai.getController().isAI() && diff > 0 && ((PlayerControllerAi) ai.getController()).getAi().getBooleanProperty(AiProps.PLAY_AGGRO)) {
diff = 0;
@@ -1106,37 +1136,9 @@ public class AiBlockController {
}
}
// assign blockers that have to block
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)) {
chumpBlockers.add(blocker);
}
}
if (!chumpBlockers.isEmpty()) {
CardLists.shuffle(attackers);
for (final Card attacker : attackers) {
blockers = getPossibleBlockers(combat, attacker, chumpBlockers, false);
for (final Card blocker : blockers) {
if (CombatUtil.canBlock(attacker, blocker, combat) && blockersLeft.contains(blocker)
&& (CombatUtil.mustBlockAnAttacker(blocker, combat, null)
|| blocker.hasKeyword("CARDNAME blocks each combat if able."))) {
combat.addBlocker(attacker, blocker);
if (!blocker.getMustBlockCards().isEmpty()) {
int mustBlockAmt = blocker.getMustBlockCards().size();
final CardCollectionView blockedSoFar = combat.getAttackersBlockedBy(blocker);
boolean canBlockAnother = CombatUtil.canBlockMoreCreatures(blocker, blockedSoFar);
if (!canBlockAnother || mustBlockAmt == blockedSoFar.size()) {
blockersLeft.remove(blocker);
}
} else {
blockersLeft.remove(blocker);
}
}
}
}
}
// block requirements
// TODO because this isn't done earlier, sometimes a good block will enforce a restriction that prevents another for the requirement
makeRequiredBlocks(combat);
// check to see if it's possible to defend a Planeswalker under attack with a chump block,
// unless life is low enough to be more worried about saving preserving the life total
@@ -1186,8 +1188,7 @@ public class AiBlockController {
* Orders a blocker that put onto the battlefield blocking. Depends heavily
* on the implementation of orderBlockers().
*/
public static CardCollection orderBlocker(final Card attacker, final Card blocker,
final CardCollection oldBlockers) {
public static CardCollection orderBlocker(final Card attacker, final Card blocker, final CardCollection oldBlockers) {
// add blocker to existing ordering
// sort by evaluate, then insert it appropriately
// relies on current implementation of orderBlockers()

View File

@@ -88,6 +88,13 @@ public class AiController {
private SpellAbilityPicker simPicker;
private int lastAttackAggression;
public AiController(final Player computerPlayer, final Game game0) {
player = computerPlayer;
game = game0;
memory = new AiCardMemory();
simPicker = new SpellAbilityPicker(game, player);
}
public boolean canCheatShuffle() {
return cheatShuffle;
}
@@ -141,13 +148,6 @@ public class AiController {
return predictedCombatNextTurn;
}
public AiController(final Player computerPlayer, final Game game0) {
player = computerPlayer;
game = game0;
memory = new AiCardMemory();
simPicker = new SpellAbilityPicker(game, player);
}
private List<SpellAbility> getPossibleETBCounters() {
CardCollection all = new CardCollection(player.getCardsIn(ZoneType.Hand));
CardCollectionView ccvPlayerLibrary = player.getCardsIn(ZoneType.Library);
@@ -709,9 +709,10 @@ public class AiController {
return AiPlayDecision.CantPlaySa;
}
final Card host = sa.getHostCard();
// Check a predefined condition
if (sa.hasParam("AICheckSVar")) {
final Card host = sa.getHostCard();
final String svarToCheck = sa.getParam("AICheckSVar");
String comparator = "GE";
int compareTo = 1;
@@ -734,7 +735,7 @@ public class AiController {
}
int oldCMC = -1;
boolean xCost = sa.costHasX() || sa.getHostCard().hasStartOfKeyword("Strive");
boolean xCost = sa.costHasX() || host.hasStartOfKeyword("Strive");
if (!xCost) {
if (!ComputerUtilCost.canPayCost(sa, player, sa.isTrigger())) {
// for most costs, it's OK to check if they can be paid early in order to avoid running a heavy API check
@@ -748,15 +749,15 @@ public class AiController {
}
// state needs to be switched here so API checks evaluate the right face
CardStateName currentState = sa.getCardState() != null && sa.getHostCard().getCurrentStateName() != sa.getCardStateName() && !sa.getHostCard().isInPlay() ? sa.getHostCard().getCurrentStateName() : null;
CardStateName currentState = sa.getCardState() != null && host.getCurrentStateName() != sa.getCardStateName() && !host.isInPlay() ? host.getCurrentStateName() : null;
if (currentState != null) {
sa.getHostCard().setState(sa.getCardStateName(), false);
host.setState(sa.getCardStateName(), false);
}
AiPlayDecision canPlay = canPlaySa(sa); // this is the "heaviest" check, which also sets up targets, defines X, etc.
if (currentState != null) {
sa.getHostCard().setState(currentState, false);
host.setState(currentState, false);
}
if (canPlay != AiPlayDecision.WillPlay) {
@@ -766,10 +767,10 @@ public class AiController {
// Account for possible Ward after the spell is fully targeted
// TODO: ideally, this should be done while targeting, so that a different target can be preferred if the best
// one is warded and can't be paid for.
if (sa.usesTargeting()) {
if (sa.usesTargeting() && CardFactoryUtil.isCounterable(host)) {
for (Card tgt : sa.getTargets().getTargetCards()) {
// TODO some older cards don't use the keyword, so check for trigger instead
if (tgt.hasKeyword(Keyword.WARD) && tgt.isInPlay() && tgt.getController().isOpponentOf(sa.getHostCard().getController())) {
if (tgt.hasKeyword(Keyword.WARD) && tgt.isInPlay() && tgt.getController().isOpponentOf(host.getController())) {
int amount = 0;
Cost wardCost = ComputerUtilCard.getTotalWardCost(tgt);
if (wardCost.hasManaCost()) {
@@ -1839,7 +1840,6 @@ public class AiController {
} else if (effect.hasParam("AICheckDredge")) {
return player.getCardsIn(ZoneType.Library).size() > 8 || player.isCardInPlay("Laboratory Maniac");
} else return sa != null && doTrigger(sa, false);
}
public List<SpellAbility> chooseSaToActivateFromOpeningHand(List<SpellAbility> usableFromOpeningHand) {

View File

@@ -329,9 +329,6 @@ public class ComputerUtil {
}
AbilityUtils.resolve(sa);
// destroys creatures if they have lethal damage, etc..
//game.getAction().checkStateEffects();
}
}
@@ -827,7 +824,7 @@ public class ComputerUtil {
}
}
if ("DesecrationDemon".equals(source.getParam("AILogic"))) {
if ("DesecrationDemon".equals(logic)) {
sacThreshold = SpecialCardAi.DesecrationDemon.getSacThreshold();
} else if (considerSacThreshold != -1) {
sacThreshold = considerSacThreshold;
@@ -1077,7 +1074,7 @@ public class ComputerUtil {
playNow = false;
break;
}
if (!playNow && c.isCreature() && ComputerUtilCombat.canAttackNextTurn(c) && c.canBeAttached(card)) {
if (!playNow && c.isCreature() && ComputerUtilCombat.canAttackNextTurn(c) && c.canBeAttached(card, null)) {
playNow = true;
}
}
@@ -1227,12 +1224,12 @@ public class ComputerUtil {
final Game game = sa.getActivatingPlayer().getGame();
final PhaseHandler ph = game.getPhaseHandler();
return (sa.getHostCard().isCreature()
return sa.getHostCard().isCreature()
&& sa.getPayCosts().hasTapCost()
&& (ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS)
&& !ph.getNextTurn().equals(sa.getActivatingPlayer()))
&& !sa.getHostCard().hasSVar("EndOfTurnLeavePlay")
&& !sa.hasParam("ActivationPhases"));
&& !sa.hasParam("ActivationPhases");
}
//returns true if it's better to wait until blockers are declared).
@@ -2601,8 +2598,7 @@ public class ComputerUtil {
}
public static CardCollection getSafeTargets(final Player ai, SpellAbility sa, CardCollectionView validCards) {
CardCollection safeCards = new CardCollection(validCards);
safeCards = CardLists.filter(safeCards, new Predicate<Card>() {
CardCollection safeCards = CardLists.filter(validCards, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
if (c.getController() == ai) {
@@ -2615,8 +2611,7 @@ public class ComputerUtil {
}
public static Card getKilledByTargeting(final SpellAbility sa, CardCollectionView validCards) {
CardCollection killables = new CardCollection(validCards);
killables = CardLists.filter(killables, new Predicate<Card>() {
CardCollection killables = CardLists.filter(validCards, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return c.getController() != sa.getActivatingPlayer() && c.getSVar("Targeting").equals("Dies");

View File

@@ -1181,11 +1181,6 @@ public class ComputerUtilCombat {
public static int predictPowerBonusOfAttacker(final Card attacker, final Card blocker, final Combat combat, boolean withoutAbilities, boolean withoutCombatStaticAbilities) {
int power = 0;
//check Exalted only for the first attacker
if (combat != null && combat.getAttackers().isEmpty()) {
power += attacker.getController().countExaltedBonus();
}
// Serene Master switches power with attacker
if (blocker!= null && blocker.getName().equals("Serene Master")) {
power += blocker.getNetPower() - attacker.getNetPower();
@@ -1268,21 +1263,17 @@ public class ComputerUtilCombat {
}
List<Card> list = Lists.newArrayList();
if (!sa.hasParam("ValidCards")) {
list = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), null);
}
if (sa.hasParam("Defined") && sa.getParam("Defined").startsWith("TriggeredAttacker")) {
list.add(attacker);
}
if (sa.hasParam("ValidCards")) {
if (attacker.isValid(sa.getParam("ValidCards").split(","), source.getController(), source, null)
|| attacker.isValid(sa.getParam("ValidCards").replace("attacking+", "").split(","),
source.getController(), source, null)) {
list.add(attacker);
}
} else {
list = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), null);
}
if (list.isEmpty()) {
continue;
if (sa.hasParam("Defined") && sa.getParam("Defined").startsWith("TriggeredAttacker")) {
list.add(attacker);
}
if (!list.contains(attacker)) {
continue;
@@ -1381,11 +1372,6 @@ public class ComputerUtilCombat {
, boolean withoutAbilities, boolean withoutCombatStaticAbilities) {
int toughness = 0;
//check Exalted only for the first attacker
if (combat != null && combat.getAttackers().isEmpty()) {
toughness += attacker.getController().countExaltedBonus();
}
if (blocker != null && attacker.getName().equals("Shape Stealer")) {
toughness += blocker.getNetToughness() - attacker.getNetToughness();
}
@@ -1464,14 +1450,15 @@ public class ComputerUtilCombat {
toughness -= predictDamageTo(attacker, damage, source, false);
continue;
} else if (ApiType.Pump.equals(sa.getApi())) {
if (!sa.hasParam("NumDef")) {
continue;
}
if (sa.hasParam("Cost")) {
if (!CostPayment.canPayAdditionalCosts(sa.getPayCosts(), sa)) {
continue;
}
}
if (!sa.hasParam("NumDef")) {
continue;
}
final String defined = sa.getParam("Defined");
CardCollection list = AbilityUtils.getDefinedCards(source, defined, sa);
if (defined != null && defined.startsWith("TriggeredAttacker")) {
@@ -1497,6 +1484,9 @@ public class ComputerUtilCombat {
toughness += AbilityUtils.calculateAmount(source, bonus, sa);
}
} else if (ApiType.PumpAll.equals(sa.getApi())) {
if (!sa.hasParam("NumDef")) {
continue;
}
if (sa.hasParam("Cost")) {
if (!CostPayment.canPayAdditionalCosts(sa.getPayCosts(), sa)) {
continue;
@@ -1506,9 +1496,6 @@ public class ComputerUtilCombat {
if (!sa.hasParam("ValidCards")) {
continue;
}
if (!sa.hasParam("NumDef")) {
continue;
}
if (!attacker.isValid(sa.getParam("ValidCards").replace("attacking+", "").split(","), source.getController(), source, sa)) {
continue;
}

View File

@@ -528,10 +528,11 @@ public class ComputerUtilCost {
sa.setActivatingPlayer(player); // complaints on NPE had came before this line was added.
}
final boolean cannotBeCountered = !CardFactoryUtil.isCounterable(sa.getHostCard());
// Check for stuff like Nether Void
int extraManaNeeded = 0;
if (sa instanceof Spell) {
final boolean cannotBeCountered = !CardFactoryUtil.isCounterable(sa.getHostCard());
for (Card c : player.getGame().getCardsIn(ZoneType.Battlefield)) {
final String snem = c.getSVar("AI_SpellsNeedExtraMana");
if (!StringUtils.isBlank(snem)) {
@@ -591,7 +592,7 @@ public class ComputerUtilCost {
}
// Ward - will be accounted for when rechecking a targeted ability
if (!(sa instanceof WrappedAbility) && sa.usesTargeting()) {
if (!(sa instanceof WrappedAbility) && sa.usesTargeting() && !cannotBeCountered) {
for (Card tgt : sa.getTargets().getTargetCards()) {
if (tgt.hasKeyword(Keyword.WARD) && tgt.isInPlay() && tgt.getController().isOpponentOf(sa.getHostCard().getController())) {
Cost wardCost = ComputerUtilCard.getTotalWardCost(tgt);
@@ -721,6 +722,17 @@ public class ComputerUtilCost {
return false;
}
// ward or human misplay
if (ApiType.Counter.equals(sa.getApi())) {
List<SpellAbility> spells = AbilityUtils.getDefinedSpellAbilities(source, sa.getParamOrDefault("Defined", "Targeted"), sa);
for (SpellAbility toBeCountered : spells) {
if (!CardFactoryUtil.isCounterable(toBeCountered.getHostCard())) {
return false;
}
// TODO check hasFizzled
}
}
// AI was crashing because the blank ability used to pay costs
// Didn't have any of the data on the original SA to pay dependant costs

View File

@@ -656,7 +656,7 @@ public class ComputerUtilMana {
ManaPool.refundMana(manaSpentToPay, ai, sa);
return manaSources;
} // getManaSourcesToPayCost()
}
private static boolean payManaCost(final ManaCostBeingPaid cost, final SpellAbility sa, final Player ai, final boolean test, boolean checkPlayable, boolean effect) {
AiCardMemory.clearMemorySet(ai, MemorySet.PAYS_TAP_COST);
@@ -680,6 +680,8 @@ public class ComputerUtilMana {
} else if (mayPlay.hasParam("MayPlayIgnoreType")) {
ignoreType = true;
}
} else if (sa.hasParam("ActivateIgnoreColor")) {
ignoreColor = true;
}
boolean hasConverge = sa.getHostCard().hasConverge();
ListMultimap<ManaCostShard, SpellAbility> sourcesForShards = getSourcesForShards(cost, sa, ai, test,
@@ -708,7 +710,7 @@ public class ComputerUtilMana {
}
manapool.applyCardMatrix(pay);
for (byte color : MagicColor.WUBRGC) {
for (byte color : ManaAtom.MANATYPES) {
if (manapool.tryPayCostWithColor(color, sa, cost)) {
found = true;
break;
@@ -884,7 +886,7 @@ public class ComputerUtilMana {
}
return true;
} // payManaCost()
}
private static void resetPayment(List<SpellAbility> payments) {
for (SpellAbility sa : payments) {
@@ -958,7 +960,6 @@ public class ComputerUtilMana {
private static void setExpressColorChoice(final SpellAbility sa, final Player ai, ManaCostBeingPaid cost,
ManaCostShard toPay, SpellAbility saPayment) {
AbilityManaPart m = saPayment.getManaPart();
if (m.isComboMana()) {
getComboManaChoice(ai, saPayment, sa, cost);
@@ -966,7 +967,7 @@ public class ComputerUtilMana {
else if (saPayment.getApi() == ApiType.ManaReflected) {
Set<String> reflected = CardUtil.getReflectableManaColors(saPayment);
for (byte c : MagicColor.WUBRG) {
for (byte c : MagicColor.WUBRGC) {
if (ai.getManaPool().canPayForShardWithColor(toPay, c) && reflected.contains(MagicColor.toLongString(c))) {
m.setExpressChoice(MagicColor.toShortString(c));
return;
@@ -1039,7 +1040,7 @@ public class ComputerUtilMana {
if (ma.getApi() == ApiType.ManaReflected) {
Set<String> reflected = CardUtil.getReflectableManaColors(ma);
for (byte c : MagicColor.WUBRG) {
for (byte c : MagicColor.WUBRGC) {
if (toPay == ManaCostShard.COLORED_X && !ManaCostBeingPaid.canColoredXShardBePaidByColor(MagicColor.toShortString(c), xManaCostPaidByColor)) {
continue;
}

View File

@@ -823,6 +823,7 @@ public abstract class GameState {
String id = rememberedEnts.getValue();
Card exiledWith = idToCard.get(Integer.parseInt(id));
exiledWith.addExiledCard(c);
c.setExiledWith(exiledWith);
c.setExiledBy(exiledWith.getController());
}
@@ -1134,7 +1135,7 @@ public abstract class GameState {
Card attachedTo = idToCard.get(entry.getValue());
Card attacher = entry.getKey();
if (attacher.isAttachment()) {
attacher.attachToEntity(attachedTo);
attacher.attachToEntity(attachedTo, null, true);
}
}
@@ -1145,7 +1146,7 @@ public abstract class GameState {
Game game = attacher.getGame();
Player attachedTo = entry.getValue() == TARGET_AI ? game.getPlayers().get(1) : game.getPlayers().get(0);
attacher.attachToEntity(attachedTo);
attacher.attachToEntity(attachedTo, null);
}
}
@@ -1341,12 +1342,14 @@ public abstract class GameState {
}
} else if (info.startsWith("Transformed")) {
c.setState(CardStateName.Transformed, true);
c.setBackSide(true);
} else if (info.startsWith("Flipped")) {
c.setState(CardStateName.Flipped, true);
} else if (info.startsWith("Meld")) {
c.setState(CardStateName.Meld, true);
} else if (info.startsWith("Modal")) {
c.setState(CardStateName.Modal, true);
c.setBackSide(true);
}
else if (info.startsWith("OnAdventure")) {
String abAdventure = "DB$ Effect | RememberObjects$ Self | StaticAbilities$ Play | ExileOnMoved$ Exile | Duration$ Permanent | ConditionDefined$ Self | ConditionPresent$ Card.nonCopiedSpell";

View File

@@ -727,7 +727,6 @@ public class PlayerControllerAi extends PlayerController {
@Override
public List<SpellAbility> chooseSaToActivateFromOpeningHand(List<SpellAbility> usableFromOpeningHand) {
// AI would play everything. But limits to one copy of (Leyline of Singularity) and (Gemstone Caverns)
return brains.chooseSaToActivateFromOpeningHand(usableFromOpeningHand);
}

View File

@@ -169,6 +169,7 @@ public enum SpellApiToAi {
.put(ApiType.StoreSVar, StoreSVarAi.class)
.put(ApiType.Subgame, AlwaysPlayAi.class)
.put(ApiType.Surveil, SurveilAi.class)
.put(ApiType.TakeInitiative, AlwaysPlayAi.class)
.put(ApiType.Tap, TapAi.class)
.put(ApiType.TapAll, TapAllAi.class)
.put(ApiType.TapOrUntap, TapOrUntapAi.class)

View File

@@ -22,7 +22,6 @@ import forge.ai.SpecialCardAi;
import forge.ai.SpellAbilityAi;
import forge.game.Game;
import forge.game.GameObject;
import forge.game.GlobalRuleChange;
import forge.game.ability.AbilityFactory;
import forge.game.ability.AbilityUtils;
import forge.game.ability.ApiType;
@@ -43,7 +42,6 @@ import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.player.PlayerActionConfirmMode;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.SpellPermanent;
import forge.game.spellability.TargetRestrictions;
import forge.game.staticability.StaticAbility;
import forge.game.trigger.Trigger;
@@ -78,9 +76,7 @@ public class AttachAi extends SpellAbilityAi {
}
}
if (!ai.getGame().getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noLegendRule)
&& source.getType().isLegendary() && sa instanceof SpellPermanent
&& ai.isCardInPlay(source.getName())) {
if (source.isAura() && sa.isSpell() && !source.ignoreLegendRule() && ai.isCardInPlay(source.getName())) {
// Don't play the second copy of a legendary enchantment already in play
// TODO: Add some extra checks for where the AI may want to cast a replacement aura
@@ -618,7 +614,7 @@ public class AttachAi extends SpellAbilityAi {
CardCollection preList = new CardCollection(lki);
preList.add(attachSourceLki);
c.getGame().getAction().checkStaticAbilities(false, Sets.newHashSet(preList), preList);
boolean result = lki.canBeAttached(attachSourceLki);
boolean result = lki.canBeAttached(attachSourceLki, null);
//reset static abilities
c.getGame().getAction().checkStaticAbilities(false);
@@ -1354,7 +1350,7 @@ public class AttachAi extends SpellAbilityAi {
if (tgt == null) {
list = AbilityUtils.getDefinedCards(attachSource, sa.getParam("Defined"), sa);
} else {
list = CardLists.filter(CardUtil.getValidCardsToTarget(tgt, sa), CardPredicates.canBeAttached(attachSource));
list = CardLists.filter(CardUtil.getValidCardsToTarget(tgt, sa), CardPredicates.canBeAttached(attachSource, sa));
}
if (list.isEmpty()) {
@@ -1609,8 +1605,6 @@ public class AttachAi extends SpellAbilityAi {
&& CombatUtil.canBlock(card, true);
} else if (keyword.equals("Reach")) {
return !card.hasKeyword(Keyword.FLYING) && CombatUtil.canBlock(card, true);
} else if (keyword.equals("CARDNAME can attack as though it didn't have defender.")) {
return card.hasKeyword(Keyword.DEFENDER) && card.getNetCombatDamage() + powerBonus > 0;
} else if (keyword.equals("Shroud") || keyword.equals("Hexproof")) {
return !card.hasKeyword(Keyword.SHROUD) && !card.hasKeyword(Keyword.HEXPROOF);
} else return !keyword.equals("Defender");
@@ -1627,7 +1621,6 @@ public class AttachAi extends SpellAbilityAi {
* @return true, if is useful keyword
*/
private static boolean isUsefulCurseKeyword(final String keyword, final Card card, final SpellAbility sa) {
final Player ai = sa.getActivatingPlayer();
if (!CardUtil.isStackingKeyword(keyword) && card.hasKeyword(keyword)) {
return false;
}

View File

@@ -11,7 +11,6 @@ import forge.card.MagicColor;
import forge.game.Game;
import forge.game.GameEntity;
import forge.game.GameObject;
import forge.game.GlobalRuleChange;
import forge.game.ability.AbilityKey;
import forge.game.ability.AbilityUtils;
import forge.game.ability.ApiType;
@@ -274,14 +273,10 @@ public class ChangeZoneAi extends SpellAbilityAi {
}
}
//Ninjutsu
if (sa.hasParam("Ninjutsu")) {
if (source.getType().isLegendary()
&& !ai.getGame().getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noLegendRule)) {
if (ai.getZone(ZoneType.Battlefield).contains(CardPredicates.nameEquals(source.getName()))) {
if (sa.isNinjutsu()) {
if (!source.ignoreLegendRule() && ai.isCardInPlay(source.getName())) {
return false;
}
}
if (ai.getGame().getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DAMAGE)) {
return false;
}
@@ -732,10 +727,9 @@ public class ChangeZoneAi extends SpellAbilityAi {
}
}
// predict Legendary cards already present
if (!ai.getGame().getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noLegendRule)) {
boolean nothingWillReturn = true;
for (final Card c : retrieval) {
if (!(c.getType().isLegendary() && ai.isCardInPlay(c.getName()))) {
if (!(!c.ignoreLegendRule() && ai.isCardInPlay(c.getName()))) {
nothingWillReturn = false;
break;
}
@@ -745,7 +739,6 @@ public class ChangeZoneAi extends SpellAbilityAi {
}
}
}
}
final AbilitySub subAb = sa.getSubAbility();
return subAb == null || SpellApiToAi.Converter.get(subAb.getApi()).chkDrawbackWithSubs(ai, subAb);

View File

@@ -214,7 +214,7 @@ public class CountersMoveAi extends SpellAbilityAi {
private static int calcAmount(final SpellAbility sa, final CounterType cType) {
final Card host = sa.getHostCard();
final String amountStr = sa.getParam("CounterNum");
final String amountStr = sa.getParamOrDefault("CounterNum", "1");
// TODO handle proper calculation of X values based on Cost
int amount = 0;
@@ -349,6 +349,9 @@ public class CountersMoveAi extends SpellAbilityAi {
}
}
return false;
} else if (sa.getMaxTargets() == 2) {
// TODO
return false;
} else {
// SA uses target for Defined
@@ -361,11 +364,9 @@ public class CountersMoveAi extends SpellAbilityAi {
}
final Card src = srcCards.get(0);
if (cType != null) {
if (src.getCounters(cType) <= 0) {
if (cType != null && src.getCounters(cType) <= 0) {
return false;
}
}
Card lki = CardUtil.getLKICopy(src);
if (cType == null) {

View File

@@ -341,7 +341,7 @@ public class CountersPutAi extends CountersAi {
if (sa.hasParam("Bolster")) {
CardCollection creatsYouCtrl = ai.getCreaturesInPlay();
CardCollection leastToughness = new CardCollection(Aggregates.listWithMin(creatsYouCtrl, CardPredicates.Accessors.fnGetDefense));
CardCollection leastToughness = new CardCollection(Aggregates.listWithMin(creatsYouCtrl, CardPredicates.Accessors.fnGetNetToughness));
if (leastToughness.isEmpty()) {
return false;
}
@@ -752,14 +752,16 @@ public class CountersPutAi extends CountersAi {
final int amount = AbilityUtils.calculateAmount(source, amountStr, sa);
int left = amount;
final String[] types;
String type = "";
if (sa.hasParam("CounterType")) {
// TODO some cards let you choose types, should check each
types = sa.getParam("CounterType").split(",");
} else {
type = types[0];
} else if (sa.hasParam("CounterTypes")) {
// all types will be added
types = sa.getParam("CounterTypes").split(",");
type = types[0];
}
final String type = types[0];
if (!sa.usesTargeting()) {
// No target. So must be defined

View File

@@ -20,11 +20,13 @@ package forge.ai.ability;
import java.util.List;
import java.util.Map;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCard;
import forge.ai.SpellAbilityAi;
import forge.game.Game;
import forge.game.GlobalRuleChange;
import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardCollectionView;
@@ -37,6 +39,7 @@ import forge.game.player.Player;
import forge.game.player.PlayerController.BinaryChoiceType;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
/**
* <p>
@@ -97,13 +100,19 @@ public class CountersPutOrRemoveAi extends SpellAbilityAi {
return true;
} else {
// currently only Clockspinning
boolean noLegendary = game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noLegendRule);
// logic to remove some counter
CardCollection countersList = CardLists.filter(list, CardPredicates.hasCounters());
if (!countersList.isEmpty()) {
if (!ai.isCardInPlay("Marit Lage") || noLegendary) {
CardCollectionView marit = ai.getCardsIn(ZoneType.Battlefield, "Marit Lage");
boolean maritEmpty = marit.isEmpty() || Iterables.contains(marit, new Predicate<Card>() {
@Override
public boolean apply(Card input) {
return input.ignoreLegendRule();
}
});
if (maritEmpty) {
CardCollectionView depthsList = CardLists.filter(countersList,
CardPredicates.nameEquals("Dark Depths"), CardPredicates.hasCounter(CounterEnumType.ICE));
@@ -197,11 +206,7 @@ public class CountersPutOrRemoveAi extends SpellAbilityAi {
*/
@Override
public CounterType chooseCounterType(List<CounterType> options, SpellAbility sa, Map<String, Object> params) {
if (options.size() > 1) {
final Player ai = sa.getActivatingPlayer();
final Game game = ai.getGame();
boolean noLegendary = game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noLegendRule);
Card tgt = (Card) params.get("Target");
@@ -230,7 +235,15 @@ public class CountersPutOrRemoveAi extends SpellAbilityAi {
} else {
// this counters are treat first to be removed
if ("Dark Depths".equals(tgt.getName()) && options.contains(CounterType.get(CounterEnumType.ICE))) {
if (!ai.isCardInPlay("Marit Lage") || noLegendary) {
CardCollectionView marit = ai.getCardsIn(ZoneType.Battlefield, "Marit Lage");
boolean maritEmpty = marit.isEmpty() || Iterables.contains(marit, new Predicate<Card>() {
@Override
public boolean apply(Card input) {
return input.ignoreLegendRule();
}
});
if (maritEmpty) {
return CounterType.get(CounterEnumType.ICE);
}
} else if (tgt.hasKeyword(Keyword.UNDYING) && options.contains(CounterType.get(CounterEnumType.P1P1))) {
@@ -246,7 +259,6 @@ public class CountersPutOrRemoveAi extends SpellAbilityAi {
}
}
}
}
return super.chooseCounterType(options, sa, params);
}
@@ -262,12 +274,10 @@ public class CountersPutOrRemoveAi extends SpellAbilityAi {
public boolean chooseBinary(BinaryChoiceType kindOfChoice, SpellAbility sa, Map<String, Object> params) {
if (kindOfChoice.equals(BinaryChoiceType.AddOrRemove)) {
final Player ai = sa.getActivatingPlayer();
final Game game = ai.getGame();
Card tgt = (Card) params.get("Target");
CounterType type = (CounterType) params.get("CounterType");
boolean noLegendary = game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noLegendRule);
if (tgt.getController().isOpponentOf(ai)) {
if (type.is(CounterEnumType.LOYALTY) && tgt.isPlaneswalker()) {
return false;
@@ -276,7 +286,15 @@ public class CountersPutOrRemoveAi extends SpellAbilityAi {
return ComputerUtil.isNegativeCounter(type, tgt);
} else {
if (type.is(CounterEnumType.ICE) && "Dark Depths".equals(tgt.getName())) {
if (!ai.isCardInPlay("Marit Lage") || noLegendary) {
CardCollectionView marit = ai.getCardsIn(ZoneType.Battlefield, "Marit Lage");
boolean maritEmpty = marit.isEmpty() || Iterables.contains(marit, new Predicate<Card>() {
@Override
public boolean apply(Card input) {
return input.ignoreLegendRule();
}
});
if (maritEmpty) {
return false;
}
} else if (type.is(CounterEnumType.M1M1) && tgt.hasKeyword(Keyword.PERSIST)) {

View File

@@ -3,7 +3,9 @@ package forge.ai.ability;
import java.util.List;
import java.util.Map;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.Iterables;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCard;
@@ -11,7 +13,6 @@ import forge.ai.ComputerUtilCost;
import forge.ai.SpellAbilityAi;
import forge.game.Game;
import forge.game.GameEntity;
import forge.game.GlobalRuleChange;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.card.CardCollection;
@@ -102,7 +103,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
final Game game = ai.getGame();
final String type = sa.getParam("CounterType");
final String amountStr = sa.getParam("CounterNum");
final String amountStr = sa.getParamOrDefault("CounterNum", "1");
// remove counter with Time might use Exile Zone too
final TargetRestrictions tgt = sa.getTargetRestrictions();
@@ -117,16 +118,21 @@ public class CountersRemoveAi extends SpellAbilityAi {
// Filter AI-specific targets if provided
list = ComputerUtil.filterAITgts(sa, ai, list, false);
boolean noLegendary = game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noLegendRule);
CardCollectionView marit = ai.getCardsIn(ZoneType.Battlefield, "Marit Lage");
boolean maritEmpty = marit.isEmpty() || Iterables.contains(marit, new Predicate<Card>() {
@Override
public boolean apply(Card input) {
return input.ignoreLegendRule();
}
});
if (type.matches("All")) {
// Logic Part for Vampire Hexmage
// Break Dark Depths
if (!ai.isCardInPlay("Marit Lage") || noLegendary) {
if (maritEmpty) {
CardCollectionView depthsList = ai.getCardsIn(ZoneType.Battlefield, "Dark Depths");
depthsList = CardLists.filter(depthsList, CardPredicates.isTargetableBy(sa),
CardPredicates.hasCounter(CounterEnumType.ICE, 3));
if (!depthsList.isEmpty()) {
sa.getTargets().add(depthsList.getFirst());
return true;
@@ -161,7 +167,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
}
// try to remove them from Dark Depths and Planeswalkers too
if (!ai.isCardInPlay("Marit Lage") || noLegendary) {
if (maritEmpty) {
CardCollectionView depthsList = ai.getCardsIn(ZoneType.Battlefield, "Dark Depths");
depthsList = CardLists.filter(depthsList, CardPredicates.isTargetableBy(sa),
CardPredicates.hasCounter(CounterEnumType.ICE));
@@ -407,9 +413,6 @@ public class CountersRemoveAi extends SpellAbilityAi {
*/
@Override
public CounterType chooseCounterType(List<CounterType> options, SpellAbility sa, Map<String, Object> params) {
if (options.size() <= 1) {
return super.chooseCounterType(options, sa, params);
}
Player ai = sa.getActivatingPlayer();
GameEntity target = (GameEntity) params.get("Target");

View File

@@ -1075,6 +1075,11 @@ public class DamageDealAi extends DamageAiBase {
return null;
}
// chaining to this could miscalculate
if (sa.isDividedAsYouChoose()) {
return null;
}
// Try to chain damage/debuff effects
if (StringUtils.isNumeric(damage) || (damage.startsWith("-") && StringUtils.isNumeric(damage.substring(1)))) {
// currently only works for predictable numeric damage

View File

@@ -353,9 +353,7 @@ public class DestroyAi extends SpellAbilityAi {
// Filter AI-specific targets if provided
preferred = ComputerUtil.filterAITgts(sa, ai, preferred, true);
for (final Card c : preferred) {
list.remove(c);
}
list.removeAll(preferred);
if (preferred.isEmpty() && !mandatory) {
return false;

View File

@@ -508,7 +508,7 @@ public class DrawAi extends SpellAbilityAi {
}
if (numCards >= computerLibrarySize - 3) {
if (ai.isCardInPlay("Laboratory Maniac")) {
if (ai.isCardInPlay("Laboratory Maniac") && !ai.cantWin()) {
return true;
}
// Don't deck yourself

View File

@@ -31,6 +31,11 @@ public class ImmediateTriggerAi extends SpellAbilityAi {
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
// always add to stack, targeting happens after payment
if (mandatory) {
return true;
}
String logic = sa.getParamOrDefault("AILogic", "");
SpellAbility trigsa = sa.getAdditionalAbility("Execute");
@@ -45,11 +50,7 @@ public class ImmediateTriggerAi extends SpellAbilityAi {
AiController aic = ((PlayerControllerAi)ai.getController()).getAi();
trigsa.setActivatingPlayer(ai);
if (!sa.hasParam("OptionalDecider")) {
return aic.doTrigger(trigsa, true);
} else {
return aic.doTrigger(trigsa, !sa.getParam("OptionalDecider").equals("You"));
}
return aic.doTrigger(trigsa, !"You".equals(sa.getParamOrDefault("OptionalDecider", "You")));
}
@Override

View File

@@ -5,6 +5,7 @@ import java.util.Map;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCard;
import forge.ai.SpellAbilityAi;
import forge.game.card.Card;
@@ -20,6 +21,7 @@ public class MutateAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
CardCollectionView mutateTgts = CardLists.getTargetableCards(aiPlayer.getCreaturesInPlay(), sa);
mutateTgts = ComputerUtil.getSafeTargets(aiPlayer, sa, mutateTgts);
// Filter out some abilities that are useless
// TODO: add other stuff useless for Mutate here

View File

@@ -1,7 +1,9 @@
package forge.ai.ability;
import forge.ai.AiAttackController;
import forge.ai.SpellAbilityAi;
import forge.ai.SpellApiToAi;
import forge.game.card.Card;
import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
@@ -9,6 +11,7 @@ import forge.game.player.PlayerActionConfirmMode;
import forge.game.spellability.AbilityStatic;
import forge.game.spellability.AbilitySub;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
/**
* TODO: Write javadoc for this type.
@@ -38,6 +41,28 @@ public class PeekAndRevealAi extends SpellAbilityAi {
}
// So far this only appears on Triggers, but will expand
// once things get converted from Dig + NoMove
Player opp = AiAttackController.choosePreferredDefenderPlayer(aiPlayer);
final Card host = sa.getHostCard();
Player libraryOwner = aiPlayer;
if (!willPayCosts(aiPlayer, sa, sa.getPayCosts(), host)) {
return false;
}
if (sa.usesTargeting()) {
sa.resetTargets();
//todo: evaluate valid targets
if (!sa.canTarget(opp)) {
return false;
}
sa.getTargets().add(opp);
libraryOwner = opp;
}
if (libraryOwner.getCardsIn(ZoneType.Library).isEmpty()) {
return false;
}
return true;
}

View File

@@ -11,8 +11,6 @@ import forge.ai.ComputerUtilMana;
import forge.ai.SpellAbilityAi;
import forge.card.CardType.Supertype;
import forge.card.mana.ManaCost;
import forge.game.Game;
import forge.game.GlobalRuleChange;
import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardCollectionView;
@@ -52,12 +50,10 @@ public class PermanentAi extends SpellAbilityAi {
@Override
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
final Card card = sa.getHostCard();
final Game game = ai.getGame();
// check on legendary
if (card.getType().isLegendary()
&& !game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noLegendRule)) {
if (ai.isCardInPlay(card.getName())) {
if (!card.ignoreLegendRule() && ai.isCardInPlay(card.getName())) {
// TODO check the risk we'd lose the effect with bad timing
if (!card.hasSVar("AILegendaryException")) {
// AiPlayDecision.WouldDestroyLegend
return false;
@@ -76,7 +72,6 @@ public class PermanentAi extends SpellAbilityAi {
}
}
}
}
/* -- not used anymore after Ixalan (Planeswalkers are now legendary, not unique by subtype) --
if (card.isPlaneswalker()) {

View File

@@ -145,6 +145,7 @@ public class PumpAi extends PumpAiBase {
}
final String counterType = moveSA.getParam("CounterType");
final String amountStr = moveSA.getParamOrDefault("CounterNum", "1");
final CounterType cType = "Any".equals(counterType) ? null : CounterType.getType(counterType);
final PhaseHandler ph = game.getPhaseHandler();
@@ -157,7 +158,6 @@ public class PumpAi extends PumpAiBase {
if (attr.isEmpty()) {
return false;
}
final String amountStr = moveSA.getParam("CounterNum");
CardCollection best = CardLists.filter(attr, new Predicate<Card>() {
@Override
public boolean apply(Card card) {
@@ -198,7 +198,6 @@ public class PumpAi extends PumpAiBase {
return true;
}
} else {
final String amountStr = moveSA.getParam("CounterNum");
final boolean sameCtrl = moveSA.getTargetRestrictions().isSameController();
List<Card> list = CardLists.getTargetableCards(game.getCardsIn(ZoneType.Battlefield), sa);
@@ -399,7 +398,7 @@ public class PumpAi extends PumpAiBase {
if (!mandatory
&& !immediately
&& game.getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS)
&& (game.getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS) && !"AnyPhase".equals(sa.getParam("AILogic")))
&& !(sa.isCurse() && defense < 0)
&& !containsNonCombatKeyword(keywords)
&& !"UntilYourNextTurn".equals(sa.getParam("Duration"))
@@ -597,7 +596,7 @@ public class PumpAi extends PumpAiBase {
}
return true;
} // pumpTgtAI()
}
private boolean pumpMandatoryTarget(final Player ai, final SpellAbility sa) {
final Game game = ai.getGame();
@@ -658,7 +657,7 @@ public class PumpAi extends PumpAiBase {
}
return true;
} // pumpMandatoryTarget()
}
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
@@ -707,7 +706,7 @@ public class PumpAi extends PumpAiBase {
}
return true;
} // pumpTriggerAI
}
@Override
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
@@ -779,7 +778,7 @@ public class PumpAi extends PumpAiBase {
}
return true;
} // pumpDrawbackAI()
}
@Override
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {

View File

@@ -399,10 +399,6 @@ public abstract class PumpAiBase extends SpellAbilityAi {
&& newPower > 0
&& !CardLists.getType(opp.getLandsInPlay(), "Forest").isEmpty()
&& Iterables.any(opp.getCreaturesInPlay(), CardPredicates.possibleBlockers(card));
} else if (keyword.endsWith("CARDNAME can attack as though it didn't have defender.")) {
return ph.isPlayerTurn(ai) && card.hasKeyword(Keyword.DEFENDER)
&& !ph.getPhase().isAfter(PhaseType.COMBAT_BEGIN)
&& !card.isTapped() && newPower > 0;
} else if (keyword.equals("Prevent all combat damage that would be dealt to CARDNAME.")) {
return combat != null && (combat.isBlocking(card) || combat.isBlocked(card));
} else if (keyword.equals("Menace")) {
@@ -428,7 +424,7 @@ public abstract class PumpAiBase extends SpellAbilityAi {
}
});
return list;
} // getPumpCreatures()
}
/**
* <p>
@@ -519,7 +515,7 @@ public abstract class PumpAiBase extends SpellAbilityAi {
}
return list;
} // getCurseCreatures()
}
protected boolean containsNonCombatKeyword(final List<String> keywords) {
for (final String keyword : keywords) {

View File

@@ -2,10 +2,8 @@ package forge.ai.ability;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.Iterables;
import forge.ai.*;
import forge.game.GlobalRuleChange;
import forge.game.card.Card;
import forge.game.card.CardPredicates;
import forge.game.phase.PhaseType;
@@ -54,16 +52,16 @@ public class RepeatAi extends SpellAbilityAi {
if (sa.usesTargeting()) {
if (logic.startsWith("CopyBestCreature")) {
Card best = null;
if (!ai.getGame().getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noLegendRule) || logic.endsWith("IgnoreLegendary")) {
best = ComputerUtilCard.getBestAI(Iterables.filter(ai.getCreaturesInPlay(), Predicates.and(
CardPredicates.isTargetableBy(sa), new Predicate<Card>() {
Iterable<Card> targetableAi = Iterables.filter(ai.getCreaturesInPlay(), CardPredicates.isTargetableBy(sa));
if (!logic.endsWith("IgnoreLegendary")) {
best = ComputerUtilCard.getBestAI(Iterables.filter(targetableAi, new Predicate<Card>() {
@Override
public boolean apply(Card card) {
return !card.getType().isLegendary();
return card.ignoreLegendRule();
}
})));
}));
} else {
best = ComputerUtilCard.getBestAI(Iterables.filter(ai.getCreaturesInPlay(), CardPredicates.isTargetableBy(sa)));
best = ComputerUtilCard.getBestAI(targetableAi);
}
if (best == null && mandatory && sa.canTarget(sa.getHostCard())) {
best = sa.getHostCard();

View File

@@ -9,7 +9,6 @@ import forge.ai.ComputerUtilCost;
import forge.ai.SpellAbilityAi;
import forge.card.CardStateName;
import forge.game.GlobalRuleChange;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.card.CardCollection;
@@ -238,7 +237,7 @@ public class SetStateAi extends SpellAbilityAi {
private boolean isSafeToTransformIntoLegendary(Player aiPlayer, Card source) {
// Prevent transform into legendary creature if copy already exists
// Check first if Legend Rule does still apply
if (!aiPlayer.getGame().getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noLegendRule)) {
if (!source.ignoreLegendRule()) {
if (!source.hasAlternateState()) {
System.err.println("Warning: SetState without ALTERNATE on " + source.getName() + ".");
return false;

View File

@@ -106,10 +106,8 @@ public abstract class TapAiBase extends SpellAbilityAi {
* @return a boolean.
*/
protected boolean tapPrefTargeting(final Player ai, final Card source, final SpellAbility sa, final boolean mandatory) {
final Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
final Game game = ai.getGame();
CardCollection tapList = CardLists.filterControlledBy(game.getCardsIn(ZoneType.Battlefield), ai.getOpponents());
tapList = CardLists.getTargetableCards(tapList, sa);
CardCollection tapList = CardLists.getTargetableCards(ai.getOpponents().getCardsIn(ZoneType.Battlefield), sa);
tapList = CardLists.filter(tapList, Presets.UNTAPPED);
tapList = CardLists.filter(tapList, new Predicate<Card>() {
@Override
@@ -129,8 +127,7 @@ public abstract class TapAiBase extends SpellAbilityAi {
//use broader approach when the cost is a positive thing
if (tapList.isEmpty() && ComputerUtil.activateForCost(sa, ai)) {
tapList = CardLists.filterControlledBy(game.getCardsIn(ZoneType.Battlefield), ai.getOpponents());
tapList = CardLists.getTargetableCards(tapList, sa);
tapList = CardLists.getTargetableCards(ai.getOpponents().getCardsIn(ZoneType.Battlefield), sa);
tapList = CardLists.filter(tapList, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
@@ -150,8 +147,10 @@ public abstract class TapAiBase extends SpellAbilityAi {
//try to exclude things that will already be tapped due to something on stack or because something is
//already targeted in a parent or sub SA
if (!sa.isTrigger() || mandatory) { // but if just confirming trigger no need to look for other targets and might still help anyway
CardCollection toExclude = ComputerUtilAbility.getCardsTargetedWithApi(ai, tapList, sa, ApiType.Tap);
tapList.removeAll(toExclude);
}
if (tapList.isEmpty()) {
return false;
@@ -176,6 +175,7 @@ public abstract class TapAiBase extends SpellAbilityAi {
}
PhaseHandler phase = game.getPhaseHandler();
final Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
Card primeTarget = ComputerUtil.getKilledByTargeting(sa, tapList);
if (primeTarget != null) {
choice = primeTarget;
@@ -193,7 +193,7 @@ public abstract class TapAiBase extends SpellAbilityAi {
return CombatUtil.canAttack(c, opp);
}
});
attackers.remove(sa.getHostCard());
attackers.remove(source);
}
Predicate<Card> findBlockers = CardPredicates.possibleBlockerForAtLeastOne(attackers);
List<Card> creatureList = CardLists.filter(tapList, findBlockers);
@@ -202,7 +202,7 @@ public abstract class TapAiBase extends SpellAbilityAi {
if (!attackers.isEmpty() && !creatureList.isEmpty()) {
choice = ComputerUtilCard.getBestCreatureAI(creatureList);
} else if (sa.getRootAbility().isTrigger() || ComputerUtil.castSpellInMain1(ai, sa)) {
} else if (sa.isTrigger() || ComputerUtil.castSpellInMain1(ai, sa)) {
choice = ComputerUtilCard.getMostExpensivePermanentAI(tapList);
}
} else if (phase.isPlayerTurn(opp)
@@ -272,7 +272,7 @@ public abstract class TapAiBase extends SpellAbilityAi {
return true;
}
// filter by enchantments and planeswalkers, their tapped state doesn't matter.
// filter by enchantments and planeswalkers, their tapped state (usually) doesn't matter.
final String[] tappablePermanents = { "Enchantment", "Planeswalker" };
tapList = CardLists.getValidCards(list, tappablePermanents, source.getController(), source, sa);

View File

@@ -53,7 +53,7 @@ public class UntapAi extends SpellAbilityAi {
return false;
}
return ComputerUtilCost.checkDiscardCost(ai, cost, sa.getHostCard(), sa);
return ComputerUtilCost.checkDiscardCost(ai, cost, source, sa);
}
@Override
@@ -133,6 +133,10 @@ public class UntapAi extends SpellAbilityAi {
CardCollection list = CardLists.getTargetableCards(targetController.getCardsIn(ZoneType.Battlefield), sa);
if (!sa.isCurse()) {
list = ComputerUtil.getSafeTargets(ai, sa, list);
}
if (list.isEmpty()) {
return false;
}
@@ -170,8 +174,10 @@ public class UntapAi extends SpellAbilityAi {
//try to exclude things that will already be untapped due to something on stack or because something is
//already targeted in a parent or sub SA
if (!sa.isTrigger() || mandatory) { // but if just confirming trigger no need to look for other targets and might still help anyway
CardCollection toExclude = ComputerUtilAbility.getCardsTargetedWithApi(ai, untapList, sa, ApiType.Untap);
untapList.removeAll(toExclude);
}
sa.resetTargets();
while (sa.canAddMoreTarget()) {
@@ -195,7 +201,7 @@ public class UntapAi extends SpellAbilityAi {
if (choice == null) {
if (CardLists.getNotType(untapList, "Creature").isEmpty()) {
choice = ComputerUtilCard.getBestCreatureAI(untapList); // if only creatures take the best
} else if (!sa.getPayCosts().hasManaCost() || sa.getRootAbility().isTrigger()
} else if (!sa.getPayCosts().hasManaCost() || sa.isTrigger()
|| "Always".equals(sa.getParam("AILogic"))) {
choice = ComputerUtilCard.getMostExpensivePermanentAI(untapList);
}
@@ -262,9 +268,7 @@ public class UntapAi extends SpellAbilityAi {
private boolean untapTargetList(final Card source, final TargetRestrictions tgt, final SpellAbility sa, final boolean mandatory,
final CardCollection tapList) {
for (final Card c : sa.getTargets().getTargetCards()) {
tapList.remove(c);
}
tapList.removeAll(sa.getTargets().getTargetCards());
if (tapList.isEmpty()) {
return false;
@@ -288,7 +292,7 @@ public class UntapAi extends SpellAbilityAi {
choice = ComputerUtilCard.getBestAI(tapList);
if (choice == null) { // can't find anything left
if (sa.getTargets().size() < tgt.getMinTargets(sa.getHostCard(), sa) || sa.getTargets().size() == 0) {
if (sa.getTargets().size() < tgt.getMinTargets(source, sa) || sa.getTargets().size() == 0) {
if (!mandatory) {
sa.resetTargets();
}
@@ -308,9 +312,7 @@ public class UntapAi extends SpellAbilityAi {
@Override
public Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> list, boolean isOptional, Player targetedPlayer, Map<String, Object> params) {
PlayerCollection pl = new PlayerCollection();
pl.add(ai);
pl.addAll(ai.getAllies());
PlayerCollection pl = ai.getYourTeam();
return ComputerUtilCard.getBestAI(CardLists.filterControlledBy(list, pl));
}

View File

@@ -39,7 +39,7 @@ public class ZoneExchangeAi extends SpellAbilityAi {
}
if (type.equals("Aura")) {
Card c = object1.getEnchantingCard();
if (!c.canBeAttached(object2)) {
if (!c.canBeAttached(object2, sa)) {
return false;
}
}

View File

@@ -6,7 +6,7 @@
<parent>
<artifactId>forge</artifactId>
<groupId>forge</groupId>
<version>1.6.50-SNAPSHOT</version>
<version>1.6.51</version>
</parent>
<artifactId>forge-core</artifactId>

View File

@@ -299,20 +299,13 @@ public final class ImageKeys {
}
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<String> 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);
}
File f = new File(dir, filename + ext);
if (f.exists()) {
return f;
}
}
} else {

View File

@@ -20,6 +20,8 @@ package forge.card;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.*;
import forge.StaticData;
import forge.card.CardEdition.CardInSet;
import forge.card.CardEdition.Type;
import forge.deck.generation.IDeckGenPool;
@@ -45,14 +47,16 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
private final Map<String, PaperCard> uniqueCardsByName = Maps.newTreeMap(String.CASE_INSENSITIVE_ORDER);
private final Map<String, CardRules> rulesByName;
private final Map<String, ICardFace> facesByName = Maps.newTreeMap(String.CASE_INSENSITIVE_ORDER);
private static Map<String, String> artPrefs = new HashMap<>();
private static Map<String, String> artPrefs = Maps.newHashMap();
private final Map<String, String> alternateName = Maps.newTreeMap(String.CASE_INSENSITIVE_ORDER);
private final Map<String, Integer> artIds = new HashMap<>();
private final Map<String, Integer> artIds = Maps.newHashMap();
private final CardEdition.Collection editions;
private List<String> filtered;
private Map<String, Boolean> nonLegendaryCreatureNames = Maps.newHashMap();
public enum CardArtPreference {
LATEST_ART_ALL_EDITIONS(false, true),
LATEST_ART_CORE_EXPANSIONS_REPRINT_ONLY(true, true),
@@ -489,7 +493,6 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
return null;
// 1. First off, try using all possible search parameters, to narrow down the actual cards looked for.
String reqEditionCode = request.edition;
PaperCard result = null;
if (reqEditionCode != null && reqEditionCode.length() > 0) {
// This get is robust even against expansion aliases (e.g. TE and TMP both valid for Tempest) -
// MOST of the extensions have two short codes, 141 out of 221 (so far)
@@ -843,6 +846,26 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
return facesByName.get(getName(name));
}
public boolean isNonLegendaryCreatureName(final String name) {
Boolean bool = nonLegendaryCreatureNames.get(name);
if (bool != null) {
return bool;
}
// check if the name is from a face
// in general token creatures does not have this
final ICardFace face = StaticData.instance().getCommonCards().getFaceByName(name);
if (face == null) {
nonLegendaryCreatureNames.put(name, false);
return false;
}
// TODO add check if face is legal in the format of the game
// name does need to be a non-legendary creature
final CardType type = face.getType();
bool = type.isCreature() && !type.isLegendary();
nonLegendaryCreatureNames.put(name, bool);
return bool;
}
@Override
public Collection<PaperCard> getAllCards() {
return Collections.unmodifiableCollection(allCardsByName.values());

View File

@@ -214,8 +214,15 @@ public final class CardRules implements ICardCharacteristics {
}
}
public boolean isEnterableDungeon() {
if (mainPart.getOracleText().contains("You can't enter this dungeon unless")) {
return false;
}
return getType().isDungeon();
}
public boolean canBeCommander() {
if (mainPart.getOracleText().contains("can be your commander")) {
if (mainPart.getOracleText().contains("can be your commander") || canBeBackground()) {
return true;
}
CardType type = mainPart.getType();
@@ -232,8 +239,15 @@ public final class CardRules implements ICardCharacteristics {
}
public boolean canBePartnerCommander() {
if (canBeBackground()) {
return true;
}
return canBeCommander() && (hasKeyword("Partner") || !this.partnerWith.isEmpty() ||
hasKeyword("Friends forever"));
hasKeyword("Friends forever") || hasKeyword("Choose a Background"));
}
public boolean canBeBackground() {
return mainPart.getType().hasSubtype("Background");
}
public boolean canBeOathbreaker() {

View File

@@ -209,6 +209,16 @@ public final class CardRulesPredicates {
};
}
public static Predicate<CardRules> deckHasExactly(final DeckHints.Type type, final String has[]) {
return new Predicate<CardRules>() {
@Override
public boolean apply(final CardRules card) {
DeckHints deckHas = card.getAiHints().getDeckHas();
return deckHas != null && deckHas.isValid() && deckHas.is(type, has);
}
};
}
/**
* Core type.
*

View File

@@ -33,6 +33,7 @@ import com.google.common.base.Predicate;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
@@ -71,6 +72,7 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
public final String pluralName;
private static Map<String, CoreType> stringToCoreType = EnumUtils.getEnumMap(CoreType.class);
private static final Set<String> allCoreTypeNames = stringToCoreType.keySet();
public static final Set<CoreType> spellTypes = ImmutableSet.of(Instant, Sorcery);
public static CoreType getEnum(String name) {
return stringToCoreType.get(name);
@@ -535,7 +537,8 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
newType = new CardType(CardType.this);
if (ct.isRemoveCardTypes()) {
newType.coreTypes.clear();
// 205.1a However, an object with either the instant or sorcery card type retains that type.
newType.coreTypes.retainAll(CoreType.spellTypes);
}
if (ct.isRemoveSuperTypes()) {
newType.supertypes.clear();
@@ -612,6 +615,9 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
if (!isPlaneswalker()) {
Iterables.removeIf(subtypes, Predicates.IS_WALKER_TYPE);
}
if (!isDungeon()) {
Iterables.removeIf(subtypes, Predicates.IS_DUNGEON_TYPE);
}
}
@Override
@@ -787,6 +793,7 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
public static final Set<String> ENCHANTMENT_TYPES = Sets.newHashSet();
public static final Set<String> ARTIFACT_TYPES = Sets.newHashSet();
public static final Set<String> WALKER_TYPES = Sets.newHashSet();
public static final Set<String> DUNGEON_TYPES = Sets.newHashSet();
// singular -> plural
public static final BiMap<String,String> pluralTypes = HashBiMap.create();
@@ -846,6 +853,12 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
return CardType.isAPlaneswalkerType(input);
}
};
public static Predicate<String> IS_DUNGEON_TYPE = new Predicate<String>() {
@Override
public boolean apply(String input) {
return CardType.isADungeonType(input);
}
};
}
///////// Utility methods
@@ -878,6 +891,7 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
sortedSubTypes.addAll(Constant.ENCHANTMENT_TYPES);
sortedSubTypes.addAll(Constant.ARTIFACT_TYPES);
sortedSubTypes.addAll(Constant.WALKER_TYPES);
sortedSubTypes.addAll(Constant.DUNGEON_TYPES);
Collections.sort(sortedSubTypes);
}
return sortedSubTypes;
@@ -933,6 +947,9 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
return (Constant.SPELL_TYPES.contains(cardType));
}
public static boolean isADungeonType(final String cardType) {
return (Constant.DUNGEON_TYPES.contains(cardType));
}
/**
* If the input is a plural type, return the corresponding singular form.
* Otherwise, simply return the input.

View File

@@ -14,6 +14,7 @@ import com.google.common.collect.Iterables;
import forge.item.PaperCard;
import forge.util.PredicateString.StringOp;
import forge.util.collect.FCollection;
/**
* DeckHints provides the ability for a Card to "want" another Card or type of
@@ -73,12 +74,26 @@ public class DeckHints {
return false;
}
for (Pair<Type, String> filter : filters) {
if (filter.getLeft() == type && filter.getRight().equals(hint)) {
if (filter.getLeft() == type && filter.getRight().contains(hint)) {
return true;
}
}
return false;
}
public boolean is(Type type, String hints[]) {
if (filters == null) {
return false;
}
for (String hint : hints) {
for (Pair<Type, String> filter : filters) {
if (filter.getLeft() == type && filter.getRight().equals(hint)) {
continue;
}
}
return false;
}
return true;
}
/**
* Returns a Map of Cards by Type from the given Iterable<PaperCard> that match this
@@ -95,6 +110,10 @@ public class DeckHints {
String param = pair.getRight();
Iterable<PaperCard> cards = getCardsForFilter(cardList, type, param);
if (cards != null) {
// if a type is used more than once intersect respective matches
if (ret.get(type) != null) {
Iterables.retainAll(cards, new FCollection<>(ret.get(type)));
}
ret.put(type, cards);
}
}
@@ -143,13 +162,16 @@ public class DeckHints {
private Iterable<PaperCard> getCardsForFilter(Iterable<PaperCard> cardList, Type type, String param) {
List<PaperCard> cards = new ArrayList<>();
switch (type) {
case ABILITY:
// this is case ABILITY, but other types can also use this when the implicit parsing would miss
String[] abilities = param.split("\\|");
for (String ability : abilities) {
Iterables.addAll(cards, getMatchingItems(cardList, CardRulesPredicates.deckHas(Type.ABILITY, ability), PaperCard.FN_GET_RULES));
Iterables.addAll(cards, getMatchingItems(cardList, CardRulesPredicates.deckHas(type, ability), PaperCard.FN_GET_RULES));
}
break;
// bonus if a DeckHas can satisfy the type with multiple ones
Iterables.addAll(cards, getMatchingItems(cardList, CardRulesPredicates.deckHasExactly(type, abilities), PaperCard.FN_GET_RULES));
switch (type) {
case COLOR:
String[] colors = param.split("\\|");
for (String color : colors) {
@@ -187,6 +209,7 @@ public class DeckHints {
}
break;
case NONE:
case ABILITY: // already done above
break;
}
return cards;

View File

@@ -262,6 +262,9 @@ public enum DeckFormat {
} else if (a.getRules().hasKeyword("Friends forever") &&
b.getRules().hasKeyword("Friends forever")) {
// Stranger Things Secret Lair gimmick partner commander
} else if (a.getRules().hasKeyword("Choose a Background") && b.getRules().canBeBackground()
|| b.getRules().hasKeyword("Choose a Background") && a.getRules().canBeBackground()) {
// commander with background
} else {
return "has an illegal commander partnership";
}

View File

@@ -75,7 +75,7 @@ public enum DeckSection {
@Override
public Boolean apply(PaperCard card) {
CardType t = card.getRules().getType();
return card.getRules().canBeCommander() || t.isPlaneswalker();
return card.getRules().canBeCommander() || t.isPlaneswalker() || card.getRules().canBeOathbreaker() || card.getRules().canBeSignatureSpell();
}
};

View File

@@ -422,7 +422,7 @@ public abstract class DeckGeneratorBase {
}
public List<String> regexLandSearch(String pattern, Iterable<PaperCard> landCards){
final List<String> dLands = new ArrayList<>();
//final List<String> dLands = new ArrayList<>();
Pattern p = Pattern.compile(pattern);
for (PaperCard card:landCards){
if (card.getRules().getAiHints().getRemAIDecks()) {

View File

@@ -37,7 +37,7 @@ public class FatPack extends BoxedProduct {
if (boosters <= 0) { return null; }
FatPack.Template d = new Template(edition);
if (d == null) { return null; }
if (d == null || null == StaticData.instance().getBoosters().get(d.getEdition())) { return null; }
return new FatPack(edition.getName(), d, d.cntBoosters);
}
};

View File

@@ -6,7 +6,7 @@
<parent>
<artifactId>forge</artifactId>
<groupId>forge</groupId>
<version>1.6.50-SNAPSHOT</version>
<version>1.6.51</version>
</parent>
<artifactId>forge-game</artifactId>

View File

@@ -259,6 +259,18 @@ public abstract class CardTraitBase extends GameObject implements IHasCardView,
}
if (params.containsKey("Revolt")) {
if ("True".equalsIgnoreCase(params.get("Revolt")) != hostController.hasRevolt()) return false;
else if ("None".equalsIgnoreCase(params.get("Revolt"))) {
boolean none = true;
for (Player p : game.getRegisteredPlayers()) {
if (p.hasRevolt()) {
none = false;
break;
}
}
if (!none) {
return false;
}
}
}
if (params.containsKey("Desert")) {
if ("True".equalsIgnoreCase(params.get("Desert")) != hostController.hasDesert()) return false;

View File

@@ -10,16 +10,21 @@ import forge.game.ability.ApiType;
import forge.game.card.Card;
import forge.game.card.CardState;
import forge.game.cost.Cost;
import forge.game.mana.Mana;
import forge.game.mana.ManaCostBeingPaid;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.SpellAbilityPredicates;
import forge.game.spellability.TargetChoices;
import forge.game.staticability.StaticAbility;
import forge.game.trigger.Trigger;
import forge.game.zone.ZoneType;
import forge.util.Expressions;
import org.apache.commons.lang3.StringUtils;
import java.util.HashSet;
import java.util.Set;
public class ForgeScript {
public static boolean cardStateHasProperty(CardState cardState, String property, Player sourceController,
@@ -178,7 +183,7 @@ public class ForgeScript {
} else if (property.equals("Modular")) {
return sa.hasParam("Modular");
} else if (property.equals("Equip")) {
return sa.hasParam("Equip");
return sa.isEquip();
} else if (property.equals("Boast")) {
return sa.isBoast();
} else if (property.equals("Mutate")) {
@@ -197,12 +202,39 @@ public class ForgeScript {
return sa.hasParam("Nightbound");
} else if (property.equals("paidPhyrexianMana")) {
return sa.getSpendPhyrexianMana();
} else if (property.startsWith("ManaSpent")) {
String[] k = property.split(" ", 2);
String comparator = k[1].substring(0, 2);
int y = AbilityUtils.calculateAmount(sa.getHostCard(), k[1].substring(2), sa);
return Expressions.compare(sa.getPayingMana().size(), comparator, y);
} else if (property.startsWith("ManaFrom")) {
final String fromWhat = property.substring(8);
boolean found = false;
for (Mana m : sa.getPayingMana()) {
final Card manaSource = m.getSourceCard();
if (manaSource != null) {
if (manaSource.isValid(fromWhat, sourceController, source, spellAbility)) {
found = true;
break;
}
}
}
return found;
} else if (property.equals("MayPlaySource")) {
StaticAbility m = sa.getMayPlay();
if (m == null) {
return false;
}
return source.equals(m.getHostCard());
} else if (property.startsWith("numTargets")) {
Set<GameObject> targets = new HashSet<>();
for (TargetChoices tc : sa.getAllTargetChoices()) {
targets.addAll(tc);
}
String[] k = property.split(" ", 2);
String comparator = k[1].substring(0, 2);
int y = AbilityUtils.calculateAmount(sa.getHostCard(), k[1].substring(2), sa);
return Expressions.compare(targets.size(), comparator, y);
} else if (property.startsWith("IsTargeting")) {
String[] k = property.split(" ", 2);
boolean found = false;

View File

@@ -117,15 +117,13 @@ public class Game {
private CardCollection lastStateBattlefield = new CardCollection();
private CardCollection lastStateGraveyard = new CardCollection();
private Map<Player, PlayerCollection> attackedThisTurn = Maps.newHashMap();
private Map<Player, PlayerCollection> attackedLastTurn = Maps.newHashMap();
private Table<CounterType, Player, List<Pair<Card, Integer>>> countersAddedThisTurn = HashBasedTable.create();
private Map<Player, Card> topLibsCast = Maps.newHashMap();
private Map<Card, Integer> facedownWhileCasting = Maps.newHashMap();
private Player monarch = null;
private Player initiative = null;
private Player monarchBeginTurn = null;
private Player startingPlayer;
@@ -176,26 +174,11 @@ public class Game {
this.monarchBeginTurn = monarchBeginTurn;
}
public Map<Player, PlayerCollection> getPlayersAttackedThisTurn() {
return attackedThisTurn;
public Player getHasInitiative() {
return initiative;
}
public Map<Player, PlayerCollection> getPlayersAttackedLastTurn() {
return attackedLastTurn;
}
public void addPlayerAttackedThisTurn(Player attacker, Player defender) {
PlayerCollection atk = attackedThisTurn.get(attacker);
if (atk == null) {
attackedThisTurn.put(attacker, new PlayerCollection());
}
attackedThisTurn.get(attacker).add(defender);
}
public void resetPlayersAttackedOnNextTurn() {
attackedLastTurn.clear();
attackedLastTurn.putAll(attackedThisTurn);
attackedThisTurn.clear();
public void setHasInitiative(final Player p ) {
initiative = p;
}
public CardCollectionView getLastStateBattlefield() {
@@ -871,6 +854,18 @@ public class Game {
}
}
if (p.hasInitiative()) {
// The third way to take the initiative is if the player who currently has the initiative leaves the game.
// When that happens, the player whose turn it is takes the initiative.
// If the player who has the initiative leaves the game on their own turn,
// or the active player left the game at the same time, the next player in turn order takes the initiative.
if (p.equals(getPhaseHandler().getPlayerTurn())) {
getAction().takeInitiative(getNextPlayerAfter(p), null);
} else {
getAction().takeInitiative(getPhaseHandler().getPlayerTurn(), null);
}
}
// Remove leftover items from
getStack().removeInstancesControlledBy(p);
@@ -1093,8 +1088,6 @@ public class Game {
public void onCleanupPhase() {
clearCounterAddedThisTurn();
// Reset the attackers this turn/last turn
resetPlayersAttackedOnNextTurn();
// some cards need this info updated even after a player lost, so don't skip them
for (Player player : getRegisteredPlayers()) {
player.onCleanupPhase();

View File

@@ -17,7 +17,6 @@
*/
package forge.game;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
@@ -31,12 +30,12 @@ import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.Pair;
import com.google.common.base.Predicate;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ComparisonChain;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import com.google.common.collect.Multimaps;
import com.google.common.collect.Sets;
import forge.GameCommand;
@@ -162,13 +161,13 @@ public class GameAction {
// need to check before it enters
if (c.isAura() && !c.isAttachedToEntity() && toBattlefield && (zoneFrom == null || !zoneFrom.is(ZoneType.Stack))) {
boolean found = false;
if (Iterables.any(game.getPlayers(), PlayerPredicates.canBeAttached(c))) {
if (Iterables.any(game.getPlayers(), PlayerPredicates.canBeAttached(c, null))) {
found = true;
}
else if (Iterables.any((CardCollectionView) params.get(AbilityKey.LastStateBattlefield), CardPredicates.canBeAttached(c))) {
else if (Iterables.any((CardCollectionView) params.get(AbilityKey.LastStateBattlefield), CardPredicates.canBeAttached(c, null))) {
found = true;
}
else if (Iterables.any((CardCollectionView) params.get(AbilityKey.LastStateGraveyard), CardPredicates.canBeAttached(c))) {
else if (Iterables.any((CardCollectionView) params.get(AbilityKey.LastStateGraveyard), CardPredicates.canBeAttached(c, null))) {
found = true;
}
if (!found) {
@@ -275,7 +274,12 @@ public class GameAction {
lastKnownInfo = CardUtil.getLKICopy(c);
}
// CR 707.12 casting of a card copy, don't copy it again
if (zoneTo.is(ZoneType.Stack) && c.isRealToken()) {
copied = c;
} else {
copied = CardFactory.copyCard(c, false);
}
if (zoneTo.is(ZoneType.Stack)) {
// when moving to stack, copy changed card information
@@ -398,13 +402,13 @@ public class GameAction {
if (copied.isAura() && !copied.isAttachedToEntity() && toBattlefield) {
if (zoneFrom != null && zoneFrom.is(ZoneType.Stack) && game.getStack().isResolving(c)) {
boolean found = false;
if (Iterables.any(game.getPlayers(), PlayerPredicates.canBeAttached(copied))) {
if (Iterables.any(game.getPlayers(), PlayerPredicates.canBeAttached(copied, null))) {
found = true;
}
if (Iterables.any((CardCollectionView) params.get(AbilityKey.LastStateBattlefield), CardPredicates.canBeAttached(copied))) {
if (Iterables.any((CardCollectionView) params.get(AbilityKey.LastStateBattlefield), CardPredicates.canBeAttached(copied, null))) {
found = true;
}
if (Iterables.any((CardCollectionView) params.get(AbilityKey.LastStateGraveyard), CardPredicates.canBeAttached(copied))) {
if (Iterables.any((CardCollectionView) params.get(AbilityKey.LastStateGraveyard), CardPredicates.canBeAttached(copied, null))) {
found = true;
}
if (!found) {
@@ -577,6 +581,8 @@ public class GameAction {
}
}
table.replaceCounterEffect(game, null, true);
// Need to apply any static effects to produce correct triggers
checkStaticAbilities();
@@ -592,8 +598,6 @@ public class GameAction {
}
game.getTriggerHandler().registerActiveTrigger(copied, false);
table.replaceCounterEffect(game, null, true);
// play the change zone sound
game.fireEvent(new GameEventCardChangeZone(c, zoneFrom, zoneTo));
@@ -1515,7 +1519,7 @@ public class GameAction {
if (c.isAttachedToEntity()) {
final GameEntity ge = c.getEntityAttachedTo();
if (!ge.canBeAttached(c, true)) {
if (!ge.canBeAttached(c, null, true)) {
unAttachList.add(c);
checkAgain = true;
}
@@ -1692,39 +1696,71 @@ public class GameAction {
}
private boolean handleLegendRule(Player p, CardCollection noRegCreats) {
final List<Card> a = CardLists.getType(p.getCardsIn(ZoneType.Battlefield), "Legendary");
if (a.isEmpty() || game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noLegendRule)) {
final List<Card> a = Lists.newArrayList();
// check for ignore legend rule
for (Card c : CardLists.getType(p.getCardsIn(ZoneType.Battlefield), "Legendary")) {
if (!c.ignoreLegendRule()) {
a.add(c);
}
}
if (a.isEmpty()) {
return false;
}
boolean recheck = false;
// TODO legend rule exception into static ability
List<Card> yamazaki = CardLists.getKeyword(a, "Legend rule doesn't apply to CARDNAME.");
a.removeAll(yamazaki);
Multimap<String, Card> uniqueLegends = ArrayListMultimap.create();
for (Card c : a) {
if (!c.isFaceDown()) {
uniqueLegends.put(c.getName(), c);
}
// Corner Case 1: Legendary with non legendary creature names
CardCollection nonLegendaryNames = new CardCollection(Iterables.filter(a, new Predicate<Card>() {
@Override
public boolean apply(Card input) {
return input.hasNonLegendaryCreatureNames();
}
// TODO handle Spy Kit
}));
Multimap<String, Card> uniqueLegends = Multimaps.index(a, CardPredicates.Accessors.fnGetNetName);
CardCollection removed = new CardCollection();
for (String name : uniqueLegends.keySet()) {
Collection<Card> cc = uniqueLegends.get(name);
// skip the ones with empty names
if (name.isEmpty()) {
continue;
}
CardCollection cc = new CardCollection(uniqueLegends.get(name));
// check if it is a non legendary creature name
// if yes, then add the other legendary with Spy Kit too
if (!name.isEmpty() && StaticData.instance().getCommonCards().isNonLegendaryCreatureName(name)) {
cc.addAll(nonLegendaryNames);
}
if (cc.size() < 2) {
continue;
}
recheck = true;
Card toKeep = p.getController().chooseSingleEntityForEffect(new CardCollection(cc), new SpellAbility.EmptySa(ApiType.InternalLegendaryRule, new Card(-1, game), p),
Card toKeep = p.getController().chooseSingleEntityForEffect(cc, new SpellAbility.EmptySa(ApiType.InternalLegendaryRule, new Card(-1, game), p),
"You have multiple legendary permanents named \""+name+"\" in play.\n\nChoose the one to stay on battlefield (the rest will be moved to graveyard)", null);
cc.remove(toKeep);
noRegCreats.addAll(cc);
removed.addAll(cc);
}
// Corner Case 2: with all non legendary creature names
CardCollection emptyNameAllNonLegendary = new CardCollection(nonLegendaryNames);
// remove the ones that got already removed by other legend rule above
emptyNameAllNonLegendary.removeAll(removed);
if (emptyNameAllNonLegendary.size() > 1) {
recheck = true;
Card toKeep = p.getController().chooseSingleEntityForEffect(emptyNameAllNonLegendary, new SpellAbility.EmptySa(ApiType.InternalLegendaryRule, new Card(-1, game), p),
"You have multiple legendary permanents with non legendary creature names in play.\n\nChoose the one to stay on battlefield (the rest will be moved to graveyard)", null);
emptyNameAllNonLegendary.remove(toKeep);
removed.addAll(emptyNameAllNonLegendary);
}
noRegCreats.addAll(removed);
return recheck;
}
@@ -1775,9 +1811,8 @@ public class GameAction {
}
// Replacement effects
final Map<AbilityKey, Object> repRunParams = AbilityKey.mapFromCard(c);
final Map<AbilityKey, Object> repRunParams = AbilityKey.mapFromAffected(c);
repRunParams.put(AbilityKey.Source, sa);
repRunParams.put(AbilityKey.Affected, c);
repRunParams.put(AbilityKey.Regeneration, regenerate);
if (params != null) {
repRunParams.putAll(params);
@@ -2168,6 +2203,32 @@ public class GameAction {
game.getTriggerHandler().runTrigger(TriggerType.BecomeMonarch, runParams, false);
}
public void takeInitiative(final Player p, final String set) {
final Player previous = game.getHasInitiative();
if (p == null) {
return;
}
if (!p.equals(previous)) {
if (previous != null) {
previous.removeInitiativeEffect();
}
if (p.hasLost()) { // the person who should take initiative is gone, it goes to next player
takeInitiative(game.getNextPlayerAfter(p), set);
}
game.setHasInitiative(p);
p.createInitiativeEffect(set);
}
// You can take the initiative even if you already have it
// Run triggers
final Map<AbilityKey, Object> runParams = AbilityKey.newMap();
runParams.put(AbilityKey.Player, p);
game.getTriggerHandler().runTrigger(TriggerType.TakesInitiative, runParams, false);
}
// Make scry an action function so that it can be used for mulligans (with a null cause)
// Assumes that the list of players is in APNAP order, which should be the case
// Optional here as well to handle the way that mulligans do the choice
@@ -2382,7 +2443,7 @@ public class GameAction {
final Player pa = p.getController().chooseSingleEntityForEffect(players, aura,
Localizer.getInstance().getMessage("lblSelectAPlayerAttachSourceTo", CardTranslation.getTranslatedName(source.getName())), null);
if (pa != null) {
source.attachToEntity(pa);
source.attachToEntity(pa, null);
return true;
}
} else {
@@ -2409,7 +2470,7 @@ public class GameAction {
final Card o = p.getController().chooseSingleEntityForEffect(list, aura,
Localizer.getInstance().getMessage("lblSelectACardAttachSourceTo", CardTranslation.getTranslatedName(source.getName())), null);
if (o != null) {
source.attachToEntity(game.getCardState(o), true);
source.attachToEntity(game.getCardState(o), null, true);
return true;
}
}

View File

@@ -341,7 +341,7 @@ public final class GameActionUtil {
alternatives.add(newSA);
}
}
if (sa.hasParam("Equip") && activator.hasKeyword("You may pay 0 rather than pay equip costs.")) {
if (sa.isEquip() && activator.hasKeyword("You may pay 0 rather than pay equip costs.")) {
for (final KeywordInterface inst : source.getKeywords()) {
// need to find the correct Keyword from which this Ability is from
if (!inst.getAbilities().contains(sa)) {
@@ -487,27 +487,17 @@ public final class GameActionUtil {
final String keyword = inst.getOriginal();
if (keyword.startsWith("AlternateAdditionalCost")) {
final List<SpellAbility> newAbilities = Lists.newArrayList();
String[] costs = TextUtil.split(keyword, ':');
for (String s : keyword.split(":", 2)[1].split(":")) {
final SpellAbility newSA = sa.copy();
newSA.setBasicSpell(false);
final Cost cost1 = new Cost(costs[1], false);
newSA.setDescription(sa.getDescription() + " (Additional cost " + cost1.toSimpleString() + ")");
newSA.setPayCosts(cost1.add(sa.getPayCosts()));
final Cost cost = new Cost(s, false);
newSA.setDescription(sa.getDescription() + " (Additional cost: " + cost.toSimpleString() + ")");
newSA.setPayCosts(cost.add(sa.getPayCosts()));
if (newSA.canPlay()) {
newAbilities.add(newSA);
}
//second option
final SpellAbility newSA2 = sa.copy();
newSA2.setBasicSpell(false);
final Cost cost2 = new Cost(costs[2], false);
newSA2.setDescription(sa.getDescription() + " (Additional cost " + cost2.toSimpleString() + ")");
newSA2.setPayCosts(cost2.add(sa.getPayCosts()));
if (newSA2.canPlay()) {
newAbilities.add(newSA2);
}
abilities.clear();

View File

@@ -212,10 +212,10 @@ public abstract class GameEntity extends GameObject implements IIdentifiable {
}
}
public boolean canBeAttached(final Card attach) {
return canBeAttached(attach, false);
public boolean canBeAttached(final Card attach, SpellAbility sa) {
return canBeAttached(attach, sa, false);
}
public boolean canBeAttached(final Card attach, boolean checkSBA) {
public boolean canBeAttached(final Card attach, SpellAbility sa, boolean checkSBA) {
// master mode
if (!attach.isAttachment() || (attach.isCreature() && !attach.hasKeyword(Keyword.RECONFIGURE))
|| equals(attach)) {
@@ -226,7 +226,7 @@ public abstract class GameEntity extends GameObject implements IIdentifiable {
if (attach.isAura() && !canBeEnchantedBy(attach)) {
return false;
}
if (attach.isEquipment() && !canBeEquippedBy(attach)) {
if (attach.isEquipment() && !canBeEquippedBy(attach, sa)) {
return false;
}
if (attach.isFortification() && !canBeFortifiedBy(attach)) {
@@ -242,7 +242,7 @@ public abstract class GameEntity extends GameObject implements IIdentifiable {
return true;
}
protected boolean canBeEquippedBy(final Card aura) {
protected boolean canBeEquippedBy(final Card aura, SpellAbility sa) {
/**
* Equip only to Creatures which are cards
*/

View File

@@ -94,10 +94,12 @@ public class GameEntityCounterTable extends ForwardingTable<Optional<Player>, Ga
for (Map<CounterType, Integer> cm : gm.getValue().values()) {
Integer old = ObjectUtils.firstNonNull(result.get(gm.getKey()), 0);
Integer v = ObjectUtils.firstNonNull(cm.get(type), 0);
if (old + v > 0) {
result.put(gm.getKey(), old + v);
}
}
}
}
return result;
}

View File

@@ -28,13 +28,11 @@ public enum GlobalRuleChange {
noCreatureETBTriggers ("Creatures entering the battlefield don't cause abilities to trigger."),
noCreatureDyingTriggers ("Creatures dying don't cause abilities to trigger."),
noNight ("It can't become night."),
noLegendRule ("The legend rule doesn't apply."),
/* onlyOneAttackerATurn ("No more than one creature can attack each turn."), */
onlyOneAttackerACombat ("No more than one creature can attack each combat."),
onlyOneBlocker ("No more than one creature can block each combat."),
onlyOneBlockerPerOpponent ("Each opponent can't block with more than one creature."),
onlyTwoBlockers ("No more than two creatures can block each combat."),
toughnessAssignsDamage ("Each creature assigns combat damage equal to its toughness rather than its power."),
blankIsChaos("Each blank roll of the planar dice is a {CHAOS} roll.");
private final String ruleText;

View File

@@ -50,7 +50,7 @@ import io.sentry.Sentry;
*/
public final class AbilityFactory {
static final List<String> additionalAbilityKeys = Lists.newArrayList(
public static final List<String> additionalAbilityKeys = Lists.newArrayList(
"WinSubAbility", "OtherwiseSubAbility", // Clash
"BidSubAbility", // BidLifeEffect
"ChooseNumberSubAbility", "Lowest", "Highest", "NotLowest", // ChooseNumber
@@ -62,7 +62,8 @@ public final class AbilityFactory {
"FallbackAbility", // Complex Unless costs which can be unpayable
"ChooseSubAbility", // Can choose a player via ChoosePlayer
"CantChooseSubAbility", // Can't choose a player via ChoosePlayer
"AnimateSubAbility" // For ChangeZone Effects to Animate before ETB
"AnimateSubAbility", // For ChangeZone Effects to Animate before ETB
"ReturnAbility" // for Delayed Trigger on Magpie
);
public enum AbilityRecordType {
@@ -333,7 +334,7 @@ public final class AbilityFactory {
String tgtWhat = mapParams.get("ValidTgts");
final String[] commonStuff = new String[] {
//list of common one word non-core type ValidTgts that should be lowercase in the target prompt
"Player", "Opponent"
"Player", "Opponent", "Card"
};
if (Arrays.asList(commonStuff).contains(tgtWhat) || CardType.CoreType.isValidEnum(tgtWhat)) {
tgtWhat = tgtWhat.toLowerCase();
@@ -364,9 +365,6 @@ public final class AbilityFactory {
abTgt.setSAValidTargeting(mapParams.get("TargetValidTargeting"));
}
if (mapParams.containsKey("TargetsSingleTarget")) {
abTgt.setSingleTarget(true);
}
if (mapParams.containsKey("TargetUnique")) {
abTgt.setUniqueTargets(true);
}

View File

@@ -118,6 +118,7 @@ public enum AbilityKey {
SourceSA("SourceSA"),
SpellAbility("SpellAbility"),
SpellAbilityStackInstance("SpellAbilityStackInstance"),
SpellAbilityTarget("SpellAbilityTarget"),
SpellAbilityTargetingCards("SpellAbilityTargetingCards"),
StackInstance("StackInstance"),
StackSa("StackSa"),

View File

@@ -535,6 +535,9 @@ public class AbilityUtils {
}
}
val = playerXCount(players, calcX[1], card, ability);
} else if (hType.startsWith("Defined")) {
String defined = hType.split("Defined")[1];
val = playerXCount(getDefinedPlayers(card, defined, ability), calcX[1], card, ability);
} else {
val = 0;
}
@@ -742,6 +745,9 @@ public class AbilityUtils {
else if (calcX[0].startsWith("Revealed")) {
list = sa.getRootAbility().getPaidList("Revealed");
}
else if (calcX[0].startsWith("Returned")) {
list = sa.getRootAbility().getPaidList("Returned");
}
else if (calcX[0].startsWith("Targeted")) {
list = sa.findTargetedCards();
}
@@ -966,7 +972,7 @@ public class AbilityUtils {
final Player player = sa instanceof SpellAbility ? ((SpellAbility)sa).getActivatingPlayer() : card.getController();
if (defined.equals("Self") || defined.equals("ThisTargetedCard") || getPaidCards(sa, defined) != null) {
if (defined.equals("Self") || defined.equals("ThisTargetedCard") || defined.startsWith("Valid") || getPaidCards(sa, defined) != null) {
// do nothing, Self is for Cards, not Players
} else if (defined.equals("TargetedOrController")) {
players.addAll(getDefinedPlayers(card, "Targeted", sa));
@@ -1035,6 +1041,11 @@ public class AbilityUtils {
if (root != null) {
addPlayer(Lists.newArrayList(root), defined, players);
}
} else if (defined.startsWith("OriginalHost")) {
Card originalHost = sa.getOriginalHost();
if (originalHost != null) {
addPlayer(Lists.newArrayList(originalHost), defined, players);
}
}
else if (defined.startsWith("DelayTriggerRemembered") && sa instanceof SpellAbility) {
SpellAbility root = ((SpellAbility)sa).getRootAbility();
@@ -1052,17 +1063,16 @@ public class AbilityUtils {
final SpellAbility root = ((SpellAbility)sa).getRootAbility();
Object o = null;
if (defParsed.endsWith("Controller")) {
String triggeringType = defParsed.substring(9);
triggeringType = triggeringType.substring(0, triggeringType.length() - 10);
final boolean orCont = defParsed.endsWith("OrController");
String triggeringType = defParsed.substring(9, defParsed.length() - (orCont ? 12 : 10));
final Object c = root.getTriggeringObject(AbilityKey.fromString(triggeringType));
if (c instanceof Card) {
if (orCont && c instanceof Player) {
o = c;
} else if (c instanceof Card) {
o = ((Card) c).getController();
}
if (c instanceof SpellAbility) {
} else if (c instanceof SpellAbility) {
o = ((SpellAbility) c).getActivatingPlayer();
}
// For merged permanent
if (c instanceof CardCollection) {
} else if (c instanceof CardCollection) { // For merged permanent
o = ((CardCollection) c).get(0).getController();
}
}
@@ -1440,7 +1450,7 @@ public class AbilityUtils {
// The player who has the chance to cancel the ability
final String pays = sa.getParamOrDefault("UnlessPayer", "TargetedController");
final FCollectionView<Player> allPayers = getDefinedPlayers(sa.getHostCard(), pays, sa);
final FCollectionView<Player> allPayers = getDefinedPlayers(source, pays, sa);
final String resolveSubs = sa.getParam("UnlessResolveSubs"); // no value means 'Always'
final boolean execSubsWhenPaid = "WhenPaid".equals(resolveSubs) || StringUtils.isBlank(resolveSubs);
final boolean execSubsWhenNotPaid = "WhenNotPaid".equals(resolveSubs) || StringUtils.isBlank(resolveSubs);
@@ -1477,7 +1487,7 @@ public class AbilityUtils {
cost = new Cost(new ManaCost(new ManaCostParser(String.valueOf(source.getChosenNumber()))), true);
}
else if (unlessCost.startsWith("DefinedCost")) {
CardCollection definedCards = getDefinedCards(sa.getHostCard(), unlessCost.split("_")[1], sa);
CardCollection definedCards = getDefinedCards(source, unlessCost.split("_")[1], sa);
if (definedCards.isEmpty()) {
sa.resolve();
resolveSubAbilities(sa, game);
@@ -1497,7 +1507,7 @@ public class AbilityUtils {
cost = new Cost(newCost.toManaCost(), true);
}
else if (unlessCost.startsWith("DefinedSACost")) {
FCollection<SpellAbility> definedSAs = getDefinedSpellAbilities(sa.getHostCard(), unlessCost.split("_")[1], sa);
FCollection<SpellAbility> definedSAs = getDefinedSpellAbilities(source, unlessCost.split("_")[1], sa);
if (definedSAs.isEmpty()) {
sa.resolve();
resolveSubAbilities(sa, game);
@@ -1594,42 +1604,39 @@ public class AbilityUtils {
if (sa.hasParam("RememberCostCards") && !sa.getPaidHash().isEmpty()) {
List <Card> noList = Lists.newArrayList();
Map<String, CardCollection> paidLists = sa.getPaidHash();
if (sa.hasParam("RememberCostExcept")) {
noList.addAll(AbilityUtils.getDefinedCards(host, sa.getParam("RememberCostExcept"), sa));
}
if (sa.getParam("Cost").contains("Exile")) {
if (paidLists.containsKey("Exiled")) {
final CardCollection paidListExiled = sa.getPaidList("Exiled");
for (final Card exiledAsCost : paidListExiled) {
if (!noList.contains(exiledAsCost)) {
host.addRemembered(exiledAsCost);
}
}
}
else if (sa.getParam("Cost").contains("Sac")) {
} else if (paidLists.containsKey("Sacrificed")) {
final CardCollection paidListSacrificed = sa.getPaidList("Sacrificed");
for (final Card sacrificedAsCost : paidListSacrificed) {
if (!noList.contains(sacrificedAsCost)) {
host.addRemembered(sacrificedAsCost);
}
}
}
else if (sa.getParam("Cost").contains("tapXType")) {
} else if (paidLists.containsKey("Tapped")) {
final CardCollection paidListTapped = sa.getPaidList("Tapped");
for (final Card tappedAsCost : paidListTapped) {
if (!noList.contains(tappedAsCost)) {
host.addRemembered(tappedAsCost);
}
}
}
else if (sa.getParam("Cost").contains("Unattach")) {
} else if (paidLists.containsKey("Unattached")) {
final CardCollection paidListUnattached = sa.getPaidList("Unattached");
for (final Card unattachedAsCost : paidListUnattached) {
if (!noList.contains(unattachedAsCost)) {
host.addRemembered(unattachedAsCost);
}
}
}
else if (sa.getParam("Cost").contains("Discard")) {
} else if (paidLists.containsKey("Discarded")) {
final CardCollection paidListDiscarded = sa.getPaidList("Discarded");
for (final Card discardedAsCost : paidListDiscarded) {
if (!noList.contains(discardedAsCost)) {
@@ -1846,9 +1853,6 @@ public class AbilityUtils {
}
}
list = CardLists.getValidCards(list, k[1], sa.getActivatingPlayer(), c, sa);
if (k[0].contains("TotalToughness")) {
return doXMath(Aggregates.sum(list, CardPredicates.Accessors.fnGetNetToughness), expr, c, ctb);
}
return doXMath(list.size(), expr, c, ctb);
}
@@ -2258,6 +2262,9 @@ public class AbilityUtils {
if (sq[0].equals("Monarch")) {
return doXMath(calculateAmount(c, sq[player.isMonarch() ? 1 : 2], ctb), expr, c, ctb);
}
if (sq[0].equals("Initiative")) {
return doXMath(calculateAmount(c, sq[player.hasInitiative() ? 1: 2], ctb), expr, c, ctb);
}
if (sq[0].equals("StartingPlayer")) {
return doXMath(calculateAmount(c, sq[player.isStartingPlayer() ? 1: 2], ctb), expr, c, ctb);
}
@@ -2345,9 +2352,6 @@ public class AbilityUtils {
if (sq[0].equals("BloodthirstAmount")) {
return doXMath(player.getBloodthirstAmount(), expr, c, ctb);
}
if (sq[0].equals("YourLandsPlayed")) {
return doXMath(player.getLandsPlayedThisTurn(), expr, c, ctb);
}
if (sq[0].startsWith("YourCounters")) {
// "YourCountersExperience" or "YourCountersPoison"
@@ -2411,16 +2415,6 @@ public class AbilityUtils {
return doXMath(cmc, expr, c, ctb);
}
if (sq[0].startsWith("ColorsCtrl")) {
final String restriction = l[0].substring(11);
final CardCollection list = CardLists.getValidCards(player.getCardsIn(ZoneType.Battlefield), restriction, player, c, ctb);
byte n = 0;
for (final Card card : list) {
n |= card.getColor().getColor();
}
return doXMath(ColorSet.fromMask(n).countColors(), expr, c, ctb);
}
// Count$AttackersDeclared
if (sq[0].startsWith("AttackersDeclared")) {
List<Card> attackers = player.getCreaturesAttackedThisTurn();
@@ -2683,20 +2677,6 @@ public class AbilityUtils {
return MyRandom.getRandom().nextInt(1+max-min) + min;
}
// Count$TotalCounters.<counterType>_<valid>
if (sq[0].startsWith("TotalCounters")) {
final String[] restrictions = l[0].split("_");
final CounterType cType = CounterType.getType(restrictions[1]);
final String[] validFilter = restrictions[2].split(",");
CardCollectionView validCards = game.getCardsIn(ZoneType.Battlefield);
validCards = CardLists.getValidCards(validCards, validFilter, player, c, ctb);
int cCount = 0;
for (final Card card : validCards) {
cCount += card.getCounters(cType);
}
return doXMath(cCount, expr, c, ctb);
}
// Count$ThisTurnCast <Valid>
// Count$LastTurnCast <Valid>
if (sq[0].startsWith("ThisTurnCast") || sq[0].startsWith("LastTurnCast")) {
@@ -2751,13 +2731,27 @@ public class AbilityUtils {
String[] paidparts = l[0].split("\\$", 2);
String[] lparts = paidparts[0].split(" ", 2);
final CardCollectionView cardsInZones;
CardCollectionView cardsInZones = null;
if (lparts[0].contains("All")) {
cardsInZones = game.getCardsInGame();
} else {
cardsInZones = lparts[0].length() > 5
? game.getCardsIn(ZoneType.listValueOf(lparts[0].substring(5)))
: game.getCardsIn(ZoneType.Battlefield);
final List<ZoneType> zones = ZoneType.listValueOf(lparts[0].length() > 5 ? lparts[0].substring(5) : "Battlefield");
boolean usedLastState = false;
if (ctb instanceof SpellAbility && zones.size() == 1) {
SpellAbility sa = (SpellAbility) ctb;
if (sa.isReplacementAbility()) {
if (zones.get(0).equals(ZoneType.Battlefield)) {
cardsInZones = sa.getLastStateBattlefield();
usedLastState = true;
} else if (zones.get(0).equals(ZoneType.Graveyard)) {
cardsInZones = sa.getLastStateGraveyard();
usedLastState = true;
}
}
}
if (!usedLastState) {
cardsInZones = game.getCardsIn(zones);
}
}
int cnt;
@@ -2843,16 +2837,23 @@ public class AbilityUtils {
return Aggregates.max(list, CardPredicates.Accessors.fnGetCmc);
}
if (sq[0].startsWith("DifferentPower_")) {
final List<Integer> powers = Lists.newArrayList();
final String restriction = l[0].substring(15);
CardCollection list = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), restriction, player, c, ctb);
final Iterable<Card> powers = Aggregates.uniqueByLast(list, CardPredicates.Accessors.fnGetNetPower);
return doXMath(Iterables.size(powers), expr, c, ctb);
}
if (sq[0].startsWith("DifferentCounterKinds_")) {
final List<CounterType> kinds = Lists.newArrayList();
final String rest = l[0].substring(22);
CardCollection list = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), rest, player, c, ctb);
for (final Card card : list) {
Integer pow = card.getNetPower();
if (!powers.contains(pow)) {
powers.add(pow);
for (final Map.Entry<CounterType, Integer> map : card.getCounters().entrySet()) {
if (!kinds.contains(map.getKey())) {
kinds.add(map.getKey());
}
}
return doXMath(powers.size(), expr, c, ctb);
}
return doXMath(kinds.size(), expr, c, ctb);
}
// Complex counting methods
@@ -3029,6 +3030,10 @@ public class AbilityUtils {
}
private static void addPlayer(Iterable<Object> objects, final String def, FCollection<Player> players) {
addPlayer(objects, def, players, false);
}
private static void addPlayer(Iterable<Object> objects, final String def, FCollection<Player> players, boolean skipRemembered) {
for (Object o : objects) {
if (o instanceof Player) {
final Player p = (Player) o;
@@ -3043,6 +3048,9 @@ public class AbilityUtils {
players.add(c.getController());
} else if (def.endsWith("Owner")) {
players.add(c.getOwner());
} else if (def.endsWith("Remembered") && !skipRemembered) {
//fixme recursive call to skip so it will not cause StackOverflow, ie Riveteers Overlook
addPlayer(c.getRemembered(), def, players, true);
}
} else if (o instanceof SpellAbility) {
final SpellAbility c = (SpellAbility) o;
@@ -3300,6 +3308,21 @@ public class AbilityUtils {
return doXMath(totPlayer, m, source, ctb);
}
if (l[0].startsWith("Condition")) {
int totPlayer = 0;
String[] parts = l[0].split(" ", 2);
boolean def = parts[0].equals("Condition");
String comparator = !def ? parts[0].substring(9, 11) : "GE";
int y = !def ? calculateAmount(source, parts[0].substring(11), ctb) : 1;
for (Player p : players) {
int x = playerXProperty(p, parts[1], source, ctb);
if (Expressions.compare(x, comparator, y)) {
totPlayer++;
}
}
return doXMath(totPlayer, m, source, ctb);
}
if (sq[0].contains("DamageThisTurn")) {
int totDmg = 0;
for (Player p : players) {
@@ -3347,6 +3370,18 @@ public class AbilityUtils {
return doXMath(cardsonbattlefield.size(), m, source, ctb);
}
if (l[0].startsWith("ThisTurnEntered")) {
final String[] workingCopy = l[0].split("_");
ZoneType destination = ZoneType.smartValueOf(workingCopy[1]);
final boolean hasFrom = workingCopy[2].equals("from");
ZoneType origin = hasFrom ? ZoneType.smartValueOf(workingCopy[3]) : null;
String validFilter = workingCopy[hasFrom ? 4 : 2] ;
final List<Card> res = CardUtil.getThisTurnEntered(destination, origin, validFilter, source, ctb, player);
return doXMath(res.size(), m, source, ctb);
}
final String[] sq = l[0].split("\\.");
final String value = sq[0];
@@ -3436,10 +3471,6 @@ public class AbilityUtils {
return doXMath(player.getNumDiscardedThisTurn(), m, source, ctb);
}
if (value.contains("TokensCreatedThisTurn")) {
return doXMath(player.getNumTokensCreatedThisTurn(), m, source, ctb);
}
if (value.contains("AttackersDeclared")) {
return doXMath(player.getCreaturesAttackedThisTurn().size(), m, source, ctb);
}
@@ -3453,10 +3484,14 @@ public class AbilityUtils {
}
if (value.equals("OpponentsAttackedThisTurn")) {
final PlayerCollection opps = game.getPlayersAttackedThisTurn().get(player);
final List<Player> opps = player.getAttackedPlayersMyTurn();
return doXMath(opps == null ? 0 : opps.size(), m, source, ctb);
}
if (value.equals("OpponentsAttackedThisCombat")) {
return doXMath(game.getCombat().getAttackedOpponents(player).size(), m, source, ctb);
}
if (value.equals("DungeonsCompleted")) {
return doXMath(player.getCompletedDungeons().size(), m, source, ctb);
}

View File

@@ -171,6 +171,7 @@ public enum ApiType {
Subgame (SubgameEffect.class),
Surveil (SurveilEffect.class),
SwitchBlock (SwitchBlockEffect.class),
TakeInitiative (TakeInitiativeEffect.class),
Tap (TapEffect.class),
TapAll (TapAllEffect.class),
TapOrUntap (TapOrUntapEffect.class),

View File

@@ -60,15 +60,6 @@ public abstract class SpellAbilityEffect {
return sa.getDescription();
}
protected static final void resolveSubAbility(final SpellAbility sa) {
// if mana production has any type of SubAbility, undoable=false
final AbilitySub abSub = sa.getSubAbility();
if (abSub != null) {
sa.setUndoable(false);
AbilityUtils.resolve(abSub);
}
}
/**
* Returns this effect description with needed prelude and epilogue.
* @param params
@@ -580,7 +571,7 @@ public abstract class SpellAbilityEffect {
// important to update defenders here, maybe some PW got removed
combat.initConstraints();
if (sa.hasParam("ChoosePlayerOrPlaneswalker")) {
PlayerCollection defendingPlayers = AbilityUtils.getDefinedPlayers(host, attacking, sa);
PlayerCollection defendingPlayers = AbilityUtils.getDefinedPlayers(sa.hasParam("ForEach") ? c : host, attacking, sa);
defs = new FCollection<>();
for (Player p : defendingPlayers) {
defs.addAll(combat.getDefendersControlledBy(p));
@@ -649,9 +640,15 @@ public abstract class SpellAbilityEffect {
return combatChanged;
}
protected static GameCommand untilHostLeavesPlayCommand(final CardZoneTable triggerList, final Card hostCard) {
protected static GameCommand untilHostLeavesPlayCommand(final CardZoneTable triggerList, final SpellAbility sa) {
final Card hostCard = sa.getHostCard();
final Game game = hostCard.getGame();
hostCard.addUntilLeavesBattlefield(triggerList.allCards());
final TriggerHandler trigHandler = game.getTriggerHandler();
final Card lki = CardUtil.getLKICopy(hostCard);
lki.clearControllers();
lki.setOwner(sa.getActivatingPlayer());
return new GameCommand() {
private static final long serialVersionUID = 1L;
@@ -678,6 +675,29 @@ public abstract class SpellAbilityEffect {
if (newCard == null || !newCard.equalsWithTimestamp(c)) {
continue;
}
Trigger trig = null;
if (sa.hasAdditionalAbility("ReturnAbility")) {
String valid = sa.getParamOrDefault("ReturnValid", "Card.IsTriggerRemembered");
String trigSA = "Mode$ ChangesZone | Origin$ " + cell.getColumnKey() + " | Destination$ " + cell.getRowKey() + " | ValidCard$ " + valid +
" | TriggerDescription$ " + sa.getAdditionalAbility("ReturnAbility").getParam("SpellDescription");
trig = TriggerHandler.parseTrigger(trigSA, hostCard, sa.isIntrinsic(), null);
trig.setSpawningAbility(sa.copy(lki, sa.getActivatingPlayer(), true));
trig.setActiveZone(null);
trig.addRemembered(newCard);
SpellAbility overridingSA = sa.getAdditionalAbility("ReturnAbility").copy(hostCard, sa.getActivatingPlayer(), false);
// need to reset the parent, additionalAbility does set it to this
if (overridingSA instanceof AbilitySub) {
((AbilitySub)overridingSA).setParent(null);
}
trig.setOverridingAbility(overridingSA);
// Delayed Trigger should only happen once, no need for cleanup?
trigHandler.registerThisTurnDelayedTrigger(trig);
}
// no cause there?
Card movedCard = game.getAction().moveTo(cell.getRowKey(), newCard, 0, null, moveParams);
untilTable.put(cell.getColumnKey(), cell.getRowKey(), movedCard);

View File

@@ -206,8 +206,9 @@ public class AnimateEffect extends AnimateEffectBase {
protected String getStackDescription(SpellAbility sa) {
final Card host = sa.getHostCard();
final StringBuilder sb = new StringBuilder();
final List<Card> tgts = getCardsfromTargets(sa);
final boolean justOne = tgts.size() == 1;
final List<Card> tgts = getDefinedCardsOrTargeted(sa);
//possible to be building stack desc before Defined is populated... for now, 0 will default to singular
final boolean justOne = tgts.size() <= 1;
if (sa.hasParam("IfDesc")) {
if (sa.getParam("IfDesc").equals("True") && sa.hasParam("SpellDescription")) {
@@ -264,7 +265,7 @@ public class AnimateEffect extends AnimateEffectBase {
} else {
sb.append("toughness ").append(toughness).append(" ");
}
} else {
} else if (sb.length() > initial) {
sb.append(justOne ? "becomes " : "become ");
}
@@ -291,7 +292,8 @@ public class AnimateEffect extends AnimateEffectBase {
}
}
if (keywords.size() > 0) {
sb.append("and gains ").append(Lang.joinHomogenous(keywords).toLowerCase()).append(" ");
sb.append(sb.length() > initial ? "and " : "").append(" gains ");
sb.append(Lang.joinHomogenous(keywords).toLowerCase()).append(" ");
}
// sb.append(abilities)
// sb.append(triggers)
@@ -317,6 +319,20 @@ public class AnimateEffect extends AnimateEffectBase {
}
sb.append(".");
if (sa.hasParam("AtEOT")) {
sb.append(" ");
final String eot = sa.getParam("AtEOT");
final String pronoun = justOne ? "it" : "them";
if (eot.equals("Hand")) {
sb.append("Return ").append(pronoun).append(" to your hand");
} else if (eot.equals("SacrificeCtrl")) {
sb.append(justOne ? "Its controller sacrifices it" : "Their controllers sacrifice them");
} else { //Sacrifice,Exile
sb.append(eot).append(" ").append(pronoun);
}
sb.append(" at the beginning of the next end step.");
}
return sb.toString();
}

View File

@@ -84,7 +84,7 @@ public class AttachEffect extends SpellAbilityEffect {
choices = AbilityUtils.getDefinedEntities(source, sa.getParam("PlayerChoices"), sa);
for (final Card attachment : attachments) {
for (GameEntity g : choices) {
if (!g.canBeAttached(attachment)) {
if (!g.canBeAttached(attachment, sa)) {
choices.remove(g);
}
}
@@ -100,7 +100,7 @@ public class AttachEffect extends SpellAbilityEffect {
if (e != null)
cardChoices.remove(e);
}
cardChoices = CardLists.filter(cardChoices, CardPredicates.canBeAttached(attachment));
cardChoices = CardLists.filter(cardChoices, CardPredicates.canBeAttached(attachment, sa));
}
choices.addAll(cardChoices);
}
@@ -138,7 +138,7 @@ public class AttachEffect extends SpellAbilityEffect {
// TODO add params for message
continue;
attachment.attachToEntity(attachTo);
attachment.attachToEntity(attachTo, sa);
if (sa.hasParam("RememberAttached") && attachment.isAttachedToEntity(attachTo)) {
source.addRemembered(attachment);
}

View File

@@ -2,11 +2,10 @@ package forge.game.ability.effects;
import java.util.List;
import org.apache.commons.lang3.StringUtils;
import forge.game.ability.SpellAbilityEffect;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.util.Lang;
public class BecomeMonarchEffect extends SpellAbilityEffect {
@@ -16,8 +15,8 @@ public class BecomeMonarchEffect extends SpellAbilityEffect {
final List<Player> tgtPlayers = getTargetPlayers(sa);
sb.append(StringUtils.join(tgtPlayers, ", "));
sb.append(" becomes the Monarch.");
sb.append(Lang.joinHomogenous(tgtPlayers)).append(tgtPlayers.size() == 1 ? " becomes" : " become");
sb.append(" the monarch.");
return sb.toString();
}

View File

@@ -192,6 +192,7 @@ public class ChangeZoneAllEffect extends SpellAbilityEffect {
if (host == null) {
host = sa.getHostCard();
}
host.addExiledCard(movedCard);
movedCard.setExiledWith(host);
movedCard.setExiledBy(host.getController());
}
@@ -260,7 +261,7 @@ public class ChangeZoneAllEffect extends SpellAbilityEffect {
triggerList.triggerChangesZoneAll(game, sa);
if (sa.hasParam("Duration")) {
addUntilCommand(sa, untilHostLeavesPlayCommand(triggerList, source));
addUntilCommand(sa, untilHostLeavesPlayCommand(triggerList, sa));
}
// if Shuffle parameter exists, and any amount of cards were owned by

View File

@@ -138,12 +138,13 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
final String ct = sa.getParam("ChangeType");
type = CardType.CoreType.isValidEnum(ct) ? ct.toLowerCase() : ct;
}
final String cardTag = type.contains("card") ? "" : " card";
final int num = sa.hasParam("ChangeNum") ? AbilityUtils.calculateAmount(host,
sa.getParam("ChangeNum"), sa) : 1;
boolean tapped = sa.hasParam("Tapped");
boolean attacking = sa.hasParam("Attacking");
if (sa.hasParam("Ninjutsu")) {
if (sa.isNinjutsu()) {
tapped = true;
attacking = true;
}
@@ -170,7 +171,6 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
} else {
sb.append(" for ");
}
final String cardTag = type.contains("card") ? "" : " card";
sb.append(Lang.nounWithNumeralExceptOne(num, type + cardTag)).append(", ");
if (!sa.hasParam("NoReveal") && ZoneType.smartValueOf(destination).isHidden()) {
if (choosers.size() == 1) {
@@ -196,8 +196,9 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
if (destination.equals("Battlefield")) {
sb.append("onto the battlefield");
if (tapped) {
sb.append(" tapped");
sb.append(" tapped").append(attacking ? " and" : "");
}
sb.append(attacking ? " attacking" : "");
if (sa.hasParam("GainControl")) {
sb.append(" under ").append(chooserNames).append("'s control");
}
@@ -259,14 +260,9 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
if (destination.equals("Battlefield")) {
sb.append(" onto the battlefield");
if (tapped) {
sb.append(" tapped");
if (attacking) {
sb.append(" and");
}
}
if (attacking) {
sb.append(" attacking");
sb.append(" tapped").append(attacking ? " and" : "");
}
sb.append(attacking ? " attacking" : "");
if (sa.hasParam("GainControl")) {
sb.append(" under ").append(chooserNames).append("'s control");
}
@@ -293,14 +289,38 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
// TODO Expand on this Description as more cards use it
// for the non-targeted SAs when you choose what is returned on resolution
sb.append("Return ").append(num).append(" ").append(type).append(" card(s) ");
sb.append(" to your ").append(destination);
sb.append(" to your ").append(destination).append(".");
} else if (origin.equals("Graveyard")) {
// for non-targeted SAs when you choose what is moved on resolution
// this will need expansion as more cards use it
sb.append(chooserNames).append(" puts ");
final String cardTag = type.contains("card") ? "" : " card";
sb.append(num != 0 ? Lang.nounWithNumeralExceptOne(num, type + cardTag) :
sa.getParamOrDefault("ChangeNumDesc", "") + " " + type + cardTag);
final boolean changeNumDesc = sa.hasParam("ChangeNumDesc");
final boolean mandatory = sa.hasParam("Mandatory");
String changed;
if (changeNumDesc) {
changed = sa.getParam("ChangeNumDesc") + " " + type + cardTag;
} else if (!mandatory) {
changed = Lang.nounWithNumeral(num, type + cardTag);
} else {
changed = Lang.nounWithNumeralExceptOne(num, type + cardTag);
}
final boolean battlefield = destination.equals("Battlefield");
sb.append(chooserNames).append(" returns ").append(mandatory || changeNumDesc ? "" : "up to ");
sb.append(changed);
// so far, it seems non-targeted only lets you return from your own graveyard
sb.append(" from their graveyard").append(choosers.size() > 1 ? "s" : "");
sb.append(battlefield ? " to the " : " into their ").append(destination.toLowerCase());
if (sa.hasParam("WithCountersType")) {
final CounterType cType = CounterType.getType(sa.getParam("WithCountersType"));
if (cType != null) {
sb.append(" with an additional ").append(cType.getName()).append(" counter on it");
} else {
sb.append(" [ChangeZoneEffect WithCountersType error]");
}
}
sb.append(".");
} else if (origin.equals("Exile")) {
// for non-targeted, moved cards are chosen on resolution will need expansion as more cards use it
sb.append(chooserNames).append(" puts ").append(Lang.nounWithNumeralExceptOne(num, type + cardTag));
sb.append(" into their ").append(destination.toLowerCase()).append(".");
}
@@ -342,14 +362,16 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
final String fromGraveyard = " from the graveyard";
if (destination.equals(ZoneType.Battlefield)) {
final boolean attacking = (sa.hasParam("Attacking"));
if (ZoneType.Graveyard.equals(origin)) {
sb.append("Return").append(targetname).append(fromGraveyard).append(" to the battlefield");
} else {
sb.append("Put").append(targetname).append(" onto the battlefield");
}
if (sa.hasParam("Tapped")) {
sb.append(" tapped");
sb.append(" tapped").append(attacking ? " and" : "");
}
sb.append(attacking ? " attacking" : "");
if (sa.hasParam("GainControl")) {
sb.append(" under your control");
}
@@ -431,7 +453,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
return;
}
if (sa.isHidden() && !sa.hasParam("Ninjutsu")) {
if (sa.isHidden() && !sa.isNinjutsu()) {
changeHiddenOriginResolve(sa);
} else {
//else if (isKnown(origin) || sa.containsKey("Ninjutsu")) {
@@ -575,7 +597,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
movedCard = game.getAction().moveToLibrary(gameCard, libraryPosition, sa);
} else {
if (destination.equals(ZoneType.Battlefield)) {
if (sa.hasParam("Tapped") || sa.hasParam("Ninjutsu")) {
if (sa.hasParam("Tapped") || sa.isNinjutsu()) {
gameCard.setTapped(true);
}
if (sa.hasParam("Untapped")) {
@@ -595,10 +617,9 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
gameCard.addEtbCounter(cType, cAmount, player);
}
if (sa.hasParam("GainControl")) {
Player newController = player;
if (sa.hasParam("NewController")) {
newController = Iterables.getFirst(AbilityUtils.getDefinedPlayers(hostCard, sa.getParam("NewController"), sa), null);
}
final String g = sa.getParam("GainControl");
Player newController = g.equals("True") ? sa.getActivatingPlayer() :
AbilityUtils.getDefinedPlayers(sa.getHostCard(), g, sa).get(0);
if (newController != null) {
if (newController != gameCard.getController()) {
gameCard.runChangeControllerCommands();
@@ -615,7 +636,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
// only valid choices are when they could be attached
// TODO for multiple Auras entering attached this way, need to use LKI info
if (!list.isEmpty()) {
list = CardLists.filter(list, CardPredicates.canBeAttached(gameCard));
list = CardLists.filter(list, CardPredicates.canBeAttached(gameCard, sa));
}
if (!list.isEmpty()) {
Map<String, Object> params = Maps.newHashMap();
@@ -624,7 +645,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
// TODO can't attach later or moveToPlay would attach indirectly
// bypass canBeAttached to skip Protection checks when trying to attach multiple auras that would grant protection
gameCard.attachToEntity(game.getCardState(attachedTo), true);
gameCard.attachToEntity(game.getCardState(attachedTo), sa, true);
} else { // When it should enter the battlefield attached to an illegal permanent it fails
continue;
}
@@ -636,7 +657,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
Map<String, Object> params = Maps.newHashMap();
params.put("Attach", gameCard);
Player attachedTo = player.getController().chooseSingleEntityForEffect(list, sa, Localizer.getInstance().getMessage("lblSelectAPlayerAttachSourceTo", gameCard.toString()), params);
gameCard.attachToEntity(attachedTo);
gameCard.attachToEntity(attachedTo, sa);
}
else { // When it should enter the battlefield attached to an illegal player it fails
continue;
@@ -689,7 +710,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
if (addToCombat(movedCard, movedCard.getController(), sa, "Attacking", "Blocking")) {
combatChanged = true;
}
if (sa.hasParam("Ninjutsu")) {
if (sa.isNinjutsu()) {
// Ninjutsu need to get the Defender of the Returned Creature
final Card returned = sa.getPaidList("Returned").getFirst();
final GameEntity defender = game.getCombat().getDefenderByAttacker(returned);
@@ -708,7 +729,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
Map<String, Object> params = Maps.newHashMap();
params.put("Attach", gameCard);
Card attachedTo = chooser.getController().chooseSingleEntityForEffect(list, sa, title, params);
movedCard.attachToEntity(attachedTo);
movedCard.attachToEntity(attachedTo, sa);
}
}
} else {
@@ -719,6 +740,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
if (host == null) {
host = sa.getHostCard();
}
host.addExiledCard(gameCard);
gameCard.setExiledWith(host);
gameCard.setExiledBy(host.getController());
}
@@ -745,6 +767,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
if (destination.equals(ZoneType.Exile) && !movedCard.isToken()) {
movedCard.setExiledWith(host);
if (host != null) {
host.addExiledCard(movedCard);
movedCard.setExiledBy(host.getController());
}
}
@@ -844,7 +867,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
registerDelayedTrigger(sa, sa.getParam("AtEOT"), triggerList.allCards());
}
if ("UntilHostLeavesPlay".equals(sa.getParam("Duration"))) {
addUntilCommand(sa, untilHostLeavesPlayCommand(triggerList, hostCard));
addUntilCommand(sa, untilHostLeavesPlayCommand(triggerList, sa));
}
// for things like Gaea's Blessing
@@ -1253,10 +1276,9 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
source.removeRemembered(c);
}
if (sa.hasParam("GainControl")) {
Player newController = sa.getActivatingPlayer();
if (sa.hasParam("NewController")) {
newController = AbilityUtils.getDefinedPlayers(sa.getHostCard(), sa.getParam("NewController"), sa).get(0);
}
final String g = sa.getParam("GainControl");
Player newController = g.equals("True") ? sa.getActivatingPlayer() :
AbilityUtils.getDefinedPlayers(sa.getHostCard(), g, sa).get(0);
if (newController != c.getController()) {
c.runChangeControllerCommands();
}
@@ -1285,7 +1307,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
// only valid choices are when they could be attached
// TODO for multiple Auras entering attached this way, need to use LKI info
if (!list.isEmpty()) {
list = CardLists.filter(list, CardPredicates.canBeAttached(c));
list = CardLists.filter(list, CardPredicates.canBeAttached(c, sa));
}
if (!list.isEmpty()) {
String title = Localizer.getInstance().getMessage("lblSelectACardAttachSourceTo", CardTranslation.getTranslatedName(c.getName()));
@@ -1295,7 +1317,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
// TODO can't attach later or moveToPlay would attach indirectly
// bypass canBeAttached to skip Protection checks when trying to attach multiple auras that would grant protection
c.attachToEntity(game.getCardState(attachedTo), true);
c.attachToEntity(game.getCardState(attachedTo), sa, true);
}
else { // When it should enter the battlefield attached to an illegal permanent it fails
continue;
@@ -1309,7 +1331,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
Map<String, Object> params = Maps.newHashMap();
params.put("Attach", c);
Player attachedTo = player.getController().chooseSingleEntityForEffect(list, sa, title, params);
c.attachToEntity(attachedTo);
c.attachToEntity(attachedTo, sa);
}
else { // When it should enter the battlefield attached to an illegal permanent it fails
continue;
@@ -1339,7 +1361,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
Map<String, Object> params = Maps.newHashMap();
params.put("Attach", movedCard);
Card attachedTo = decider.getController().chooseSingleEntityForEffect(list, sa, title, params);
movedCard.attachToEntity(attachedTo);
movedCard.attachToEntity(attachedTo, sa);
}
}
}
@@ -1350,6 +1372,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
if (host == null) {
host = sa.getHostCard();
}
host.addExiledCard(movedCard);
movedCard.setExiledWith(host);
movedCard.setExiledBy(host.getController());
}
@@ -1409,6 +1432,9 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
}
}
}
if (sa.hasParam("RememberLKI")) {
source.addRemembered(CardUtil.getLKICopy(c));
}
if (forget) {
source.removeRemembered(movedCard);
}
@@ -1446,7 +1472,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
triggerList.triggerChangesZoneAll(game, sa);
if ("UntilHostLeavesPlay".equals(sa.getParam("Duration"))) {
addUntilCommand(sa, untilHostLeavesPlayCommand(triggerList, source));
addUntilCommand(sa, untilHostLeavesPlayCommand(triggerList, sa));
}
}
@@ -1541,6 +1567,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
host = srcSA.getHostCard();
}
movedCard = game.getAction().exile(tgtHost, srcSA, params);
host.addExiledCard(movedCard);
movedCard.setExiledWith(host);
movedCard.setExiledBy(host.getController());
} else if (srcSA.getParam("Destination").equals("TopOfLibrary")) {

View File

@@ -2,6 +2,7 @@ package forge.game.ability.effects;
import forge.game.Game;
import forge.game.GameEntity;
import forge.game.ability.AbilityUtils;
import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card;
@@ -18,7 +19,12 @@ public class CleanUpEffect extends SpellAbilityEffect {
*/
@Override
public void resolve(SpellAbility sa) {
Card source = sa.getHostCard();
Card source;
if (sa.hasParam("Defined")) {
source = getDefinedCardsOrTargeted(sa).get(0);
} else {
source = sa.getHostCard();
}
final Game game = source.getGame();
String logMessage = "";
@@ -31,13 +37,8 @@ public class CleanUpEffect extends SpellAbilityEffect {
game.getCardState(source).clearRemembered();
}
if (sa.hasParam("ForgetDefined")) {
for (final Card card : AbilityUtils.getDefinedCards(source, sa.getParam("ForgetDefined"), sa)) {
source.removeRemembered(card);
}
}
if (sa.hasParam("ForgetDefinedPlayer")) {
for (final Player player : AbilityUtils.getDefinedPlayers(source, sa.getParam("ForgetDefinedPlayer"), sa)) {
source.removeRemembered(player);
for (final GameEntity ge : AbilityUtils.getDefinedEntities(source, sa.getParam("ForgetDefined"), sa)) {
source.removeRemembered(ge);
}
}
if (sa.hasParam("ClearImprinted")) {

View File

@@ -12,7 +12,10 @@ import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
import forge.util.Lang;
import forge.util.Localizer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
@@ -24,14 +27,15 @@ public class ConniveEffect extends SpellAbilityEffect {
*/
@Override
protected String getStackDescription(SpellAbility sa) {
final StringBuilder sb = new StringBuilder();
List<Card> tgt = getTargetCards(sa);
if (tgt.size() <= 0) {
return "";
} else {
final StringBuilder sb = new StringBuilder();
sb.append(Lang.joinHomogenous(tgt)).append(tgt.size() > 1 ? " connive." : " connives.");
return sb.toString();
}
}
/* (non-Javadoc)
* @see forge.game.ability.SpellAbilityEffect#resolve(forge.game.spellability.SpellAbility)
@@ -39,10 +43,30 @@ public class ConniveEffect extends SpellAbilityEffect {
@Override
public void resolve(SpellAbility sa) {
final Card host = sa.getHostCard();
final Player hostCon = host.getController();
final Game game = host.getGame();
final int num = AbilityUtils.calculateAmount(host, sa.getParamOrDefault("ConniveNum", "1"), sa);
CardCollection toConnive = getTargetCards(sa);
if (toConnive.isEmpty()) { // if nothing is conniving, we're done
return;
}
List<Player> controllers = new ArrayList<>();
for (Card c : toConnive) {
final Player controller = c.getController();
if (!controllers.contains(controller)) {
controllers.add(controller);
}
}
//order controllers by APNAP
int indexAP = controllers.indexOf(game.getPhaseHandler().getPlayerTurn());
if (indexAP != -1) {
Collections.rotate(controllers, - indexAP);
}
for (final Player p : controllers) {
CardCollection connivers = CardLists.filterControlledBy(toConnive, p);
while (connivers.size() > 0) {
GameEntityCounterTable table = new GameEntityCounterTable();
final CardZoneTable triggerList = new CardZoneTable();
Map<Player, CardCollectionView> discardedMap = Maps.newHashMap();
@@ -50,44 +74,39 @@ public class ConniveEffect extends SpellAbilityEffect {
moveParams.put(AbilityKey.LastStateBattlefield, sa.getLastStateBattlefield());
moveParams.put(AbilityKey.LastStateGraveyard, sa.getLastStateGraveyard());
for (final Card c : getTargetCards(sa)) {
final Player p = c.getController();
Card conniver = connivers.size() > 1 ? p.getController().chooseSingleEntityForEffect(connivers, sa,
Localizer.getInstance().getMessage("lblChooseConniver"), null) : connivers.get(0);
p.drawCards(num, sa, moveParams);
CardCollectionView dPHand = p.getCardsIn(ZoneType.Hand);
dPHand = CardLists.filter(dPHand, CardPredicates.Presets.NON_TOKEN);
if (dPHand.isEmpty()) { // seems unlikely, but just to be safe
continue; // for loop over players
}
CardCollection validCards = CardLists.getValidCards(dPHand, "Card", hostCon, host, sa);
if (!p.canDiscardBy(sa, true)) {
CardCollection validDisards =
CardLists.filter(p.getCardsIn(ZoneType.Hand), CardPredicates.Presets.NON_TOKEN);
if (validDisards.isEmpty() || !p.canDiscardBy(sa, true)) { // hand being empty unlikely, just to be safe
continue;
}
int amt = Math.min(validCards.size(), num);
int amt = Math.min(validDisards.size(), num);
CardCollectionView toBeDiscarded = amt == 0 ? CardCollection.EMPTY :
p.getController().chooseCardsToDiscardFrom(p, sa, validCards, amt, amt);
p.getController().chooseCardsToDiscardFrom(p, sa, validDisards, amt, amt);
if (toBeDiscarded.size() > 1) {
toBeDiscarded = GameActionUtil.orderCardsByTheirOwners(game, toBeDiscarded, ZoneType.Graveyard, sa);
}
discardedMap.put(p, toBeDiscarded);
int numCntrs = CardLists.getValidCardCount(toBeDiscarded, "Card.nonLand", hostCon, host, sa);
int numCntrs = CardLists.getValidCardCount(toBeDiscarded, "Card.nonLand", p, host, sa);
// need to get newest game state to check if it is still on the battlefield and the timestamp didn't change
Card gamec = game.getCardState(c);
Card gamec = game.getCardState(conniver);
// if the card is not in the game anymore, this might still return true, but it's no problem
if (game.getZoneOf(gamec).is(ZoneType.Battlefield) && gamec.equalsWithTimestamp(c)) {
c.addCounter(CounterEnumType.P1P1, numCntrs, p, table);
}
if (game.getZoneOf(gamec).is(ZoneType.Battlefield) && gamec.equalsWithTimestamp(conniver)) {
conniver.addCounter(CounterEnumType.P1P1, numCntrs, p, table);
}
connivers.remove(conniver);
discardedMap.put(p, CardCollection.getView(toBeDiscarded));
discard(sa, triggerList, true, discardedMap, moveParams);
table.replaceCounterEffect(game, sa, true);
triggerList.triggerChangesZoneAll(game, sa);
}
}
}
}

View File

@@ -1,8 +1,5 @@
package forge.game.ability.effects;
import java.util.Arrays;
import java.util.List;
import com.google.common.collect.Lists;
import forge.GameCommand;
@@ -17,6 +14,10 @@ import forge.game.event.GameEventCombatChanged;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
import forge.util.Localizer;
import java.util.Arrays;
import java.util.List;
public class ControlGainEffect extends SpellAbilityEffect {
@@ -102,6 +103,7 @@ public class ControlGainEffect extends SpellAbilityEffect {
@Override
public void resolve(SpellAbility sa) {
Card source = sa.getHostCard();
final Player activator = sa.getActivatingPlayer();
final boolean bUntap = sa.hasParam("Untap");
final boolean bTapOnLose = sa.hasParam("TapOnLose");
@@ -112,12 +114,25 @@ public class ControlGainEffect extends SpellAbilityEffect {
final List<Player> controllers = getDefinedPlayersOrTargeted(sa, "NewController");
final Player newController = controllers.isEmpty() ? sa.getActivatingPlayer() : controllers.get(0);
final Player newController = controllers.isEmpty() ? activator : controllers.get(0);
final Game game = newController.getGame();
CardCollectionView tgtCards = getDefinedCards(sa);
CardCollectionView tgtCards = null;
if (sa.hasParam("Choices")) {
Player chooser = sa.hasParam("Chooser") ? AbilityUtils.getDefinedPlayers(source,
sa.getParam("Chooser"), sa).get(0) : activator;
CardCollectionView choices = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield),
sa.getParam("Choices"), activator, source, sa);
if (!choices.isEmpty()) {
String title = sa.hasParam("ChoiceTitle") ? sa.getParam("ChoiceTitle") :
Localizer.getInstance().getMessage("lblChooseaCard") +" ";
tgtCards = chooser.getController().chooseCardsForEffect(choices, sa, title, 1, 1, false, null);
}
} else {
tgtCards = getDefinedCards(sa);
}
if (sa.hasParam("ControlledByTarget")) {
if (tgtCards != null & sa.hasParam("ControlledByTarget")) {
tgtCards = CardLists.filterControlledBy(tgtCards, getTargetPlayers(sa));
}

View File

@@ -33,7 +33,6 @@ import forge.util.Aggregates;
import forge.util.Localizer;
import forge.util.PredicateString.StringOp;
import forge.util.TextUtil;
import forge.util.collect.FCollectionView;
public class CopyPermanentEffect extends TokenEffectBase {
@@ -68,7 +67,9 @@ public class CopyPermanentEffect extends TokenEffectBase {
if (sa.hasParam("AddTriggers")) {
final String oDesc = sa.getDescription();
final String trigStg = oDesc.substring(oDesc.indexOf("\""),oDesc.lastIndexOf("\"") + 1);
final String trigStg = oDesc.contains("\"") ?
oDesc.substring(oDesc.indexOf("\""),oDesc.lastIndexOf("\"") + 1) :
"[trigger text parsing error]";
if (addKWs) {
sb.append(" and ").append(trigStg);
} else {
@@ -103,24 +104,37 @@ public class CopyPermanentEffect extends TokenEffectBase {
final Card host = sa.getHostCard();
final Player activator = sa.getActivatingPlayer();
final Game game = host.getGame();
boolean useZoneTable = true;
CardZoneTable triggerList = sa.getChangeZoneTable();
if (triggerList == null) {
triggerList = new CardZoneTable();
useZoneTable = false;
}
if (sa.hasParam("ChangeZoneTable")) {
sa.setChangeZoneTable(triggerList);
useZoneTable = true;
}
if (sa.hasParam("Optional") && !activator.getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblCopyPermanentConfirm"))) {
MutableBoolean combatChanged = new MutableBoolean(false);
TokenCreateTable tokenTable = new TokenCreateTable();
if (sa.hasParam("Optional") && !activator.getController().confirmAction(sa, null,
Localizer.getInstance().getMessage("lblCopyPermanentConfirm"))) {
return;
}
final int numCopies = sa.hasParam("NumCopies") ? AbilityUtils.calculateAmount(host, sa.getParam("NumCopies"), sa) : 1;
final int numCopies = sa.hasParam("NumCopies") ? AbilityUtils.calculateAmount(host,
sa.getParam("NumCopies"), sa) : 1;
Player controller = null;
List<Player> controllers = Lists.newArrayList();
if (sa.hasParam("Controller")) {
final FCollectionView<Player> defined = AbilityUtils.getDefinedPlayers(host, sa.getParam("Controller"), sa);
if (!defined.isEmpty()) {
controller = defined.getFirst();
controllers = AbilityUtils.getDefinedPlayers(host, sa.getParam("Controller"), sa);
}
}
if (controller == null) {
controller = activator;
if (controllers.isEmpty()) {
controllers.add(activator);
}
for (final Player controller : controllers) {
List<Card> tgtCards = Lists.newArrayList();
if (sa.hasParam("ValidSupportedCopy")) {
@@ -209,27 +223,29 @@ public class CopyPermanentEffect extends TokenEffectBase {
tgtCards = getDefinedCardsOrTargeted(sa);
}
boolean useZoneTable = true;
CardZoneTable triggerList = sa.getChangeZoneTable();
if (triggerList == null) {
triggerList = new CardZoneTable();
useZoneTable = false;
}
if (sa.hasParam("ChangeZoneTable")) {
sa.setChangeZoneTable(triggerList);
useZoneTable = true;
}
MutableBoolean combatChanged = new MutableBoolean(false);
TokenCreateTable tokenTable = new TokenCreateTable();
for (final Card c : tgtCards) {
// 111.5. Similarly, if an effect would create a token that is a copy of an instant or sorcery card, no token is created.
// instant and sorcery can't enter the battlefield
// and it can't be replaced by other tokens
if (c.isInstant() || c.isSorcery()) {
continue;
}
// if it only targets player, it already got all needed cards from defined
if (sa.usesTargeting() && !sa.getTargetRestrictions().canTgtPlayer() && !c.canBeTargetedBy(sa)) {
continue;
}
if (sa.hasParam("ForEach")) {
for (Player p : AbilityUtils.getDefinedPlayers(host, sa.getParam("ForEach"), sa)) {
Card proto = getProtoType(sa, c, controller);
proto.addRemembered(p);
tokenTable.put(controller, proto, numCopies);
}
} else {
tokenTable.put(controller, getProtoType(sa, c, controller), numCopies);
}
} // end foreach Card
}
makeTokenTable(tokenTable, true, triggerList, combatChanged, sa);
@@ -243,7 +259,7 @@ public class CopyPermanentEffect extends TokenEffectBase {
}
} // end resolve
private Card getProtoType(final SpellAbility sa, final Card original, final Player newOwner) {
public static Card getProtoType(final SpellAbility sa, final Card original, final Player newOwner) {
final Card host = sa.getHostCard();
int id = newOwner == null ? 0 : newOwner.getGame().nextCardId();
final Card copy = new Card(id, original.getPaperCard(), host.getGame());

View File

@@ -26,6 +26,7 @@ import forge.util.Aggregates;
import forge.util.CardTranslation;
import forge.util.Localizer;
import forge.util.Lang;
import forge.util.collect.FCollection;
public class CopySpellAbilityEffect extends SpellAbilityEffect {
@@ -128,22 +129,26 @@ public class CopySpellAbilityEffect extends SpellAbilityEffect {
final Player p = (Player) o;
if (p.equals(originalTargetPlayer))
continue;
if (p.isValid(type.split(","), chosenSA.getActivatingPlayer(), chosenSA.getHostCard(), sa)) {
if (p.isValid(type.split(","), sa.getActivatingPlayer(), sa.getHostCard(), sa)) {
players.add(p);
}
}
}
valid = CardLists.getValidCards(valid, type, chosenSA.getActivatingPlayer(), chosenSA.getHostCard(), sa);
valid = CardLists.getValidCards(valid, type, sa.getActivatingPlayer(), sa.getHostCard(), sa);
Card originalTarget = Iterables.getFirst(getTargetCards(chosenSA), null);
valid.remove(originalTarget);
if (sa.hasParam("ChooseOnlyOne")) {
Card choice = controller.getController().chooseSingleEntityForEffect(valid, sa, Localizer.getInstance().getMessage("lblChooseOne"), null);
FCollection<GameEntity> all = new FCollection<>(valid);
all.addAll(players);
GameEntity choice = controller.getController().chooseSingleEntityForEffect(all, sa,
Localizer.getInstance().getMessage("lblChooseOne"), null);
if (choice != null) {
valid = new CardCollection(choice);
SpellAbility copy = CardFactory.copySpellAbilityAndPossiblyHost(sa, chosenSA, controller);
resetFirstTargetOnCopy(copy, choice, targetedSA);
copies.add(copy);
}
}
} else {
for (final Card c : valid) {
SpellAbility copy = CardFactory.copySpellAbilityAndPossiblyHost(sa, chosenSA, controller);
resetFirstTargetOnCopy(copy, c, targetedSA);
@@ -155,6 +160,8 @@ public class CopySpellAbilityEffect extends SpellAbilityEffect {
copies.add(copy);
}
}
}
} else {
for (int i = 0; i < amount; i++) {
SpellAbility copy = CardFactory.copySpellAbilityAndPossiblyHost(sa, chosenSA, controller);

View File

@@ -45,10 +45,10 @@ public class CountersMoveEffect extends SpellAbilityEffect {
}
}
final String countername = sa.getParam("CounterType");
final String counterAmount = sa.getParam("CounterNum");
final String counterAmount = sa.getParamOrDefault("CounterNum", "1");
int amount = 0;
if (!"Any".equals(counterAmount) && !"All".equals(counterAmount)) {
amount = AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("CounterNum"), sa);
amount = AbilityUtils.calculateAmount(sa.getHostCard(), counterAmount, sa);
}
sb.append("Move ");
@@ -81,7 +81,7 @@ public class CountersMoveEffect extends SpellAbilityEffect {
public void resolve(SpellAbility sa) {
final Card host = sa.getHostCard();
final String counterName = sa.getParam("CounterType");
final String counterNum = sa.getParam("CounterNum");
final String counterNum = sa.getParamOrDefault("CounterNum", "1");
final Player player = sa.getActivatingPlayer();
final PlayerController pc = player.getController();
final Game game = host.getGame();
@@ -320,7 +320,6 @@ public class CountersMoveEffect extends SpellAbilityEffect {
protected void removeCounter(SpellAbility sa, final Card src, final Card dest, CounterType cType, String counterNum, Map<CounterType, Integer> countersToAdd) {
final Card host = sa.getHostCard();
//final String counterNum = sa.getParam("CounterNum");
final Player player = sa.getActivatingPlayer();
final PlayerController pc = player.getController();
final Game game = host.getGame();

View File

@@ -20,7 +20,7 @@ public class CountersPutAllEffect extends SpellAbilityEffect {
final StringBuilder sb = new StringBuilder();
final CounterType cType = CounterType.getType(sa.getParam("CounterType"));
final int amount = AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("CounterNum"), sa);
final int amount = AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParamOrDefault("CounterNum", "1"), sa);
final String zone = sa.getParamOrDefault("ValidZone", "Battlefield");
sb.append("Put ");
@@ -45,7 +45,7 @@ public class CountersPutAllEffect extends SpellAbilityEffect {
final Card host = sa.getHostCard();
final Player activator = sa.getActivatingPlayer();
final CounterType type = CounterType.getType(sa.getParam("CounterType"));
final int counterAmount = AbilityUtils.calculateAmount(host, sa.getParam("CounterNum"), sa);
final int counterAmount = AbilityUtils.calculateAmount(host, sa.getParamOrDefault("CounterNum", "1"), sa);
final String valid = sa.getParam("ValidCards");
final ZoneType zone = sa.hasParam("ValidZone") ? ZoneType.smartValueOf(sa.getParam("ValidZone")) : ZoneType.Battlefield;
final Game game = activator.getGame();

View File

@@ -34,54 +34,85 @@ import forge.util.Localizer;
public class CountersPutEffect extends SpellAbilityEffect {
@Override
protected String getStackDescription(SpellAbility spellAbility) {
protected String getStackDescription(SpellAbility sa) {
final StringBuilder stringBuilder = new StringBuilder();
final Card card = spellAbility.getHostCard();
final Card card = sa.getHostCard();
final String who = sa.getActivatingPlayer().getName();
boolean pronoun = false;
final int amount = AbilityUtils.calculateAmount(card,
spellAbility.getParamOrDefault("CounterNum", "1"), spellAbility);
if (spellAbility.hasParam("CounterTypes")) {
stringBuilder.append(spellAbility.getActivatingPlayer()).append(" ");
String desc = spellAbility.getDescription();
if (desc.contains("Put")) {
if (sa.hasParam("IfDesc")) {
final String ifD = sa.getParam("IfDesc");
if (ifD.equals("True")) {
String ifDesc = sa.getDescription();
if (ifDesc.contains(",")) {
if (ifDesc.contains(" you ")) {
ifDesc = ifDesc.replaceFirst(" you ", " " + who + " ");
pronoun = true;
if (ifDesc.contains(" you ")) {
ifDesc = ifDesc.replaceAll(" you ", " they ");
}
if (ifDesc.contains(" your ")) {
ifDesc = ifDesc.replaceAll(" your ", " their ");
}
}
stringBuilder.append(ifDesc, 0, ifDesc.indexOf(",") + 1);
} else {
stringBuilder.append("[CountersPutEffect IfDesc parsing error]");
}
} else {
stringBuilder.append(ifD);
}
stringBuilder.append(" ");
}
stringBuilder.append(pronoun ? "they" : who).append(" ");
if (sa.hasParam("CounterTypes")) {
String desc = sa.getDescription();
if (desc.contains("Put ") && desc.contains(" on ")) {
desc = desc.substring(desc.indexOf("Put "), desc.indexOf(" on ") + 4)
.replaceFirst("Put ", "puts ");
}
stringBuilder.append(desc).append(Lang.joinHomogenous(getTargets(spellAbility))).append(".");
stringBuilder.append(desc).append(Lang.joinHomogenous(getTargets(sa))).append(".");
return stringBuilder.toString();
}
// skip the StringBuilder if no targets are chosen ("up to" scenario)
if (spellAbility.usesTargeting()) {
final List<Card> targetCards = SpellAbilityEffect.getTargetCards(spellAbility);
if (sa.usesTargeting()) {
final List<Card> targetCards = SpellAbilityEffect.getTargetCards(sa);
if (targetCards.size() == 0) {
return stringBuilder.toString();
}
}
if (spellAbility.hasParam("Bolster")) {
stringBuilder.append("Bolster ").append(amount);
final int amount = AbilityUtils.calculateAmount(card,
sa.getParamOrDefault("CounterNum", "1"), sa);
if (sa.hasParam("Bolster")) {
stringBuilder.append("bolsters ").append(amount).append(".");
return stringBuilder.toString();
}
boolean divAsChoose = spellAbility.isDividedAsYouChoose();
boolean divAsChoose = sa.isDividedAsYouChoose();
final boolean divRandom = sa.hasParam("DividedRandomly");
if (divAsChoose) {
stringBuilder.append("Distribute ");
} else if (spellAbility.hasParam("DividedRandomly")) {
stringBuilder.append("Randomly distribute ");
stringBuilder.append(pronoun ? "distribute " : "distributes ");
} else if (divRandom) {
stringBuilder.append(pronoun ? "randomly distribute " : "randomly distributes ");
} else {
stringBuilder.append("Put ");
stringBuilder.append(pronoun ? "put " : "puts ");
}
if (spellAbility.hasParam("UpTo")) {
if (sa.hasParam("UpTo")) {
stringBuilder.append("up to ");
}
final String typeName = CounterType.getType(spellAbility.getParam("CounterType")).getName().toLowerCase();
final String typeName = CounterType.getType(sa.getParam("CounterType")).getName().toLowerCase();
stringBuilder.append(Lang.nounWithNumeralExceptOne(amount, typeName + " counter"));
stringBuilder.append(divAsChoose || spellAbility.hasParam("DividedRandomly") ? " among " : " on ");
stringBuilder.append(divAsChoose || divRandom ? " among " : " on ");
// special handling for multiple Defined
if (spellAbility.hasParam("Defined") && spellAbility.getParam("Defined").contains(" & ")) {
String[] def = spellAbility.getParam("Defined").split(" & ");
if (sa.hasParam("Defined") && sa.getParam("Defined").contains(" & ")) {
String[] def = sa.getParam("Defined").split(" & ");
for (int i = 0; i < def.length; i++) {
stringBuilder.append(AbilityUtils.getDefinedEntities(card, def[i], spellAbility).toString()
stringBuilder.append(AbilityUtils.getDefinedEntities(card, def[i], sa).toString()
.replaceAll("[\\[\\]]", ""));
if (i + 1 < def.length) {
stringBuilder.append(" and ");
@@ -89,12 +120,12 @@ public class CountersPutEffect extends SpellAbilityEffect {
}
}
// if use targeting we show all targets and corresponding counters
} else if (spellAbility.usesTargeting()) {
final List<Card> targetCards = SpellAbilityEffect.getTargetCards(spellAbility);
} else if (sa.usesTargeting()) {
final List<Card> targetCards = SpellAbilityEffect.getTargetCards(sa);
for (int i = 0; i < targetCards.size(); i++) {
Card targetCard = targetCards.get(i);
stringBuilder.append(targetCard);
Integer v = spellAbility.getDividedValue(targetCard);
Integer v = sa.getDividedValue(targetCard);
if (v != null) // fix null counter stack description
stringBuilder.append(" (").append(v).append(v == 1 ? " counter)" : " counters)");
@@ -104,8 +135,12 @@ public class CountersPutEffect extends SpellAbilityEffect {
stringBuilder.append(", ");
}
}
} else if (sa.hasParam("Choices")) {
int n = AbilityUtils.calculateAmount(card, sa.getParamOrDefault("ChoiceAmount", "1"), sa);
String what = (sa.getParamOrDefault("ChoicesDesc", sa.getParam("Choices")));
stringBuilder.append(Lang.nounWithNumeralExceptOne(n, what));
} else {
final List<Card> targetCards = SpellAbilityEffect.getTargetCards(spellAbility);
final List<Card> targetCards = SpellAbilityEffect.getTargetCards(sa);
final Iterator<Card> it = targetCards.iterator();
while (it.hasNext()) {
final Card targetCard = it.next();
@@ -135,6 +170,8 @@ public class CountersPutEffect extends SpellAbilityEffect {
boolean existingCounter = sa.hasParam("CounterType") && sa.getParam("CounterType").equals("ExistingCounter");
boolean eachExistingCounter = sa.hasParam("EachExistingCounter");
boolean putOnEachOther = sa.hasParam("PutOnEachOther");
boolean putOnDefined = sa.hasParam("PutOnDefined");
if (sa.hasParam("Optional") && !pc.confirmAction
(sa, null, Localizer.getInstance().getMessage("lblDoYouWantPutCounter"))) {
@@ -146,14 +183,14 @@ public class CountersPutEffect extends SpellAbilityEffect {
if (sa.hasParam("Bolster")) {
CardCollection creatsYouCtrl = activator.getCreaturesInPlay();
CardCollection leastToughness = new CardCollection(
Aggregates.listWithMin(creatsYouCtrl, CardPredicates.Accessors.fnGetDefense));
Aggregates.listWithMin(creatsYouCtrl, CardPredicates.Accessors.fnGetNetToughness));
Map<String, Object> params = Maps.newHashMap();
params.put("CounterType", counterType);
Iterables.addAll(tgtObjects, activator.getController().chooseCardsForEffect(leastToughness, sa,
Localizer.getInstance().getMessage("lblChooseACreatureWithLeastToughness"), 1, 1, false, params));
} else if (sa.hasParam("Choices") && counterType != null) {
} else if (sa.hasParam("Choices") && (counterType != null || putOnEachOther || putOnDefined)) {
ZoneType choiceZone = ZoneType.Battlefield;
if (sa.hasParam("ChoiceZone")) {
choiceZone = ZoneType.smartValueOf(sa.getParam("ChoiceZone"));
@@ -186,6 +223,11 @@ public class CountersPutEffect extends SpellAbilityEffect {
}
if ((sa.hasParam("ChoiceTitle") || sa.hasParam("SpecifyCounter")) && counterType != null) {
title += " (" + counterType.getName() + ")";
} else if (putOnEachOther || putOnDefined) {
title += Localizer.getInstance().getMessage("lblWithKindCounter");
if (putOnEachOther) {
title += " " + Localizer.getInstance().getMessage("lblEachOther");
}
}
Map<String, Object> params = Maps.newHashMap();
@@ -298,6 +340,10 @@ public class CountersPutEffect extends SpellAbilityEffect {
}
}
for (CounterType ct : counterTypes) {
if (sa.hasParam("AltChoiceForEach")) {
String typeChoices = sa.getParam("AltChoiceForEach") + "," + ct.toString();
ct = chooseTypeFromList(sa, typeChoices, obj, pc);
}
resolvePerType(sa, placer, ct, counterAmount, table, false);
}
} else {
@@ -309,9 +355,13 @@ public class CountersPutEffect extends SpellAbilityEffect {
((Player) obj).addCounter(ct, counterAmount, placer, table);
}
if (obj instanceof Card) {
if (etbcounter) {
gameCard.addEtbCounter(ct, counterAmount, placer);
} else {
gameCard.addCounter(ct, counterAmount, placer, table);
}
}
}
continue;
}
@@ -319,7 +369,7 @@ public class CountersPutEffect extends SpellAbilityEffect {
final List<CounterType> choices = Lists.newArrayList();
// get types of counters
for (CounterType ct : obj.getCounters().keySet()) {
if (obj.canReceiveCounters(ct)) {
if (obj.canReceiveCounters(ct) || putOnEachOther) {
choices.add(ct);
}
}
@@ -343,10 +393,30 @@ public class CountersPutEffect extends SpellAbilityEffect {
} else {
Map<String, Object> params = Maps.newHashMap();
params.put("Target", obj);
StringBuilder sb = new StringBuilder();
sb.append(Localizer.getInstance().getMessage("lblSelectCounterTypeAddTo") + " ");
sb.append(obj);
counterType = pc.chooseCounterType(choices, sa, sb.toString(), params);
String sb = Localizer.getInstance().getMessage("lblSelectCounterTypeAddTo") +
" " + (putOnEachOther ? Localizer.getInstance().getMessage("lblEachOther") : obj);
counterType = pc.chooseCounterType(choices, sa, sb, params);
}
if (putOnEachOther) {
List<Card> others = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield),
sa.getParam("PutOnEachOther"), activator, card, sa);
for (Card other : others) {
if (other.equals(obj)) {
continue;
}
Card otherGCard = game.getCardState(other, null);
otherGCard.addCounter(counterType, counterAmount, placer, table);
}
continue;
} else if (putOnDefined) {
List<Card> defs = AbilityUtils.getDefinedCards(card, sa.getParam("PutOnDefined"), sa);
for (Card c : defs) {
Card gCard = game.getCardState(c, null);
if (!sa.hasParam("OnlyNewKind") || gCard.getCounters(counterType) < 1) {
gCard.addCounter(counterType, counterAmount, placer, table);
}
}
continue;
}
}
@@ -362,6 +432,7 @@ public class CountersPutEffect extends SpellAbilityEffect {
}
if (sa.hasParam("CounterTypePerDefined") || sa.hasParam("UniqueType")) {
counterType = chooseTypeFromList(sa, sa.getParam("CounterType"), obj, pc);
if (counterType == null) continue;
}
if (obj instanceof Card) {
@@ -528,7 +599,8 @@ public class CountersPutEffect extends SpellAbilityEffect {
CounterType counterType = null;
if (!sa.hasParam("EachExistingCounter") && !sa.hasParam("EachFromSource")
&& !sa.hasParam("UniqueType") && !sa.hasParam("CounterTypePerDefined")
&& !sa.hasParam("CounterTypes") && !sa.hasParam("ChooseDifferent")) {
&& !sa.hasParam("CounterTypes") && !sa.hasParam("ChooseDifferent")
&& !sa.hasParam("PutOnEachOther") && !sa.hasParam("PutOnDefined")) {
try {
counterType = chooseTypeFromList(sa, sa.getParam("CounterType"), null,
placer.getController());
@@ -596,7 +668,10 @@ public class CountersPutEffect extends SpellAbilityEffect {
List<CounterType> choices = Lists.newArrayList();
for (String s : list.split(",")) {
if (!s.equals("") && (!sa.hasParam("UniqueType") || obj.getCounters(CounterType.getType(s)) == 0)) {
choices.add(CounterType.getType(s));
CounterType type = CounterType.getType(s);
if (!choices.contains(type)) {
choices.add(type);
}
}
}
if (sa.hasParam("RandomType")) {

View File

@@ -2,6 +2,7 @@ package forge.game.ability.effects;
import java.util.Map;
import forge.game.card.*;
import org.apache.commons.lang3.tuple.Pair;
import com.google.common.collect.ImmutableList;
@@ -12,10 +13,6 @@ import forge.game.Game;
import forge.game.GameEntity;
import forge.game.ability.AbilityUtils;
import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card;
import forge.game.card.CardCollectionView;
import forge.game.card.CardLists;
import forge.game.card.CounterType;
import forge.game.player.Player;
import forge.game.player.PlayerController;
import forge.game.spellability.SpellAbility;
@@ -29,7 +26,7 @@ public class CountersRemoveEffect extends SpellAbilityEffect {
final StringBuilder sb = new StringBuilder();
final String counterName = sa.getParam("CounterType");
final String num = sa.getParam("CounterNum");
final String num = sa.getParamOrDefault("CounterNum", "1");
int amount = 0;
if (!num.equals("All") && !num.equals("Any")) {
@@ -77,7 +74,7 @@ public class CountersRemoveEffect extends SpellAbilityEffect {
PlayerController pc = player.getController();
final String type = sa.getParam("CounterType");
final String num = sa.getParam("CounterNum");
final String num = sa.getParamOrDefault("CounterNum", "1");
int cntToRemove = 0;
if (!num.equals("All") && !num.equals("Any")) {
@@ -126,15 +123,29 @@ public class CountersRemoveEffect extends SpellAbilityEffect {
}
CardCollectionView srcCards = null;
String typeforPrompt = counterType == null ? "" : counterType.getName();
String title = Localizer.getInstance().getMessage("lblChooseCardsToTakeTargetCounters", typeforPrompt);
title = title.replace(" ", " ");
if (sa.hasParam("ValidSource")) {
srcCards = game.getCardsIn(ZoneType.Battlefield);
srcCards = CardLists.getValidCards(srcCards, sa.getParam("ValidSource"), player, card, sa);
if (num.equals("Any")) {
String title = Localizer.getInstance().getMessage("lblChooseCardsToTakeTargetCounters", counterType.getName());
Map<String, Object> params = Maps.newHashMap();
params.put("CounterType", counterType);
srcCards = player.getController().chooseCardsForEffect(srcCards, sa, title, 0, srcCards.size(), true, params);
}
} else if (sa.hasParam("Choices") && counterType != null) {
ZoneType choiceZone = sa.hasParam("ChoiceZone") ? ZoneType.smartValueOf(sa.getParam("ChoiceZone"))
: ZoneType.Battlefield;
CardCollection choices = CardLists.getValidCards(game.getCardsIn(choiceZone), sa.getParam("Choices"),
player, card, sa);
//currently only used by one card, so for now
//amount is locked at 1 and choice is mandatory
srcCards = pc.chooseCardsForEffect(choices, sa, title, 1, 1,
false, null);
} else {
srcCards = getTargetCards(sa);
}
@@ -171,7 +182,7 @@ public class CountersRemoveEffect extends SpellAbilityEffect {
Map<String, Object> params = Maps.newHashMap();
params.put("Target", gameCard);
params.put("CounterType", counterType);
String title = Localizer.getInstance().getMessage("lblSelectRemoveCountersNumberOfTarget", type);
title = Localizer.getInstance().getMessage("lblSelectRemoveCountersNumberOfTarget", type);
cntToRemove = pc.chooseNumber(sa, title, 0, cntToRemove, params);
}
}

View File

@@ -108,6 +108,17 @@ public class DelayedTriggerEffect extends SpellAbilityEffect {
}
};
game.getCleanup().addUntil(nextTurnTrig);
} else if (mapParams.containsKey("UpcomingTurn")) {
final GameCommand upcomingTurnTrig = new GameCommand() {
private static final long serialVersionUID = -5860518814760461373L;
@Override
public void run() {
trigHandler.registerDelayedTrigger(delTrig);
}
};
game.getCleanup().addUntil(upcomingTurnTrig);
} else {
trigHandler.registerDelayedTrigger(delTrig);
}

View File

@@ -26,6 +26,7 @@ import forge.util.Lang;
import forge.util.Localizer;
import forge.util.TextUtil;
import forge.util.collect.FCollectionView;
import org.apache.commons.lang3.StringUtils;
public class DigEffect extends SpellAbilityEffect {
@@ -33,10 +34,14 @@ public class DigEffect extends SpellAbilityEffect {
protected String getStackDescription(SpellAbility sa) {
final Card host = sa.getHostCard();
final StringBuilder sb = new StringBuilder();
final List<Player> tgtPlayers = getTargetPlayers(sa);
final String spellDesc = sa.getParamOrDefault("SpellDescription", "");
if (spellDesc.contains("X card")) { // X value can be changed after this goes to the stack, so use set desc
sb.append("[").append(host.getController()).append("] ").append(spellDesc);
} else {
final int numToDig = AbilityUtils.calculateAmount(host, sa.getParam("DigNum"), sa);
final String toChange = sa.getParamOrDefault("ChangeNum", "1");
final int numToChange = toChange.startsWith("All") ? numToDig : AbilityUtils.calculateAmount(host, sa.getParam("ChangeNum"), sa);
final List<Player> tgtPlayers = getTargetPlayers(sa);
String verb = " looks at ";
if (sa.hasParam("Reveal") && sa.getParam("Reveal").equals("True")) {
@@ -46,7 +51,7 @@ public class DigEffect extends SpellAbilityEffect {
verb = " exiles ";
}
sb.append(host.getController()).append(verb).append("the top ");
sb.append(numToDig == 1 ? "card" : (Lang.getNumeral(numToDig) + " cards")).append(" of ");
sb.append(numToDig == 1 ? "card" : Lang.getNumeral(numToDig) + " cards").append(" of ");
if (tgtPlayers.contains(host.getController())) {
sb.append("their ");
@@ -67,7 +72,7 @@ public class DigEffect extends SpellAbilityEffect {
}
String verb2 = "put ";
String where = " in their hand ";
String where = " into their hand ";
if (destZone1.equals("exile")) {
verb2 = "exile ";
where = " ";
@@ -80,6 +85,9 @@ public class DigEffect extends SpellAbilityEffect {
if (sa.hasParam("ChangeValid")) {
String what = sa.hasParam("ChangeValidDesc") ? sa.getParam("ChangeValidDesc") :
sa.getParam("ChangeValid");
if (!StringUtils.containsIgnoreCase(what, "card")) {
what = what + " card";
}
sb.append(Lang.nounWithNumeralExceptOne(numToChange, what)).append(" from among them").append(where);
} else {
sb.append(Lang.getNumeral(numToChange)).append(" of them").append(where);
@@ -96,7 +104,7 @@ public class DigEffect extends SpellAbilityEffect {
}
sb.append("put the rest ").append(destZone2);
}
}
return sb.toString();
}
@@ -409,7 +417,9 @@ public class DigEffect extends SpellAbilityEffect {
c.setTapped(true);
}
if (destZone1.equals(ZoneType.Battlefield) && sa.hasParam("WithCounter")) {
c.addEtbCounter(CounterType.getType(sa.getParam("WithCounter")), 1, player);
final int numCtr = AbilityUtils.calculateAmount(host,
sa.getParamOrDefault("WithCounterNum", "1"), sa);
c.addEtbCounter(CounterType.getType(sa.getParam("WithCounter")), numCtr, player);
}
c = game.getAction().moveTo(zone, c, sa, moveParams);
if (destZone1.equals(ZoneType.Battlefield)) {
@@ -420,6 +430,7 @@ public class DigEffect extends SpellAbilityEffect {
if (sa.hasParam("ExileWithCounter")) {
c.addCounter(CounterType.getType(sa.getParam("ExileWithCounter")), 1, player, counterTable);
}
effectHost.addExiledCard(c);
c.setExiledWith(effectHost);
c.setExiledBy(effectHost.getController());
}
@@ -492,6 +503,7 @@ public class DigEffect extends SpellAbilityEffect {
if (sa.hasParam("ExileWithCounter")) {
c.addCounter(CounterType.getType(sa.getParam("ExileWithCounter")), 1, player, counterTable);
}
effectHost.addExiledCard(c);
c.setExiledWith(effectHost);
c.setExiledBy(effectHost.getController());
if (remZone2) {

View File

@@ -64,7 +64,7 @@ public class DiscardEffect extends SpellAbilityEffect {
if (sa.hasParam("DiscardValid")) {
String validD = sa.hasParam("DiscardValidDesc") ? sa.getParam("DiscardValidDesc")
: sa.getParam("DiscardValid");
if (validD.equals("card.nonLand")) {
if (validD.equals("Card.nonLand")) {
validD = "nonland";
} else if (CardType.CoreType.isValidEnum(validD)) {
validD = validD.toLowerCase();

View File

@@ -36,9 +36,7 @@ public class DrainManaEffect extends SpellAbilityEffect {
}
if (sa.hasParam("DrainMana")) {
for (Mana mana : drained) {
sa.getActivatingPlayer().getManaPool().addMana(mana);
}
sa.getActivatingPlayer().getManaPool().add(drained);
}
if (sa.hasParam("RememberDrainedMana")) {
sa.getHostCard().addRemembered(Integer.valueOf(drained.size()));

View File

@@ -40,7 +40,10 @@ public class DrawEffect extends SpellAbilityEffect {
sb.append(" each");
}
sb.append(Lang.joinVerb(tgtPlayers, " draw")).append(" ");
sb.append(numCards == 1 ? "a card" : (Lang.getNumeral(numCards) + " cards"));
//if NumCards calculation could change between getStackDescription and resolve, use NumCardsDesc to avoid
//a "wrong" stack description
sb.append(sa.hasParam("NumCardsDesc") ? sa.getParam("NumCardsDesc") : numCards == 1 ? "a card" :
(Lang.getNumeral(numCards) + " cards"));
sb.append(".");
}

View File

@@ -60,7 +60,7 @@ public class EffectEffect extends SpellAbilityEffect {
boolean imprintOnHost = false;
final String duration = sa.getParam("Duration");
if (("UntilHostLeavesPlay".equals(duration) || "UntilLoseControlOfHost".equals(duration))
if (((duration != null && duration.startsWith("UntilHostLeavesPlay")) || "UntilLoseControlOfHost".equals(duration))
&& !(hostCard.isInPlay() || hostCard.isInZone(ZoneType.Stack))) {
return;
}
@@ -271,7 +271,7 @@ public class EffectEffect extends SpellAbilityEffect {
}
if (sa.hasParam("CopySVar")) {
eff.setSVar(sa.getParam("CopySVar"), sa.getHostCard().getSVar(sa.getParam("CopySVar")));
eff.setSVar(sa.getParam("CopySVar"), hostCard.getSVar(sa.getParam("CopySVar")));
}
// Copy text changes

View File

@@ -73,8 +73,6 @@ public class EncodeEffect extends SpellAbilityEffect {
// store hostcard in encoded array
choice.addEncodedCard(movedCard);
movedCard.setEncodingCard(choice);
return;
}
}

View File

@@ -165,7 +165,9 @@ public class FightEffect extends DamageBaseEffect {
damageMap.put(fighterA, fighterB, dmg1);
damageMap.put(fighterB, fighterA, dmg2);
fighterB.setFoughtThisTurn(true);
}
fighterA.setFoughtThisTurn(true);
if (!usedDamageMap) {
sa.getHostCard().getGame().getAction().dealDamage(false, damageMap, preventMap, counterTable, sa);

View File

@@ -171,8 +171,6 @@ public class FlipCoinEffect extends SpellAbilityEffect {
}
}
}
// AllZone.getTriggerHandler().runTrigger("FlipsACoin",runParams);
}
/**

View File

@@ -18,12 +18,12 @@ public class GoadEffect extends SpellAbilityEffect {
final boolean remember = sa.hasParam("RememberGoaded");
for (final Card tgtC : getDefinedCardsOrTargeted(sa)) {
// only pump things in PumpZone
// only goad things on the battlefield
if (!game.getCardsIn(ZoneType.Battlefield).contains(tgtC)) {
continue;
}
// if pump is a target, make sure we can still target now
// make sure we can still target now if using targeting
if (sa.usesTargeting() && !sa.getTargetRestrictions().canTgtPlayer() && !tgtC.canBeTargetedBy(sa)) {
continue;
}
@@ -31,6 +31,8 @@ public class GoadEffect extends SpellAbilityEffect {
// 701.38d is handled by getGoaded
tgtC.addGoad(timestamp, player);
// currently, only Life of the Party uses Duration$ Duration$ Permanent
if (!sa.hasParam("Duration")) {
final GameCommand untilEOT = new GameCommand() {
private static final long serialVersionUID = -1731759226844770852L;
@@ -41,6 +43,7 @@ public class GoadEffect extends SpellAbilityEffect {
};
game.getCleanup().addUntil(player, untilEOT);
}
if (remember && tgtC.isGoaded()) {
sa.getHostCard().addRemembered(tgtC);

View File

@@ -245,8 +245,6 @@ public class ManaEffect extends SpellAbilityEffect {
// Only clear express choice after mana has been produced
abMana.clearExpressChoice();
//resolveDrawback(sa);
}
/**
@@ -270,9 +268,17 @@ public class ManaEffect extends SpellAbilityEffect {
? GameActionUtil.generatedMana(sa) : "mana";
sb.append("Add ").append(toManaString(mana)).append(".");
if (sa.hasParam("RestrictValid")) {
String desc = sa.getDescription();
sb.append(" ");
final String desc = sa.getDescription();
if (desc.contains("Spend this") && desc.contains(".")) {
int i = desc.indexOf("Spend this");
sb.append(" ").append(desc, i, desc.indexOf(".", i) + 1);
sb.append(desc, i, desc.indexOf(".", i) + 1);
} else if (desc.contains("This mana can't") && desc.contains(".")) { //for negative restrictions (Jegantha)
int i = desc.indexOf("This mana can't");
sb.append(desc, i, desc.indexOf(".", i) + 1);
} else {
sb.append("[failed to add RestrictValid to StackDesc]");
}
}
return sb.toString();
}

View File

@@ -23,7 +23,7 @@ public class ManaReflectedEffect extends SpellAbilityEffect {
public void resolve(SpellAbility sa) {
// Spells are not undoable
AbilityManaPart ma = sa.getManaPart();
sa.setUndoable(sa.isAbility() && sa.isUndoable());
sa.setUndoable(sa.isAbility() && sa.isUndoable() && sa.getSubAbility() == null);
final Collection<String> colors = CardUtil.getReflectableManaColors(sa);
@@ -31,8 +31,6 @@ public class ManaReflectedEffect extends SpellAbilityEffect {
final String generated = generatedReflectedMana(sa, colors, player);
ma.produceMana(generated, player, sa);
}
resolveSubAbility(sa);
}

View File

@@ -11,6 +11,7 @@ import forge.game.card.Card;
import forge.game.card.CardCollectionView;
import forge.game.card.CardZoneTable;
import forge.game.player.Player;
import forge.game.player.PlayerCollection;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
import forge.util.Lang;
@@ -68,6 +69,7 @@ public class MillEffect extends SpellAbilityEffect {
host = sa.getHostCard();
}
for (final Card c : milled) {
host.addExiledCard(c);
c.setExiledWith(host);
if (facedown) {
c.turnFaceDown(true);
@@ -91,17 +93,40 @@ public class MillEffect extends SpellAbilityEffect {
protected String getStackDescription(SpellAbility sa) {
final StringBuilder sb = new StringBuilder();
final int numCards = sa.hasParam("NumCards") ? AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("NumCards"), sa) : 1;
final boolean optional = sa.hasParam("Optional");
final boolean eachP = sa.hasParam("Defined") && sa.getParam("Defined").equals("Player");
String each = "Each player";
final PlayerCollection tgtPs = getTargetPlayers(sa);
sb.append(Lang.joinHomogenous(getTargetPlayers(sa))).append(" ");
if (sa.hasParam("IfDesc")) {
final String ifD = sa.getParam("IfDesc");
if (ifD.equals("True")) {
String ifDesc = sa.getDescription();
if (ifDesc.contains(",")) {
sb.append(ifDesc, 0, ifDesc.indexOf(",") + 1);
} else {
sb.append("[MillEffect IfDesc parsing error]");
}
} else {
sb.append(ifD);
}
sb.append(" ");
each = each.toLowerCase();
}
sb.append(eachP ? each : Lang.joinHomogenous(tgtPs));
sb.append(" ");
final ZoneType dest = ZoneType.smartValueOf(sa.getParam("Destination"));
sb.append(optional ? "may " : "");
if ((dest == null) || dest.equals(ZoneType.Graveyard)) {
sb.append("mills ");
sb.append("mill");
} else if (dest.equals(ZoneType.Exile)) {
sb.append("exiles ");
sb.append("exile");
} else if (dest.equals(ZoneType.Ante)) {
sb.append("antes ");
sb.append("ante");
}
sb.append((optional || tgtPs.size() > 1) && !eachP ? " " : "s ");
sb.append(Lang.nounWithNumeralExceptOne(numCards, "card")).append(".");

View File

@@ -29,21 +29,23 @@ public class PeekAndRevealEffect extends SpellAbilityEffect {
@Override
protected String getStackDescription(SpellAbility sa) {
final Player peeker = sa.getActivatingPlayer();
final int numPeek = sa.hasParam("PeekAmount") ?
AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("PeekAmount"), sa) : 1;
final String verb = sa.hasParam("NoReveal") || sa.hasParam("RevealOptional") ? " looks at " :
" reveals ";
final String defined = sa.getParamOrDefault("Defined", "their");
String whose;
if (defined.equals("Player")) {
whose = "each player's";
} else { // other else ifs for specific defined can be added above as needs arise
whose = Lang.joinHomogenous(getTargetPlayers(sa));
}
final String defined = sa.getParamOrDefault("Defined", "");
final List<Player> libraryPlayers = getDefinedPlayersOrTargeted(sa);
final String defString = Lang.joinHomogenous(libraryPlayers);
String who = defined.equals("Player") && verb.equals(" reveals ") ? "Each player" :
sa.hasParam("NoPeek") && verb.equals(" reveals ") ? defString : "";
String whose = defined.equals("Player") && verb.equals(" looks at ") ? "each player's"
: libraryPlayers.size() == 1 && libraryPlayers.get(0) == peeker ? "their" :
defString + "'s";
final StringBuilder sb = new StringBuilder();
sb.append(peeker).append(verb).append("the top ");
sb.append(who.equals("") ? peeker : who);
sb.append(verb).append("the top ");
sb.append(numPeek > 1 ? Lang.getNumeral(numPeek) + " cards " : "card ").append("of ").append(whose);
sb.append(" library.");
@@ -58,11 +60,12 @@ public class PeekAndRevealEffect extends SpellAbilityEffect {
final Card source = sa.getHostCard();
final boolean rememberRevealed = sa.hasParam("RememberRevealed");
final boolean imprintRevealed = sa.hasParam("ImprintRevealed");
final boolean noPeek = sa.hasParam("NoPeek");
String revealValid = sa.getParamOrDefault("RevealValid", "Card");
String peekAmount = sa.getParamOrDefault("PeekAmount", "1");
int numPeek = AbilityUtils.calculateAmount(source, peekAmount, sa);
List<Player> libraryPlayers = AbilityUtils.getDefinedPlayers(source, sa.getParam("Defined"), sa);
List<Player> libraryPlayers = getDefinedPlayersOrTargeted(sa);
Player peekingPlayer = sa.getActivatingPlayer();
for (Player libraryToPeek : libraryPlayers) {
@@ -77,17 +80,19 @@ public class PeekAndRevealEffect extends SpellAbilityEffect {
CardCollectionView revealableCards = CardLists.getValidCards(peekCards, revealValid,
sa.getActivatingPlayer(), source, sa);
boolean doReveal = !sa.hasParam("NoReveal") && !revealableCards.isEmpty();
if (!sa.hasParam("NoPeek")) {
if (!noPeek) {
peekingPlayer.getController().reveal(peekCards, ZoneType.Library, libraryToPeek,
CardTranslation.getTranslatedName(source.getName()) + " - " +
Localizer.getInstance().getMessage("lblRevealingCardFrom"));
Localizer.getInstance().getMessage("lblLookingCardFrom"));
}
if (doReveal && sa.hasParam("RevealOptional"))
doReveal = peekingPlayer.getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblRevealCardToOtherPlayers"));
if (doReveal) {
peekingPlayer.getGame().getAction().reveal(revealableCards, peekingPlayer);
peekingPlayer.getGame().getAction().reveal(revealableCards, ZoneType.Library, libraryToPeek, !noPeek,
CardTranslation.getTranslatedName(source.getName()) + " - " +
Localizer.getInstance().getMessage("lblRevealingCardFrom"));
if (rememberRevealed) {
Map<Integer, Card> cachedMap = Maps.newHashMap();

View File

@@ -9,8 +9,10 @@ import forge.game.ability.AbilityUtils;
import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card;
import forge.game.card.CardCollectionView;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
import forge.util.Localizer;
public class PhasesEffect extends SpellAbilityEffect {
@@ -36,7 +38,8 @@ public class PhasesEffect extends SpellAbilityEffect {
@Override
public void resolve(SpellAbility sa) {
CardCollectionView tgtCards;
final Game game = sa.getActivatingPlayer().getGame();
final Player activator = sa.getActivatingPlayer();
final Game game = activator.getGame();
final Card source = sa.getHostCard();
final boolean phaseInOrOut = sa.hasParam("PhaseInOrOut");
final boolean wontPhaseInNormal = sa.hasParam("WontPhaseInNormal");
@@ -53,6 +56,11 @@ public class PhasesEffect extends SpellAbilityEffect {
} else {
tgtCards = getTargetCards(sa);
}
if (sa.hasParam("AnyNumber")) {
tgtCards = activator.getController().chooseCardsForEffect(tgtCards, sa,
Localizer.getInstance().getMessage("lblChooseAnyNumberToPhase"),
0, tgtCards.size(), true, null);
}
if (phaseInOrOut) { // Time and Tide and Oubliette
for (final Card tgtC : tgtCards) {
tgtC.phase(false);

View File

@@ -25,6 +25,7 @@ import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardFactoryUtil;
import forge.game.card.CardZoneTable;
import forge.game.cost.Cost;
import forge.game.cost.CostDiscard;
import forge.game.cost.CostPart;
@@ -86,9 +87,11 @@ public class PlayEffect extends SpellAbilityEffect {
long controlledByTimeStamp = -1;
final Game game = activator.getGame();
boolean optional = sa.hasParam("Optional");
boolean remember = sa.hasParam("RememberPlayed");
final boolean remember = sa.hasParam("RememberPlayed");
final boolean imprint = sa.hasParam("ImprintPlayed");
final boolean forget = sa.hasParam("ForgetPlayed");
final boolean hasTotalCMCLimit = sa.hasParam("WithTotalCMC");
int amount = 1;
boolean hasTotalCMCLimit = sa.hasParam("WithTotalCMC");
int totalCMCLimit = Integer.MAX_VALUE;
if (sa.hasParam("Amount") && !sa.getParam("Amount").equals("All")) {
amount = AbilityUtils.calculateAmount(source, sa.getParam("Amount"), sa);
@@ -319,6 +322,9 @@ public class PlayEffect extends SpellAbilityEffect {
continue;
}
final CardZoneTable triggerList = new CardZoneTable();
final Zone originZone = tgtCard.getZone();
// lands will be played
if (tgtSA instanceof LandAbility) {
tgtSA.resolve();
@@ -326,6 +332,20 @@ public class PlayEffect extends SpellAbilityEffect {
if (remember) {
source.addRemembered(tgtCard);
}
if (imprint) {
source.addImprintedCard(tgtCard);
}
//Forget only if playing was successful
if (forget) {
source.removeRemembered(tgtCard);
}
final Zone currentZone = game.getCardState(tgtCard).getZone();
if (!originZone.equals(currentZone)) {
triggerList.put(originZone.getZoneType(), currentZone.getZoneType(), game.getCardState(tgtCard));
}
triggerList.triggerChangesZoneAll(game, sa);
continue;
}
@@ -399,18 +419,26 @@ public class PlayEffect extends SpellAbilityEffect {
}
if (controller.getController().playSaFromPlayEffect(tgtSA)) {
final Card played = tgtSA.getHostCard();
if (remember) {
source.addRemembered(tgtSA.getHostCard());
source.addRemembered(played);
}
if (imprint) {
source.addImprintedCard(played);
}
//Forgot only if playing was successful
if (sa.hasParam("ForgetRemembered")) {
source.clearRemembered();
}
if (sa.hasParam("ForgetTargetRemembered")) {
if (forget) {
source.removeRemembered(tgtCard);
}
final Zone currentZone = game.getCardState(tgtCard).getZone();
if (!originZone.equals(currentZone)) {
triggerList.put(originZone.getZoneType(), currentZone.getZoneType(), game.getCardState(tgtCard));
}
triggerList.triggerChangesZoneAll(game, sa);
}
amount--;

View File

@@ -37,13 +37,15 @@ public class PumpEffect extends SpellAbilityEffect {
final long timestamp) {
final Card host = sa.getHostCard();
final Game game = host.getGame();
final String duration = sa.getParam("Duration");
//if host is not on the battlefield don't apply
// Suspend should does Affect the Stack
if (("UntilHostLeavesPlay".equals(sa.getParam("Duration")) || "UntilLoseControlOfHost".equals(sa.getParam("Duration")))
if (((duration != null && duration.startsWith("UntilHostLeavesPlay")) || "UntilLoseControlOfHost".equals(duration))
&& !(host.isInPlay() || host.isInZone(ZoneType.Stack))) {
return;
}
if ("UntilLoseControlOfHost".equals(sa.getParam("Duration")) && host.getController() != sa.getActivatingPlayer()) {
if ("UntilLoseControlOfHost".equals(duration) && host.getController() != sa.getActivatingPlayer()) {
return;
}
@@ -92,7 +94,7 @@ public class PumpEffect extends SpellAbilityEffect {
addLeaveBattlefieldReplacement(gameCard, sa, sa.getParam("LeaveBattlefield"));
}
if (!"Permanent".equals(sa.getParam("Duration"))) {
if (!"Permanent".equals(duration)) {
// If not Permanent, remove Pumped at EOT
final GameCommand untilEOT = new GameCommand() {
private static final long serialVersionUID = -42244224L;
@@ -123,9 +125,11 @@ public class PumpEffect extends SpellAbilityEffect {
private static void applyPump(final SpellAbility sa, final Player p,
final List<String> keywords, final long timestamp) {
final Card host = sa.getHostCard();
final String duration = sa.getParam("Duration");
//if host is not on the battlefield don't apply
// Suspend should does Affect the Stack
if (("UntilHostLeavesPlay".equals(sa.getParam("Duration")) || "UntilLoseControlOfHost".equals(sa.getParam("Duration")))
if (((duration != null && duration.startsWith("UntilHostLeavesPlay")) || "UntilLoseControlOfHost".equals(duration))
&& !(host.isInPlay() || host.isInZone(ZoneType.Stack))) {
return;
}
@@ -134,7 +138,7 @@ public class PumpEffect extends SpellAbilityEffect {
p.addChangedKeywords(keywords, ImmutableList.of(), timestamp, 0);
}
if (!"Permanent".equals(sa.getParam("Duration"))) {
if (!"Permanent".equals(duration)) {
// If not Permanent, remove Pumped at EOT
final GameCommand untilEOT = new GameCommand() {
private static final long serialVersionUID = -32453460L;
@@ -185,6 +189,16 @@ public class PumpEffect extends SpellAbilityEffect {
keywords.addAll(Arrays.asList(sa.getParam("KW").split(" & ")));
}
if (sa.hasParam("IfDesc")) {
if (sa.getParam("IfDesc").equals("True") && sa.hasParam("SpellDescription")) {
String ifDesc = sa.getParam("SpellDescription");
sb.append(ifDesc, 0, ifDesc.indexOf(",") + 1);
} else {
sb.append(sa.getParam("IfDesc"));
}
sb.append(" ");
}
if (sa instanceof AbilitySub & sa.getRootAbility().getTargets().containsAll(tgts)) {
//try to avoid having the same long list of targets twice in a StackDescription
sb.append(tgts.size() == 1 && tgts.get(0) instanceof Card ? "It " : "They ");

View File

@@ -109,7 +109,6 @@ public class RepeatEachEffect extends SpellAbilityEffect {
} else {
source.addRemembered(card);
}
AbilityUtils.resolve(repeat);
if (useImprinted) {
source.removeImprintedCard(card);

View File

@@ -4,8 +4,12 @@ import java.util.Map;
import java.util.Set;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.Pair;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import com.google.common.collect.Sets;
import forge.game.Game;
@@ -13,11 +17,15 @@ import forge.game.ability.AbilityKey;
import forge.game.ability.AbilityUtils;
import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card;
import forge.game.card.CardCollectionView;
import forge.game.card.CardLists;
import forge.game.card.TokenCreateTable;
import forge.game.card.token.TokenInfo;
import forge.game.player.Player;
import forge.game.replacement.ReplacementResult;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
import forge.util.Localizer;
public class ReplaceTokenEffect extends SpellAbilityEffect {
@@ -26,14 +34,18 @@ public class ReplaceTokenEffect extends SpellAbilityEffect {
final Card card = sa.getHostCard();
final Player p = sa.getActivatingPlayer();
final Game game = card.getGame();
SpellAbility repSA = sa;
if (repSA.getReplacingObjects().isEmpty()) {
repSA = sa.getRootAbility();
}
// ReplaceToken Effect only applies to one Player
Player affected = (Player) sa.getReplacingObject(AbilityKey.Player);
TokenCreateTable table = (TokenCreateTable) sa.getReplacingObject(AbilityKey.Token);
Player affected = (Player) repSA.getReplacingObject(AbilityKey.Player);
TokenCreateTable table = (TokenCreateTable) repSA.getReplacingObject(AbilityKey.Token);
@SuppressWarnings("unchecked")
Map<AbilityKey, Object> originalParams = (Map<AbilityKey, Object>) sa
.getReplacingObject(AbilityKey.OriginalParams);
Map<AbilityKey, Object> originalParams =
(Map<AbilityKey, Object>) repSA.getReplacingObject(AbilityKey.OriginalParams);
// currently the only ones that changes the amount does double it
if ("Amount".equals(sa.getParam("Type"))) {
@@ -78,36 +90,56 @@ public class ReplaceTokenEffect extends SpellAbilityEffect {
}
}
} else if ("ReplaceToken".equals(sa.getParam("Type"))) {
Card chosen = null;
if (sa.hasParam("ValidChoices")) {
CardCollectionView choices = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), sa.getParam("ValidChoices").split(","), p, card, sa);
if (choices.isEmpty()) {
originalParams.put(AbilityKey.ReplacementResult, ReplacementResult.NotReplaced);
return;
}
chosen = p.getController().chooseSingleEntityForEffect(choices, sa, Localizer.getInstance().getMessage("lblChooseaCard"), false, null);
}
long timestamp = game.getNextTimestamp();
Map<Player, Integer> toInsertMap = Maps.newHashMap();
Multimap<Player, Pair<Integer, Iterable<Object>>> toInsertMap = ArrayListMultimap.create();
Set<Card> toRemoveSet = Sets.newHashSet();
for (Map.Entry<Card, Integer> e : table.row(affected).entrySet()) {
if (!sa.matchesValidParam("ValidCard", e.getKey())) {
continue;
}
Player controller = e.getKey().getController();
int old = ObjectUtils.defaultIfNull(toInsertMap.get(controller), 0);
toInsertMap.put(controller, old + e.getValue());
// TODO should still merge the amounts to avoid additional prototypes when sourceSA doesn't use ForEach
//int old = ObjectUtils.defaultIfNull(toInsertMap.get(controller), 0);
Pair<Integer, Iterable<Object>> tokenAmountPair = new ImmutablePair<>(e.getValue(), e.getKey().getRemembered());
toInsertMap.put(controller, tokenAmountPair);
toRemoveSet.add(e.getKey());
}
// remove replaced tokens
table.row(affected).keySet().removeAll(toRemoveSet);
// insert new tokens
for (Map.Entry<Player, Integer> pe : toInsertMap.entrySet()) {
if (pe.getValue() <= 0) {
for (Map.Entry<Player, Pair<Integer, Iterable<Object>>> pe : toInsertMap.entries()) {
int amt = pe.getValue().getLeft();
if (amt <= 0) {
continue;
}
for (String script : sa.getParam("TokenScript").split(",")) {
final Card token = TokenInfo.getProtoType(script, sa, pe.getKey());
final Card token;
if (script.equals("Chosen")) {
token = CopyPermanentEffect.getProtoType(sa, chosen, pe.getKey());
} else {
token = TokenInfo.getProtoType(script, sa, pe.getKey());
}
if (token == null) {
throw new RuntimeException("don't find Token for TokenScript: " + script);
}
token.setController(pe.getKey(), timestamp);
table.put(affected, token, pe.getValue());
// if token is created from ForEach keep that
token.addRemembered(pe.getValue().getRight());
table.put(affected, token, amt);
}
}
} else if ("ReplaceController".equals(sa.getParam("Type"))) {

View File

@@ -54,9 +54,8 @@ public class RestartGameEffect extends SpellAbilityEffect {
game.getStack().reset();
game.clearCounterAddedThisTurn();
game.resetPlayersAttackedOnNextTurn();
game.resetPlayersAttackedOnNextTurn();
game.setMonarch(null);
game.setHasInitiative(null);
game.setDayTime(null);
GameAction action = game.getAction();

View File

@@ -1,5 +1,6 @@
package forge.game.ability.effects;
import java.util.List;
import java.util.Map;
import com.google.common.collect.Maps;
@@ -18,25 +19,27 @@ import forge.game.card.CardZoneTable;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
import forge.util.Lang;
public class SacrificeAllEffect extends SpellAbilityEffect {
@Override
protected String getStackDescription(SpellAbility sa) {
// when getStackDesc is called, just build exactly what is happening
final StringBuilder sb = new StringBuilder();
/*
* This is not currently targeted ArrayList<Player> tgtPlayers;
*
* Target tgt = af.getAbTgt(); if (tgt != null) tgtPlayers =
* tgt.getTargetPlayers(); else tgtPlayers =
* AbilityFactory.getDefinedPlayers(sa.getHostCard(),
* sa.get("Defined"), sa);
*/
sb.append("Sacrifice permanents.");
if (sa.hasParam("Controller")) {
List<Player> conts = getDefinedPlayersOrTargeted(sa, "Controller");
sb.append(Lang.joinHomogenous(conts)).append(conts.size() == 1 ? " sacrifices " : " sacrifice ");
} else {
sb.append("Sacrifice ");
}
if (sa.hasParam("Defined")) {
List<Card> toSac = getDefinedCardsOrTargeted(sa);
sb.append(Lang.joinHomogenous(toSac)).append(".");
} else {
sb.append("permanents.");
}
return sb.toString();
}

View File

@@ -0,0 +1,35 @@
package forge.game.ability.effects;
import java.util.List;
import forge.game.ability.SpellAbilityEffect;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.util.Lang;
public class TakeInitiativeEffect extends SpellAbilityEffect {
@Override
protected String getStackDescription(SpellAbility sa) {
final StringBuilder sb = new StringBuilder();
final List<Player> tgtPlayers = getTargetPlayers(sa);
sb.append(Lang.joinHomogenous(tgtPlayers)).append(tgtPlayers.size() == 1 ? " takes" : " take");
sb.append(" the initiative.");
return sb.toString();
}
@Override
public void resolve(SpellAbility sa) {
// TODO: improve ai and fix corner cases
final String set = sa.getHostCard().getSetCode();
for (final Player p : getTargetPlayers(sa)) {
if (!sa.usesTargeting() || p.canBeTargetedBy(sa)) {
p.getGame().getAction().takeInitiative(p, set);
}
}
}
}

View File

@@ -38,11 +38,21 @@ public class TokenEffect extends TokenEffectBase {
@Override
protected String getStackDescription(SpellAbility sa) {
if (sa.hasParam("SpellDescription")) {
String desc = sa.getParam("SpellDescription");
if (StringUtils.containsIgnoreCase(desc,"Create")) {
final Card host = sa.getHostCard();
String desc = sa.getParam("SpellDescription");
List<String> words = Arrays.asList(desc.split(" "));
final List<Player> creators = AbilityUtils.getDefinedPlayers(host, sa.getParamOrDefault("TokenOwner",
"You"), sa);
if (!words.get(0).equalsIgnoreCase("Create") && desc.contains(" create")) {
String[] parts = desc.split(" create", 2);
desc = parts[0] + " " + Lang.joinHomogenous(creators) + " create" + parts[1];
if (creators.size() == 1) {
desc = desc.replaceAll(" create ", " creates ");
}
if (desc.contains("you")) {
desc = desc.replaceAll("you", sa.getActivatingPlayer().getName());
}
} else if (StringUtils.containsIgnoreCase(desc,"Create")) {
String verb = creators.size() == 1 ? "creates" : "create";
String start = Lang.joinHomogenous(creators) + " " + verb;
String create = desc.contains("Create") ? "Create" : "create";
@@ -54,7 +64,6 @@ public class TokenEffect extends TokenEffectBase {
if (numTokens != 0) { //0 probably means calculation isn't ready in time for stack
if (numTokens != 1) { //if we are making more than one, substitute the numeral for a/an
String numeral = " " + Lang.getNumeral(numTokens) + " ";
List<String> words = Arrays.asList(desc.split(" "));
String target = " " + words.get(words.indexOf(verb) + 1) + " ";
desc = desc.replaceFirst(target, numeral);
}

View File

@@ -117,6 +117,7 @@ public abstract class TokenEffectBase extends SpellAbilityEffect {
Player creator = c.getRowKey();
Player controller = prototype.getController();
int cellAmount = c.getValue();
for (int i = 0; i < cellAmount; i++) {
Card tok = CardFactory.copyCard(prototype, true);
// Crafty Cutpurse would change under which control it does enter,
@@ -170,6 +171,7 @@ public abstract class TokenEffectBase extends SpellAbilityEffect {
if (clone) {
moved.setCloneOrigin(host);
}
if (!pumpKeywords.isEmpty()) {
moved.addChangedCardKeywords(pumpKeywords, Lists.newArrayList(), false, timestamp, 0);
addPumpUntil(sa, moved, timestamp);
@@ -202,6 +204,10 @@ public abstract class TokenEffectBase extends SpellAbilityEffect {
moved.addRemembered(AbilityUtils.getDefinedObjects(host, sa.getParam("TokenRemembered"), sa));
}
allTokens.add(moved);
if (sa.hasParam("CleanupForEach")) {
moved.removeRemembered(prototype.getRemembered());
}
}
}
@@ -232,7 +238,7 @@ public abstract class TokenEffectBase extends SpellAbilityEffect {
boolean canAttach = lki.isAttachment();
if (canAttach && !ge.canBeAttached(lki)) {
if (canAttach && !ge.canBeAttached(lki, sa)) {
canAttach = false;
}
@@ -248,7 +254,7 @@ public abstract class TokenEffectBase extends SpellAbilityEffect {
return false;
}
tok.attachToEntity(ge);
tok.attachToEntity(ge, sa);
return true;
}
// not a GameEntity, cant be attach

View File

@@ -42,12 +42,18 @@ public class VentureEffect extends SpellAbilityEffect {
}
}
Card dungeon = null;
if (sa.hasParam("Dungeon")) {
dungeon = Card.fromPaperCard(StaticData.instance().getVariantCards().getUniqueByName(
sa.getParam("Dungeon")), player);
} else {
// Create a new dungeon card chosen by player in command zone.
List<PaperCard> dungeonCards = StaticData.instance().getVariantCards().getAllCards(
Predicates.compose(CardRulesPredicates.Presets.IS_DUNGEON, PaperCard.FN_GET_RULES));
dungeonCards.removeIf(c -> !c.getRules().isEnterableDungeon());
String message = Localizer.getInstance().getMessage("lblChooseDungeon");
Card dungeon = player.getController().chooseDungeon(player, dungeonCards, message);
dungeon = player.getController().chooseDungeon(player, dungeonCards, message);
}
game.getTriggerHandler().suppressMode(TriggerType.ChangesZone);
game.getAction().moveTo(ZoneType.Command, dungeon, sa, moveParams);

View File

@@ -81,14 +81,14 @@ public class ZoneExchangeEffect extends SpellAbilityEffect {
Card c = null;
if (type != null && type.equals("Aura") && object1.getEnchantingCard() != null) {
c = object1.getEnchantingCard();
if (!c.canBeAttached(object2)) {
if (!c.canBeAttached(object2, sa)) {
return;
}
}
// Enchant first
if (c != null) {
object1.unattachFromEntity(c);
object2.attachToEntity(c);
object2.attachToEntity(c, sa);
}
Map<AbilityKey, Object> moveParams = AbilityKey.newMap();
moveParams.put(AbilityKey.LastStateBattlefield, sa.getLastStateBattlefield());

View File

@@ -54,12 +54,7 @@ import forge.game.replacement.ReplacementHandler;
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.staticability.StaticAbilityCantPutCounter;
import forge.game.staticability.StaticAbilityCantSacrifice;
import forge.game.staticability.StaticAbilityCantTarget;
import forge.game.staticability.StaticAbilityCantTransform;
import forge.game.staticability.*;
import forge.game.trigger.Trigger;
import forge.game.trigger.TriggerHandler;
import forge.game.trigger.TriggerType;
@@ -109,7 +104,8 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
private final Table<Long, Long, List<String>> hiddenExtrinsicKeywords = TreeBasedTable.create();
// cards attached or otherwise linked to this card
private CardCollection hauntedBy, devouredCards, exploitedCards, delvedCards, convokedCards, imprintedCards, encodedCards;
private CardCollection hauntedBy, devouredCards, exploitedCards, delvedCards, convokedCards, imprintedCards,
exiledCards, encodedCards;
private CardCollection gainControlTargets, chosenCards;
private CardCollection mergedCards;
private Map<Long, CardCollection> mustBlockCards = Maps.newHashMap();
@@ -195,6 +191,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
private boolean startsGameInPlay = false;
private boolean drawnThisTurn = false;
private boolean foughtThisTurn = false;
private boolean becameTargetThisTurn = false;
private boolean startedTheTurnUntapped = false;
private boolean cameUnderControlSinceLastUpkeep = true; // for Echo
@@ -337,6 +334,9 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
private CombatLki combatLKI;
private ReplacementEffect shieldCounterReplaceDamage = null;
private ReplacementEffect shieldCounterReplaceDestroy = null;
// Enumeration for CMC request types
public enum SplitCMCMode {
CurrentSideCMC,
@@ -529,7 +529,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
return currentStateName;
}
// use by CopyPermament
// use by CopyPermanent
public void setStates(Map<CardStateName, CardState> map) {
states.clear();
states.putAll(map);
@@ -1079,6 +1079,31 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
imprintedCards = view.clearCards(imprintedCards, TrackableProperty.ImprintedCards);
}
public final CardCollectionView getExiledCards() {
return CardCollection.getView(exiledCards);
}
public final boolean hasExiledCard() {
return FCollection.hasElements(exiledCards);
}
public final boolean hasExiledCard(Card c) {
return FCollection.hasElement(exiledCards, c);
}
public final void addExiledCard(final Card c) {
exiledCards = view.addCard(exiledCards, c, TrackableProperty.ExiledCards);
}
public final void addExiledCards(final Iterable<Card> cards) {
exiledCards = view.addCards(exiledCards, cards, TrackableProperty.ExiledCards);
}
public final void removeExiledCard(final Card c) {
exiledCards = view.removeCard(exiledCards, c, TrackableProperty.ExiledCards);
}
public final void removeExiledCards(final Iterable<Card> cards) {
exiledCards = view.removeCards(exiledCards, cards, TrackableProperty.ExiledCards);
}
public final void clearExiledCards() {
exiledCards = view.clearCards(exiledCards, TrackableProperty.ExiledCards);
}
public final CardCollectionView getEncodedCards() {
return CardCollection.getView(encodedCards);
}
@@ -1668,6 +1693,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
return;
}
exiledWith.removeExiledCard(this);
exiledWith.removeUntilLeavesBattlefield(this);
exiledWith = null;
@@ -1833,6 +1859,13 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
drawnThisTurn = b;
}
public final boolean getFoughtThisTurn() {
return foughtThisTurn;
}
public final void setFoughtThisTurn(final boolean b) {
foughtThisTurn = b;
}
public final CardCollectionView getGainControlTargets() { //used primarily with AbilityFactory_GainControl
return CardCollection.getView(gainControlTargets);
}
@@ -1981,15 +2014,17 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
} else if (keyword.startsWith("Alternative Cost")) {
sbLong.append("Has alternative cost.");
} else if (keyword.startsWith("AlternateAdditionalCost")) {
final String costString1 = keyword.split(":")[1];
final String costString2 = keyword.split(":")[2];
final Cost cost1 = new Cost(costString1, false);
final Cost cost2 = new Cost(costString2, false);
sbLong.append("As an additional cost to cast this spell, ")
.append(StringUtils.uncapitalize(cost1.toSimpleString()))
.append(" or pay ")
.append(StringUtils.uncapitalize(cost2.toSimpleString()))
.append(".\r\n\r\n");
final String[] costs = keyword.split(":", 2)[1].split(":");
sbLong.append("As an additional cost to cast this spell, ");
for (int n = 0; n < costs.length; n++) {
final Cost cost = new Cost(costs[n], false);
if (cost.isOnlyManaCost()) {
sbLong.append(" pay ");
}
sbLong.append(StringUtils.uncapitalize(cost.toSimpleString()));
sbLong.append(n + 1 == costs.length ? ".\r\n\r\n" : n + 2 == costs.length && costs.length > 2
? ", or " : n + 2 == costs.length ? " or " : ", ");
}
} else if (keyword.startsWith("Multikicker")) {
if (!keyword.endsWith("Generic")) {
final String[] n = keyword.split(":");
@@ -2050,7 +2085,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|| keyword.equals("Ascend") || keyword.equals("Totem armor")
|| keyword.equals("Battle cry") || keyword.equals("Devoid") || keyword.equals("Riot")
|| keyword.equals("Daybound") || keyword.equals("Nightbound")
|| keyword.equals("Friends forever")) {
|| keyword.equals("Friends forever") || keyword.equals("Choose a Background")) {
sbLong.append(keyword).append(" (").append(inst.getReminderText()).append(")");
} else if (keyword.startsWith("Partner:")) {
final String[] k = keyword.split(":");
@@ -2542,7 +2577,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|| keyword.equals("Devoid") || keyword.equals("Lifelink")
|| keyword.equals("Split second")) {
sbBefore.append(keyword).append(" (").append(inst.getReminderText()).append(")");
sbBefore.append("\r\n");
sbBefore.append("\r\n\r\n");
} else if (keyword.equals("Conspire") || keyword.equals("Epic")
|| keyword.equals("Suspend") || keyword.equals("Jump-start")
|| keyword.equals("Fuse")) {
@@ -2593,7 +2628,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
final Cost cost2 = new Cost(n[2], false);
sbx.append(cost2.toSimpleString());
}
sbx.append(" (").append(inst.getReminderText()).append(")");
sbx.append(" (").append(inst.getReminderText()).append(")\r\n");
} else {
sbx.append("As an additional cost to cast this spell, you may ");
String costS = StringUtils.uncapitalize(cost.toSimpleString());
@@ -2602,14 +2637,17 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
}
sbBefore.append(sbx).append("\r\n");
} else if (keyword.startsWith("AlternateAdditionalCost")) {
final String[] k = keyword.split(":");
final Cost cost1 = new Cost(k[1], false);
final Cost cost2 = new Cost(k[2], false);
sbBefore.append("As an additional cost to cast this spell, ")
.append(StringUtils.uncapitalize(cost1.toSimpleString()))
.append(" or pay ")
.append(StringUtils.uncapitalize(cost2.toSimpleString()))
.append(".\r\n\r\n");
final String[] costs = keyword.split(":", 2)[1].split(":");
sbBefore.append("As an additional cost to cast this spell, ");
for (int n = 0; n < costs.length; n++) {
final Cost cost = new Cost(costs[n], false);
if (cost.isOnlyManaCost()) {
sbBefore.append(" pay ");
}
sbBefore.append(StringUtils.uncapitalize(cost.toSimpleString()));
sbBefore.append(n + 1 == costs.length ? ".\r\n\r\n" : n + 2 == costs.length && costs.length > 2
? ", or " : n + 2 == costs.length ? " or " : ", ");
}
} else if (keyword.startsWith("Presence") || keyword.startsWith("MayFlash")) {
// Pseudo keywords, only print Reminder
sbBefore.append(inst.getReminderText());
@@ -3262,8 +3300,14 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
}
public final void removeTempController(final Player player) {
boolean changed = false;
// Remove each key that yields this player
this.tempControllers.values().remove(player);
while (tempControllers.values().remove(player)) {
changed = true;
}
if (changed) {
view.updateController(this);
}
}
public final void clearTempControllers() {
@@ -3405,22 +3449,6 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
return this.isAttachedToEntity();
}
public final void equipCard(final Card c) {
if (!isEquipment()) {
return;
}
this.attachToEntity(c);
}
public final void fortifyCard(final Card c) {
if (!isFortification()) {
return;
}
this.attachToEntity(c);
}
public final void unEquipCard(final Card c) { // equipment.unEquipCard(equippedCard);
this.unattachFromEntity(c);
}
@@ -3476,11 +3504,11 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
return getEnchantingCard() != null;
}
public final void attachToEntity(final GameEntity entity) {
attachToEntity(entity, false);
public final void attachToEntity(final GameEntity entity, SpellAbility sa) {
attachToEntity(entity, sa, false);
}
public final void attachToEntity(final GameEntity entity, boolean overwrite) {
if (!overwrite && !entity.canBeAttached(this)) {
public final void attachToEntity(final GameEntity entity, SpellAbility sa, boolean overwrite) {
if (!overwrite && !entity.canBeAttached(this, sa)) {
return;
}
@@ -4096,8 +4124,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
}
public final boolean toughnessAssignsDamage() {
return getGame().getStaticEffects().getGlobalRuleChange(GlobalRuleChange.toughnessAssignsDamage)
|| hasKeyword("CARDNAME assigns combat damage equal to its toughness rather than its power");
return StaticAbilityCombatDamageToughness.combatDamageToughness(this);
}
// How much combat damage does the card deal
@@ -5159,17 +5186,9 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
} else if (getName().isEmpty()) {
// if this does not have a name, then there is no name to share
return false;
} else {
} else if (StaticData.instance().getCommonCards().isNonLegendaryCreatureName(getName())) {
// check if this card has a name from a face
// in general token creatures does not have this
final ICardFace face = StaticData.instance().getCommonCards().getFaceByName(getName());
if (face == null) {
return false;
}
// TODO add check if face is legal in the format of the game
// name does need to be a non-legendary creature
final CardType type = face.getType();
if (type != null && type.isCreature() && !type.isLegendary())
return true;
}
}
@@ -5193,15 +5212,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
if (!shares && hasNonLegendaryCreatureNames()) {
// check if the name is from a face
// in general token creatures does not have this
final ICardFace face = StaticData.instance().getCommonCards().getFaceByName(name);
if (face == null) {
return false;
}
// TODO add check if face is legal in the format of the game
// name does need to be a non-legendary creature
final CardType type = face.getType();
if (type.isCreature() && !type.isLegendary())
return true;
return StaticData.instance().getCommonCards().isNonLegendaryCreatureName(name);
}
return shares;
}
@@ -6047,18 +6058,15 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
}
@Override
protected final boolean canBeEquippedBy(final Card equip) {
if (isCreature() && isInPlay()) {
return true;
} else if (isPlaneswalker() && isInPlay()) {
for (KeywordInterface inst : equip.getKeywords(Keyword.EQUIP)) {
if (inst.getOriginal().contains("planeswalker")) {
return true;
}
}
}
protected final boolean canBeEquippedBy(final Card equip, SpellAbility sa) {
if (!isInPlay()) {
return false;
}
if (sa != null && sa.isEquip()) {
return isValid(sa.getTargetRestrictions().getValidTgts(), sa.getActivatingPlayer(), equip, sa);
}
return isCreature();
}
@Override
protected boolean canBeFortifiedBy(final Card fort) {
@@ -6069,13 +6077,13 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
* @see forge.game.GameEntity#canBeAttached(forge.game.card.Card, boolean)
*/
@Override
public boolean canBeAttached(Card attach, boolean checkSBA) {
public boolean canBeAttached(Card attach, SpellAbility sa, boolean checkSBA) {
// phase check there
if (isPhasedOut() && !attach.isPhasedOut()) {
return false;
}
return super.canBeAttached(attach, checkSBA);
return super.canBeAttached(attach, sa, checkSBA);
}
public FCollectionView<ReplacementEffect> getReplacementEffects() {
@@ -6104,6 +6112,26 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
for (KeywordInterface kw : getUnhiddenKeywords(state)) {
list.addAll(kw.getReplacements());
}
// Shield Counter aren't affected by Changed Card Traits
if (this.getCounters(CounterEnumType.SHIELD) > 0) {
String sa = "DB$ RemoveCounter | Defined$ Self | CounterType$ Shield | CounterNum$ 1";
if (shieldCounterReplaceDamage == null) {
String reStr = "Event$ DamageDone | ActiveZones$ Battlefield | ValidTarget$ Card.Self | PreventionEffect$ True | AlwaysReplace$ True "
+ "| Description$ If damage would be dealt to this permanent, prevent that damage and remove a shield counter from it.";
shieldCounterReplaceDamage = ReplacementHandler.parseReplacement(reStr, this, false, null);
shieldCounterReplaceDamage.setOverridingAbility(AbilityFactory.getAbility(sa, this));
}
if (shieldCounterReplaceDestroy == null) {
String reStr = "Event$ Destroy | ActiveZones$ Battlefield | ValidCard$ Card.Self | ValidSource$ SpellAbility "
+ "| Description$ If this permanent would be destroyed as the result of an effect, instead remove a shield counter from it.";
shieldCounterReplaceDestroy = ReplacementHandler.parseReplacement(reStr, this, false, null);
shieldCounterReplaceDestroy.setOverridingAbility(AbilityFactory.getAbility(sa, this));
}
list.add(shieldCounterReplaceDamage);
list.add(shieldCounterReplaceDestroy);
}
}
public boolean hasReplacementEffect(final ReplacementEffect re) {
@@ -6187,12 +6215,10 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
setRegeneratedThisTurn(0);
resetShield();
setBecameTargetThisTurn(false);
setFoughtThisTurn(false);
clearMustBlockCards();
getDamageHistory().setCreatureAttackedLastTurnOf(turn, getDamageHistory().getCreatureAttacksThisTurn() > 0);
getDamageHistory().newTurn();
getDamageHistory().setCreatureAttackedLastTurnOf(turn, getDamageHistory().getCreatureAttackedThisTurn());
getDamageHistory().setCreatureAttackedThisTurn(false);
getDamageHistory().setCreatureAttacksThisTurn(0);
getDamageHistory().setHasBeenDealtNonCombatDamageThisTurn(false);
clearBlockedByThisTurn();
clearBlockedThisTurn();
resetMayPlayTurn();
@@ -7062,4 +7088,16 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
}
return getGame().getCombat().isAttacking(this);
}
public boolean ignoreLegendRule() {
// not legendary
if (!getType().isLegendary()) {
return true;
}
// empty name and no "has non legendary creature names"
if (this.getName().isEmpty() && !hasNonLegendaryCreatureNames()) {
return true;
}
return StaticAbilityIgnoreLegendRule.ignoreLegendRule(this);
}
}

View File

@@ -9,6 +9,8 @@ import com.google.common.collect.Maps;
import forge.game.GameEntity;
import forge.game.player.Player;
import forge.game.zone.ZoneType;
import forge.util.collect.FCollection;
/**
* TODO: Write javadoc for this type.
@@ -16,25 +18,29 @@ import forge.game.player.Player;
*/
public class CardDamageHistory {
private boolean creatureAttackedThisTurn = false;
private boolean creatureAttackedThisCombat = false;
private boolean creatureBlockedThisCombat = false;
private boolean creatureGotBlockedThisCombat = false;
private boolean receivedNonCombatDamageThisTurn = false;
private int attacksThisTurn = 0;
boolean hasdealtDamagetoAny = false;
private List<GameEntity> attackedThisTurn = Lists.newArrayList();
private final List<Player> creatureAttackedLastTurnOf = Lists.newArrayList();
private final List<Player> NotAttackedSinceLastUpkeepOf = Lists.newArrayList();
private final List<Player> NotBlockedSinceLastUpkeepOf = Lists.newArrayList();
private final List<Player> NotBeenBlockedSinceLastUpkeepOf = Lists.newArrayList();
private final Map<GameEntity, Integer> damagedThisCombat = Maps.newHashMap();
// only needed for Glen Elendra (Plane)
private final List<Player> damagedThisCombat = Lists.newArrayList();
// only needed for The Fallen
private final FCollection<GameEntity> damagedThisGame = new FCollection<>();
private final Map<GameEntity, Integer> damagedThisTurn = Maps.newHashMap();
private final Map<GameEntity, Integer> damagedThisTurnInCombat = Maps.newHashMap();
private final Map<GameEntity, Integer> damagedThisGame = Maps.newHashMap();
private boolean receivedNonCombatDamageThisTurn = false;
public final boolean getHasdealtDamagetoAny() {
return !damagedThisGame.isEmpty();
return hasdealtDamagetoAny;
}
// used to see if an attacking creature with a triggering attack ability
@@ -47,12 +53,11 @@ public class CardDamageHistory {
* @param hasAttacked
* a boolean.
*/
public final void setCreatureAttackedThisCombat(final boolean hasAttacked) {
this.creatureAttackedThisCombat = hasAttacked;
public final void setCreatureAttackedThisCombat(GameEntity defender) {
this.creatureAttackedThisCombat = defender != null;
if (hasAttacked) {
this.setCreatureAttackedThisTurn(true);
this.attacksThisTurn++;
if (defender != null) {
attackedThisTurn.add(defender);
}
}
/**
@@ -65,38 +70,6 @@ public class CardDamageHistory {
public final boolean getCreatureAttackedThisCombat() {
return this.creatureAttackedThisCombat;
}
/**
* <p>
* Setter for the field <code>creatureAttackedThisTurn</code>.
* </p>
*
* @param b
* a boolean.
*/
public final void setCreatureAttackedThisTurn(final boolean b) {
this.creatureAttackedThisTurn = b;
}
/**
* <p>
* Getter for the field <code>creatureAttackedThisTurn</code>.
* </p>
*
* @return a boolean.
*/
public final boolean getCreatureAttackedThisTurn() {
return this.creatureAttackedThisTurn;
}
/**
* <p>
* Setter for the field <code>attacksThisTurn</code>.
* </p>
*
* @param num
* a integer.
*/
public final void setCreatureAttacksThisTurn(final int num) {
this.attacksThisTurn = num;
}
/**
* <p>
* Getter for the field <code>attacksThisTurn</code>.
@@ -105,7 +78,10 @@ public class CardDamageHistory {
* @return a int.
*/
public final int getCreatureAttacksThisTurn() {
return this.attacksThisTurn;
return this.attackedThisTurn.size();
}
public final boolean hasAttackedThisTurn(GameEntity e) {
return this.attackedThisTurn.contains(e);
}
/**
* <p>
@@ -272,7 +248,7 @@ public class CardDamageHistory {
this.receivedNonCombatDamageThisTurn = b;
}
public final Map<GameEntity, Integer> getThisCombatDamaged() {
public final List<Player> getThisCombatDamaged() {
return damagedThisCombat;
}
public final Map<GameEntity, Integer> getThisTurnDamaged() {
@@ -281,7 +257,7 @@ public class CardDamageHistory {
public final Map<GameEntity, Integer> getThisTurnCombatDamaged() {
return damagedThisTurnInCombat;
}
public final Map<GameEntity, Integer> getThisGameDamaged() {
public final FCollection<GameEntity> getThisGameDamaged() {
return damagedThisGame;
}
/**
@@ -290,27 +266,15 @@ public class CardDamageHistory {
*/
public void registerCombatDamage(GameEntity entity, int amount) {
int old = 0;
if (damagedThisCombat.containsKey(entity)) {
old = damagedThisCombat.get(entity);
if (entity instanceof Player) {
damagedThisCombat.add((Player) entity);
}
damagedThisCombat.put(entity, old + amount);
old = 0;
if (damagedThisTurnInCombat.containsKey(entity)) {
old = damagedThisTurnInCombat.get(entity);
}
damagedThisTurnInCombat.put(entity, old + amount);
}
/**
* TODO: Write javadoc for this method.
*/
public void newTurn() {
damagedThisCombat.clear();
damagedThisTurnInCombat.clear();
damagedThisTurn.clear();
}
public void endCombat() {
damagedThisCombat.clear();
hasdealtDamagetoAny = true;
}
/**
@@ -323,11 +287,30 @@ public class CardDamageHistory {
old = damagedThisTurn.get(entity);
}
damagedThisTurn.put(entity, old + amount);
old = 0;
if (damagedThisGame.containsKey(entity)) {
old = damagedThisGame.get(entity);
}
damagedThisGame.put(entity, old + amount);
damagedThisGame.add(entity);
hasdealtDamagetoAny = true;
}
public void newTurn() {
damagedThisCombat.clear();
damagedThisTurnInCombat.clear();
damagedThisTurn.clear();
attackedThisTurn.clear();
// if card already LTB we can safely dereference (allows quite a few objects to be cleaned up earlier for bigger boardstates)
CardCollection toRemove = new CardCollection();
for (GameEntity e : damagedThisGame) {
if (e instanceof Card) {
if (((Card) e).getZone().getZoneType() != ZoneType.Battlefield) {
toRemove.add((Card)e);
}
}
}
damagedThisGame.removeAll(toRemove);
setHasBeenDealtNonCombatDamageThisTurn(false);
}
public void endCombat() {
damagedThisCombat.clear();
}
}

Some files were not shown because too many files have changed in this diff Show More