Compare commits

..

2218 Commits

Author SHA1 Message Date
Michael Kamensky
d658df35d6 [maven-release-plugin] prepare release forge-1.6.41 2021-06-12 16:13:31 +03:00
Michael Kamensky
91ae1036de - Add planeswalker achievements for MH2. 2021-06-12 16:00:33 +03:00
Michael Kamensky
844098a302 Merge branch 'master' of git.cardforge.org:core-developers/forge into agetian-master 2021-06-12 11:28:43 +03:00
Michael Kamensky
08454489b4 Merge branch 'net_decks' into 'master'
Net decks updates

See merge request core-developers/forge!4861
2021-06-12 08:28:02 +00:00
Michael Kamensky
71110568fc Merge branch 'askai' into 'master'
Add Ask AI button

See merge request core-developers/forge!4867
2021-06-12 04:18:22 +00:00
Michael Kamensky
9990f0ad1a Merge branch 'aiPW' into 'master'
Attack Planeswalker prioritization

See merge request core-developers/forge!4866
2021-06-12 04:17:10 +00:00
Bug Hunter
735a966312 Attack Planeswalker prioritization 2021-06-12 04:17:09 +00:00
tool4EvEr
828d4e17c9 Add Ask AI button 2021-06-11 23:25:57 +02:00
paul_snoops
233c1f6d00 Merge branch 'master' into net_decks 2021-06-11 22:24:22 +01:00
Sol
dbe1683ba9 Merge branch 'zabaz' into 'master'
Zabaz: add Cause to ReplaceAddCounter

See merge request core-developers/forge!4863
2021-06-11 12:29:46 +00:00
Bug Hunter
a5b7f93971 Merge branch 'fixnpe' into 'master'
Fix CostTapType NPE

See merge request core-developers/forge!4865
2021-06-11 10:34:21 +00:00
tool4EvEr
d853ca6615 Fix CostTapType NPE 2021-06-11 12:33:37 +02:00
Michael Kamensky
418d2bf012 Merge branch 'update_card_translations' into 'master'
Update card translations

See merge request core-developers/forge!4862
2021-06-11 10:07:29 +00:00
Lyu Zong-Hong
030f1f4056 Update Japanese translations 2021-06-11 16:56:17 +09:00
Hans Mackowiak
fc82dbb9aa Zabaz: add Cause to ReplaceAddCounter 2021-06-11 08:31:30 +02:00
Lyu Zong-Hong
6feb18c11c Update card translations 2021-06-11 15:10:49 +09:00
Michael Kamensky
c07f3ea34d Merge branch 'master' of git.cardforge.org:core-developers/forge into agetian-master 2021-06-11 07:50:33 +03:00
Michael Kamensky
8948210e0a Merge branch 'fixland' into 'master'
Fix playLand ETB check failing if lands changed by another effect

See merge request core-developers/forge!4860
2021-06-11 03:47:55 +00:00
paul_snoops
df0b5362c2 Net decks updates 2021-06-10 20:09:40 +01:00
tool4EvEr
a9b60a87e0 Fix playLand ETB check failing if lands changed by another effect 2021-06-10 18:10:42 +02:00
Sol
694317a50f Merge branch 'fixes' into 'master'
More small fixes

See merge request core-developers/forge!4855
2021-06-10 15:36:25 +00:00
tool4EvEr
a856834693 Refactor guard 2021-06-10 17:29:26 +02:00
Michael Kamensky
35b71da140 Merge branch 'master' into 'master'
Gauntlet Updates

See merge request core-developers/forge!4859
2021-06-10 11:49:55 +00:00
Churrufli
3b2e39808e Gauntlet Updates 2021-06-10 10:41:45 +02:00
Churrufli
697a2ace20 Gauntlet Updates 2021-06-10 10:37:24 +02:00
Sol
c38c53d513 Merge branch 'fix-play-effect' into 'master'
isBlank() is from Java 11

See merge request core-developers/forge!4858
2021-06-10 01:58:13 +00:00
friarsol
31745fe31e isBlank() is from Java 11 2021-06-09 21:57:47 -04:00
Sol
f8e3f4dd31 Merge branch 'migrate-mh2' into 'master'
Migrate Modern Horizons 2

See merge request core-developers/forge!4857
2021-06-10 01:47:40 +00:00
friarsol
965ec4bc47 Migrate Modern Horizons 2 2021-06-09 21:46:33 -04:00
Sol
9cf28e7041 Update u_0_3_crab.txt 2021-06-10 01:28:52 +00:00
Sol
71f639d45e Merge branch 'garth_one_eye' into 'master'
Add Garth One-Eye and necessary support

See merge request core-developers/forge!4856
2021-06-10 00:43:30 +00:00
tool4EvEr
084476b8b0 Clean up 2021-06-09 18:43:34 +02:00
Lyu Zong-Hong
f9abb8ca75 Add Garth One-Eye and necessary support 2021-06-09 23:07:55 +09:00
tool4EvEr
4b0c748eaf Fix Myr Battlesphere allowing you to tap for X = 0 2021-06-09 11:40:11 +02:00
tool4EvEr
083674071b Reset xPaid after ability resolved 2021-06-09 11:40:01 +02:00
tool4EvEr
96ccf0afc6 More small fixes 2021-06-09 09:43:02 +02:00
Michael Kamensky
cfb755ce20 Merge branch 'clean' into 'master'
Small fixes

See merge request core-developers/forge!4854
2021-06-09 05:52:47 +00:00
tool4EvEr
cc8753b563 Small fixes 2021-06-08 23:17:55 +02:00
Michael Kamensky
454c51f1b1 Merge branch 'fix' into 'master'
Various card patches

See merge request core-developers/forge!4831
2021-06-08 14:56:34 +00:00
Michael Kamensky
fa83326710 Merge branch 'mh2_7' into 'master'
MH2 - 7 June

See merge request core-developers/forge!4848
2021-06-08 14:55:48 +00:00
Northmoc
eb60690973 viashino_lashclaw.txt add AILogic 2021-06-08 10:17:43 -04:00
Northmoc
0983c72ea0 viashino_lashclaw.txt 2021-06-08 10:17:42 -04:00
Northmoc
49436a00b3 graceful_restoration.txt 2021-06-08 10:17:42 -04:00
Northmoc
8345bf890b glimmer_bairn.txt 2021-06-08 10:17:42 -04:00
Northmoc
5b2980562b emergent_growth.txt clean up stackdesc 2021-06-08 10:16:03 -04:00
Northmoc
b86249635f drake_stone.txt fix ManaCost 2021-06-08 10:16:03 -04:00
Northmoc
4286429402 gravebreaker_lamia.txt add Mandatory 2021-06-08 10:16:02 -04:00
Michael Kamensky
6578fb79e6 Merge branch 'master' into 'master'
Net Decks Archive Updates - New Gauntlets

See merge request core-developers/forge!4852
2021-06-08 10:33:26 +00:00
Bug Hunter
f1169511ab Merge branch 'TRT-master-patch-20578' into 'master'
Update forge-gui/res/cardsfolder/b/bog_rats.txt

Closes #1883

See merge request core-developers/forge!4853
2021-06-08 07:33:29 +00:00
Bug Hunter
6f5f04bb7e Update forge-gui/res/cardsfolder/b/bog_rats.txt 2021-06-08 07:32:14 +00:00
Churrufli
b33595d840 Net Decks Archive Updates - New Gauntlets 2021-06-08 09:25:52 +02:00
Churrufli
653b484674 Net Decks Archive Updates - New Gauntlets 2021-06-08 09:16:34 +02:00
Michael Kamensky
5a0a63940d Merge branch 'typefix' into 'master'
Fix missing type

See merge request core-developers/forge!4849
2021-06-08 03:31:57 +00:00
Michael Kamensky
d734fdf761 Merge branch 'fixnpe' into 'master'
Fix NPE with Grakmaw when run from ChooseType

See merge request core-developers/forge!4850
2021-06-08 03:31:41 +00:00
tool4EvEr
3ab7fcf224 Fix NPE with Grakmaw when run from ChooseType 2021-06-07 22:40:16 +02:00
tool4EvEr
4f4fe04683 Fix missing type 2021-06-07 22:08:21 +02:00
Michael Kamensky
c83d03a2d6 Merge branch 'H1R' into 'master'
RMH1 to H1R fix

See merge request core-developers/forge!4847
2021-06-07 18:28:45 +00:00
paul_snoops
8ffa95a26a RMH1 has been changed to H1R 2021-06-07 16:31:39 +01:00
paul_snoops
da96b580a4 RMH1 has been changed to H1R 2021-06-07 16:28:29 +01:00
paul_snoops
5a862ab742 RMH1 has been changed to H1R 2021-06-07 15:18:51 +01:00
Michael Kamensky
7e6f077b0a Merge branch 'TRT-master-patch-82400' into 'master'
Update forge-gui/res/cardsfolder/upcoming/search_the_premises.txt

See merge request core-developers/forge!4846
2021-06-07 08:02:42 +00:00
Bug Hunter
b6676c9d58 Update forge-gui/res/cardsfolder/upcoming/search_the_premises.txt 2021-06-07 04:56:28 +00:00
Michael Kamensky
37099156f3 Merge branch 'lion' into 'master'
Diamond Lion: Fix cost

See merge request core-developers/forge!4845
2021-06-07 04:32:08 +00:00
tool4EvEr
018a2c080b Fix cost 2021-06-06 21:46:27 +02:00
Michael Kamensky
890ef37b3f Merge branch 'snowblind' into 'master'
Card fixes

See merge request core-developers/forge!4843
2021-06-06 18:04:04 +00:00
Michael Kamensky
c7de3866c7 Merge branch 'PLIST' into 'master'
Edition updates PLIST and UST

See merge request core-developers/forge!4844
2021-06-06 18:03:49 +00:00
tool4EvEr
4cf11743f2 Fix P/T 2021-06-06 19:36:40 +02:00
tool4EvEr
3ee3a28fd5 Fix cost 2021-06-06 18:54:21 +02:00
paul_snoops
39ba048d07 Updating "The List" edition file and fix dragon token in Unstable 2021-06-06 16:55:56 +01:00
tool4EvEr
40a9229681 Fix check 2021-06-06 15:19:23 +02:00
tool4EvEr
8a7873d0c9 Fix triggers 2021-06-06 15:11:34 +02:00
Bug Hunter
996ddb4360 Merge branch 'twosat-master-patch-69468' into 'master'
Update de-DE.properties

See merge request core-developers/forge!4842
2021-06-06 12:57:58 +00:00
tool4EvEr
eec11c6ec5 Fix Snowblind 2021-06-06 14:57:24 +02:00
Andreas Bendel
197cbb1808 Update de-DE.properties
translated new lines under VAssignGenericAmount
2021-06-06 12:51:06 +00:00
Michael Kamensky
32df7f3eae Merge branch 'fixsuspend' into 'master'
Suspend Fixes

See merge request core-developers/forge!4828
2021-06-06 03:29:09 +00:00
Bug Hunter
4b3839b143 Suspend Fixes 2021-06-06 03:29:08 +00:00
Michael Kamensky
79216eaa8d Merge branch 'mh2_fix' into 'master'
MH2 fixes

See merge request core-developers/forge!4841
2021-06-06 03:28:46 +00:00
Michael Kamensky
ba2998541d Merge branch 'aicleanup' into 'master'
Small fix

See merge request core-developers/forge!4840
2021-06-06 03:28:26 +00:00
Michael Kamensky
d5aaa4dc45 Merge branch 'repeateach' into 'master'
RepeatPlayers - Swap out other remembered while resolving

See merge request core-developers/forge!4834
2021-06-06 03:27:03 +00:00
Bug Hunter
999e4f81ee RepeatPlayers - Swap out other remembered while resolving 2021-06-06 03:27:02 +00:00
Michael Kamensky
c38d2f531c Merge branch 'carth' into 'master'
MH2: Carth the Lion

See merge request core-developers/forge!4760
2021-06-06 03:26:09 +00:00
Michael Kamensky
27d8af3551 Merge branch 'carth' into 'master'
Carth support

See merge request core-developers/forge!4827
2021-06-06 03:26:07 +00:00
Bug Hunter
b10ce1b884 Carth support 2021-06-06 03:26:07 +00:00
Northmoc
18c207f39b arcbound_tracker.txt trim unneeded 2021-06-05 17:03:32 -04:00
tool4EvEr
898df454d5 Small fix 2021-06-05 23:02:34 +02:00
Northmoc
577c5ca055 restore edition labels 2021-06-05 17:01:07 -04:00
Northmoc
04558e93b3 correct lizard name 2021-06-05 17:00:44 -04:00
Michael Kamensky
6b0a88cd95 Merge branch 'phpr-prm-scryfall-editions' into 'master'
Latest Scryfall codes for PHPR and PRM Editions

See merge request core-developers/forge!4838
2021-06-05 19:03:02 +00:00
Michael Kamensky
bd125df278 Merge branch 'smallfix' into 'master'
Small fixes

See merge request core-developers/forge!4837
2021-06-05 19:02:53 +00:00
tool4EvEr
fc31881150 Small fixes 2021-06-05 20:52:59 +02:00
leriomaggio
9a13b06452 Matched with Magic Online Promo 2021-06-05 19:44:24 +01:00
leriomaggio
01e3a726fe Matched all with PHPR Set on Scryfall 2021-06-05 19:44:11 +01:00
leriomaggio
bcaa318941 Matched with Dragon Con on Scryfall 2021-06-05 19:43:46 +01:00
leriomaggio
c64e5c2d5e Merge remote-tracking branch 'upstream/master' into add-scryfallcodes-neweditions 2021-06-05 19:40:42 +01:00
Bug Hunter
caafcf61aa Merge branch 'city' into 'master'
Fix crash with City in a Bottle & Facedown

See merge request core-developers/forge!4836
2021-06-05 18:22:27 +00:00
tool4EvEr
e6280377ce Fix crash with City in a Bottle & Facedown 2021-06-05 20:22:30 +02:00
Bug Hunter
a2e66550bf Merge branch 'add-scryfallcodes-neweditions' into 'master'
Scryfall Code Added

See merge request core-developers/forge!4835
2021-06-05 18:21:31 +00:00
leriomaggio
abad45f874 Scryfall Code Added
(Not really needed, just to keep edition files aligned)
2021-06-05 18:21:31 +00:00
leriomaggio
fa649c1cd0 Scryfall Code Added
(Not really needed, just to keep edition files aligned)
2021-06-05 19:10:39 +01:00
leriomaggio
96ad12a1b7 FIX collector numbers w/ those on scryfall and scryfallcode added 2021-06-05 19:10:08 +01:00
Michael Kamensky
669979531c Merge branch 'patch-imagefetcher-scryfall' into 'master'
FIX performance with ImageFetcher, New ScryfallCodes, and Updated CollectorNumbers

See merge request core-developers/forge!4823
2021-06-05 16:25:51 +00:00
leriomaggio
0c81f5c36c FIX unicode char for alternate in Portal 2021-06-05 17:16:44 +01:00
leriomaggio
7dfc48744c Merge remote-tracking branch 'upstream/master' into patch-imagefetcher-scryfall 2021-06-05 17:00:53 +01:00
leriomaggio
943d95eed0 Added new scryfall codes to latest 67 editions 2021-06-05 16:58:07 +01:00
leriomaggio
4b6e5e32d1 Setup Scryfall Code 2021-06-05 16:54:22 +01:00
leriomaggio
a1132fbe90 Updated latest collector numbers 2021-06-05 16:54:05 +01:00
Michael Kamensky
ea37370738 Merge branch 'master' of git.cardforge.org:core-developers/forge into agetian-master 2021-06-05 18:13:54 +03:00
Michael Kamensky
3b73f86e1b Merge branch 'Williams-master-patch-27805' into 'master'
MH2 Contributions 04/06

See merge request core-developers/forge!4822
2021-06-05 15:11:24 +00:00
Michael Kamensky
1633dedee3 Merge branch 'master' of git.cardforge.org:core-developers/forge into agetian-master 2021-06-05 17:34:06 +03:00
Michael Kamensky
9ae41c67a2 Merge branch 'replace_damage_missing_cards' into 'master'
Implement remaining damage prevention/redirection cards

See merge request core-developers/forge!4832
2021-06-05 14:28:35 +00:00
Michael Kamensky
58819f48bc Merge branch 'mh2_token' into 'master'
MH2 token issues

See merge request core-developers/forge!4833
2021-06-05 14:27:22 +00:00
Michael Kamensky
1f2cc92533 Merge branch 'phasing' into 'master'
Phasing Fixes

Closes #531

See merge request core-developers/forge!4808
2021-06-05 14:27:11 +00:00
leriomaggio
2e550b95ca New Card Language option to support fetching cards in different languages 2021-06-05 15:21:49 +01:00
leriomaggio
6f27a0db4d ImageFetcher now gets language code directly from CardEdition and not from current locale 2021-06-05 15:17:32 +01:00
leriomaggio
ace7998c40 Removed unused method 2021-06-05 15:17:10 +01:00
leriomaggio
969d65bbb2 Setup card language 2021-06-05 15:06:46 +01:00
leriomaggio
dd3c6abdd2 FIX collector number with unicode characters 2021-06-05 15:06:26 +01:00
leriomaggio
45c50c1f6e Set card language 2021-06-05 14:54:10 +01:00
leriomaggio
93d284dc82 Updated collector numbers with unicode characters 2021-06-05 14:54:00 +01:00
leriomaggio
9f6269c3e8 Updated collector numbers 2021-06-05 14:53:25 +01:00
leriomaggio
54ed9cfe22 Updated collector numbers 2021-06-05 14:53:21 +01:00
Northmoc
efe0b7ace0 u_0_3_crab.txt add newline 2021-06-05 08:27:03 -04:00
Northmoc
96eeac14ea u_0_3_crab.txt 2021-06-05 08:26:46 -04:00
Northmoc
85409f1616 fix golem token 2021-06-05 08:26:26 -04:00
Lyu Zong-Hong
f7f020cc26 Update language files 2021-06-05 21:02:50 +09:00
Northmoc
889173a682 move PutCounter cost earlier 2021-06-05 08:02:27 -04:00
Northmoc
e2fab353fd carth_the_lion.txt 2021-06-05 08:02:26 -04:00
leriomaggio
41a0769cca Updated **all** collector numbers 2021-06-05 12:05:47 +01:00
Tim Mocny
f73fac0eb4 Merge branch 'fixdroid' into 'master'
hotfix for Android

See merge request core-developers/forge!4830
2021-06-05 11:02:46 +00:00
Michael Kamensky
3047e40116 Merge branch 'Williams-master-patch-76451' into 'master'
Vintage/Legacy Championship "Promos", MH2 tokens

See merge request core-developers/forge!4829
2021-06-05 11:00:19 +00:00
Northmoc
24c45c7cc2 hotfix for Android 2021-06-05 06:59:27 -04:00
John
88b2ce84d6 Update Modern Horizons 2.txt 2021-06-05 10:23:38 +00:00
leriomaggio
9c55543f1b Updated card language 2021-06-05 11:21:31 +01:00
leriomaggio
76eac28192 Updated card collector numbers 2021-06-05 11:21:24 +01:00
leriomaggio
b3d2c28f06 Updated card collector numbers and ScryfallCode 2021-06-05 11:19:23 +01:00
leriomaggio
a4c0a56734 Updated card language with Japanese 2021-06-05 11:15:28 +01:00
leriomaggio
fc78637d2f Updated collector numbers and scryfallcode 2021-06-05 11:13:30 +01:00
leriomaggio
7605706b89 Updated collector numbers with unicode characters 2021-06-05 11:09:09 +01:00
leriomaggio
862f3e4e9d Updated collector numbers 2021-06-05 11:01:54 +01:00
leriomaggio
64de8327a6 Updated collector numbers 2021-06-05 11:00:26 +01:00
John
c8d5624600 Add new file 2021-06-05 09:58:31 +00:00
leriomaggio
abe2d7d10a Updated Card language and collector numbers 2021-06-05 10:58:17 +01:00
leriomaggio
27dbddac94 Updated Card language 2021-06-05 10:55:58 +01:00
John
cfd18eef5c Add new file 2021-06-05 09:55:08 +00:00
leriomaggio
e2b30b8f71 Extended Regexp to support Historic Anthology 5 CN types
Historic Anthology 5 introduced a new "type" of collector number, namely
"999-CH". Regexp has been extended to also include those cases.
2021-06-05 10:50:16 +01:00
leriomaggio
0f86978d8a Updated Collector Numbers with Scryfall 2021-06-05 10:49:09 +01:00
leriomaggio
1e7c21bdcf Updated Collector Numbers with Scyfall 2021-06-05 10:49:00 +01:00
leriomaggio
9e8fa209d5 FIX collector numbers with unicode characters 2021-06-05 10:48:47 +01:00
John
83caef1a97 Update vermin_gorger.txt 2021-06-05 09:47:53 +00:00
John
056daed06b Update crack_open.txt 2021-06-05 09:47:30 +00:00
Lyu Zong-Hong
ab31c8fa9d Implement missing cards with dividable shields for damage replacement effects 2021-06-05 18:20:02 +09:00
Lyu Zong-Hong
f95fb7d08b Choose source for prevent/redirect next N damage effect 2021-06-05 18:20:01 +09:00
leriomaggio
b2939f511d FIX collector numbers with unicode characters 2021-06-05 09:15:50 +01:00
leriomaggio
e1a08629a4 Updated collector numbers 2021-06-05 09:15:35 +01:00
leriomaggio
9c5eba7372 Updated collector numbers 2021-06-05 09:06:20 +01:00
leriomaggio
97ecf45da3 FIX collector number with unicode character 2021-06-05 09:05:05 +01:00
leriomaggio
cf4321492e FIX collector number with unicode character 2021-06-05 08:36:13 +01:00
leriomaggio
d5f996d249 added card language 2021-06-05 08:34:12 +01:00
leriomaggio
dc65f639c7 FIX collector number with unicode characters 2021-06-05 08:33:58 +01:00
leriomaggio
64aa9fe0ae Set Scryfall Code 2021-06-05 08:30:00 +01:00
leriomaggio
73f65505b0 Set Card language to Japanese (ja) 2021-06-05 08:28:14 +01:00
leriomaggio
49dc938eb3 FIX collector numbers with unicode characters 2021-06-05 08:25:30 +01:00
leriomaggio
687b547b47 FIX collector numbers with unicode characters 2021-06-05 08:23:08 +01:00
leriomaggio
4b85ef2a99 New CardLang Option
Certain Cards are only available in a specific language (e.g. JP, HE).
This option will be read into card Edition and used by content downloader later on.
2021-06-05 08:22:01 +01:00
leriomaggio
be7ef159cd FIX collector numbers with unicode characters 2021-06-05 08:21:04 +01:00
leriomaggio
8b6f0fb8eb FIX collector numbers with unicode characters 2021-06-05 08:13:15 +01:00
leriomaggio
ef29c61fb0 FIX collector numbers with unicode characters 2021-06-05 08:11:33 +01:00
leriomaggio
77ba81d621 FIX collector numbers with unicode characters 2021-06-05 08:08:25 +01:00
leriomaggio
4d645f761c FIX collector numbers with unicode characters 2021-06-05 08:06:06 +01:00
leriomaggio
b0aeaaabb4 FIX collector numbers with unicode characters 2021-06-05 08:05:02 +01:00
leriomaggio
80661e1646 FIX collector number to a few more missing 2021-06-05 08:03:57 +01:00
Michael Kamensky
f53e4495de Merge branch 'thrasta' into 'master'
MH2: Thrasta and "Trample over planeswalkers"

See merge request core-developers/forge!4824
2021-06-05 03:17:27 +00:00
Michael Kamensky
76ee1e776f Merge branch 'mh2_draft' into 'master'
MH2 limited

See merge request core-developers/forge!4814
2021-06-05 03:16:49 +00:00
Michael Kamensky
81c880efb1 Merge branch 'mh2_3' into 'master'
MH2 - 3 June

See merge request core-developers/forge!4816
2021-06-05 03:16:25 +00:00
Northmoc
3b3c955e2c echoing_return.txt 2021-06-04 15:57:53 -04:00
Northmoc
d786509a06 vile_entomber.txt 2021-06-04 15:31:12 -04:00
Northmoc
7e682810fb final tweaks for now 2021-06-04 15:11:39 -04:00
leriomaggio
446fc7dc35 Enhancing regexp to also support CN with symbol in front (e.g. *180 in Theros promos) 2021-06-04 19:55:16 +01:00
leriomaggio
76ccc2f8b1 Updating collector numbers with unicode chars for matching on scryfall 2021-06-04 19:54:33 +01:00
leriomaggio
6f99e6b840 updating collector numbers with unicode in Arabian nights 2021-06-04 19:54:10 +01:00
tool4EvEr
08d6b11add Clean up 2021-06-04 20:27:44 +02:00
tool4EvEr
dccf161d54 Fix Untap of card that should't after phasing in 2021-06-04 20:27:44 +02:00
tool4EvEr
c648a8ccbc Stop counting phased out cards 2021-06-04 20:27:43 +02:00
tool4EvEr
83dc9b688b Fix for phasing 2021-06-04 20:27:42 +02:00
tool4EvEr
2d53623ab2 Fix echo cost not skipable with phasing 2021-06-04 20:27:41 +02:00
tool4EvEr
d0d14b9006 Fix missing trigger when phased out 2021-06-04 20:27:41 +02:00
tool4EvEr
db85958008 Fix effects on phased out cards 2021-06-04 20:27:40 +02:00
tool4EvEr
58866b0595 when phasing out end Until 2021-06-04 20:27:39 +02:00
tool4EvEr
f6f2d5e4de Fix Attachments not phasing back in when the card attached to does 2021-06-04 20:27:39 +02:00
Bug Hunter
118aa4bda1 Merge branch 'payfix' into 'master'
Better fix for manapool payment

See merge request core-developers/forge!4826
2021-06-04 18:26:10 +00:00
tool4EvEr
ba9b4617f7 Better fix for manapool payment 2021-06-04 20:25:05 +02:00
Bug Hunter
a3646e83c8 Merge branch 'cleanimport' into 'master'
Clean import

See merge request core-developers/forge!4825
2021-06-04 17:24:15 +00:00
tool4EvEr
b51b8e9c63 Clean import 2021-06-04 19:24:22 +02:00
Northmoc
3638372602 nykthos_paragon.txt ai hint 2021-06-04 13:04:08 -04:00
Northmoc
647a0d8a2f brown acorns 2021-06-04 13:02:58 -04:00
Northmoc
0bcaf9cb41 chitterspitter.txt (Suthro) 2021-06-04 13:02:58 -04:00
Northmoc
52c6fb5d06 flourishing_strike.txt (Suthro) 2021-06-04 13:02:57 -04:00
Northmoc
a1a192946c phantasmal_dreadmaw.txt fix Oracle 2021-06-04 13:02:57 -04:00
Northmoc
713b199f21 resurgent_belief.txt 2021-06-04 13:02:56 -04:00
Northmoc
2364e8dd26 Combat.java set up defending planeswalker and controller to receive damage with trample over planeswalkers 2021-06-04 13:00:22 -04:00
Northmoc
60f98c84d5 make sure damage assign window pops up for trampling over planeswalkers 2021-06-04 13:00:21 -04:00
Northmoc
dfea87ed0e properly calculate lethal for planeswalkers being trampled over 2021-06-04 13:00:20 -04:00
Northmoc
3010657620 handle Trample over planeswalkers keyword 2021-06-04 13:00:20 -04:00
Northmoc
35477a5edb Trample over planeswalkers reminder text 2021-06-04 13:00:20 -04:00
Northmoc
793bb97909 thrasta_tempests_roar.txt 2021-06-04 13:00:19 -04:00
Northmoc
ca4e9c2ea5 add "alternate frame" and "bundle" to EditionSectionWithCollectorNumbers, tweak Edition 2021-06-04 12:59:07 -04:00
Northmoc
fa68e989c4 add preliminary MH2 rankings 2021-06-04 12:59:07 -04:00
Northmoc
47291206ae 36 packs in box 2021-06-04 12:59:06 -04:00
Northmoc
77578174b7 - improve booster 2021-06-04 12:59:06 -04:00
Northmoc
f0f7b9e6e4 Modern Horizons 2.txt boosters (initial attempt) 2021-06-04 12:59:05 -04:00
Northmoc
dbb87a6a79 blocks.txt add MH2 2021-06-04 12:59:05 -04:00
John
8d8c1c505c Add new file 2021-06-04 16:47:16 +00:00
John
970db2b6d1 Add new file 2021-06-04 16:46:46 +00:00
John
330e0818fa Add new file 2021-06-04 16:46:15 +00:00
John
7f4f48aa32 Add new file 2021-06-04 16:45:39 +00:00
John
fb4300fe4d Add new file 2021-06-04 16:44:00 +00:00
John
4e89b94916 Add new file 2021-06-04 16:43:09 +00:00
John
0f76d42b8c Add new file 2021-06-04 16:42:39 +00:00
John
6cc3060dc2 Add new file 2021-06-04 16:41:22 +00:00
John
83d629c397 Add new file 2021-06-04 16:40:44 +00:00
John
b7fe9ba794 Add new file 2021-06-04 16:40:08 +00:00
John
c0594c5449 Add new file 2021-06-04 16:38:42 +00:00
John
0eb562adf9 Add new file 2021-06-04 16:30:37 +00:00
John
181bd88902 Add new file 2021-06-04 16:28:11 +00:00
John
3d6cfd2457 Add new file 2021-06-04 16:26:40 +00:00
John
bcb5191ecc Add new file 2021-06-04 16:26:08 +00:00
John
5581283c52 Add new file 2021-06-04 16:25:37 +00:00
John
c3cb530aa1 Add new file 2021-06-04 16:25:01 +00:00
John
5cc0665a9d Add new file 2021-06-04 15:58:32 +00:00
John
6c0dde8e79 Add new file 2021-06-04 15:56:52 +00:00
John
a009e06fdf Add new file 2021-06-04 15:55:52 +00:00
John
4282169fc9 Add new file 2021-06-04 15:55:14 +00:00
John
7c76e72a7f Add new file 2021-06-04 15:54:41 +00:00
John
e22e514d29 Update necrogoyf.txt 2021-06-04 15:52:33 +00:00
John
edfbc6ac95 Update wrens_run_hydra.txt 2021-06-04 15:52:13 +00:00
Michael Kamensky
a23a1fa928 Merge branch 'handcount' into 'master'
Fix full fizzling for targeted players & SA

See merge request core-developers/forge!4817
2021-06-04 15:39:04 +00:00
John
8b2bb48dbc Add new file 2021-06-04 15:34:24 +00:00
John
930183b0aa Add new file 2021-06-04 15:33:36 +00:00
John
bfbf1c5e71 Add new file 2021-06-04 15:32:27 +00:00
John
67684ee0e6 Add new file 2021-06-04 15:31:22 +00:00
John
336ccd2ec4 Add new file 2021-06-04 14:34:15 +00:00
John
21f4a79dd5 Upload New File 2021-06-04 14:33:37 +00:00
John
3e94b2854c Upload New File 2021-06-04 14:33:06 +00:00
leriomaggio
9539060404 Merge remote-tracking branch 'upstream/master' into patch-imagefetcher-scryfall 2021-06-04 15:20:59 +01:00
leriomaggio
9f73524dd9 ImageFetcher now uses scryfallCode instead of testing URLs to fetch images. 2021-06-04 15:20:44 +01:00
leriomaggio
321360e180 Added new ScryfallCode to CardEdition (used by ImageFetcher) 2021-06-04 15:20:21 +01:00
leriomaggio
8ac3d0a2a6 Adding new ScryfallCode for all the 404 out of 486 editions found on Scryfall
There are still 82 editions to be matched manually.
2021-06-04 15:06:36 +01:00
John
b80118c6d0 Update specimen_collector.txt 2021-06-04 12:56:37 +00:00
John
d795812fd4 Update specimen_collector.txt 2021-06-04 12:44:45 +00:00
John
aa945e491c Add new file 2021-06-04 12:41:22 +00:00
John
1e566808cb Add new file 2021-06-04 12:40:32 +00:00
John
6eaa1e9edd Add new file 2021-06-04 12:39:50 +00:00
John
a2d53ce217 Add new file 2021-06-04 12:38:56 +00:00
John
39707005bc Add new file 2021-06-04 12:38:20 +00:00
John
dc8d24322f Add new file 2021-06-04 12:37:49 +00:00
John
912ee35721 Add new file 2021-06-04 12:37:18 +00:00
John
9321eea8f8 Add new file 2021-06-04 12:36:26 +00:00
John
347503d96f recalibrate 2021-06-04 12:34:55 +00:00
swordshine
fca3aade63 Merge branch 'restartg' into 'master'
RestartGameEffect: Reset extras turns

See merge request core-developers/forge!4821
2021-06-04 11:19:43 +00:00
tool4EvEr
a2cd1fa8ef Reset extras after restart 2021-06-04 12:46:40 +02:00
Michael Kamensky
d9db3a6edf Merge branch 'fixpay' into 'master'
Fix source of manapool for payment of second human

See merge request core-developers/forge!4815
2021-06-04 03:55:22 +00:00
Michael Kamensky
1d749eba48 Merge branch 'entwine' into 'master'
Entwine reminder text

See merge request core-developers/forge!4818
2021-06-04 03:55:02 +00:00
Michael Kamensky
22917b7e0a Merge branch 'caprichrome' into 'master'
MH2: Caprichrome with necessary Devour keyword tweaks

See merge request core-developers/forge!4811
2021-06-04 03:54:47 +00:00
Michael Kamensky
e4ca35cae8 Merge branch 'fix' into 'master'
tiamat.txt fix

See merge request core-developers/forge!4820
2021-06-04 03:54:13 +00:00
Michael Kamensky
0c74ee2602 Merge branch 'cardupdates' into 'master'
Small card updates

See merge request core-developers/forge!4819
2021-06-04 03:53:59 +00:00
Michael Kamensky
40e533d9d8 Merge branch 'Williams-master-patch-03324' into 'master'
MH2 Contributions 02/06

See merge request core-developers/forge!4807
2021-06-04 03:53:22 +00:00
Northmoc
01f4bb9494 tiamat.txt fix 2021-06-03 18:38:31 -04:00
tool4EvEr
e386c8e87c Update X 2021-06-03 23:43:32 +02:00
tool4EvEr
bf3b1b03e8 Update card 2021-06-03 23:18:41 +02:00
tool4EvEr
a69c1534f6 Small card updates 2021-06-03 22:22:08 +02:00
Northmoc
41671d7acf caprichrome.txt add AI hints 2021-06-03 16:18:16 -04:00
Northmoc
a909fc1fa8 CardFactoryUtil expand Devour keyword possibilities 2021-06-03 16:18:15 -04:00
Northmoc
919a49b22e Card.java override reminder text for Devouring non-creature stuff 2021-06-03 16:18:15 -04:00
Northmoc
9ae599cf05 caprichrome.txt 2021-06-03 16:18:14 -04:00
Northmoc
5aad37c613 Entwine reminder text 2021-06-03 16:07:29 -04:00
tool4EvEr
69e580e37d Fix full fizzling 2021-06-03 21:09:16 +02:00
tool4EvEr
c735195d33 Fix source of manapool for payment of second human 2021-06-03 19:16:27 +02:00
John
857f0d2dcc Update wavesifter.txt 2021-06-03 14:41:12 +00:00
John
9b6e1f4c64 Update lazotep_chancellor.txt 2021-06-03 14:40:51 +00:00
John
8a41b35317 Update foundry_helix.txt 2021-06-03 14:40:30 +00:00
Michael Kamensky
f08cb0f0b5 Merge branch 'activation' into 'master'
ActivationTable: Fix NPE for triggers without OverridingAbility

Closes #1859

See merge request core-developers/forge!4799
2021-06-03 14:07:15 +00:00
Michael Kamensky
bbf1e373ad Merge branch 'fixstuff' into 'master'
Some smaller fixes

Closes #1868

See merge request core-developers/forge!4806
2021-06-03 14:02:50 +00:00
tool4EvEr
069403ab6c Clean up 2021-06-03 14:22:56 +02:00
Tim Mocny
73ac435b92 Merge branch 'editions' into 'master'
MH2 and RMH1 updates

See merge request core-developers/forge!4812
2021-06-03 12:13:05 +00:00
Tim Mocny
4b439dc7bb Merge branch 'boon' into 'master'
skyblades_boon.txt fix

See merge request core-developers/forge!4813
2021-06-03 12:12:44 +00:00
Northmoc
4c7879f423 skyblades_boon.txt fix 2021-06-03 08:10:51 -04:00
tool4EvEr
fc7540ed1e Fix multiplayer rulings (count player who lost) 2021-06-03 11:52:37 +02:00
tool4EvEr
08fd9b98c8 Fix missing sub 2021-06-03 11:19:50 +02:00
tool4EvEr
1af8cee719 Fix crash on payment with Channel 2021-06-03 10:39:50 +02:00
John
ca6c9c454d Add new file 2021-06-03 08:25:22 +00:00
John
8adcc14422 Add new file 2021-06-03 08:24:56 +00:00
John
65777750ab Add new file 2021-06-03 08:24:31 +00:00
John
96b437c0b4 Add new file 2021-06-03 08:24:01 +00:00
John
d3ae5c38d9 Add new file 2021-06-03 08:23:26 +00:00
John
c6452b2fea Add new file 2021-06-03 08:23:01 +00:00
John
caf1900484 Add new file 2021-06-03 08:22:30 +00:00
John
2e79d34f75 Add new file 2021-06-03 08:22:03 +00:00
John
28566c4272 Add new file 2021-06-03 08:21:34 +00:00
John
28fd2d18fa Add new file 2021-06-03 08:20:44 +00:00
John
df7ce97d40 Add new file 2021-06-03 08:20:16 +00:00
John
5b2504ba3f Add new file 2021-06-03 08:19:51 +00:00
John
cbf4d17c7f Add new file 2021-06-03 08:18:35 +00:00
John
642ba05dcf Add new file 2021-06-03 08:18:08 +00:00
John
b2b5e87aed Add new file 2021-06-03 08:17:47 +00:00
John
3562f3192f Add new file 2021-06-03 08:17:19 +00:00
John
e58d8b35b8 Add new file 2021-06-03 08:16:53 +00:00
John
2761a3778a Add new file 2021-06-03 08:16:21 +00:00
John
1eaa8d9b67 Add new file 2021-06-03 08:15:57 +00:00
John
169b292301 Add new file 2021-06-03 08:15:21 +00:00
John
249cfdb78d Add new file 2021-06-03 08:14:53 +00:00
John
3f34e17bd4 Add new file 2021-06-03 08:14:29 +00:00
tool4EvEr
196b48cbd3 Reduce FailedToTarget scenario 2021-06-03 10:14:16 +02:00
John
01bfa59c6a Update sythis_harvests_hand.txt 2021-06-03 07:51:46 +00:00
John
a921ef1ec7 Update goblin_traprunner.txt 2021-06-03 07:51:19 +00:00
John
d30f2b9d16 Update hard_evidence.txt 2021-06-03 07:50:58 +00:00
John
8f887e5242 Update goblin_traprunner.txt 2021-06-03 07:50:39 +00:00
John
f742a65af8 Update captain_ripley_vance.txt 2021-06-03 07:50:17 +00:00
John
868ba6d232 Update floodhound.txt 2021-06-03 07:49:58 +00:00
paul_snoops
47384bec0e MH2 and RMH1 updates 2021-06-03 08:29:26 +01:00
Michael Kamensky
2144de87d4 Merge branch 'velus' into 'master'
MH2: Mount Velus Manticore and support

See merge request core-developers/forge!4809
2021-06-03 05:15:59 +00:00
Michael Kamensky
7a3c9c5b49 Merge branch 'sojourn' into 'master'
MH2: Sojourner's Companion w/ updates to TypeCycling to parse "Artifact.Land"

See merge request core-developers/forge!4810
2021-06-03 05:15:34 +00:00
Michael Kamensky
df35468771 Merge branch 'mh2_2' into 'master'
MH2 - 2 June

See merge request core-developers/forge!4805
2021-06-03 05:14:59 +00:00
Northmoc
5ac9d3fdff AbilityUtils support for Count$CardNumTypes / Count$RememberedCardNumTypes 2021-06-02 15:52:41 -04:00
Northmoc
de73ed1986 mount_velus_manticore.txt 2021-06-02 15:51:13 -04:00
Northmoc
da2a92b74a CardFactoryUtil prep TypeCycling for Artifact landcycling 2021-06-02 15:06:29 -04:00
Northmoc
9aba2db8ce KeywordWithCostAndType.java set artifact landcycling for reminder text 2021-06-02 15:05:30 -04:00
Northmoc
5141be475b sojourners_companion.txt 2021-06-02 15:04:38 -04:00
John
9f8e7616d1 Delete chitterspitter.txt 2021-06-02 17:03:13 +00:00
John
f7c7cdef8c Delete nykthos_paragon.txt 2021-06-02 17:02:59 +00:00
John
98e529304f Add new file 2021-06-02 16:11:23 +00:00
John
c04290493e Add new file 2021-06-02 16:10:28 +00:00
John
12695faba2 Add new file 2021-06-02 16:09:55 +00:00
John
c1a064e40a Add new file 2021-06-02 16:09:06 +00:00
John
7798594071 Add new file 2021-06-02 16:04:16 +00:00
John
34281c2850 Add new file 2021-06-02 16:03:29 +00:00
John
17365a6e2c Add new file 2021-06-02 16:02:40 +00:00
John
9b150e3263 Add new file 2021-06-02 16:01:56 +00:00
John
a1648c70c6 Add new file 2021-06-02 16:01:11 +00:00
John
1a0aa37992 Add new file 2021-06-02 15:59:28 +00:00
John
ad4772ba01 Add new file 2021-06-02 15:55:32 +00:00
John
78b6e16eb3 Add new file 2021-06-02 15:54:59 +00:00
John
682c61d025 Add new file 2021-06-02 15:54:29 +00:00
Northmoc
b026479f78 gaeas_will.txt tidy 2021-06-02 11:54:07 -04:00
John
1d6fbba6d2 Add new file 2021-06-02 15:53:41 +00:00
John
142a47346d Add new file 2021-06-02 15:51:36 +00:00
John
406d1023e8 Add new file 2021-06-02 15:49:57 +00:00
John
895db0526b Add new file 2021-06-02 15:49:16 +00:00
John
d2ecf48af9 Add new file 2021-06-02 15:48:20 +00:00
John
edca8bec50 Add new file 2021-06-02 15:47:16 +00:00
John
b99571c28f Add new file 2021-06-02 15:46:43 +00:00
John
70d9d9273b Add new file 2021-06-02 15:45:59 +00:00
John
bcd0b0cd46 Add new file 2021-06-02 15:44:37 +00:00
John
49238a1283 Add new file 2021-06-02 15:43:32 +00:00
John
7a11129a5e Add new file 2021-06-02 15:41:56 +00:00
Northmoc
395fc13142 blossoming_calm.txt (medusa) 2021-06-02 11:35:14 -04:00
tool4EvEr
3b594d239c Update script 2021-06-02 17:30:31 +02:00
Northmoc
e7701ee0f9 rw_4_4_golem.txt for General Ferrous 2021-06-02 11:27:46 -04:00
Bug Hunter
80e8f5db8a Update goblin_artisans.txt 2021-06-02 06:51:46 +00:00
Michael Kamensky
608eea8e7d Merge branch 'mh2_1' into 'master'
MH2 - 1 June

See merge request core-developers/forge!4804
2021-06-02 04:42:00 +00:00
Michael Kamensky
7dae92b042 Merge branch 'fixing' into 'master'
Card fixes

Closes #1866

See merge request core-developers/forge!4803
2021-06-02 04:41:15 +00:00
Northmoc
62bc67cbe1 blacksmiths_skill.txt (Suthro) 2021-06-01 19:17:55 -04:00
Northmoc
fbcbf0afe5 barbed_spike.txt (Suthro) 2021-06-01 19:13:50 -04:00
Northmoc
6b06873568 arcbound_javelineer.txt (medusa) 2021-06-01 19:10:18 -04:00
Northmoc
2007a8d05c kaleidoscorch.txt 2021-06-01 18:53:51 -04:00
Northmoc
1007772ef3 funnel_web_recluse.txt 2021-06-01 18:53:19 -04:00
Northmoc
23785ea8b6 add AI hint 2021-06-01 18:52:58 -04:00
Northmoc
10a6b43a97 tavern_scoundrel.txt 2021-06-01 18:39:12 -04:00
Northmoc
72e1d8177c ornithopter_of_paradise.txt 2021-06-01 18:38:53 -04:00
tool4EvEr
6ab9bfa0fa Add pump 2021-06-01 22:33:49 +02:00
tool4EvEr
faeb73d8b7 Clean up 2021-06-01 22:29:15 +02:00
tool4EvEr
beff3d4832 Fix Runaway Carriage 2021-06-01 22:11:01 +02:00
tool4EvEr
29a4fdb47a prevent StackDescription from revealing face 2021-06-01 21:47:58 +02:00
tool4EvEr
931411b6e4 Fix Wandering Archaic 2021-06-01 18:38:26 +02:00
tool4EvEr
8157172a9f Fix City in a Bottle 2021-06-01 17:53:53 +02:00
Michael Kamensky
5ac73d9f90 Merge branch 'fix-deck-editor-custom-editions-image-view' into 'master'
Improvements to Deck Editor for Custom Editions

See merge request core-developers/forge!4796
2021-06-01 04:26:05 +00:00
Michael Kamensky
27085243c4 Merge branch 'historic-formats' into 'master'
Update the "historic" formats

See merge request core-developers/forge!4802
2021-06-01 04:25:39 +00:00
Michael Kamensky
16b2fd93d8 Merge branch 'spellcast' into 'master'
Fix missing intrinsic Cast trigger from copied host

See merge request core-developers/forge!4754
2021-06-01 04:25:28 +00:00
Bug Hunter
edce6ed9e8 Fix missing intrinsic Cast trigger from copied host 2021-06-01 04:25:27 +00:00
Rob Schnautz
e9b6db6994 erroneous date/name 2021-05-31 20:30:24 +00:00
Rob Schnautz
f5a1671e86 Catching up old formats 2021-05-31 20:04:04 +00:00
leriomaggio
560d8ec5c0 Refactored duplicate code in StaticData w/ new getCardEdition method
StaticData now includes a new method, namely `getCardEdition` which looks for a CardEdition instance into editions and customeditions given an input set code.

This new method has been refactored and used throughout the new changes in Advanced Search and Filters.
2021-05-31 19:57:33 +01:00
leriomaggio
4e7a3fe515 Updated Chinese translation after CCTV-1 suggestions in MR comments 2021-05-31 19:30:43 +01:00
leriomaggio
1ac28b5203 Merge remote-tracking branch 'upstream/master' into fix-deck-editor-custom-editions-image-view 2021-05-31 19:25:20 +01:00
Michael Kamensky
9b69d79a78 Merge branch 'Williams-master-patch-60436' into 'master'
MH2 Contributions 30/05

See merge request core-developers/forge!4801
2021-05-31 16:53:19 +00:00
John
9e78a4ef99 Update tireless_provisioner.txt 2021-05-31 13:17:30 +00:00
John
8d2ef41d54 Update tide_shaper.txt 2021-05-31 13:17:03 +00:00
John
c507e960fd Update thought_monitor.txt 2021-05-31 13:16:05 +00:00
John
490f700e37 Update skyblades_boon.txt 2021-05-31 13:15:47 +00:00
John
355f379f21 Update combine_chrysalis.txt 2021-05-31 13:13:34 +00:00
Michael Kamensky
e69f8a2e7a Merge branch 'fixes' into 'master'
Some card fixes

Closes #1860

See merge request core-developers/forge!4798
2021-05-31 11:10:12 +00:00
Bug Hunter
450d5b6144 Some card fixes 2021-05-31 11:10:11 +00:00
John
41fdc485b4 Add new file 2021-05-31 10:33:54 +00:00
John
4d25aee6dc Add new file 2021-05-31 10:33:16 +00:00
John
46574f8137 Add new file 2021-05-31 10:32:35 +00:00
John
71cbd1221b Add new file 2021-05-31 10:31:22 +00:00
John
7386a9b614 Add new file 2021-05-31 10:30:50 +00:00
John
d5cfc39744 Add new file 2021-05-31 10:29:35 +00:00
leriomaggio
8a1770ffda FIX Editions in Checkboxlist sorted (latest to earliest) 2021-05-31 11:19:08 +01:00
leriomaggio
01e51ecb73 Custom Editions are now included in Advanced Search Filters 2021-05-31 10:45:49 +01:00
leriomaggio
43a62813e6 Custom Editions are now included in GameFormat when checking for sets 2021-05-31 10:44:48 +01:00
leriomaggio
1b25438fca New Extra Panel with Checkbox list for Custom Edition
Besides: since Custom Editions can be sometimes not that many, the number of displayed rows in Checkbox lists has been fixed to 20 to have all panels of same height.
2021-05-31 10:39:31 +01:00
leriomaggio
5858de4860 ColumnDef now includes Custom Editionsi in sorting 2021-05-31 10:36:11 +01:00
leriomaggio
8128a3374b New Label for Other and Custom Sets for Advanced Filter Panel 2021-05-31 10:35:23 +01:00
leriomaggio
32cc860eda Changed getSortedEditions to now also include Custom Sets 2021-05-31 10:18:21 +01:00
leriomaggio
03d6687c64 Merge remote-tracking branch 'upstream/master' into fix-deck-editor-custom-editions-image-view 2021-05-31 09:12:32 +01:00
Michael Kamensky
51924d16db Merge branch 'scrolling' into 'master'
Workshop: Add scrollbars

Closes #6

See merge request core-developers/forge!4800
2021-05-31 06:43:25 +00:00
Michael Kamensky
cd167313b7 Merge branch 'Williams-master-patch-52159' into 'master'
MH2 Contributions 29/05

See merge request core-developers/forge!4793
2021-05-31 06:43:12 +00:00
John
365142c860 MH2 Contributions 29/05 2021-05-31 06:43:11 +00:00
Michael Kamensky
990f6f148a Merge branch 'refactor_replace_damage_buffered' into 'master'
Refactor replace damage to buffer SA execution

See merge request core-developers/forge!4797
2021-05-31 06:41:17 +00:00
Alumi
1eb6f33207 Refactor replace damage to handle all damage related replacments first, then run all buffered SAs after 2021-05-31 06:41:16 +00:00
tool4EvEr
2d23a5b37d Add scrollbars 2021-05-30 22:26:35 +02:00
tool4EvEr
65f400cf46 Fix NPE 2021-05-30 18:34:44 +02:00
Michael Kamensky
84dfdfc11c Merge branch 'mh2_29' into 'master'
MH2 - 29 May

See merge request core-developers/forge!4795
2021-05-30 05:36:47 +00:00
Michael Kamensky
ecb1cb8a0e Merge branch 'cleaning' into 'master'
Fix mana not replaced when generated for different player

See merge request core-developers/forge!4794
2021-05-30 05:36:23 +00:00
Michael Kamensky
b9a8da1231 Merge branch 'junk' into 'master'
Junk Winder - Affinity detail/reminder text updates for more complex affinities

See merge request core-developers/forge!4787
2021-05-30 05:36:19 +00:00
Michael Kamensky
27c9bee138 Merge branch 'patch-sortable-collector-number-non-numerical' into 'master'
FIX non-numerical CollectorNumber as Sorting Key

See merge request core-developers/forge!4789
2021-05-30 05:36:09 +00:00
leriomaggio
9902718082 FIX and Impros to CardEdition, CardInSet and Reader
- Reader has a new updated regexp to deal with non-numerical collectorNumbers
- CardInSet have been now made sortable based on CollectorNumber.
To do so, collectorNumbers are transformed accordingly to allow for natural ordering (as expected) instead of lexicographic order.
- CardEdition now return cards (CardInSet) as sorted, to allow for correct artIndex matching when creating corresponding `PaperCard` instances.
Moreover, `compareTo` of card edition has been improved to also take into account set name (in cases of same release date).
2021-05-30 05:36:09 +00:00
Michael Kamensky
270ab5e843 Merge branch 'untapp' into 'master'
avoid pumping opponent creature

Closes #1134

See merge request core-developers/forge!4792
2021-05-30 05:23:56 +00:00
leriomaggio
62961889d5 Fixed typo in Label Name: RepRints (r missing) 2021-05-30 02:05:09 +01:00
leriomaggio
0217c4b4b2 Support collectorNumber for Cards in Custom sets
retrieveCollectorNumber now also considers customEditions when looking for the matching collectorNumber of a PaperCard instance.
2021-05-30 00:21:05 +01:00
leriomaggio
1c950a8a57 ImageView now includes also CustomEditions in Sets Map
This fix solves an issue with Custom Editions appearing as "Other" in ImageView. The bug was due to customEditions not being included in sets map.
2021-05-29 23:43:47 +01:00
leriomaggio
f5563ccedb New method to return the Collection of Custom Editions 2021-05-29 23:42:34 +01:00
Northmoc
0672477817 moderation.txt 2021-05-29 17:08:29 -04:00
Northmoc
c28a1f740e nettlecyst.txt 2021-05-29 16:59:12 -04:00
Northmoc
e0f3b65251 strike_it_rich.txt 2021-05-29 16:57:16 -04:00
Northmoc
001acda866 fast_furious.txt yank Cost 2021-05-29 16:57:01 -04:00
Northmoc
eb9ceb4dc8 fast_furious.txt 2021-05-29 16:48:42 -04:00
tool4EvEr
d622f9075a Add ValidActivator 2021-05-29 20:59:49 +02:00
Northmoc
76699ad323 batterbone.txt 2021-05-29 11:14:43 -04:00
tool4EvEr
a5fc5c1e40 Fix mana not replaced when generated for different player 2021-05-29 17:06:08 +02:00
Tim Mocny
cb9302b2fe Merge branch 'svyelun' into 'master'
Svyelun name fix

See merge request core-developers/forge!4791
2021-05-29 15:04:32 +00:00
Northmoc
254ddf8dfb KeywordWithType.java special parsing for Affinity 2021-05-29 11:00:36 -04:00
Northmoc
4995cc8876 CardFactoryUtil.java implement optional additional param for complex Affinities 2021-05-29 10:59:15 -04:00
Northmoc
1f120a7faf Affinity reminder text remove extra "you" 2021-05-29 10:58:42 -04:00
Northmoc
4411910930 junk_winder.txt fix and add to keyword 2021-05-29 10:53:41 -04:00
tool4EvEr
931dc04ad2 avoid pumping opponent creature 2021-05-29 12:01:10 +02:00
paul_snoops
e6b390addc Svyelun name fix 2021-05-29 10:55:46 +01:00
paul_snoops
a4027c0ad8 Svyelun name fix 2021-05-29 10:47:52 +01:00
Bug Hunter
3451fbf42a Merge branch 'rename' into 'master'
Fix card filename

See merge request core-developers/forge!4790
2021-05-29 09:01:44 +00:00
tool4EvEr
65e3c24de6 Fix card filename 2021-05-29 11:01:45 +02:00
Michael Kamensky
90e74c1c3a Merge branch 'Williams-master-patch-93999' into 'master'
MH2 Contributions 28/05

See merge request core-developers/forge!4781
2021-05-29 06:54:18 +00:00
Michael Kamensky
4610729a82 Merge branch 'patch-imagefetcher-scryfall' into 'master'
FIX ImageFetcher against Scryfall, so that download can be robust (against different set-codes) and reliable.

See merge request core-developers/forge!4788
2021-05-29 06:54:06 +00:00
Michael Kamensky
ab8c2c8b10 Merge branch 'mh2_28' into 'master'
MH2 - 28 May

See merge request core-developers/forge!4786
2021-05-29 06:53:50 +00:00
Michael Kamensky
bf0a5e13ae Merge branch 'fix' into 'master'
Fixes!

See merge request core-developers/forge!4785
2021-05-29 06:52:24 +00:00
Northmoc
e4f8f7852f naya_soulbeast.txt fix 2021-05-28 21:47:27 -04:00
leriomaggio
348365b8fd Merge remote-tracking branch 'upstream/master' into patch-imagefetcher-scryfall 2021-05-28 23:30:06 +01:00
leriomaggio
5c2f62defa FIX Scryfall Download retrieval, that is robust with different set-codes.
Imagefetcher now tries MCI code first and if that's not working, tries other set codes until one matching scryfall will be found. If none, the not-working set will be recorded to avoid future computation, and returns.
2021-05-28 23:29:29 +01:00
Northmoc
bd5a98fe13 academy_manufactor.txt 2021-05-28 17:01:44 -04:00
Northmoc
8a0da67705 crawling_barrens.txt tidy up StackDesc 2021-05-28 17:01:14 -04:00
Northmoc
479cbbf671 suspend.txt tighten up, add AI 2021-05-28 17:00:57 -04:00
Northmoc
6708ca00b5 suspend.txt fixup 2021-05-28 16:59:37 -04:00
John
53e08bbf55 Update verdant_command.txt 2021-05-28 20:25:53 +00:00
John
d389050a66 Update underworld_hermit.txt 2021-05-28 20:25:36 +00:00
John
5d4f628b8b Update tourach_dread_cantor.txt 2021-05-28 20:25:19 +00:00
John
2ecbd26477 Update sweep_the_skies.txt 2021-05-28 20:25:01 +00:00
John
427e0d895f Update svyelun_of_sea_and_sky.txt 2021-05-28 20:24:42 +00:00
John
dfb86d40a5 Update squirrel_mob.txt 2021-05-28 20:24:20 +00:00
John
b08c7cc9e6 Update scuttletide.txt 2021-05-28 20:24:01 +00:00
John
2bbe9464b8 Update necromancers_familiar.txt 2021-05-28 20:23:43 +00:00
John
ef5937d5ee Update grist_the_hunger_tide.txt 2021-05-28 20:23:24 +00:00
John
289846a53a Update grist_the_hunger_tide.txt 2021-05-28 20:22:43 +00:00
John
6bc361bd0c Update feast_of_sanity.txt 2021-05-28 20:22:15 +00:00
John
47274b0085 Update breyas_apprentice.txt 2021-05-28 20:21:41 +00:00
Northmoc
d3ebd83c32 angelic_arbiter.txt correct param 2021-05-28 14:40:34 -04:00
leriomaggio
b076a57811 New utility method to compose Scryfall Download url that is compliant with new API specs 2021-05-28 19:19:08 +01:00
Tim Mocny
e9285ad46d Merge branch 'editions' into 'master'
MH2 & RMH1 edition updates

See merge request core-developers/forge!4784
2021-05-28 17:55:46 +00:00
Paul Hammerton
5d91a1159e MH2 & RMH1 edition updates 2021-05-28 17:55:46 +00:00
John
ee65c22a40 Add new file 2021-05-28 16:08:31 +00:00
John
1cb5c9a711 Add new file 2021-05-28 16:08:01 +00:00
John
f22a3e081d Add new file 2021-05-28 16:07:19 +00:00
John
fff6a529e5 Add new file 2021-05-28 16:06:37 +00:00
John
2260957b00 Add new file 2021-05-28 16:05:55 +00:00
Michael Kamensky
42c845bb72 Merge branch 'collector-number-in-card-list-and-card-db-refactoring' into 'master'
Improvement to Performance to CardEdition, and FIX bug for non-existing cards.

See merge request core-developers/forge!4783
2021-05-28 15:37:45 +00:00
leriomaggio
8b7ae19508 FIX and Impros to CardEdition, CardInSet and Reader
- Reader has a new updated regexp to deal with non-numerical collectorNumbers
- CardInSet have been now made sortable based on CollectorNumber.
To do so, collectorNumbers are transformed accordingly to allow for natural ordering (as expected) instead of lexicographic order.
- CardEdition now return cards (CardInSet) as sorted, to allow for correct artIndex matching when creating corresponding `PaperCard` instances.
Moreover, `compareTo` of card edition has been improved to also take into account set name (in cases of same release date).
2021-05-28 15:37:44 +00:00
Michael Kamensky
c13274d0a7 Merge branch 'editions' into 'master'
AFR, MH2 & RMH1 edition updates

See merge request core-developers/forge!4778
2021-05-28 15:36:18 +00:00
Michael Kamensky
2a424ca3a9 Merge branch 'wave' into 'master'
Fix Parallax Wave

Closes #1853

See merge request core-developers/forge!4774
2021-05-28 15:35:21 +00:00
Bug Hunter
8effb2cb76 Fix Parallax Wave 2021-05-28 15:35:21 +00:00
Michael Kamensky
a03ce0e82c Merge branch 'fix_mutated_flip' into 'master'
Fix bug that can't flip or transform when mutated

See merge request core-developers/forge!4782
2021-05-28 15:34:27 +00:00
John
c0913a1b05 Add new file 2021-05-28 15:28:06 +00:00
John
f504581468 Add new file 2021-05-28 15:27:31 +00:00
John
f4528151ac Add new file 2021-05-28 15:26:24 +00:00
John
2475ea9ec9 Add new file 2021-05-28 15:25:47 +00:00
John
cc380a99f1 Add new file 2021-05-28 15:22:02 +00:00
John
7c34720cf7 Add new file 2021-05-28 15:21:17 +00:00
John
6debbe6853 Add new file 2021-05-28 15:18:18 +00:00
paul_snoops
d9cf784498 AFR, MH2 & RMH1 edition updates 2021-05-28 15:47:14 +01:00
John
12b8ec740b Add new file 2021-05-28 14:22:43 +00:00
paul_snoops
ff6d492857 AFR, MH2 & RMH1 edition updates 2021-05-28 15:11:21 +01:00
paul_snoops
4ea29526b1 AFR, MH2 & RMH1 edition updates 2021-05-28 14:16:06 +01:00
paul_snoops
aae3772a3a AFR, MH2 & RMH1 edition updates 2021-05-28 14:05:30 +01:00
paul_snoops
3639ced11f AFR, MH2 & RMH1 edition updates 2021-05-28 13:43:17 +01:00
John
a5dfcd4eb6 Add new file 2021-05-28 12:33:05 +00:00
John
204191973e Add new file 2021-05-28 12:32:01 +00:00
John
39b55f9045 Add new file 2021-05-28 12:30:39 +00:00
John
f02bb9d4ba Add new file 2021-05-28 12:29:47 +00:00
John
8b897c8a55 Add new file 2021-05-28 12:29:23 +00:00
John
f25bc35ee3 Add new file 2021-05-28 12:28:27 +00:00
John
81a41fb941 Update TypeLists.txt 2021-05-28 12:23:09 +00:00
John
c714ac2175 Add new file 2021-05-28 12:21:51 +00:00
John
f131bcb0d9 Add new file 2021-05-28 12:19:40 +00:00
John
07627b9320 Add new file 2021-05-28 12:18:56 +00:00
John
f32dab48a4 Add new file 2021-05-28 12:18:32 +00:00
John
61abb8cf0d Add new file 2021-05-28 12:17:47 +00:00
Lyu Zong-Hong
bbbee3421d Fix bug that can't flip or transform when mutated 2021-05-28 21:17:24 +09:00
John
972a79309b Add new file 2021-05-28 12:17:00 +00:00
John
d318606102 Add new file 2021-05-28 12:16:34 +00:00
John
8b057ea199 Add new file 2021-05-28 12:16:02 +00:00
John
edaa9f885a Add new file 2021-05-28 12:15:31 +00:00
paul_snoops
616760cbbb MH2 & RMH1 edition updates 2021-05-28 13:14:44 +01:00
paul_snoops
6a120f5839 MH2 & RMH1 edition updates 2021-05-28 13:11:35 +01:00
John
3fa7546ba6 Add new file 2021-05-28 11:34:59 +00:00
John
b7fd4d094c Add new file 2021-05-28 11:34:35 +00:00
John
fb92e06d62 Add new file 2021-05-28 11:34:08 +00:00
John
744573817a Add new file 2021-05-28 11:33:40 +00:00
John
b84dc7b721 Add new file 2021-05-28 11:33:10 +00:00
John
e88191c54a Add new file 2021-05-28 11:32:40 +00:00
John
a32bc8c378 Add new file 2021-05-28 11:32:12 +00:00
John
cb5be7cec7 Add new file 2021-05-28 11:31:40 +00:00
John
209eb9c397 Add new file 2021-05-28 11:31:10 +00:00
John
58b6b1cd0c Add new file 2021-05-28 11:30:05 +00:00
paul_snoops
b8c27ec2a9 MH2 & RMH1 edition updates 2021-05-28 12:20:44 +01:00
Anthony Calosa
a17c42e07e Merge branch 'kevlahnota-master-patch-91638' into 'master'
unused import

See merge request core-developers/forge!4780
2021-05-28 10:20:32 +00:00
Anthony Calosa
cb9a2d7b8b unused import 2021-05-28 10:20:13 +00:00
Anthony Calosa
a7b215fa19 Merge branch 'kevlahnota-master-patch-18523' into 'master'
Update FileUtil.java

See merge request core-developers/forge!4779
2021-05-28 10:12:15 +00:00
Anthony Calosa
367170d33b Update FileUtil.java 2021-05-28 10:10:54 +00:00
paul_snoops
695cb1496d MH2 & RMH1 edition updates 2021-05-28 10:20:20 +01:00
Michael Kamensky
741ca2646e Merge branch 'read_file_with_utf8' into 'master'
Update FileUtil.readFile to use UTF-8 charset to read files.

See merge request core-developers/forge!4777
2021-05-28 08:59:54 +00:00
Michael Kamensky
2ac4b923fe Merge branch 'vectis' into 'master'
MH2: Vectis Gloves and support

See merge request core-developers/forge!4772
2021-05-28 08:59:01 +00:00
Michael Kamensky
cac15b1436 Merge branch 'taste' into 'master'
MH2: Discerning Taste and Support

See merge request core-developers/forge!4770
2021-05-28 08:57:41 +00:00
Michael Kamensky
e77281cfb6 Merge branch 'patch-content-downloader-macos' into 'master'
FIX annoying bug increasing display brightness on macOS when starting content downloader

See merge request core-developers/forge!4768
2021-05-28 08:57:09 +00:00
Lyu Zong-Hong
81d0a71060 Update FileUtil.readFile to use UTF-8 charset to read files. 2021-05-28 17:25:12 +09:00
leriomaggio
66cb1c28f7 Switched to F1 - should be less annoying than F2 2021-05-28 07:05:06 +01:00
Michael Kamensky
051e3eb922 Merge branch 'mh2_27' into 'master'
MH2 - 27 May

See merge request core-developers/forge!4776
2021-05-28 03:39:34 +00:00
Michael Kamensky
bd93dd543e Merge branch 'drainlife' into 'master'
Fix Drain Life

Closes #1854

See merge request core-developers/forge!4775
2021-05-28 03:38:26 +00:00
Michael Kamensky
1382ef1724 Merge branch 'Williams-master-patch-31829' into 'master'
MH2 Contributions 27/05

See merge request core-developers/forge!4773
2021-05-28 03:38:07 +00:00
Michael Kamensky
6004850792 Merge branch 'fix' into 'master'
Various card fixes

See merge request core-developers/forge!4769
2021-05-28 03:36:15 +00:00
Northmoc
79d43f76b3 harmonic_prodigy.txt 2021-05-27 20:47:11 -04:00
Northmoc
d387287a04 dermotaxi.txt 2021-05-27 20:36:07 -04:00
Northmoc
413e209b0b out_of_time.txt 2021-05-27 19:50:08 -04:00
Northmoc
1124069e52 calibrated_blast.txt 2021-05-27 18:54:47 -04:00
tool4EvEr
3d0009f23e Fix Drain Life 2021-05-27 23:32:33 +02:00
John
4df0cf1049 Add new file 2021-05-27 21:03:44 +00:00
John
eb91e10661 Add new file 2021-05-27 21:03:23 +00:00
John
88dbfba282 Add new file 2021-05-27 21:02:43 +00:00
John
f6358f10f2 Add new file 2021-05-27 21:02:03 +00:00
John
51b5c81aac Update so_shiny.txt 2021-05-27 21:00:37 +00:00
Northmoc
20ff40896f infuse_with_vitality.txt fix 2021-05-27 16:47:46 -04:00
John
df78d375fe Update junk_winder.txt 2021-05-27 19:55:53 +00:00
John
206194353c Update arcbound_mouser.txt 2021-05-27 19:55:23 +00:00
John
dcd16704bd Update dress_down.txt 2021-05-27 19:54:26 +00:00
John
2b6b2a5e95 Update arcbound_mouser.txt 2021-05-27 19:53:45 +00:00
John
c767a3b3ad Add new file 2021-05-27 17:11:10 +00:00
John
06a4eac1c9 Add new file 2021-05-27 17:10:27 +00:00
John
ffba3ef18a Add new file 2021-05-27 17:09:41 +00:00
John
1a6d6e1290 Add new file 2021-05-27 17:09:03 +00:00
John
e75dedcffa Add new file 2021-05-27 16:36:16 +00:00
John
a6e50452f0 Add new file 2021-05-27 16:35:40 +00:00
John
008dd2f1d7 Add new file 2021-05-27 16:34:14 +00:00
John
82c3f32a5e Add new file 2021-05-27 16:33:41 +00:00
John
5c0a03e242 Add new file 2021-05-27 16:32:12 +00:00
John
e25ce911ed Add new file 2021-05-27 16:31:46 +00:00
John
b032002c33 Add new file 2021-05-27 16:29:27 +00:00
Northmoc
667bd138c8 bridge cycle (Suthro) 2021-05-27 11:36:07 -04:00
Northmoc
f0f017713e add Artifact landwalk to CombatUtil 2021-05-27 11:13:01 -04:00
Northmoc
331ae6ff44 vectis_gloves.txt 2021-05-27 11:12:27 -04:00
Tim Mocny
ee05f34963 Merge branch 'editions' into 'master'
MH2 & RMH1 edition updates

See merge request core-developers/forge!4771
2021-05-27 14:17:24 +00:00
paul_snoops
2f6aa3dc0f MH2 & RMH1 edition updates 2021-05-27 14:49:29 +01:00
paul_snoops
c9c5ae0058 MH2 & RMH1 edition updates 2021-05-27 14:43:39 +01:00
Northmoc
bff5a13c25 add support for "RememberMovedToZone" to DigEffect 2021-05-27 09:08:52 -04:00
Northmoc
68781783c4 discerning_taste.txt 2021-05-27 09:07:11 -04:00
Northmoc
04ba6bdd7b add Ultimate tag for PW achievements 2021-05-27 08:30:47 -04:00
Michael Kamensky
f557420c7a Merge branch 'collector-number-in-card-list-and-card-db-refactoring' into 'master'
FIX and Improvements to Card Edition Sorting, Cards Processing from Oracle, and Cards Art Index Matching

See merge request core-developers/forge!4756
2021-05-27 10:38:51 +00:00
leriomaggio
8d2d633f6d FIX and Impros to CardEdition, CardInSet and Reader
- Reader has a new updated regexp to deal with non-numerical collectorNumbers
- CardInSet have been now made sortable based on CollectorNumber.
To do so, collectorNumbers are transformed accordingly to allow for natural ordering (as expected) instead of lexicographic order.
- CardEdition now return cards (CardInSet) as sorted, to allow for correct artIndex matching when creating corresponding `PaperCard` instances.
Moreover, `compareTo` of card edition has been improved to also take into account set name (in cases of same release date).
2021-05-27 10:38:51 +00:00
leriomaggio
39ed24d133 FIX annoying bug increasing display brightness on macOS when starting content downloader 2021-05-27 11:26:28 +01:00
Anthony Calosa
94ec700c78 Merge branch 'kevlahnota-master-patch-24830' into 'master'
Update ChangeTargetsEffect.java

See merge request core-developers/forge!4767
2021-05-27 08:55:13 +00:00
Anthony Calosa
92ac19fa2e Merge branch 'kevlahnota-master-patch-27790' into 'master'
Update CopySpellAbilityEffect.java

See merge request core-developers/forge!4766
2021-05-27 08:55:04 +00:00
Anthony Calosa
c96aeb25e5 Update ChangeTargetsEffect.java 2021-05-27 08:48:52 +00:00
Anthony Calosa
63ebd462b9 Update CopySpellAbilityEffect.java 2021-05-27 08:48:11 +00:00
Michael Kamensky
68fe024d5a Merge branch 'drawai' into 'master'
Fix drawing negative amounts

See merge request core-developers/forge!4761
2021-05-27 04:25:49 +00:00
Michael Kamensky
12c0207f22 Merge branch 'chefs_kiss' into 'master'
MH2: Chef's Kiss and support

See merge request core-developers/forge!4765
2021-05-27 04:25:33 +00:00
Michael Kamensky
402eade4c2 Merge branch 'fix' into 'master'
binding_the_old_gods.txt fix

See merge request core-developers/forge!4762
2021-05-27 04:25:07 +00:00
Northmoc
c3f88773d2 support for RandomTargetRestriction on ChangeTargetsEffect 2021-05-26 23:36:29 -04:00
Northmoc
5c689ebf9b support for RandomTarget and RandomTargetRestriction on CopySpellAbilityEffect 2021-05-26 23:35:47 -04:00
Northmoc
efa99cb9ac chefs_kiss.txt 2021-05-26 23:33:44 -04:00
swordshine
19a96aac00 Merge branch 'suicide' into 'master'
Wheel of Misfortune: Fix suicide

See merge request core-developers/forge!4755
2021-05-27 01:01:03 +00:00
swordshine
6c84d433f6 Merge branch 'young' into 'master'
MH2: Young Necromancer

See merge request core-developers/forge!4759
2021-05-27 01:00:30 +00:00
swordshine
63c4ab822c Merge branch 'mh2_26' into 'master'
MH2 - 26 May

See merge request core-developers/forge!4763
2021-05-27 00:55:37 +00:00
swordshine
f343397a68 Merge branch 'ezellohar-master-patch-51670' into 'master'
Fixed a few typos and other minor improvements

See merge request core-developers/forge!4764
2021-05-27 00:51:39 +00:00
ezellohar
3993f5a715 Fixed a few typos and other minor improvements 2021-05-26 21:33:15 +00:00
Northmoc
ac66b6ac1b young_necromancer.txt "When you do" 2021-05-26 16:04:56 -04:00
Northmoc
de5c07d74b the_underworld_cookbook.txt 2021-05-26 15:53:12 -04:00
Northmoc
4f8a9b4d3f asmoranomardicadaistinaculdacar.txt 2021-05-26 15:52:57 -04:00
Northmoc
1e643b3a99 binding_the_old_gods.txt fix 2021-05-26 15:45:10 -04:00
tool4EvEr
eddbe33f2e Fix drawing negative amounts 2021-05-26 18:19:07 +02:00
Tim Mocny
6b07e07bea Merge branch 'edition_updates' into 'master'
New edition file updates

See merge request core-developers/forge!4757
2021-05-26 13:36:21 +00:00
Northmoc
8ff8502fed young_necromancer.txt 2021-05-26 09:32:46 -04:00
paul_snoops
31ee93a2a0 AFR, RMH1 & MH2 - New cards added
HA5 - Whole set added
SLD - Jumpstart foil lands added

RMH1 & MH2: The occasional collector number may be incorrect but will finalise when whole set is spolied
2021-05-26 14:27:47 +01:00
tool4EvEr
ce17685154 Fix suicide 2021-05-26 12:56:16 +02:00
Bug Hunter
4927e6135d Merge branch 'collector-number-in-card-list-and-card-db-refactoring' into 'master'
Restore Missing entries in Card Edition Resource files

See merge request core-developers/forge!4749
2021-05-26 08:57:46 +00:00
leriomaggio
6725873c8c Restore Missing entries in Card Edition Resource files 2021-05-26 08:57:45 +00:00
Bug Hunter
90b1a244a5 Merge branch 'fix_flowstone_shambler' into 'master'
Fix attack pump for Flowstone Shambler and Gateway Shade

See merge request core-developers/forge!4753
2021-05-26 07:30:55 +00:00
Lyu Zong-Hong
c3a7191f31 Fix attack pump for Flowstone Shambler and Gateway Shade 2021-05-26 14:00:24 +09:00
Michael Kamensky
e3a2a98c2f Merge branch 'mimicvat' into 'master'
Mimic Vat: Update trigger so it doesn't resolve when card moved

See merge request core-developers/forge!4741
2021-05-26 04:34:55 +00:00
Michael Kamensky
35aaafeba6 Merge branch 'fires' into 'master'
Fix Fires of Invention

See merge request core-developers/forge!4736
2021-05-26 04:34:29 +00:00
Michael Kamensky
0c9d3c9152 Merge branch 'update-nassari' into 'master'
Update nassari

See merge request core-developers/forge!4752
2021-05-26 04:33:25 +00:00
Michael Kamensky
6a93608427 Merge branch 'yusri' into 'master'
MH2: Yusri and support

See merge request core-developers/forge!4750
2021-05-26 04:32:52 +00:00
Michael Kamensky
c2b84ce240 Merge branch 'mh2_25' into 'master'
MH2 - 25 May

See merge request core-developers/forge!4748
2021-05-26 04:32:21 +00:00
Michael Kamensky
e6b876314c Merge branch 'void' into 'master'
MH2: Void Mirror and support

See merge request core-developers/forge!4747
2021-05-26 04:31:48 +00:00
Michael Kamensky
9cb73fe9fc Merge branch 'mh2_24' into 'master'
MH2 - 24 May

See merge request core-developers/forge!4746
2021-05-26 04:30:44 +00:00
Michael Kamensky
f4f0ae990f Merge branch 'Williams-master-patch-29065' into 'master'
MH2/AFR Contributions

See merge request core-developers/forge!4740
2021-05-26 04:30:21 +00:00
Michael Kamensky
50c314af7d Merge branch 'ezellohar-master-patch-05605' into 'master'
Update it-IT.properties

See merge request core-developers/forge!4751
2021-05-26 04:30:04 +00:00
friarsol
bd18a5bb41 Update nassari 2021-05-25 23:38:30 -04:00
tool4EvEr
263faa9895 Clean up 2021-05-25 22:29:49 +02:00
Filippo Vomiero
36cc45b227 Update it-IT.properties 2021-05-25 19:12:34 +00:00
Northmoc
66ce27593b add support for multiple called flips to FlipCoinEffect 2021-05-25 13:57:33 -04:00
Northmoc
9a62690627 yusri_fortunes_flame.txt 2021-05-25 13:55:33 -04:00
Northmoc
26dcee20dc ravenous_squirrel.txt (Suthro) 2021-05-25 09:29:01 -04:00
Northmoc
8c73de6f94 squirrel_sovereign.txt (Suthro) 2021-05-25 09:26:05 -04:00
Northmoc
8a3a3d05c4 squirrel_sanctuary.txt (Suthro) 2021-05-25 09:22:19 -04:00
Northmoc
11d78d0b44 spreading_insurrection.txt (Suthro) 2021-05-25 08:51:25 -04:00
Northmoc
ae3fb2d41e tourachs_canticle.txt (Suthro) 2021-05-25 08:48:54 -04:00
Northmoc
54d66c0119 lucid_dreams.txt (Suthro) 2021-05-25 08:45:18 -04:00
Northmoc
beb37bad9a Support for "NoColoredMana" 2021-05-25 08:41:40 -04:00
Northmoc
cb25353e60 void_mirror.txt 2021-05-25 08:41:39 -04:00
Northmoc
7bd24f52c7 late_to_dinner.txt (Suthro) 2021-05-25 08:40:47 -04:00
Northmoc
bfd646f409 break_ties.txt (Suthro) 2021-05-25 08:40:46 -04:00
Northmoc
ec5ade7b38 chatterfang_squirrel_general.txt 2021-05-25 08:40:45 -04:00
Northmoc
4bb6a64153 fractured_sanity.txt 2021-05-25 08:39:25 -04:00
John
e7a29f6626 Update lolth_spider_queen.txt 2021-05-25 10:50:28 +00:00
John
7d168d7f24 Update timeless_dragon.txt 2021-05-25 10:16:53 +00:00
John
8b0e10442f Update unmarked_grave.txt 2021-05-25 10:16:28 +00:00
John
10ed279650 Update lolth_spider_queen.txt 2021-05-25 10:11:05 +00:00
John
18300fb1a2 Update flametongue_yearling.txt 2021-05-25 10:08:46 +00:00
John
82543ee67e Update dakkon_shadow_slayer.txt 2021-05-25 10:07:58 +00:00
John
58c8a23279 Update dakkon_shadow_slayer.txt 2021-05-25 10:07:23 +00:00
Michael Kamensky
6b7db83a40 Merge branch 'collector-number-in-card-list-and-card-db-refactoring' into 'master'
Cards CollectorNumber plus Miscs

See merge request core-developers/forge!4743
2021-05-25 06:23:30 +00:00
Michael Kamensky
3d5a7e9bc7 Merge branch 'foul' into 'master'
Foul Emissary: Fix NPE

See merge request core-developers/forge!4739
2021-05-25 06:21:36 +00:00
Michael Kamensky
d1e8f9db6b Merge branch 'prevent' into 'master'
Update check for detecting prevented damage

See merge request core-developers/forge!4742
2021-05-25 06:21:23 +00:00
Michael Kamensky
532e6bee5b Merge branch 'kevlahnota-master-patch-82009' into 'master'
fix typo

See merge request core-developers/forge!4744
2021-05-25 06:20:48 +00:00
Michael Kamensky
a7af611dfb Merge branch 'rise' into 'master'
MH2: Rise and Shine and support

See merge request core-developers/forge!4745
2021-05-25 06:20:42 +00:00
Northmoc
de1296f750 add support for "RememberAnimated" to AnimateEffectBase 2021-05-24 22:06:55 -04:00
Northmoc
43e1c20046 rise_and_shine.txt 2021-05-24 22:06:55 -04:00
Anthony Calosa
b285205376 fix typo 2021-05-24 23:39:22 +00:00
leriomaggio
db4ec5eb07 Merge branch 'collector-number-in-card-list-and-card-db-refactoring' of git.cardforge.org:leriomaggio/forge into collector-number-in-card-list-and-card-db-refactoring 2021-05-24 21:10:19 +01:00
leriomaggio
51e6b6c908 Merge remote-tracking branch 'upstream/master' into collector-number-in-card-list-and-card-db-refactoring 2021-05-24 21:09:58 +01:00
leriomaggio
e57ab4cdb0 Updated missing collector numbers from scryfall 2021-05-24 21:04:39 +01:00
leriomaggio
c4d4a7f2c3 Resetting ImageFetcher to use collectorNumber as string 2021-05-24 19:37:25 +01:00
leriomaggio
66045abf16 Resetting ImageFetcher to use collectorNumber as string 2021-05-24 19:36:24 +01:00
leriomaggio
019af5d238 Robust sorting of CollectorNumber (as string) handling properly non-numerical nrs. and N.A.s 2021-05-24 15:50:54 +01:00
leriomaggio
d71d10a97f Improved translations for CollectorNumber in Italian and Spanish, and while at it...
...I got rid of duplication for the lblSet col, while also improving translations.
In particular, the "lblSetEdition" column was translated as the "Mistery column" (for some reason :D), whereas a second lblSet2 was instead defined and used, namely "the label tooltip for the Set Columndef".
On this note, this col was also mistranslated in ES, IT, DE - those are the language I could handle so far.
In fact "Set" was intended (and so translated) as "to set" instead as for "Expansion/Edition".
Corrected accordingly! :)
2021-05-24 15:02:25 +01:00
tool4EvEr
f374466d0a Update check for detecting prevented damage 2021-05-24 14:31:15 +02:00
tool4EvEr
97d9c57951 Update trigger so it doesn't resolve when card moved 2021-05-24 14:04:46 +02:00
leriomaggio
b651c1f9e9 Added CollectorNumberColumn to pileBy menu. 2021-05-24 11:49:05 +01:00
leriomaggio
d448b59dcc Just code formatting 2021-05-24 11:48:22 +01:00
John
06ccc2d9c6 Update TypeLists.txt 2021-05-24 10:46:43 +00:00
John
bf2ee271fd Add new file 2021-05-24 10:41:57 +00:00
John
b9486e8853 Add new file 2021-05-24 10:41:25 +00:00
John
7911f9bd20 Add new file 2021-05-24 10:40:46 +00:00
John
6753039b92 Add new file 2021-05-24 10:40:15 +00:00
John
802eff0796 Add new file 2021-05-24 10:39:48 +00:00
John
ff8141705d Add new file 2021-05-24 10:39:20 +00:00
John
b28f8f50e1 Add new file 2021-05-24 10:38:41 +00:00
John
49a1f9527c Add new file 2021-05-24 10:36:58 +00:00
John
240f5e6419 Add new file 2021-05-24 10:35:47 +00:00
tool4EvEr
2f26cbe676 Fix NPE 2021-05-24 12:24:15 +02:00
leriomaggio
2a304bd413 Made sorting on CN more reliable with zero-padded strings 2021-05-24 10:14:22 +01:00
leriomaggio
189b0ea122 Formatting adjustments 2021-05-24 10:13:40 +01:00
tool4EvEr
70b24f9b86 Fix Sen Triplets interaction 2021-05-24 10:55:51 +02:00
Anthony Calosa
6c11701032 Merge branch 'master' into 'master'
cleanup

See merge request core-developers/forge!4738
2021-05-24 08:24:21 +00:00
Anthony Calosa
d4a28c0e7b uncomment 2021-05-24 16:23:23 +08:00
Anthony Calosa
0e50de91b4 cleanup 2021-05-24 16:22:09 +08:00
Anthony Calosa
ac78b96c2b Merge branch 'master' into 'master'
[Mobile] Refactor preloading ItemPool

See merge request core-developers/forge!4737
2021-05-24 08:13:25 +00:00
Anthony Calosa
aca091e3c3 [Mobile] Refactor preloading ItemPool
Dont preload ItemPool on Android devices with less than 5GB RAM and Desktop/Mobile port
2021-05-24 16:09:40 +08:00
leriomaggio
572e7f5be8 Updated Card Edition Resources with Collector Numbers 2021-05-24 07:17:16 +01:00
tool4EvEr
ef4ab9570a Clean up 2021-05-24 08:13:09 +02:00
Michael Kamensky
6207299219 Merge branch 'cleanup' into 'master'
Another few small non-functional changes to clean up some redundancy

See merge request core-developers/forge!4734
2021-05-24 03:29:07 +00:00
Michael Kamensky
8eb8c666b6 Merge branch 'hellkite' into 'master'
Refactor CardDamageHistory

See merge request core-developers/forge!4711
2021-05-24 03:28:58 +00:00
Bug Hunter
8f40e8ae04 Refactor CardDamageHistory 2021-05-24 03:28:57 +00:00
Michael Kamensky
b93131b990 Merge branch 'fixai' into 'master'
ChangeZoneAi: Fix casting without paying mana cost using X values

See merge request core-developers/forge!4735
2021-05-24 03:28:37 +00:00
tool4EvEr
6bce621da4 Fix Fires of Ivention 2021-05-24 00:42:34 +02:00
tool4EvEr
9e9bc5193f Cleanup 2021-05-23 23:23:42 +02:00
tool4EvEr
178f937727 Fix AI casting without paying mana cost using X values 2021-05-23 22:45:46 +02:00
Michael Kamensky
d94abfaef9 Merge branch 'Williams-master-patch-16712' into 'master'
Update Resale Promos.txt

See merge request core-developers/forge!4733
2021-05-23 17:26:57 +00:00
leriomaggio
c03da7eb74 Revert to CollectorNumber in PaperCard as String to handle cases of mulitple art (e.g. 21a). 2021-05-23 16:52:12 +01:00
tool4EvEr
c39313d973 Clean up 2021-05-23 17:36:07 +02:00
leriomaggio
a0c126f39e Merge remote-tracking branch 'upstream/master' into collector-number-in-card-list-and-card-db-refactoring 2021-05-23 14:50:49 +01:00
leriomaggio
354d184664 Display new collectorNumber info in Column List view for cards
This addresses Issue #1846
Further changes are due to CardEditions missing collector number information
2021-05-23 14:45:29 +01:00
leriomaggio
0e49d103b1 Update to the new getFoiledMethod in PaperCard API 2021-05-23 14:34:38 +01:00
leriomaggio
fbbae218c0 Integration with PaperCard CollectorNumber, and FIX to new Scryfall API specs 2021-05-23 14:33:48 +01:00
leriomaggio
12387f0efb Refactoring of PaperCard API for collectorNumber and GetFoil.
PaperCard instance now includes a new attribute (i.e. `collectorNumber`) that will be used later on to fill in the "CN" Column in List view (see `ColumnDef.java`).
To allow for minor disruption to existing API, the collectorNumber attribute will be handled in a _Property-like_ fashion via the getter method (i.e. `getCollectorNumber`).
In particular, if collectorNumber is marked as `UNSET` (i.e. `PaperCard.UNSET_COLLECTOR_NUMBER`), the corresponding value will be automatically retrieved from the matched `CardEdition`, and updated for later use.
Nevertheless, Constructors including the new parameter have been also set for future API change.
Moreover, the new attribute has been also considered in methods for object comparison, for a more accurate sorting criterion.

The second new addition to PaperCard API regards FOIL cards (i.e. `getFoiled()`).
This method was originally part of `CardDb` API as described in ISSUE #1848.
With this API change, an internal reference to a `PaperCard` (foil) instance will be used to return the foil version of "itself" for each PaperCard Instance. This will avoid proliferation of PaperCard instances in memory, while keeping all information consistent with the original "unfoiled" card.
NOTE: at the moment, every card can be Foiled, with no restriction whatsoever on the _actual_ existence of original (true) PaperCard.
2021-05-23 13:07:39 +01:00
leriomaggio
41be0bb069 PaperToken aligned with the new IPaperCard Interface (collectorNr always to default for tokens) 2021-05-23 12:56:10 +01:00
leriomaggio
6edcb95aab Refactor IPaperCard Interface to include collectorNumber
The IPaperCard interface now includes a method to return the Collector Number of a Card.
Moreover, two new constants have been included for Default Art Index (i.e. 1) and a flag to mark the absence of a Collector Number (i.e. 0).
2021-05-23 12:55:15 +01:00
John
f022419ceb Update Resale Promos.txt 2021-05-23 11:42:11 +00:00
Michael Kamensky
9232042b02 Merge branch 'exception-thrown-when-discard-card-when-more-then-eight' into 'master'
Exception thrown when discard card when more then eight

See merge request core-developers/forge!4732
2021-05-23 04:17:34 +00:00
brngl
1c642a3cb2 corrected a wrong translation 2021-05-23 02:30:09 +02:00
brngl
b5e86473a4 corrected a wrong translation 2021-05-23 01:24:37 +02:00
Michael Kamensky
8417f5cb7a Merge branch 'counterai' into 'master'
CounterAi: Fix AI casting without paying mana cost using X values

See merge request core-developers/forge!4731
2021-05-22 17:41:59 +00:00
tool4EvEr
f434be45b6 Fix AI casting without paying mana cost using X values 2021-05-22 19:38:55 +02:00
Michael Kamensky
6d493aeb9c Merge branch 'paid' into 'master'
Fix Plumb the Forbidden

Closes #1849

See merge request core-developers/forge!4730
2021-05-22 09:32:22 +00:00
Michael Kamensky
b4052039e3 Merge branch 'cards-2021-05-20' into 'master'
Drizzt Do'Urden

See merge request core-developers/forge!4728
2021-05-22 09:32:00 +00:00
tool4EvEr
727689a3bd Fix Plumb the Forbidden 2021-05-22 11:02:57 +02:00
Hythonia
b86f9792f3 Drizzt: DeckHas 2021-05-22 09:48:26 +02:00
Michael Kamensky
83f4a5ea73 Merge branch 'playeffect' into 'master'
PlayEffect: avoid confirmAction

Closes #843

See merge request core-developers/forge!4729
2021-05-22 06:05:05 +00:00
tool4EvEr
80ddea2c8c Clean up 2021-05-21 23:07:42 +02:00
tool4EvEr
6618f71297 PlayEffect: avoid confirmAction 2021-05-21 23:03:33 +02:00
Hythonia
5c1c039188 AFR: Drizzit and the Spider token 2021-05-21 19:51:16 +02:00
Michael Kamensky
af1afb5348 Merge branch 'lki' into 'master'
Fix Luminate Primordial

See merge request core-developers/forge!4727
2021-05-21 17:30:23 +00:00
tool4EvEr
5bbd527837 Fix LKI 2021-05-21 12:16:53 +02:00
Michael Kamensky
2eb71fe1c9 Merge branch 'fixup' into 'master'
CombatUtil: Fix AI workaround

See merge request core-developers/forge!4726
2021-05-21 07:48:27 +00:00
Michael Kamensky
e1157ad01a Merge branch 'Williams-master-patch-78424' into 'master'
Update 2021 Lunar New Year.txt

See merge request core-developers/forge!4724
2021-05-21 07:48:23 +00:00
tool4EvEr
e5df3b9cb3 Fix workaround 2021-05-21 08:43:36 +02:00
Anthony Calosa
a52f7dc540 Merge branch 'master' into 'master'
[Mobile] Disable loading of HD Textures for Devices with RAM < 5GB

See merge request core-developers/forge!4725
2021-05-21 03:03:48 +00:00
Anthony Calosa
eff69eba7d Merge remote-tracking branch 'remotes/core/master' 2021-05-21 10:58:27 +08:00
Anthony Calosa
f8a41b39ec [Mobile] Disable loading of HD Textures for Devices with RAM < 5GB 2021-05-21 10:58:03 +08:00
John
cd31806100 Update 2021 Lunar New Year.txt 2021-05-20 12:48:54 +00:00
Bug Hunter
e5d588c4eb Merge branch 'TRT-master-patch-14456' into 'master'
Update forge-gui/res/cardsfolder/b/battlemages_bracers.txt

See merge request core-developers/forge!4723
2021-05-20 12:44:39 +00:00
Bug Hunter
3b0483752c Update forge-gui/res/cardsfolder/b/battlemages_bracers.txt 2021-05-20 12:43:42 +00:00
Anthony Calosa
5ea5ca0ef7 Merge branch 'master' into 'master'
Net Decks Archive Updates

See merge request core-developers/forge!4722
2021-05-20 12:13:35 +00:00
Churrufli
08bd2c4724 Net Decks Archive Updates 2021-05-20 12:42:24 +02:00
Michael Kamensky
1c1a4d92f4 Merge branch 'bracers' into 'master'
Illusionist's Bracers: Fix triggering for Card that's removed while paying activation cost

See merge request core-developers/forge!4721
2021-05-20 09:21:36 +00:00
Michael Kamensky
37e5dda38f Merge branch 'npefix' into 'master'
chooseSomeType: Fix NPE

See merge request core-developers/forge!4715
2021-05-20 04:53:12 +00:00
Michael Kamensky
be6232ee82 Merge branch 'fixcount' into 'master'
Cleanup xCount

See merge request core-developers/forge!4718
2021-05-20 04:52:29 +00:00
Michael Kamensky
651fa42e4a Merge branch 'guile' into 'master'
Kaya's Guile: Fix moving own cards

See merge request core-developers/forge!4720
2021-05-20 04:52:01 +00:00
Michael Kamensky
c69d261e56 Merge branch 'rescue' into 'master'
Fix Rescue from the Underworld

See merge request core-developers/forge!4719
2021-05-20 04:51:55 +00:00
Michael Kamensky
54446949a5 Merge branch 'combats' into 'master'
Fix deadlock for impossible blocking requirements

See merge request core-developers/forge!4681
2021-05-20 04:51:42 +00:00
Michael Kamensky
161d4ed314 Merge branch 'angel' into 'master'
Fix paying negative life

See merge request core-developers/forge!4716
2021-05-20 04:45:40 +00:00
tool4EvEr
a88c08bf3f Fix triggering for Card that's removed while paying activation cost 2021-05-19 23:07:31 +02:00
tool4EvEr
eae4a0be35 Fix moving own cards 2021-05-19 20:54:58 +02:00
tool4EvEr
0e28b40c25 Fix Rescue from the Underworld 2021-05-19 20:28:42 +02:00
tool4EvEr
f8adbcc252 Cleanup xCount 2021-05-19 20:03:17 +02:00
tool4EvEr
6f9d557a64 Clean up 2021-05-19 16:29:38 +02:00
Michael Kamensky
ac939de42f Merge branch 'italian-translations-fix' into 'master'
Italian translations fix

See merge request core-developers/forge!4717
2021-05-19 03:53:12 +00:00
Michael Kamensky
82756e1073 Merge branch 'cardfixes' into 'master'
Minor card fixes

See merge request core-developers/forge!4714
2021-05-19 03:52:38 +00:00
brngl
1b14148b0f corrected some wrong translations 2021-05-19 00:23:18 +02:00
brngl
0cfb5eff0e corrected some wrong translations 2021-05-19 00:15:38 +02:00
tool4EvEr
ce3bab8a33 Fix for Temporal Extortion 2021-05-19 00:09:55 +02:00
tool4EvEr
e56b8545c7 Fix paying negative life 2021-05-18 23:36:56 +02:00
tool4EvEr
ac7fc21db5 Fix NPE 2021-05-18 22:58:56 +02:00
Michael Kamensky
48bc2174a4 Merge branch 'master' into 'master'
update simplified chinese translation

See merge request core-developers/forge!4709
2021-05-18 19:34:07 +00:00
Michael Kamensky
d11d58889b Merge branch 'kevlahnota-master-patch-22574' into 'master'
NoSuchMethodError forge.game.trigger.TriggerAbilityTriggered in addTriggeringObject

See merge request core-developers/forge!4710
2021-05-18 19:33:58 +00:00
Michael Kamensky
8cf01c318a Merge branch 'cleanup' into 'master'
Fix onCleanupPhase skipping losers

See merge request core-developers/forge!4712
2021-05-18 19:33:46 +00:00
Michael Kamensky
e18007e902 Merge branch 'fixnpe' into 'master'
UntapAi: Fix NPE

See merge request core-developers/forge!4713
2021-05-18 19:33:37 +00:00
Hythonia
1f13b8d43d Minor card fixes 2021-05-18 20:11:24 +02:00
tool4EvEr
c6b82043d0 Fix NPE 2021-05-18 19:29:37 +02:00
tool4EvEr
671937ff7f Fix onCleanupPhase skipping losers 2021-05-18 19:07:54 +02:00
Anthony Calosa
27478966bc NoSuchMethodError forge.game.trigger.TriggerAbilityTriggered in addTriggeringObject 2021-05-18 14:43:43 +00:00
CCTV-1
442ea94cde translate "AltZoneTabs" 2021-05-18 18:47:29 +08:00
Michael Kamensky
13bd24ef83 Merge branch 'changezone' into 'master'
ChangeZoneEffect: Don't modify tapped when replaced

See merge request core-developers/forge!4704
2021-05-18 03:50:25 +00:00
Bug Hunter
32240b6004 ChangeZoneEffect: Don't modify tapped when replaced 2021-05-18 03:50:24 +00:00
tool4EvEr
abd0880c66 Fix deadlock for impossible blocking requirements 2021-05-17 17:51:09 +02:00
Hans Mackowiak
bb6c5f8e44 Merge branch '1840-npe-at-forge-game-ability-abilityutils-xcount-abilityutils-java-1677' into 'master'
Resolve "NPE at forge.game.ability.AbilityUtils.xCount(AbilityUtils.java:1677)"

Closes #1840

See merge request core-developers/forge!4703
2021-05-16 13:56:21 +00:00
Bug Hunter
66d0141fae Merge branch 'fixcard' into 'master'
Fix card

See merge request core-developers/forge!4707
2021-05-16 13:02:04 +00:00
tool4EvEr
57cf7a582b Fix card 2021-05-16 15:02:00 +02:00
Michael Kamensky
706827fc0c Merge branch 'Williams-master-patch-55192' into 'master'
Update Secret Lair Ultimate Edition.txt

See merge request core-developers/forge!4706
2021-05-16 12:51:43 +00:00
Michael Kamensky
54009dcb06 Merge branch 'aicleanup' into 'master'
Minor cleanup

See merge request core-developers/forge!4705
2021-05-16 12:43:17 +00:00
John
1049deb299 Update Secret Lair Ultimate Edition.txt 2021-05-16 12:43:06 +00:00
tool4EvEr
19b928782a Clean import 2021-05-16 12:59:13 +02:00
tool4EvEr
4aafafb5bd Minor cleanup 2021-05-16 12:52:02 +02:00
leriomaggio
65ff5a8c51 Added Spotlight rubbish to ignore list 2021-05-16 11:46:23 +01:00
Hans Mackowiak
d2449c181a AiController: need to set triggered Object for ETB check 2021-05-16 09:18:31 +02:00
Hans Mackowiak
9527a33a58 Merge branch 'refactor_damage_replacement' into 'master'
Deal damage use damage map

See merge request core-developers/forge!4696
2021-05-16 06:51:39 +00:00
Alumi
419a513d8f Deal damage use damage map 2021-05-16 06:51:38 +00:00
Michael Kamensky
a1e7ecbecc Merge branch 'etali' into 'master'
PlayEffect: Fix loop of selecting same card when optional

Closes #1835

See merge request core-developers/forge!4697
2021-05-16 03:47:07 +00:00
Michael Kamensky
f996c90653 Merge branch 'nextturn' into 'master'
Refactor addUntilCommand

See merge request core-developers/forge!4702
2021-05-15 17:33:05 +00:00
Bug Hunter
be2a7f5a51 Refactor addUntilCommand 2021-05-15 17:33:03 +00:00
Michael Kamensky
a6ca193ae0 Merge branch 'cardPropertyTargetedPlayer' into 'master'
Card property targeted player

See merge request core-developers/forge!4606
2021-05-15 15:27:16 +00:00
Hans Mackowiak
ff7f81c307 Card property targeted player 2021-05-15 15:27:15 +00:00
Michael Kamensky
cad42948cd Merge branch '1836-npe-at-forge-game-gameaction-sacrificedestroy-gameaction-java-1709' into 'master'
Resolve "NPE at forge.game.GameAction.sacrificeDestroy(GameAction.java:1709)"

Closes #1836

See merge request core-developers/forge!4699
2021-05-15 12:40:13 +00:00
Hans Mackowiak
5ad88099e1 Resolve "NPE at forge.game.GameAction.sacrificeDestroy(GameAction.java:1709)" 2021-05-15 12:40:12 +00:00
Sol
c849c2b867 Merge branch 'scourge' into 'master'
Fix Boneyard Scourge

See merge request core-developers/forge!4700
2021-05-15 03:23:35 +00:00
Michael Kamensky
1571263575 Merge branch 'addtocombat' into 'master'
Fix addToCombat not checking for changes in defenders

See merge request core-developers/forge!4701
2021-05-14 17:35:06 +00:00
tool4EvEr
a335d60fcb Fix addToCombat not checking for changes in defenders 2021-05-14 16:17:21 +02:00
tool4EvEr
c78c182e32 Fix trigger 2021-05-14 15:15:50 +02:00
Michael Kamensky
8ff5d05078 Merge branch 'wall' into 'master'
Fix trigger running with no zone for Wall of Stolen Identity

See merge request core-developers/forge!4666
2021-05-14 05:39:09 +00:00
Bug Hunter
80a1c13a07 Fix trigger running with no zone for Wall of Stolen Identity 2021-05-14 05:39:08 +00:00
Hans Mackowiak
0b6ed46a0a Merge branch 'basecamp' into 'master'
Fix Base Camp

See merge request core-developers/forge!4698
2021-05-13 19:29:57 +00:00
tool4EvEr
cdaba3483a Fix Base Camp 2021-05-13 14:50:32 +02:00
tool4EvEr
3681dba5fb Fix loop of selecting same card when optional 2021-05-13 13:55:19 +02:00
Hans Mackowiak
ddbe5af21c Merge branch 'TRT-master-patch-01974' into 'master'
Update forge-gui/res/cardsfolder/r/rot_hulk.txt

See merge request core-developers/forge!4695
2021-05-12 14:52:37 +00:00
Hans Mackowiak
da4263131e Merge branch 'elspeth' into 'master'
Fix stateBasedAction_Saga

Closes #1831

See merge request core-developers/forge!4693
2021-05-12 14:47:33 +00:00
Bug Hunter
f3b771cb7d Fix stateBasedAction_Saga 2021-05-12 14:47:32 +00:00
Bug Hunter
39c764e283 Update forge-gui/res/cardsfolder/r/rot_hulk.txt 2021-05-12 07:38:40 +00:00
Michael Kamensky
9d4e02c2d4 Merge branch 'defenderStatic' into 'master'
StaticAbilities: moved Defender into CantAttack

See merge request core-developers/forge!4563
2021-05-12 06:35:16 +00:00
Hans Mackowiak
34ab157046 StaticAbilities: moved Defender into CantAttack 2021-05-12 06:35:15 +00:00
Michael Kamensky
e76403f54f Merge branch 'ruxa' into 'master'
Fix Muraganda Petroglyphs not affecting face-down with morph

See merge request core-developers/forge!4692
2021-05-12 05:23:50 +00:00
Michael Kamensky
3f7b1ce204 Merge branch 'fix_spellskite' into 'master'
Fix BecomesTarget not triggering for Spellskite's ability

See merge request core-developers/forge!4694
2021-05-12 05:23:42 +00:00
Lyu Zong-Hong
0eff1225b8 Fix BecomesTarget not triggering for Spellskite's ability 2021-05-12 09:26:06 +09:00
tool4EvEr
4e2d024b2a Fix Muraganda Petroglyphs not affecting face-down with morph 2021-05-11 20:11:26 +02:00
Michael Kamensky
1db289c35e Merge branch 'soulecho' into 'master'
Fix Soul Echo

See merge request core-developers/forge!4691
2021-05-11 18:10:46 +00:00
tool4EvEr
c8a4a41eaf Fix Soul Echo 2021-05-11 19:51:00 +02:00
Michael Kamensky
6221f87b8c Merge branch 'master' into 'master'
Net Decks Archive Updates

See merge request core-developers/forge!4689
2021-05-11 16:48:10 +00:00
Michael Kamensky
ab3b682c47 Merge branch 'tokencards' into 'master'
Add Time Sidewalk and Bone Rattler

See merge request core-developers/forge!4690
2021-05-11 16:48:08 +00:00
Michael Kamensky
38d6ab9895 Merge branch 'refactor_cant_be_prevented' into 'master'
Refactor damage can't be prevented effect

Closes #1232

See merge request core-developers/forge!4686
2021-05-11 16:47:55 +00:00
Alumi
4d6da7454c Refactor damage can't be prevented effect 2021-05-11 16:47:54 +00:00
Churrufli
19ac30eed0 Net Decks Archive Updates 2021-05-11 09:29:42 +02:00
Hythonia
84a7b79e3c Add Time Sidewalk and Bone Rattler 2021-05-11 09:12:05 +02:00
Michael Kamensky
e2fc8015b8 Merge branch 'twosat-master-patch-29098' into 'master'
Update de-DE.properties

See merge request core-developers/forge!4687
2021-05-11 03:46:02 +00:00
Hans Mackowiak
5aac30f44a Merge branch 'colors' into 'master'
ColorIdentity: Check for CDA text

See merge request core-developers/forge!4672
2021-05-10 19:36:09 +00:00
Bug Hunter
d9340349fa ColorIdentity: Check for CDA text 2021-05-10 19:36:09 +00:00
Hans Mackowiak
82019b5f40 Merge branch 'samename' into 'master'
ChangeZoneAi: Fix crash

Closes #1830

See merge request core-developers/forge!4688
2021-05-10 19:35:22 +00:00
tool4EvEr
06e0a82927 Fix crash 2021-05-10 21:17:17 +02:00
Andreas Bendel
53409a6c70 Update de-DE.properties
translated SwitchStates and AltZoneTabs
2021-05-10 18:25:37 +00:00
Hans Mackowiak
59b68d80ca Merge branch 'servant' into 'master'
Fix Grindstone & Painter's Servant interaction

See merge request core-developers/forge!4679
2021-05-10 16:51:06 +00:00
Bug Hunter
4f38f5a389 Fix Grindstone & Painter's Servant interaction 2021-05-10 16:51:06 +00:00
Anthony Calosa
7a0bb07fc1 Merge branch 'kevlahnota-master-patch-01624' into 'master'
unused import

See merge request core-developers/forge!4685
2021-05-10 07:50:36 +00:00
Anthony Calosa
b1d3e712be unused import 2021-05-10 07:50:23 +00:00
Anthony Calosa
bc0cd4a783 Merge branch 'prevent' into 'master'
prevent damage bug

See merge request core-developers/forge!4682
2021-05-10 07:46:32 +00:00
Michael Kamensky
d4dc65c45f Merge branch 'show_remaining_shield' into 'master'
Show remaining shields amount for damage prevention and redirection effect

See merge request core-developers/forge!4684
2021-05-10 03:27:03 +00:00
Michael Kamensky
dd40a1de6b Merge branch 'update_prevention_predition' into 'master'
Update AI damage prevention prediction code

See merge request core-developers/forge!4683
2021-05-10 03:26:41 +00:00
Lyu Zong-Hong
ea65a3423e Show remaining shields amount for damage prevention and redirection effect 2021-05-10 10:04:45 +09:00
Lyu Zong-Hong
04811da9cc Update AI damage prevention prediction code 2021-05-10 08:09:27 +09:00
tool4EvEr
46a4243093 prevent damage bug 2021-05-09 22:47:31 +02:00
Michael Kamensky
03bee3e11e Merge branch 'refactor_fog_protection' into 'master'
Refactor Damage Prevention - Step 3

See merge request core-developers/forge!4680
2021-05-09 18:15:44 +00:00
Lyu Zong-Hong
20491e1136 Refactor fog and protection to use replacement effect 2021-05-09 20:17:26 +09:00
Michael Kamensky
4ea01ea135 Merge branch 'new_sets' into 'master'
Initial edition files for MH2, AFR, PWP21

See merge request core-developers/forge!4671
2021-05-09 04:01:31 +00:00
Michael Kamensky
8440f72e9e Merge branch 'cascade' into 'master'
PlayAi confirmAction only when card is worth it

See merge request core-developers/forge!4661
2021-05-09 04:01:22 +00:00
Michael Kamensky
65291259ee Merge branch 'fix_get_total_shield_npe' into 'master'
Fix NPE in getTotalPreventionShieldAmount

Closes #1827

See merge request core-developers/forge!4678
2021-05-09 04:00:20 +00:00
Lyu Zong-Hong
d42ee05740 Fix NPE in getTotalPreventionShieldAmount 2021-05-09 07:46:04 +09:00
Michael Kamensky
c4bf644240 Merge branch 'refactor_static_prevention' into 'master'
Refactor Damage Prevention - Step 2

See merge request core-developers/forge!4676
2021-05-08 13:57:04 +00:00
Alumi
fe95ad2e11 Refactor Damage Prevention - Step 2 2021-05-08 13:57:03 +00:00
Michael Kamensky
644f80df57 Merge branch 'Williams-master-patch-45382' into 'master'
Update Resale Promos.txt

See merge request core-developers/forge!4677
2021-05-08 13:56:17 +00:00
Michael Kamensky
46e5c3233a Merge branch 'master' into 'master'
Spoiled cards from MH2 and AFR

See merge request core-developers/forge!4674
2021-05-08 13:56:08 +00:00
John
1da27edde6 Update Resale Promos.txt 2021-05-08 09:37:38 +00:00
Hythonia
75da8833dc Urza's Saga fix 2021-05-08 10:22:33 +02:00
Michael Kamensky
c40a132811 Merge branch 'refactor_prevention' into 'master'
Refactor Damage Prevention - Step 1

See merge request core-developers/forge!4675
2021-05-08 04:34:44 +00:00
Alumi
288267ae11 Refactor Damage Prevention - Step 1 2021-05-08 04:34:42 +00:00
Michael Kamensky
8f1a493327 Merge branch 'digreveal' into 'master'
add support to Dig for Reveal$ False

See merge request core-developers/forge!4667
2021-05-08 04:34:08 +00:00
Northmoc
f97adc325f simplify boolean function 2021-05-07 15:06:40 -04:00
Hythonia
8aa72e9397 MH2 and AFR 2021-05-07 19:45:55 +02:00
tool4EvEr
3ab7886eab Refactor confirmAction 2021-05-07 18:14:20 +02:00
paul_snoops
551968a38c Initial edition files for MH2, AFR, PWP21 2021-05-07 15:37:18 +01:00
paul_snoops
e954102f81 Initial edition files for MH2, AFR, PWP21 2021-05-07 14:23:04 +01:00
Michael Kamensky
5a1d5c0a2d Merge branch 'master' into 'master'
Net Decks Archive Updates

See merge request core-developers/forge!4670
2021-05-07 13:11:36 +00:00
paul_snoops
42b553266a Initial edition files for MH2, AFR, PWP21 2021-05-07 09:26:45 +01:00
Churrufli
d98d2915ca Net Deck Archive Updates 2021-05-07 09:18:14 +02:00
Anthony Calosa
634dced5aa Merge branch 'master' into 'master'
[Mobile] NullPointerException forge.toolbox.FChoiceList in showAlternate

See merge request core-developers/forge!4669
2021-05-07 00:48:38 +00:00
Anthony Calosa
5ee94199bb Merge branch 'fixturns' into 'master'
Fix multiplayer extra turns deadlock

See merge request core-developers/forge!4665
2021-05-07 00:48:21 +00:00
Anthony Calosa
23422220ce [Mobile] update check 2021-05-07 08:43:40 +08:00
Sol
c141462432 Merge branch 'migrate-upcoming' into 'master'
Migrate STX and other scripts to alpha folders

See merge request core-developers/forge!4668
2021-05-07 00:43:28 +00:00
Anthony Calosa
41abf876a2 [Mobile] Fix NPE on FChoiceList.java 2021-05-07 08:42:17 +08:00
friarsol
28793abc7a Migrate STX and other scripts to alpha folders 2021-05-06 20:42:17 -04:00
Northmoc
1ce213004a add support to Dig for Reveal$ False 2021-05-06 17:23:45 -04:00
tool4EvEr
e223fa2002 Fix extra turns deadlock 2021-05-06 21:39:24 +02:00
Michael Kamensky
6fc542ff7d Merge branch 'clash' into 'master'
ClashEffect: Fix Triggers

Closes #1821

See merge request core-developers/forge!4662
2021-05-06 03:46:14 +00:00
Michael Kamensky
a1dd4ab0e9 Merge branch 'escalate' into 'master'
Fix Escalate Cost

Closes #1819

See merge request core-developers/forge!4663
2021-05-06 03:45:50 +00:00
tool4EvEr
1232f1be28 Fix Escalate Cost 2021-05-05 22:20:57 +02:00
tool4EvEr
738c7b968a Fix Triggers 2021-05-05 21:59:01 +02:00
tool4EvEr
d6e7d1ffee Check if card for PlayEffect is worth it 2021-05-05 21:14:15 +02:00
Michael Kamensky
6fcdee14e4 Merge branch 'master' into 'master'
Better AI for SpecificCard Auras

See merge request core-developers/forge!4660
2021-05-05 17:47:31 +00:00
Hythonia
8c139ad044 Better AI for SpecificCard Auras, attempt 2 2021-05-05 15:47:43 +02:00
Michael Kamensky
122baba999 Merge branch 'master' into 'master'
Update hyperlinks to point to the git.cardforge.org wiki pages instead of the old slightlymagic.net

See merge request core-developers/forge!4659
2021-05-05 13:06:29 +00:00
Jimmy
27b12f5263 Update hyperlinks to point to the git.cardforge.org wiki pages instead of the old slightlymagic.net 2021-05-05 13:06:28 +00:00
Hythonia
a801424b35 Better AI for SpecificCard Auras 2021-05-05 14:48:40 +02:00
Hans Mackowiak
717a3eaede Merge branch 'fixstatic' into 'master'
Fix StaticEffect missing changes from RemoveLandTypes

See merge request core-developers/forge!4655
2021-05-05 10:41:02 +00:00
Anthony Calosa
0061f35b46 Merge branch 'master' into 'master'
[Mobile] add Alternate Player Zone Layout (Landscape)

See merge request core-developers/forge!4658
2021-05-05 03:53:49 +00:00
Anthony Calosa
ff20646a63 Merge branch 'escape' into 'master'
Fix Escape Protocol

Closes #1817

See merge request core-developers/forge!4657
2021-05-05 03:53:26 +00:00
Anthony Calosa
a5f4375ff2 Merge branch 'fixpay' into 'master'
Fix playSpellAbilityWithoutPayingManaCost not checking amount modifiers

Closes #1816

See merge request core-developers/forge!4656
2021-05-05 03:53:16 +00:00
Anthony Calosa
99b2cbb606 [Mobile] add Alternate Player Zone Layout (Landscape) 2021-05-05 11:34:36 +08:00
tool4EvEr
15e8eee373 Fix Escape Protocol 2021-05-04 20:38:03 +02:00
tool4EvEr
4cd2bc266a Fix playSpellAbilityWithoutPayingManaCost not checking amount modifiers 2021-05-04 20:30:56 +02:00
Michael Kamensky
92472c815a Merge branch 'partner2' into 'master'
Partner with: better fix

See merge request core-developers/forge!4650
2021-05-04 18:18:02 +00:00
Michael Kamensky
a246257eff Merge branch 'master' into 'master'
Update simplified chinese translation

See merge request core-developers/forge!4652
2021-05-04 18:17:55 +00:00
Michael Kamensky
b6bab552be Merge branch 'fix-auto-updater' into 'master'
Fix AutoUpdater to point to the new snapshot location

See merge request core-developers/forge!4651
2021-05-04 18:17:44 +00:00
Michael Kamensky
1afce00069 Merge branch 'controllers' into 'master'
getAllPossibleAbilities: Restore old controllers

Closes #1806

See merge request core-developers/forge!4646
2021-05-04 18:17:41 +00:00
Michael Kamensky
9ddfdf3638 Merge branch 'master' into 'master'
Better AI logic for Boltlands and more

Closes #1573

See merge request core-developers/forge!4654
2021-05-04 18:17:03 +00:00
tool4EvEr
06230ffa40 Fix StaticEffect missing changes from RemoveLandTypes 2021-05-04 19:21:47 +02:00
Hythonia
cac7ac2a8a Remove UnlessAI from shocklands and boltlands 2021-05-04 12:12:26 +02:00
Hythonia
fbbe4f5579 Magus of the Scroll AI 2021-05-04 10:34:38 +02:00
Hythonia
7d8373643f Remove leftover code 2021-05-04 09:25:35 +02:00
Hythonia
97bf88421e Better AI logic for Boltlands 2021-05-04 09:24:41 +02:00
CCTV-1
c067ab7cc9 translate new setting 2021-05-04 09:38:03 +08:00
friarsol
b21e4f5296 Fix AutoUpdater to point to the new snapshot location 2021-05-03 21:32:55 -04:00
tool4EvEr
ea0add13ad Partner with: cleaner fix 2021-05-03 20:14:56 +02:00
Michael Kamensky
bfc3157186 Merge branch 'partner_with' into 'master'
Partner with support

Closes #1760

See merge request core-developers/forge!4649
2021-05-03 17:22:13 +00:00
tool4EvEr
a5aeb43305 Partner with support 2021-05-03 18:57:42 +02:00
Michael Kamensky
2b77a15845 Merge branch 'wouldPhaseBeSkipped' into 'master'
Update wouldPhaseBeSkipped logic

See merge request core-developers/forge!4648
2021-05-03 15:57:16 +00:00
tool4EvEr
0d1b751600 Update wouldPhaseBeSkipped logic 2021-05-03 17:06:39 +02:00
Bug Hunter
754e2a7aa7 Merge branch 'TRT-master-patch-29542' into 'master'
Update forge-gui/res/cardsfolder/upcoming/flamescroll_celebrant_revel_in_silence.txt

See merge request core-developers/forge!4647
2021-05-03 14:27:36 +00:00
Bug Hunter
731f375332 Update forge-gui/res/cardsfolder/upcoming/flamescroll_celebrant_revel_in_silence.txt 2021-05-03 14:27:12 +00:00
tool4EvEr
faa8013208 Restore old controllers for AI checks 2021-05-02 17:03:44 +02:00
Michael Kamensky
90afe81856 Merge branch 'modaloption' into 'master'
Add preference how to display alternate card states in deck views

See merge request core-developers/forge!4625
2021-05-02 14:25:36 +00:00
Bug Hunter
c13e379908 Add preference how to display alternate card states in deck views 2021-05-02 14:25:35 +00:00
Michael Kamensky
689d3bb9b5 Merge branch 'fixloop' into 'master'
Fix multiplayer loop from SA controlled by loser

See merge request core-developers/forge!4636
2021-05-02 14:25:27 +00:00
Michael Kamensky
9ae8fcf3bc Merge branch 'killing_wave' into 'master'
payCostToPreventEffect ignores X amount

See merge request core-developers/forge!4645
2021-05-02 14:25:16 +00:00
tool4EvEr
74f3a0a5ed Fix payCostToPreventEffect for X amount 2021-05-02 14:38:19 +02:00
Anthony Calosa
d51e20bffe Merge branch 'master' into 'master'
fix localizer

See merge request core-developers/forge!4644
2021-05-02 11:47:15 +00:00
Anthony Calosa
c5d6214ab2 fix localizer 2021-05-02 19:44:19 +08:00
Michael Kamensky
898921ea4f Merge branch 'untapall' into 'master'
UntapAllAi: improve logic (Najeela)

See merge request core-developers/forge!4643
2021-05-02 10:42:12 +00:00
tool4EvEr
8c6194da35 improve logic (Najeela) 2021-05-02 11:55:04 +02:00
Michael Kamensky
8b2a3ccb42 Merge branch 'costfix' into 'master'
Restore Balance: Fix Cost

Closes #1814

See merge request core-developers/forge!4642
2021-05-02 09:47:43 +00:00
tool4EvEr
820c869e2c Avoid clearTriggers 2021-05-02 11:44:41 +02:00
tool4EvEr
9a21a3e2a4 Fix Cost 2021-05-02 11:04:59 +02:00
Hans Mackowiak
03e47cee56 Merge branch 'suspend' into 'master'
AI: Fix backing out of Suspend

See merge request core-developers/forge!4640
2021-05-02 08:55:10 +00:00
Michael Kamensky
1a28f15f75 Merge branch 'NPE' into 'master'
viewDeckList: Fix NPE

See merge request core-developers/forge!4639
2021-05-02 03:55:14 +00:00
Anthony Calosa
72d5c74631 Merge branch 'master' into 'master'
[Mobile] Fix ExceptionInInitializerError forge.screens.home.NewGameMenu$NewGameScreen

See merge request core-developers/forge!4641
2021-05-02 01:20:52 +00:00
Anthony Calosa
322ebdf8a6 Update spacing 2021-05-02 09:16:55 +08:00
Anthony Calosa
3d3296d1b7 [Mobile] Fix ExceptionInInitializerError forge.screens.home.NewGameMenu$NewGameScreen
- some mobile issues reported via Sentry is related to Translation errors and it should return the default original value from English locale.
2021-05-02 09:13:49 +08:00
tool4EvEr
e3715c3f81 Update ceasetoExist check 2021-05-02 00:02:49 +02:00
tool4EvEr
db9e019519 Fix backing out of Suspend 2021-05-01 23:05:52 +02:00
tool4EvEr
3a2ea56c92 Fix NPE 2021-05-01 21:26:49 +02:00
tool4EvEr
df6b552ac1 Fully clear unwanted triggers 2021-05-01 20:36:42 +02:00
Michael Kamensky
9bb4d7ed93 Merge branch 'turns' into 'master'
Update advanceToNextPhase logic

See merge request core-developers/forge!4633
2021-05-01 17:36:38 +00:00
Anthony Calosa
562ef1b147 Merge branch 'kevlahnota-master-patch-03506' into 'master'
Update GuiDownloadZipService.java

See merge request core-developers/forge!4638
2021-05-01 14:27:56 +00:00
Anthony Calosa
6d31a2f656 Update GuiDownloadZipService.java 2021-05-01 14:27:02 +00:00
Bug Hunter
62c16d9f08 Merge branch 'fixcost' into 'master'
Fix typo

Closes #1813

See merge request core-developers/forge!4637
2021-05-01 10:44:55 +00:00
tool4EvEr
db17973ea6 Fix cost 2021-05-01 12:43:26 +02:00
tool4EvEr
8155376c45 Fix multiplayer loop from SA controlled by loser 2021-05-01 12:33:25 +02:00
Anthony Calosa
ae26c7bc7a Merge branch 'master' into 'master'
[Mobile] ExceptionInInitializerError (forge.screens.FScreen in <init>)

See merge request core-developers/forge!4635
2021-05-01 05:17:33 +00:00
Anthony Calosa
fa4e4cb2bb [Mobile] ExceptionInInitializerError (forge.screens.FScreen in <init>)
- it seems it was trying to draw the bug reporter interface while ending the graphics if sentry is not enabled
2021-05-01 13:15:10 +08:00
Hans Mackowiak
7842b7e0fd Merge branch 'ResolvedThisTurn' into 'master'
Fix ResolvedThisTurn

See merge request core-developers/forge!4631
2021-04-30 23:33:44 +00:00
Anthony Calosa
52d6245b4a Merge branch 'master' into 'master'
[Mobile] fix cache capacity initialization

See merge request core-developers/forge!4634
2021-04-30 22:42:19 +00:00
Anthony Calosa
4c03d4f0b4 [Mobile] fix cache capacity initialization 2021-05-01 06:41:04 +08:00
tool4EvEr
de8aaf35a3 Update advanceToNextPhase logic 2021-04-30 23:34:16 +02:00
Anthony Calosa
dda5d8206f Merge branch 'master' into 'master'
[Mobile] fix black image

See merge request core-developers/forge!4632
2021-04-30 18:53:22 +00:00
Anthony Calosa
f8ca2a50b7 [Mobile] fix black image 2021-05-01 02:34:04 +08:00
tool4EvEr
329f4072be Fix ResolvedThisTurn 2021-04-30 19:36:29 +02:00
Anthony Calosa
a88b392a43 Merge branch 'kevlahnota-master-patch-90708' into 'master'
unused Import

See merge request core-developers/forge!4630
2021-04-30 14:47:01 +00:00
Anthony Calosa
28ec418c76 unused Import 2021-04-30 14:46:49 +00:00
Anthony Calosa
ce56b87b16 Merge branch 'master' into 'master'
minor fixes

See merge request core-developers/forge!4629
2021-04-30 14:37:18 +00:00
Anthony Calosa
6d3155dff9 prevent NPE 2021-04-30 22:27:07 +08:00
Anthony Calosa
9fee1fda1e update order Message 2021-04-30 22:21:56 +08:00
Anthony Calosa
1e1c5fa139 update Filter 2021-04-30 22:20:26 +08:00
Anthony Calosa
70fb1090ca Merge branch 'master' into 'master'
[Android] Android 11+ mandatory use of app-specific directory (obb)

See merge request core-developers/forge!4628
2021-04-30 13:32:57 +00:00
Anthony Calosa
6f13aa4724 [Android] Android 11+ mandatory use of app-specific directory (obb)
- before migration to obb folder, run Forge first and let it download and install necessary files. If Forge is not running (after Android 11 update, white screen bug), disable storage permission and enable storage permission again for Forge. After succesful run, exit Forge then copy your existing Forge folder to Android/obb/forge.app (as suggested by CptKird).
2021-04-30 21:27:46 +08:00
Anthony Calosa
584f923c4c Merge branch 'master' into 'master'
[Android] Enable Sentry

See merge request core-developers/forge!4627
2021-04-30 05:47:31 +00:00
Anthony Calosa
3d1c39da30 [Android] Enable Sentry 2021-04-30 13:45:13 +08:00
Anthony Calosa
a3148c9825 Merge branch 'master' into 'master'
[Mobile] Swap Main Tab and Commander Tab on portrait mode

See merge request core-developers/forge!4626
2021-04-30 05:36:27 +00:00
Anthony Calosa
40e33a43a9 [Mobile] Swap Main Tab and Commander Tab on portrait mode 2021-04-30 13:34:44 +08:00
Michael Kamensky
a3fc74c44e Merge branch 'plague_of_vermin' into 'master'
Add Plague of Vermin

See merge request core-developers/forge!4623
2021-04-29 17:48:39 +00:00
Michael Kamensky
3b28b5fa1c Merge branch 'fixseal' into 'master'
Fix Seal of the Guildpact

See merge request core-developers/forge!4624
2021-04-29 17:47:54 +00:00
tool4EvEr
6070dc9db9 Fix Seal of the Guildpact 2021-04-29 19:41:07 +02:00
Lyu Zong-Hong
7608f02c21 Add Plague of Vermin 2021-04-29 16:10:58 +09:00
Hans Mackowiak
cec5ec0a78 Merge branch 'cards' into 'master'
Fix some Counterspells not exiling when target can't be countered

See merge request core-developers/forge!4616
2021-04-29 06:45:29 +00:00
Michael Kamensky
c72c683915 Merge branch 'update_eureka' into 'master'
Update Eureka and Hypergenesis to put chosen permanent on battlefield sequentially

See merge request core-developers/forge!4622
2021-04-29 05:11:52 +00:00
Lyu Zong-Hong
3384c773da Update Eureka and Hypergenesis to put chosen permanent on battlefield sequentially 2021-04-29 10:51:03 +09:00
Anthony Calosa
8dfc16880e Merge branch 'kevlahnota-master-patch-06695' into 'master'
unused import

See merge request core-developers/forge!4621
2021-04-28 22:01:07 +00:00
Anthony Calosa
5d65861c49 unused import 2021-04-28 22:00:46 +00:00
Hans Mackowiak
179b1419ab Merge branch 'bracers' into 'master'
Battlemage's Bracers: Fix Cost

See merge request core-developers/forge!4615
2021-04-28 21:15:13 +00:00
Hans Mackowiak
4bebc8486c Merge branch 'fixbandingnpe' into 'master'
Fix Banding NPE

See merge request core-developers/forge!4617
2021-04-28 21:14:51 +00:00
Hans Mackowiak
fd0f6e4113 Merge branch 'fixNPE' into 'master'
getTargetableCards: Fix AI NPE

See merge request core-developers/forge!4611
2021-04-28 21:13:19 +00:00
Bug Hunter
8e4c40ba2a getTargetableCards: Fix AI NPE 2021-04-28 21:13:19 +00:00
Hans Mackowiak
abf1a92977 Merge branch 'fixcrash' into 'master'
GameLossAi: Fix crash with Slaughter Pact

See merge request core-developers/forge!4620
2021-04-28 21:12:52 +00:00
Hans Mackowiak
e9ff6ece35 Merge branch 'fixtrig' into 'master'
Fix Karmic Justice

See merge request core-developers/forge!4613
2021-04-28 21:12:08 +00:00
Bug Hunter
ba02ce9261 Fix Karmic Justice 2021-04-28 21:12:07 +00:00
Hans Mackowiak
0ece4427b6 Merge branch 'mana' into 'master'
ManaEffect: Skip producing mana from nothing

See merge request core-developers/forge!4593
2021-04-28 21:00:57 +00:00
Bug Hunter
9afc192154 Revert "Check all RE order quietly"
This reverts commit 0ad6552f8ac900f24f6756bb42973e4b4cbf7a2a.
2021-04-28 21:00:56 +00:00
tool4EvEr
27691d8563 GameLossAi: Fix crash with Slaughter Pact 2021-04-28 21:48:11 +02:00
Anthony Calosa
0ebc6dd2d8 Merge branch 'master' into 'master'
[Desktop] remove unnecessary codes

See merge request core-developers/forge!4619
2021-04-28 18:24:58 +00:00
Anthony Calosa
5a2f57172f [Desktop] remove unnecessary codes 2021-04-29 02:22:26 +08:00
Anthony Calosa
47d30c81b6 Merge branch 'master' into 'master'
[Desktop] update image reader

See merge request core-developers/forge!4618
2021-04-28 17:55:31 +00:00
Anthony Calosa
b80f78d12e [Desktop] update reader 2021-04-29 01:50:37 +08:00
tool4EvEr
835ffaafd4 Fix Banding NPE 2021-04-28 19:28:22 +02:00
tool4EvEr
66f0d580b9 Fix uncounterable 2021-04-28 19:17:04 +02:00
tool4EvEr
524998811c Fix Cost 2021-04-28 18:39:03 +02:00
Anthony Calosa
1b3a8d0cf5 Merge branch 'master' into 'master'
[Desktop] IIOException: Unsupported Image Type

See merge request core-developers/forge!4614
2021-04-28 14:14:16 +00:00
Anthony Calosa
0134d343b2 [Desktop] IIOException: Unsupported Image Type 2021-04-28 22:12:09 +08:00
Michael Kamensky
202ddd8157 Merge branch 'clone' into 'master'
Fix temporary clones keeping Remembered on Self

Closes #1810

See merge request core-developers/forge!4612
2021-04-28 04:09:01 +00:00
Michael Kamensky
76159b2cc4 Merge branch 'undefined' into 'master'
Revised King Goldemar 2 version 2

See merge request core-developers/forge!4594
2021-04-28 04:08:38 +00:00
tool4EvEr
620e95cebf Fix temporary clones keeping Remembered on Self 2021-04-27 21:52:12 +02:00
Michael Kamensky
5f5357d28a Merge branch 'cosmos' into 'master'
Glimpse the Cosmos: Fix AltCost

See merge request core-developers/forge!4604
2021-04-27 19:04:26 +00:00
Michael Kamensky
faee08bb90 Merge branch 'sharesColorWithOther' into 'master'
CardProperty: add SharesColorWithOther to stop comparing cards with themselves

See merge request core-developers/forge!4610
2021-04-27 19:04:20 +00:00
Hans Mackowiak
178f98c82f CardProperty: add SharesColorWithOther to stop comparing cards with themselves 2021-04-27 15:15:08 +02:00
Anthony Calosa
d0dc16aa6f Merge branch 'master' into 'master'
[Android] don't load forge.profile.properties when the user is using the obb directory

See merge request core-developers/forge!4609
2021-04-27 13:07:13 +00:00
Anthony Calosa
77e56e8da9 [Android] don't load forge.profile.properties when the user is using the obb directory
- until google permits unrestricted access outside the app-specific directory on certain apps via flag, the option to use obb as entrypoint is required since accessing outside the scoped storage is really not recommended (https://issuetracker.google.com/issues?q=scoped%20storage%20slow).
2021-04-27 21:04:51 +08:00
Michael Kamensky
045d7bb5ae Merge branch 'fix_grindstone' into 'master'
Fix Grindstone and Sphinx's Tutelage

See merge request core-developers/forge!4608
2021-04-27 12:12:43 +00:00
Lyu Zong-Hong
135f043b60 Fix Grindstone and Sphinx's Tutelage 2021-04-27 18:38:59 +09:00
Hans Mackowiak
682884355f TargetRestrictions: hotfix isDividedAsYouChoose to fix TestCase 2021-04-27 09:17:16 +02:00
Michael Kamensky
702b473453 Merge branch 'fixghired' into 'master'
Fix some cards failing to target for X=0

See merge request core-developers/forge!4601
2021-04-27 04:50:01 +00:00
Anthony Calosa
3f803a3f48 Merge branch 'master' into 'master'
fix divided allocation

See merge request core-developers/forge!4605
2021-04-26 20:01:36 +00:00
Anthony Calosa
742be996fe fix divided allocation 2021-04-27 03:54:45 +08:00
tool4EvEr
228d929a5c Add AffectedZone 2021-04-26 21:05:17 +02:00
tool4EvEr
703fddeb65 Check isDividedAsYouChoose 2021-04-26 18:20:59 +02:00
tool4EvEr
be89397e48 Fix targeting for spells with DividedValue and X=0 2021-04-26 17:44:46 +02:00
Anthony Calosa
2a71716ba4 Merge branch 'master' into 'master'
Net Deck Archive Updates

See merge request core-developers/forge!4603
2021-04-26 09:32:51 +00:00
Churrufli
b8b5561850 Update net-decks-archive-standard.txt 2021-04-26 09:11:54 +00:00
Churrufli
88694f4256 Net Deck Archive Updates 2021-04-26 10:33:45 +02:00
Michael Kamensky
4d7126d3c3 Merge branch 'growth' into 'master'
AI & Exponential Growth: Fix illegal target

See merge request core-developers/forge!4599
2021-04-26 07:20:13 +00:00
Anthony Calosa
87ab6c1de2 Merge branch 'master' into 'master'
[Mobile-Dev] Fix alt-tab when fullscreen

See merge request core-developers/forge!4602
2021-04-26 02:35:58 +00:00
Anthony Calosa
e649c9128e [Mobile-Dev] Fix alt-tab when fullscreen 2021-04-26 10:34:44 +08:00
tool4EvEr
9b774734c3 Fix some cards failing to target for X=0 2021-04-25 22:47:54 +02:00
tool4EvEr
841164bafb Fix illegal target 2021-04-25 21:28:13 +02:00
Michael Kamensky
bc61ec0495 Merge branch 'kozilek' into 'master'
Fix Kozilek, the Great Distortion

See merge request core-developers/forge!4598
2021-04-25 10:36:06 +00:00
tool4EvEr
c37bc213e5 Fix Kozilek, the Great Distortion 2021-04-25 11:49:09 +02:00
Michael Kamensky
080639d2d9 Merge branch 'shusher' into 'master'
Fix Vexing Shusher

See merge request core-developers/forge!4597
2021-04-25 09:10:33 +00:00
Michael Kamensky
ab2d0771ab Merge branch 'punch' into 'master'
Gravitic Punch: Add DamageSource

See merge request core-developers/forge!4596
2021-04-25 09:10:21 +00:00
tool4EvEr
204d502c24 Fix Vexing Shusher 2021-04-25 10:59:36 +02:00
tool4EvEr
985b60a565 Add DamageSource 2021-04-25 10:44:19 +02:00
Michael Kamensky
8202c4abe3 Merge branch 'fixstack' into 'master'
removeInstancesControlledBy - also filter simultaneousStackEntryList

See merge request core-developers/forge!4587
2021-04-25 05:45:44 +00:00
Michael Kamensky
4de73cebe0 Merge branch 'AImdfc' into 'master'
AI: basic support playing MDFC lands

See merge request core-developers/forge!4589
2021-04-25 05:45:34 +00:00
Anthony Calosa
9bb6e833fb Merge branch 'master' into 'master'
refactor getStartingPlaneswalkerOptions

See merge request core-developers/forge!4595
2021-04-25 03:51:39 +00:00
Anthony Calosa
d227da0c10 refactor getStartingPlaneswalkerOptions 2021-04-25 11:49:05 +08:00
tool4EvEr
962330d491 Fix ETB 2021-04-25 00:00:09 +02:00
Tim McFadden
c5d9a98549 Upload New File 2021-04-24 11:09:04 +00:00
Michael Kamensky
9b83c78346 Merge branch 'conquest' into 'master'
Allow cards from precon products in Planar Conquest

See merge request core-developers/forge!4573
2021-04-24 07:36:12 +00:00
Michael Kamensky
8a7d0d4242 Merge branch 'master' of git.cardforge.org:core-developers/forge into agetian-master 2021-04-24 10:23:45 +03:00
Michael Kamensky
45440adc01 Merge branch 'master' into 'master'
Prepare Forge for Android publish 1.6.40.001 [incremental].

See merge request core-developers/forge!4592
2021-04-24 07:22:29 +00:00
Michael Kamensky
8feaf9ed3e [maven-release-plugin] prepare for next development iteration 2021-04-24 08:26:10 +03:00
Michael Kamensky
636020d870 [maven-release-plugin] prepare release forge-1.6.40 2021-04-24 08:25:57 +03:00
Michael Kamensky
3150aa2e7c - Prepare Forge for Android publish 1.6.40.001 [incremental]. 2021-04-24 08:13:13 +03:00
swordshine
ce22449f41 Merge branch 'shadrix' into 'master'
Shadrix Silverquill: placer is target

See merge request core-developers/forge!4588
2021-04-24 03:04:02 +00:00
swordshine
1c938b5d29 Merge branch 'arix' into 'master'
Arixmethes: always remove counter

See merge request core-developers/forge!4590
2021-04-24 03:03:39 +00:00
swordshine
0493104beb Merge branch 'master' into 'master'
update translations

See merge request core-developers/forge!4591
2021-04-24 03:02:56 +00:00
CCTV-1
9786d9f927 update cmc translation,add missing translation. 2021-04-24 10:39:37 +08:00
CCTV-1
73e5254e2b add missing string 2021-04-24 10:38:51 +08:00
tool4EvEr
7c934d29ac always remove counter 2021-04-23 23:41:24 +02:00
tool4EvEr
bccbf3e55e Add more logic 2021-04-23 23:04:05 +02:00
tool4EvEr
8196ee531a AI: basic support playing MDFC lands 2021-04-23 22:43:14 +02:00
tool4EvEr
39729cb3da placer is target 2021-04-23 22:00:40 +02:00
tool4EvEr
cdc1f6014e also filter simultaneousStackEntryList 2021-04-23 21:25:58 +02:00
Michael Kamensky
aadfdac4c5 Merge branch 'Williams-master-patch-36117' into 'master'
SLD Dr Lair Secretorium Super Drop

See merge request core-developers/forge!4583
2021-04-23 19:17:23 +00:00
John
2e358116c2 Update Secret Lair Drop Series.txt 2021-04-23 14:50:59 +00:00
swordshine
71d88523b2 Merge branch 'update_japanese_localization_mana_value' into 'master'
Update Mana Value in Japanese Localization

See merge request core-developers/forge!4582
2021-04-23 10:12:14 +00:00
swordshine
65e702aed7 Merge branch 'patch' into 'master'
More cleanup

See merge request core-developers/forge!4585
2021-04-23 10:07:58 +00:00
sowrdshine
99bb45d96a More cleanup 2021-04-23 18:07:04 +08:00
swordshine
4f41be641a Merge branch 'patch' into 'master'
Fixed Leyline Invocation

See merge request core-developers/forge!4584
2021-04-23 09:18:45 +00:00
sowrdshine
5ab030242b Fixed Leyline Invocation 2021-04-23 17:17:34 +08:00
John
736d9bbd98 Update Secret Lair Drop Series.txt 2021-04-23 08:23:48 +00:00
Lyu Zong-Hong
6295ebdb09 Update Mana Value in Japanese Localization 2021-04-23 13:35:44 +09:00
Michael Kamensky
380a27b83d Merge branch 'update_in_game_text' into 'master'
Update in-game text and also English and Japanese localization (CMC -> mana value and etc.)

See merge request core-developers/forge!4581
2021-04-23 04:30:06 +00:00
Michael Kamensky
691af037a4 Merge branch 'phoenix' into 'master'
Retriever Phoenix: Fix check

See merge request core-developers/forge!4577
2021-04-23 04:29:03 +00:00
Michael Kamensky
70cb78a67d Merge branch 'scrollmenu' into 'master'
Support scrolling when abilities Popup has too many entries

See merge request core-developers/forge!4567
2021-04-23 04:28:50 +00:00
Michael Kamensky
4bd5fe0178 Merge branch 'changetargetsai' into 'master'
ChangeTargetsAI: Improve decision making

See merge request core-developers/forge!4569
2021-04-23 04:27:57 +00:00
Michael Kamensky
babd80edd9 Merge branch 'update_oracle_stx' into 'master'
Update Oracle after STX release

See merge request core-developers/forge!4564
2021-04-23 04:27:30 +00:00
Alumi
74e4efba52 Update Oracle after STX release 2021-04-23 04:27:27 +00:00
Lyu Zong-Hong
2d7e88a8c2 Update in-game text and also English and Japanese localization 2021-04-23 11:01:34 +09:00
Anthony Calosa
d82c8d466a Merge branch 'master' into 'master'
update check

See merge request core-developers/forge!4580
2021-04-23 01:15:12 +00:00
Sol
9edbbf399f Merge branch 'esix' into 'master'
Esix: Script typo

See merge request core-developers/forge!4575
2021-04-23 01:13:11 +00:00
Anthony Calosa
345316b9b0 update check 2021-04-23 09:12:02 +08:00
Anthony Calosa
0bb6636613 Merge branch 'master' into 'master'
[Android] add option to use obb directory as assets directory for Forge

See merge request core-developers/forge!4579
2021-04-22 23:14:25 +00:00
Anthony Calosa
1929b75e34 [Android] add option to use obb directory as assets directory for Forge 2021-04-23 06:45:35 +08:00
tool4EvEr
245827825e Fix check 2021-04-22 20:41:36 +02:00
Bug Hunter
fc9f81c5c9 Merge branch 'fixplaymain1' into 'master'
Fix PlayMain1 scripts

See merge request core-developers/forge!4576
2021-04-22 18:15:13 +00:00
tool4EvEr
987dce2643 Fix PlayMain1 scripts 2021-04-22 20:14:31 +02:00
tool4EvEr
4f2abad39d Script typo 2021-04-22 20:03:19 +02:00
tool4EvEr
354000082b Clean up 2021-04-22 18:50:06 +02:00
Michael Kamensky
b8e87604c2 Merge branch 'master' into 'master'
Achievements for STX and C21 by Marek.

See merge request core-developers/forge!4574
2021-04-22 16:36:16 +00:00
Michael Kamensky
49f7ad1281 - Achievements for STX and C21 by Marek. 2021-04-22 19:35:37 +03:00
Michael Kamensky
6731cdcc9d Merge branch 'twosat-master-patch-53531' into 'master'
Update de-DE.properties

See merge request core-developers/forge!4571
2021-04-22 16:25:21 +00:00
Michael Kamensky
5306510ec1 Merge branch 'nacatl' into 'master'
Nacatl War-Pride and support

See merge request core-developers/forge!4572
2021-04-22 16:25:15 +00:00
Northmoc
e8c0655186 allow precon cards in Planar Conquest 2021-04-22 12:18:42 -04:00
Northmoc
18d0bf1c7b Add support for keyword: CARDNAME must be blocked by exactly one creature if able. 2021-04-22 12:00:18 -04:00
Northmoc
0b9755405e nacatl_war_pride.txt 2021-04-22 11:58:36 -04:00
Andreas Bendel
8b7a183e63 Update de-DE.properties
CMC is now Mana Value
2021-04-22 15:20:52 +00:00
Tim Mocny
7a094478f3 Merge branch 'typo' into 'master'
teachings_of_the_archaics.txt only 3 cards

See merge request core-developers/forge!4570
2021-04-22 14:07:47 +00:00
Michael Kamensky
bd063c71f8 Merge branch 'fixNPE' into 'master'
Rework ceaseToExist

See merge request core-developers/forge!4559
2021-04-22 11:25:36 +00:00
Michael Kamensky
693ef6c4d3 Merge branch 'countersMove' into 'master'
CountersMoveAI: Improve logic for Ozolith

See merge request core-developers/forge!4568
2021-04-22 05:03:54 +00:00
tool4EvEr
60a02cd2a5 Improve decision making for multiplayer 2021-04-22 00:08:19 +02:00
tool4EvEr
e25c7dac4a Improve logic for Ozolith 2021-04-21 22:32:37 +02:00
tool4EvEr
d918cade67 Hold triggers until all cards are processed 2021-04-21 21:07:47 +02:00
tool4EvEr
982400b74d Clean up 2021-04-21 20:50:13 +02:00
tool4EvEr
c00a384591 Add MouseWheel support 2021-04-21 19:55:14 +02:00
Michael Kamensky
b0e1630fc5 Merge branch 'tokens' into 'master'
update some token lists

See merge request core-developers/forge!4566
2021-04-21 17:52:24 +00:00
tool4EvEr
50ffd7b4a9 Support scrolling when Popup has too many entries 2021-04-21 19:37:05 +02:00
Northmoc
e4e75a5f73 update some token lists 2021-04-21 13:18:27 -04:00
Northmoc
7da4381d38 update some token lists 2021-04-21 13:18:27 -04:00
Hans Mackowiak
ef8076b626 GameEntityCounterTable: fix NPE and filterToRemove 2021-04-21 17:35:41 +02:00
Bug Hunter
ab5f5c1ce9 Merge branch 'yarok' into 'master'
Yarok: Fix staticability check

See merge request core-developers/forge!4565
2021-04-21 15:26:05 +00:00
tool4EvEr
6d442b12a0 Yarok: Fix staticability check 2021-04-21 17:25:42 +02:00
Sol
0019f83e33 Merge branch 'breena' into 'master'
Fix Breena Trigger

See merge request core-developers/forge!4560
2021-04-21 14:13:20 +00:00
Hans Mackowiak
f3011b1790 Merge branch 'fixMana' into 'master'
Fix mana conversion checking wrong card from hand

See merge request core-developers/forge!4561
2021-04-21 04:36:17 +00:00
Hans Mackowiak
ea79f84b71 Merge branch 'fixyarok' into 'master'
Fix Yarok

See merge request core-developers/forge!4562
2021-04-21 04:35:53 +00:00
tool4EvEr
040d6929dc Fix Yarok 2021-04-20 23:15:32 +02:00
tool4EvEr
2fe9c38fff Fix mana conversion checking wrong card from hand 2021-04-20 20:21:39 +02:00
tool4EvEr
256b54b6b5 Fix Breena Trigger 2021-04-20 19:01:57 +02:00
Michael Kamensky
062ad5d92f Merge branch 'esix_fractal_bloom' into 'master'
Add Esix, Fractal Bloom and necessary supports

See merge request core-developers/forge!4557
2021-04-20 16:44:22 +00:00
Michael Kamensky
5806ef248f Merge branch 'ee' into 'master'
Last STX card! elemental_expressionist.txt

See merge request core-developers/forge!4554
2021-04-20 16:44:04 +00:00
Michael Kamensky
257fcf1b3d Merge branch 'elspeth' into 'master'
AI: Fix Elspeth Conquers Death

See merge request core-developers/forge!4552
2021-04-20 16:43:47 +00:00
tool4EvEr
284db016a0 Rework ceaseToExist 2021-04-20 18:28:01 +02:00
tool4EvEr
5e2898f0ae Improve Counter check 2021-04-20 17:29:20 +02:00
Northmoc
207408ae20 elemental_expressionist.txt 2021-04-20 08:59:54 -04:00
Bug Hunter
2843ca9b49 Merge branch 'TRT-master-patch-20042' into 'master'
Update forge-gui/res/cardsfolder/upcoming/detention_vortex.txt

See merge request core-developers/forge!4558
2021-04-20 12:05:08 +00:00
Bug Hunter
efc09d8a3f Update forge-gui/res/cardsfolder/upcoming/detention_vortex.txt 2021-04-20 12:03:43 +00:00
Lyu Zong-Hong
e6714bf45e Add Esix, Fractal Bloom and necessary supports 2021-04-20 20:47:22 +09:00
Michael Kamensky
8049e2c322 Merge branch 'update_japanese_translation' into 'master'
Update Japanese Card Translation

See merge request core-developers/forge!4556
2021-04-20 11:45:46 +00:00
Lyu Zong-Hong
fdb0db2d41 Update Japanese Card Translation 2021-04-20 19:54:12 +09:00
Michael Kamensky
7465031d44 Merge branch 'fixTriggerCardState' into 'master'
TriggerHandler: fix setting CardState for Trigger

See merge request core-developers/forge!4555
2021-04-20 07:17:09 +00:00
Hans Mackowiak
15eb3a421d TriggerHandler: fix setting CardState for Trigger 2021-04-20 08:45:48 +02:00
Michael Kamensky
ec52a07fe9 Merge branch 'clone' into 'master'
Temporary clones: restore Remembered/Imprinted

Closes #1792

See merge request core-developers/forge!4490
2021-04-20 04:00:42 +00:00
Bug Hunter
b7a475cc0a Temporary clones: restore Remembered/Imprinted 2021-04-20 04:00:41 +00:00
Michael Kamensky
aed022d099 Merge branch 'master' into 'master'
[Mobile] Update LibGDX to 1.10.0, Update Mobile Dev to LWJGL3

See merge request core-developers/forge!4548
2021-04-20 03:59:15 +00:00
Michael Kamensky
86b37e7df0 Merge branch 'chaos' into 'master'
Chaos Warp: Fix wrong target

See merge request core-developers/forge!4551
2021-04-20 03:58:15 +00:00
Michael Kamensky
09e9b46efd Merge branch 'cleanup' into 'master'
Fixes - 19 Apr

See merge request core-developers/forge!4553
2021-04-20 03:58:06 +00:00
Hans Mackowiak
90b685c15d Merge branch 'causeRemoval' into 'master'
remove AbilityUtils.getCause

See merge request core-developers/forge!4550
2021-04-20 00:01:50 +00:00
Northmoc
daedffc0da specter_of_the_fens.txt add Flying! 2021-04-19 15:53:25 -04:00
Northmoc
c489f88f77 brainstorm.txt cleanup Descs 2021-04-19 15:50:55 -04:00
tool4EvEr
33779bb6cf AI: Fix Elspeth Conquers Death 2021-04-19 19:56:13 +02:00
tool4EvEr
660806fb7c Fix wrong target 2021-04-19 19:31:46 +02:00
Hans Mackowiak
40a52ed00b remove AbilityUtils.getCause 2021-04-19 18:12:16 +02:00
Michael Kamensky
c216498198 Merge branch 'master' into 'master'
Update cards translations

See merge request core-developers/forge!4540
2021-04-19 13:52:31 +00:00
Michael Kamensky
547c887c0b Merge branch 'master' into 'master'
Add a NPE guard to ChooseGenericEffectAi

See merge request core-developers/forge!4549
2021-04-19 13:51:41 +00:00
Michael Kamensky
dfe23da44d - Add a NPE guard to ChooseGenericEffectAi 2021-04-19 16:51:00 +03:00
Anthony Calosa
a5e8b88e32 [Mobile] Update LibGDX to 1.10.0, Update Mobile Dev to LWJGL3
refer here for LibGDX Migration/Changes: https://libgdx.com/news/2021/04/the-ultimate-migration-guide
2021-04-19 21:41:34 +08:00
Hans Mackowiak
539c94f36f fix descendants path 2021-04-19 15:02:40 +02:00
Churrufli
fce5ae0498 Merge branch 'master' of https://git.cardforge.org/core-developers/forge 2021-04-19 13:09:01 +02:00
Hans Mackowiak
8fbf96098e CountersPutAi: fix NPE in Polukranos 2021-04-19 13:08:19 +02:00
Hans Mackowiak
c8b43fa8ab Kwain: fix Ability Description 2021-04-19 13:08:19 +02:00
Michael Kamensky
9f305347a8 Merge branch 'PolukranosLogicFix' into 'master'
CountersPutAi: fix NPE in Polukranos

See merge request core-developers/forge!4547
2021-04-19 10:37:47 +00:00
Hans Mackowiak
94d826d2a4 CountersPutAi: fix NPE in Polukranos 2021-04-19 10:37:23 +00:00
Hans Mackowiak
118cf70e40 Kwain: fix Ability Description 2021-04-19 10:21:43 +02:00
Bug Hunter
895d8d594b Bishop of Binding: Update script 2021-04-19 09:07:44 +02:00
tool4EvEr
60ce467859 Fix wrong activator 2021-04-19 09:07:44 +02:00
tool4EvEr
6efa58006e Fix getReflectableManaColors 2021-04-19 09:07:43 +02:00
paul_snoops
f253019daf Brawl STX update 2021-04-19 09:07:43 +02:00
Alumi
8600aceb64 Fix STX typos in edition file 2021-04-19 09:07:43 +02:00
tool4EvEr
a358af534a Improve logic for preventing loops 2021-04-19 09:07:43 +02:00
Anthony Calosa
d080676997 Update mila_crafty_companion_lukka_wayward_bonder.txt 2021-04-19 09:07:43 +02:00
Anthony Calosa
b3412fcfb4 fix lukka wayward bonder ability 2021-04-19 09:07:43 +02:00
Adam Pantel
b5e3db279d Mill quick fix 2021-04-19 09:07:43 +02:00
Adam Pantel
a29ca46ca3 Add ChangesZoneAll trigger in GameAction 2021-04-19 09:07:43 +02:00
Adam Pantel
2fe54a28b1 Add TriggerZones 2021-04-19 09:07:43 +02:00
Adam Pantel
6f9bc11078 Costs should trigger Ranar/Laelia 2021-04-19 09:07:43 +02:00
Northmoc
cd5186cd0d jesters_cap.txt + Mandatory 2021-04-19 09:07:43 +02:00
Northmoc
34cdc299b7 + Mandatory to some ChangeZone effects 2021-04-19 09:07:42 +02:00
Northmoc
274a3f78f2 rampaging_cyclops.txt CheckSVar -> IsPresent 2021-04-19 09:07:42 +02:00
Northmoc
0e31ad96a5 coralhelm_commander.txt + PresentCompare 2021-04-19 09:07:42 +02:00
Northmoc
b191e4d83a beastbreaker_of_bala_ged.txt CheckSVar -> IsPresent 2021-04-19 09:07:42 +02:00
Northmoc
16c554aa43 angelic_field_marshal.txt CheckSVar -> IsPresent, AI 2021-04-19 09:07:42 +02:00
Northmoc
80550cd42e battle_brawler.txt CheckSVar -> IsPresent 2021-04-19 09:07:42 +02:00
Northmoc
3ac28aa279 desperate_castaways.txt CheckSVar -> IsPresent 2021-04-19 09:07:42 +02:00
Northmoc
f16ea99cbc angelic_voices.txt CheckSVar -> IsPresent, AI 2021-04-19 09:07:42 +02:00
Northmoc
d4353bd237 woodborn_behemoth.txt CheckSVar -> IsPresent 2021-04-19 09:07:42 +02:00
Northmoc
8efa82abc2 drover_of_the_mighty.txt CheckSVar -> IsPresent, AI 2021-04-19 09:07:42 +02:00
Northmoc
a5f42f77ad aeronaut_tinkerer.txt CheckSVar -> IsPresent, AI 2021-04-19 09:07:42 +02:00
Northmoc
384314d9ab gate_hound.txt CheckSVar -> IsPresent, AI 2021-04-19 09:07:41 +02:00
Northmoc
56d63a46bf angelic_overseer.txt CheckSVar -> IsPresent, AI 2021-04-19 09:07:41 +02:00
Northmoc
da00ccff58 court_homunculus.txt CheckSVar -> IsPresent, AI 2021-04-19 09:07:41 +02:00
Northmoc
313d5ab762 dauntless_dourbark.txt CheckSVar -> IsPresent, AI 2021-04-19 09:07:41 +02:00
Northmoc
a569029c40 coralhelm_commander.txt CheckSVar -> IsPresent, AI 2021-04-19 09:07:41 +02:00
Northmoc
04748cf03f abzan_kin_guard.txt CheckSVar -> IsPresent 2021-04-19 09:07:41 +02:00
Northmoc
91871fb96d ember_weaver.txt CheckSVar -> IsPresent 2021-04-19 09:07:41 +02:00
Northmoc
7292a12ff4 nightfire_giant.txt CheckSVar -> IsPresent 2021-04-19 09:07:41 +02:00
Northmoc
9b04e8ff38 cliffrunner_behemoth.txt CheckSVar -> IsPresent 2021-04-19 09:07:41 +02:00
Northmoc
b85a2ea6d2 pterodon_knight.txt CheckSVar -> IsPresent 2021-04-19 09:07:41 +02:00
Northmoc
da6c134540 kondas_hatamoto.txt CheckSVar -> IsPresent 2021-04-19 09:07:40 +02:00
Northmoc
e09da1a600 jund_hackblade.txt CheckSVar -> IsPresent 2021-04-19 09:07:40 +02:00
Northmoc
5aa1f9863d ashenmoor_cohort.txt CheckSVar -> IsPresent 2021-04-19 09:07:40 +02:00
Northmoc
1eeb3637e8 villainous_ogre.txt CheckSVar -> IsPresent, AI 2021-04-19 09:07:40 +02:00
Northmoc
79e229467e ereboss_titan.txt CheckSVar -> IsPresent 2021-04-19 09:07:40 +02:00
Northmoc
d483ea7f47 - add SelectPrompt 2021-04-19 09:07:40 +02:00
Northmoc
4e4b31a0d5 bond_of_insight.txt clean up StackDesc and AI 2021-04-19 09:07:40 +02:00
Northmoc
eb38c1ec15 shadrix_silverquill.txt improve TgtPrompts for modes 2021-04-19 09:07:40 +02:00
Northmoc
b33406da4d garrison_sergeant.txt use IsPresent, improve AI 2021-04-19 09:07:40 +02:00
Northmoc
10f4da71b8 gravecrawler.txt fix static and typos, add AI 2021-04-19 09:07:40 +02:00
Michael Kamensky
6d27a8e489 Merge branch 'bishop' into 'master'
Bishop of Binding: Update script

See merge request core-developers/forge!4544
2021-04-19 04:22:11 +00:00
Bug Hunter
c63477c34e Bishop of Binding: Update script 2021-04-19 04:22:10 +00:00
Michael Kamensky
761d1877fa Merge branch 'fixmana' into 'master'
Fix getReflectableManaColors

See merge request core-developers/forge!4537
2021-04-18 14:11:59 +00:00
tool4EvEr
cf4dbf6ed9 Fix wrong activator 2021-04-18 14:11:46 +00:00
tool4EvEr
53e2df8aa2 Fix getReflectableManaColors 2021-04-18 14:11:46 +00:00
Michael Kamensky
07278de968 Merge branch 'brawl' into 'master'
Brawl STX update

See merge request core-developers/forge!4543
2021-04-18 14:11:28 +00:00
paul_snoops
6710c47db0 Brawl STX update 2021-04-18 14:11:17 +00:00
Michael Kamensky
a5cc820ea6 Merge branch 'fix_stx_typos' into 'master'
Fix STX typos in edition file

See merge request core-developers/forge!4541
2021-04-18 14:10:02 +00:00
Alumi
5eb95980bf Fix STX typos in edition file 2021-04-18 14:10:02 +00:00
Michael Kamensky
57cc0e920a Merge branch 'cloneAI' into 'master'
CloneAI: Improve logic for preventing loops

See merge request core-developers/forge!4545
2021-04-18 14:09:12 +00:00
tool4EvEr
902b707b5e Improve logic for preventing loops 2021-04-18 15:32:50 +02:00
Anthony Calosa
98600e9a19 Merge branch 'kevlahnota-master-patch-80770' into 'master'
fix lukka wayward bonder ability

See merge request core-developers/forge!4542
2021-04-18 06:43:21 +00:00
Anthony Calosa
fbf8ac4883 Update mila_crafty_companion_lukka_wayward_bonder.txt 2021-04-18 06:41:45 +00:00
Anthony Calosa
4c7a25407a fix lukka wayward bonder ability 2021-04-18 06:24:02 +00:00
Michael Kamensky
af9359850d Merge branch 'ranar-2' into 'master'
Some Ranar/Laelia fixes

See merge request core-developers/forge!4538
2021-04-18 04:19:44 +00:00
Adam Pantel
abe5638af5 Mill quick fix 2021-04-18 04:19:32 +00:00
Adam Pantel
c45e8a11d7 Add ChangesZoneAll trigger in GameAction 2021-04-18 04:19:32 +00:00
Adam Pantel
62ef84ea07 Add TriggerZones 2021-04-18 04:19:32 +00:00
Adam Pantel
17b5098602 Costs should trigger Ranar/Laelia 2021-04-18 04:19:32 +00:00
Michael Kamensky
272a7afe9e Merge branch 'fix' into 'master'
Fixes - 4/17

See merge request core-developers/forge!4539
2021-04-18 04:17:57 +00:00
Northmoc
9c7fa1a6a8 jesters_cap.txt + Mandatory 2021-04-17 19:34:26 -04:00
Northmoc
38a050dd13 + Mandatory to some ChangeZone effects 2021-04-17 19:32:18 -04:00
Northmoc
377396d77c rampaging_cyclops.txt CheckSVar -> IsPresent 2021-04-17 19:26:39 -04:00
Northmoc
80fc8eddb9 coralhelm_commander.txt + PresentCompare 2021-04-17 19:25:01 -04:00
Northmoc
d9c23ece15 beastbreaker_of_bala_ged.txt CheckSVar -> IsPresent 2021-04-17 19:23:26 -04:00
Northmoc
84a913451f angelic_field_marshal.txt CheckSVar -> IsPresent, AI 2021-04-17 19:19:41 -04:00
Northmoc
59794efc95 battle_brawler.txt CheckSVar -> IsPresent 2021-04-17 19:15:38 -04:00
Northmoc
13977a07e5 desperate_castaways.txt CheckSVar -> IsPresent 2021-04-17 19:15:37 -04:00
Northmoc
b1eb8bd8ec angelic_voices.txt CheckSVar -> IsPresent, AI 2021-04-17 19:15:37 -04:00
Northmoc
69043cfd1c woodborn_behemoth.txt CheckSVar -> IsPresent 2021-04-17 19:15:36 -04:00
Northmoc
f34c713e7e drover_of_the_mighty.txt CheckSVar -> IsPresent, AI 2021-04-17 19:15:36 -04:00
Northmoc
e180bf21a7 aeronaut_tinkerer.txt CheckSVar -> IsPresent, AI 2021-04-17 19:15:35 -04:00
Northmoc
fdaaa4be30 gate_hound.txt CheckSVar -> IsPresent, AI 2021-04-17 19:15:35 -04:00
Northmoc
4db445dd3e angelic_overseer.txt CheckSVar -> IsPresent, AI 2021-04-17 19:15:34 -04:00
Northmoc
530d4c0b08 court_homunculus.txt CheckSVar -> IsPresent, AI 2021-04-17 19:15:34 -04:00
Northmoc
100ce6ded9 dauntless_dourbark.txt CheckSVar -> IsPresent, AI 2021-04-17 19:15:33 -04:00
Northmoc
03deb78aab coralhelm_commander.txt CheckSVar -> IsPresent, AI 2021-04-17 19:15:33 -04:00
Northmoc
bb220366be abzan_kin_guard.txt CheckSVar -> IsPresent 2021-04-17 19:15:32 -04:00
Northmoc
cd4e4a0892 ember_weaver.txt CheckSVar -> IsPresent 2021-04-17 19:15:32 -04:00
Northmoc
aa42c302aa nightfire_giant.txt CheckSVar -> IsPresent 2021-04-17 19:15:31 -04:00
Northmoc
5128557e4e cliffrunner_behemoth.txt CheckSVar -> IsPresent 2021-04-17 19:15:31 -04:00
Northmoc
a181f5d1db pterodon_knight.txt CheckSVar -> IsPresent 2021-04-17 19:15:31 -04:00
Northmoc
5e3bc6c507 kondas_hatamoto.txt CheckSVar -> IsPresent 2021-04-17 19:15:30 -04:00
Northmoc
b11a9557d7 jund_hackblade.txt CheckSVar -> IsPresent 2021-04-17 19:15:30 -04:00
Northmoc
593e172832 ashenmoor_cohort.txt CheckSVar -> IsPresent 2021-04-17 19:15:29 -04:00
Northmoc
6b62a7fde3 villainous_ogre.txt CheckSVar -> IsPresent, AI 2021-04-17 19:15:29 -04:00
Northmoc
9a80028ec7 ereboss_titan.txt CheckSVar -> IsPresent 2021-04-17 19:15:28 -04:00
Northmoc
1252e22743 - add SelectPrompt 2021-04-17 19:15:28 -04:00
Northmoc
a3a25d9bef bond_of_insight.txt clean up StackDesc and AI 2021-04-17 19:15:27 -04:00
Northmoc
08fa3a5768 shadrix_silverquill.txt improve TgtPrompts for modes 2021-04-17 19:15:27 -04:00
Northmoc
52cd521902 garrison_sergeant.txt use IsPresent, improve AI 2021-04-17 19:15:26 -04:00
Northmoc
6dd5f1991f gravecrawler.txt fix static and typos, add AI 2021-04-17 19:15:26 -04:00
Churrufli
d3d29e03be Update cards translations 2021-04-18 00:07:34 +02:00
Michael Kamensky
2ddc88dd87 Merge branch 'mudfix' into 'master'
Mudslide cleanup

See merge request core-developers/forge!4535
2021-04-17 14:46:22 +00:00
tool4EvEr
f768aa360f Mudslide cleanup 2021-04-17 14:46:07 +00:00
Bug Hunter
5ba12d009b Merge branch 'ringtypo' into 'master'
Replicating Ring: Fix typo

See merge request core-developers/forge!4536
2021-04-17 10:59:02 +00:00
tool4EvEr
6259891312 Replicating Ring: Fix typo 2021-04-17 12:58:14 +02:00
Michael Kamensky
fad59e4a0f Merge branch 'fix' into 'master'
Fixes - 16 Apr

See merge request core-developers/forge!4532
2021-04-17 04:23:48 +00:00
Michael Kamensky
81852fe8c6 Merge branch 'ranar-2' into 'master'
Ranar v2

See merge request core-developers/forge!4534
2021-04-17 04:22:26 +00:00
Adam Pantel
b33e262de6 Ranar v2 2021-04-17 04:22:25 +00:00
Northmoc
7963919b72 dungeon_master.txt rogue "" 2021-04-16 18:48:59 -04:00
Northmoc
e7f87478b4 fix SpellDesc 2021-04-16 18:00:51 -04:00
Northmoc
c6b8d95747 ruthless_winnower.txt tidy up 2021-04-16 17:22:35 -04:00
Anthony Calosa
8468c5ac52 Merge branch 'master' into 'master'
refactor decompression

See merge request core-developers/forge!4533
2021-04-16 19:42:50 +00:00
Northmoc
27d7ae4b78 allow Coalition Honor Guard in MB1 boosters 2021-04-16 15:38:25 -04:00
Anthony Calosa
fffd2afe5e refactor decompression 2021-04-17 03:27:46 +08:00
Anthony Calosa
15ba670d5a Merge remote-tracking branch 'remotes/core/master' 2021-04-17 00:49:09 +08:00
Northmoc
7e7279a642 wandering_archaic_explore_the_vastlands.txt add Controller$ You to CopySpellAbility 2021-04-16 10:34:56 -04:00
Northmoc
1b765e2408 sky_swallower.txt fix GainControl line 2021-04-16 10:32:25 -04:00
Northmoc
db0ae9253e mage_duel.txt remove bad Cost 2021-04-16 10:28:47 -04:00
Bug Hunter
5573218794 Merge branch 'Northmoc-master-patch-13728' into 'master'
Update lorehold_pledgemage.txt

See merge request core-developers/forge!4531
2021-04-16 14:27:55 +00:00
Northmoc
6e38f16c83 mentors_guidance.txt remove bad Cost 2021-04-16 10:26:45 -04:00
Northmoc
4f0d2370ac bold_plagiarist.txt TriggerZone 2021-04-16 10:26:44 -04:00
Tim Mocny
f49f677107 Update lorehold_pledgemage.txt 2021-04-16 14:18:53 +00:00
Michael Kamensky
cbb11ea723 Merge branch 'kardur' into 'master'
Remove Goad from Kardur

See merge request core-developers/forge!4527
2021-04-16 12:44:41 +00:00
Michael Kamensky
566d011e2e Merge branch 'Williams-master-patch-46381' into 'master'
C21 Tokens

See merge request core-developers/forge!4529
2021-04-16 12:43:34 +00:00
Michael Kamensky
1d2a62d06c Merge branch 'svarsStackInstance' into 'master'
StackInstance only store direct SVars

See merge request core-developers/forge!4528
2021-04-16 12:43:19 +00:00
Hans Mackowiak
743163fce7 StackInstance only store direct SVars 2021-04-16 12:43:19 +00:00
Michael Kamensky
afe5333c88 Merge branch 'filter_more_targetable_cards' into 'master'
Filter more cards in getTargetableCards

See merge request core-developers/forge!4513
2021-04-16 12:41:10 +00:00
John
3bf15b5a35 Update Commander 2021.txt 2021-04-16 08:10:23 +00:00
Hans Mackowiak
1c9cfbc4bb Merge branch 'card-fixes' into 'master'
Fix Zurzoth, Akiri

See merge request core-developers/forge!4519
2021-04-16 07:15:29 +00:00
Adam Pantel
65093b0388 Fix Zurzoth, Akiri 2021-04-16 07:15:29 +00:00
Anthony Calosa
e2320ea77c Merge remote-tracking branch 'remotes/core/master' 2021-04-16 13:30:25 +08:00
Michael Kamensky
59f414f707 Merge branch 'fixmp' into 'master'
Fix Multiplayer LTB triggers

Closes #1324

See merge request core-developers/forge!4511
2021-04-16 04:59:35 +00:00
Michael Kamensky
2a2c12cf77 Merge branch 'titan' into 'master'
ChangeZoneTable for Triplicate Titan

See merge request core-developers/forge!4522
2021-04-16 04:58:41 +00:00
Michael Kamensky
5fd34dc967 Merge branch 'twosat-master-patch-34511' into 'master'
Update de-DE.properties

See merge request core-developers/forge!4523
2021-04-16 04:58:26 +00:00
Michael Kamensky
65df6765da Merge branch 'shamans_trance' into 'master'
Add Shaman's Trance

See merge request core-developers/forge!4526
2021-04-16 04:56:57 +00:00
Northmoc
ff9729d27a CombatUtil.java add "quasi-goad" logic 2021-04-15 20:03:44 -04:00
Northmoc
9876741c50 kardur_doomscourge.txt remove Goad effect 2021-04-15 20:02:44 -04:00
Lyu Zong-Hong
a1f69f0732 Add Shaman's Trance 2021-04-16 08:22:37 +09:00
Andreas Bendel
8174b883a0 Update de-DE.properties
added line and translation lblRandomHistoricArchetypeDecks
translated lblInvalidTargetSpecification
2021-04-15 19:38:32 +00:00
tool4EvEr
337065271e ChangeZoneTable for Triplicate Titan 2021-04-15 21:27:31 +02:00
Anthony Calosa
01dd0e7fc4 Merge remote-tracking branch 'remotes/core/master' 2021-04-16 03:15:02 +08:00
Anthony Calosa
f3ee08ab9a Merge branch 'master' into 'master'
add tokenscript

Closes #1803

See merge request core-developers/forge!4521
2021-04-15 19:09:34 +00:00
Anthony Calosa
ba08c09607 fix triplicate titan 2021-04-15 19:09:29 +00:00
Anthony Calosa
aecfd12911 remove accidental commit 2021-04-16 03:07:54 +08:00
Anthony Calosa
c081e48467 fix triplicate titan 2021-04-16 03:05:40 +08:00
tool4EvEr
237b0324c1 Clean up 2021-04-15 20:26:28 +02:00
tool4EvEr
5aaf39f40b LTB triggers for opponents 2021-04-15 20:20:52 +02:00
austinio7116
7a4ed29945 Merge branch 'historicsanctionfix' into 'master'
Historic GA fix

See merge request core-developers/forge!4520
2021-04-15 17:59:59 +00:00
austinio7116
84014ce9b5 Historic GA fix 2021-04-15 18:56:25 +01:00
austinio7116
08c8e84d72 Merge branch 'ldabuildonly2' into 'master'
LDA module build fixes

See merge request core-developers/forge!4476
2021-04-15 16:55:40 +00:00
tool4EvEr
43171879a0 Fix logic 2021-04-15 18:54:58 +02:00
tool4EvEr
2f7a30f920 Fix Multiplayer LTB triggers 2021-04-15 18:54:57 +02:00
Anthony Calosa
27a93b318d Merge branch 'master' into 'master'
update zip decompression

Closes #1802

See merge request core-developers/forge!4518
2021-04-15 15:27:08 +00:00
Anthony Calosa
816bb2008a update zip decompression
closes #1802
2021-04-15 23:18:40 +08:00
Anthony Calosa
8eb430dffd Merge branch 'kevlahnota-master-patch-49827' into 'master'
Update CardArchetypeLDAGenerator.java

See merge request core-developers/forge!4517
2021-04-15 10:08:11 +00:00
Anthony Calosa
641b5af8eb Update CardArchetypeLDAGenerator.java 2021-04-15 10:06:33 +00:00
Anthony Calosa
38ef1e4ed9 Merge branch 'revert-ed5f5d3c' into 'master'
Revert "Update LDAModelGenetrator.java"

See merge request core-developers/forge!4516
2021-04-15 09:48:01 +00:00
Anthony Calosa
123c41b4e3 Revert "Update LDAModelGenetrator.java"
This reverts commit ed5f5d3c6b
2021-04-15 09:47:31 +00:00
Anthony Calosa
7001f17c88 Merge branch 'master' into 'master'
update simplified chinese translation

See merge request core-developers/forge!4514
2021-04-15 09:42:53 +00:00
Anthony Calosa
1f10191bc7 Merge branch 'kevlahnota-master-patch-10151' into 'master'
Update LDAModelGenetrator.java

See merge request core-developers/forge!4515
2021-04-15 09:42:23 +00:00
Anthony Calosa
ed5f5d3c6b Update LDAModelGenetrator.java 2021-04-15 09:42:04 +00:00
CCTV-1
a03bb7a353 translate new string. 2021-04-15 16:49:58 +08:00
Lyu Zong-Hong
44d4eae8a1 Filter more cards in getTargetableCards 2021-04-15 16:15:59 +09:00
Michael Kamensky
22ffcf5660 Merge branch 'historicLDA' into 'master'
Historic Archetype Random Deck Generation

See merge request core-developers/forge!4488
2021-04-15 06:30:19 +00:00
Michael Kamensky
f40ea6cc8d Merge branch 'rainbow' into 'master'
Fix DelayedTriggers running when card owner left game

See merge request core-developers/forge!4512
2021-04-15 06:30:05 +00:00
Hans Mackowiak
adfcb2b4bf Merge branch 'facedownYedoraMutate' into 'master'
Card: changed some logic with facedown and copy states

See merge request core-developers/forge!4506
2021-04-15 05:59:29 +00:00
Hans Mackowiak
8d42be3bac Card: changed some logic with facedown and copy states 2021-04-15 05:59:28 +00:00
tool4EvEr
e2036a5203 Clean delayedTriggers from player who lost 2021-04-14 22:58:08 +02:00
tool4EvEr
7107f05adb Fix DelayedTriggers running when card owner left game 2021-04-14 22:12:14 +02:00
austinio7116
529fc42b72 Merge branch 'coremaster' into historicLDA
# Conflicts:
#	forge-gui/res/formats/Sanctioned/Historic.txt
2021-04-14 19:09:01 +01:00
Michael Kamensky
7808605a8c Merge branch 'fix' into 'master'
Some fixes

See merge request core-developers/forge!4508
2021-04-14 18:08:07 +00:00
Michael Kamensky
0930013cc2 Merge branch 'card-fixes' into 'master'
Adrix and Nev only applies to you.

See merge request core-developers/forge!4509
2021-04-14 18:07:58 +00:00
Michael Kamensky
51054b70d6 Merge branch 'codie' into 'master'
[C21] Codie tweaks

See merge request core-developers/forge!4507
2021-04-14 18:07:20 +00:00
Michael Kamensky
471fb621e5 Merge branch 'flagbearer' into 'master'
Add Flagbearer

See merge request core-developers/forge!4495
2021-04-14 18:06:56 +00:00
Alumi
1e0c3e7621 Check if chosen targets meets MustTarget requirements in setupTargets() too. (For spell that has multiple targeting SAs) 2021-04-14 18:06:55 +00:00
Northmoc
a75d312770 oversimplify.txt - RememberLKI needed 2021-04-14 13:21:52 -04:00
austinio7116
0fd2ec58be Merge remote-tracking branch 'Core/master' into coremaster 2021-04-14 18:13:19 +01:00
Northmoc
be484a8077 fix oversimplify.txt 2021-04-14 13:07:06 -04:00
Adam Pantel
3d8388ad92 Adrix and Nev only applies to you. 2021-04-14 12:52:00 -04:00
Northmoc
7a5eac3621 support "an opponent" choosing for multiplayer (and add AI hint) 2021-04-14 12:19:31 -04:00
Hans Mackowiak
ce34e937d6 CostExile: fixed exile cost with mana value X 2021-04-14 17:48:42 +02:00
Northmoc
c8646208f0 codie_vociferous_codex.txt add AI hints 2021-04-14 11:39:20 -04:00
Northmoc
3ed83e4f59 codie_vociferous_codex.txt - add ThisTurn$ to delayed trigger - add MayPlay Effect 2021-04-14 11:33:20 -04:00
Northmoc
ebe6a12555 allow no reordering on zones other than library 2021-04-14 11:28:59 -04:00
Northmoc
1727456487 digsite_engineer.txt DB -> AB 2021-04-14 10:37:29 -04:00
swordshine
ef895777ca Merge branch 'master' into 'master'
Card cleanup and Historic update

See merge request core-developers/forge!4505
2021-04-14 10:58:48 +00:00
swordshine
33fa10e9c1 Merge branch 'emblazoned_golem_remove_max_limit' into 'master'
Remove X max limit for Emblazoned Golem

See merge request core-developers/forge!4503
2021-04-14 10:56:51 +00:00
Hythonia
1f9094caec Removed semicolon 2021-04-14 12:43:33 +02:00
Hythonia
9a5c58aae8 Add Strixhaven and Mystical Archives 2021-04-14 12:41:40 +02:00
Hythonia
1b1e6c394d Card cleanup and Historic update 2021-04-14 12:38:17 +02:00
Anthony Calosa
504872ac1e Merge branch 'master' into 'master'
remove extra svar

See merge request core-developers/forge!4504
2021-04-14 10:07:40 +00:00
Anthony Calosa
edc40f4a6c remove extra svar 2021-04-14 18:06:27 +08:00
Lyu Zong-Hong
7b6e08f791 Remove X max limit for Emblazoned Golem 2021-04-14 16:29:57 +09:00
Michael Kamensky
b184db2ee6 Merge branch 'morophon' into 'master'
Add AILogic to Morophon, the Boundless and move two scripts into correct folders

See merge request core-developers/forge!4500
2021-04-14 06:37:04 +00:00
Michael Kamensky
d10bf7fa35 Merge branch 'fixplay' into 'master'
Once Upon A Time: Fix AltCost

Closes #1801

See merge request core-developers/forge!4501
2021-04-14 06:36:54 +00:00
Michael Kamensky
ce20d21f4c Merge branch 'khm_rankings' into 'master'
KHM updated rankings + nonbasic land rankings!

See merge request core-developers/forge!4502
2021-04-14 06:36:47 +00:00
Michael Kamensky
48a35746a0 Merge branch 'c21_11' into 'master'
promise_of_loyalty.txt

See merge request core-developers/forge!4485
2021-04-14 06:33:19 +00:00
swordshine
bf9afd4ba1 Merge branch 'card-fixes' into 'master'
Card fixes

See merge request core-developers/forge!4499
2021-04-14 04:07:13 +00:00
Northmoc
0eb2d59ecc add AI DeckHas 2021-04-13 20:07:13 -04:00
Northmoc
b9bcfef817 promise_of_loyalty.txt 2021-04-13 20:07:13 -04:00
Northmoc
27d01500fa KHM updated rankings + nonbasic land rankings! 2021-04-13 16:39:47 -04:00
paul_snoops
d2ea3e4986 Add AILogic to Morophon, the Boundless and move to scripts into correct folders 2021-04-13 18:07:52 +01:00
Adam Pantel
b0cecebe34 Card fixes 2021-04-13 12:48:30 -04:00
tool4EvEr
f4971589c6 Fix AltCost 2021-04-13 18:26:34 +02:00
Bug Hunter
506d64dc8b Merge branch 'pennant' into 'master'
Team Pennant: Fix typo

See merge request core-developers/forge!4498
2021-04-13 16:18:36 +00:00
tool4EvEr
f56aa6cc29 Fix typo 2021-04-13 18:17:51 +02:00
Bug Hunter
a17ec8c2db Merge branch 'admiral' into 'master'
Azure Fleet Admiral: Fix typo

See merge request core-developers/forge!4497
2021-04-13 15:56:25 +00:00
tool4EvEr
a718986e2d Fix typo 2021-04-13 17:55:57 +02:00
Michael Kamensky
242e0e7fdc Merge branch 'stx_draft_fix' into 'master'
fix:stx booster missing sta and lesson cards.

See merge request core-developers/forge!4493
2021-04-13 13:40:28 +00:00
Anthony Calosa
75f7cb6df7 Merge branch 'master' into 'master'
update cards

See merge request core-developers/forge!4496
2021-04-13 13:16:01 +00:00
Anthony Calosa
cf7d0792a2 update cards 2021-04-13 21:13:46 +08:00
Hans Mackowiak
29d260db9d Cards: use CharacteristicDefining for Alternative Cost for now 2021-04-13 10:32:16 +02:00
austinio7116
d7dfad1fe8 More Historic LDA data 2021-04-13 08:27:41 +01:00
CCTV-1
82f404e627 each draft boosters should be contain 1 sta card and 1 lesson card. 2021-04-13 14:08:56 +08:00
Anthony Calosa
c49b4c45c8 Merge branch 'kevlahnota-master-patch-11851' into 'master'
finalize Commander 2021.txt

See merge request core-developers/forge!4492
2021-04-13 05:11:16 +00:00
Anthony Calosa
39f3feb210 Update Commander 2021.txt 2021-04-13 05:10:27 +00:00
Michael Kamensky
4ed89e9515 Merge branch 'bangchuckersAI' into 'master'
FlipACoinAI: add logic for Goblin Bangchuckers

Closes #1666

See merge request core-developers/forge!4487
2021-04-13 04:40:07 +00:00
Michael Kamensky
0b6989c60c Merge branch 'update_japanese_localization' into 'master'
Update Japanese Localization

See merge request core-developers/forge!4491
2021-04-13 04:39:12 +00:00
Michael Kamensky
a9139c6023 Merge branch 'nametypos' into 'master'
Make Your Mark fixes and some typos

See merge request core-developers/forge!4486
2021-04-13 04:39:00 +00:00
Michael Kamensky
2de580236e Merge branch 'refine' into 'master'
Gifts Ungiven and Intuition: eliminate ChooseCard line

See merge request core-developers/forge!4489
2021-04-13 04:38:32 +00:00
Lyu Zong-Hong
e5d4979f3e Update Japanese Localization 2021-04-13 10:13:51 +09:00
Rob Schnautz
8f00de273a october 12, 2020 banned and restricted announcement 2021-04-13 00:15:55 +00:00
Rob Schnautz
1a0f5a7460 september 28, 2020 banned and restricted announcement 2021-04-13 00:11:26 +00:00
Rob Schnautz
b0229fb528 zendikar rising historic 2021-04-13 00:07:25 +00:00
Rob Schnautz
454f1aa00a jumpstart historic 2021-04-12 23:50:25 +00:00
Rob Schnautz
12c25d786f core set 2021 historic 2021-04-12 23:46:35 +00:00
Rob Schnautz
7c65eca1fb ikoria commander historic 2021-04-12 23:40:10 +00:00
Rob Schnautz
6a87988d4c commander 2019 historic 2021-04-12 23:38:32 +00:00
Rob Schnautz
b65a17c867 commander 2018 historic 2021-04-12 23:36:11 +00:00
Rob Schnautz
f067fc9896 commander 2017 historic 2021-04-12 23:33:12 +00:00
Rob Schnautz
36cc53b4f8 commander 2016 historic 2021-04-12 23:29:37 +00:00
Rob Schnautz
497df9db96 commander 2015 historic formats 2021-04-12 23:25:53 +00:00
Rob Schnautz
26f66b0377 commander 2014 in historic formats 2021-04-12 23:20:49 +00:00
Rob Schnautz
69c448f4e6 commander 2013 in legacy / vintage historic formats 2021-04-12 23:12:56 +00:00
Rob Schnautz
df08191857 add commander to legacy and vintage historic formats 2021-04-12 23:06:52 +00:00
Rob Schnautz
bb88ff7649 ikoria lair of behemoths historic formats 2021-04-12 22:46:39 +00:00
Rob Schnautz
7329bf8eef theros beyond death historic formats 2021-04-12 22:42:40 +00:00
Rob Schnautz
a58e409665 january 13, 2020 banned and restricted announcement 2021-04-12 22:33:45 +00:00
Rob Schnautz
511a55e22c november 18, 2019 banned and restricted announcement 2021-04-12 22:30:45 +00:00
Rob Schnautz
cfa2cfeded november 4, 2019 pioneer banned and restricted announcement 2021-04-12 22:17:57 +00:00
Rob Schnautz
9d30414539 pioneer 2021-04-12 22:15:55 +00:00
Rob Schnautz
1b34de81eb october 21, 2019 banned and restricted announcement 2021-04-12 22:08:05 +00:00
Rob Schnautz
827824500b throne of eldraine historic formats 2021-04-12 22:01:23 +00:00
Rob Schnautz
35198ddbe8 august 26, 2019 banned and restricted announcement 2021-04-12 21:54:00 +00:00
Rob Schnautz
546545a800 M20 historic formats 2021-04-12 21:42:06 +00:00
Northmoc
1023cc5cb2 make_your_mark.txt remove unneeded cleanup 2021-04-12 17:15:56 -04:00
Northmoc
094cf9e400 make_your_mark.txt improve Effect behavior + add cleanup 2021-04-12 13:30:33 -04:00
Northmoc
1bc834efe8 add DeckHints 2021-04-12 13:24:32 -04:00
Northmoc
b4b039b2b0 intuition.txt eliminate ChooseCard line 2021-04-12 13:03:54 -04:00
Hans Mackowiak
d8e850673b StaticAbilityContinuous: fix GainsAbilitiesOfDefined 2021-04-12 17:56:42 +02:00
Northmoc
35d7046d69 gifts_ungiven.txt eliminate ChooseCard line 2021-04-12 11:20:36 -04:00
austinio7116
fad87abfea Historic Random Deck Generation initial data 2021-04-12 15:24:45 +01:00
Hans Mackowiak
10d10ff18d FlipACoinAI: add logic for Goblin Bangchuckers 2021-04-12 16:17:47 +02:00
Northmoc
161b99f252 elven_bow.txt remove default/bad params and desc typo 2021-04-12 09:53:58 -04:00
Northmoc
87ff260c9f dwarven_hammer.txt remove default/bad params 2021-04-12 09:51:18 -04:00
Northmoc
cc4dafa400 draugrs_helm.txt remove default/bad params 2021-04-12 09:50:22 -04:00
Northmoc
9d29904ec5 valkyries_sword.txt remove default/bad params 2021-04-12 09:48:55 -04:00
austinio7116
50be4f4ef8 Merge branch 'coremaster' into historicLDA 2021-04-12 14:46:43 +01:00
austinio7116
5f9c05080f Merge remote-tracking branch 'Core/master' into coremaster 2021-04-12 14:46:33 +01:00
Northmoc
bbfcc87a68 giants_amulet.txt tidy up 2021-04-12 09:46:00 -04:00
austinio7116
d7e9510353 Merge branch 'keeptrying' into 'master'
Improved deck naming removing "-" and updated pioneer deckgen data

See merge request core-developers/forge!4484
2021-04-12 13:45:09 +00:00
austinio7116
1a1057f56f Improved deck naming removing "-" and updated pioneer deckgen data 2021-04-12 13:45:09 +00:00
austinio7116
93ff8cfe1b Added Historic Random Deck Generation 2021-04-12 14:36:57 +01:00
Northmoc
4d05e085ef raiders_karve.txt fix TrigDesc 2021-04-12 09:24:25 -04:00
Northmoc
d2d96d2ece make_your_mark.txt 2021-04-12 09:23:59 -04:00
austinio7116
78762cd479 Reverted accidental test change 2021-04-12 13:40:44 +01:00
austinio7116
cdaa37c5fd Initial basic fix for LDA module to ensure it builds without errors - also removed some unneeded imports which the Maven checks were blocking on 2021-04-12 13:39:49 +01:00
austinio7116
76140fb555 Reverted accidental test change 2021-04-12 13:37:54 +01:00
austinio7116
352c5ea3d3 Corrected version in pom 2021-04-12 13:36:01 +01:00
austinio7116
f167d4fa3b Merge branch 'ldabuildonly2' of https://git.cardforge.org/Austin/forge into ldabuildonly2 2021-04-12 13:32:36 +01:00
austinio7116
7528cd3b58 Merge branch 'master' into 'ldabuildonly2'
# Conflicts:
#   forge-gui/src/main/java/forge/gamemodes/limited/ArchetypeDeckBuilder.java
2021-04-12 12:31:42 +00:00
austinio7116
cab8d0fb97 Merge branch 'coremaster' into ldabuildonly2
# Conflicts:
#	forge-gui/src/main/java/forge/gamemodes/limited/ArchetypeDeckBuilder.java
2021-04-12 13:11:52 +01:00
austinio7116
c7d9c174fd Merge remote-tracking branch 'Core/master' into coremaster 2021-04-12 13:02:52 +01:00
Michael Kamensky
b7787c8cf6 Merge branch 'Williams-master-patch-65541' into 'master'
C21 Revival Experiment

See merge request core-developers/forge!4480
2021-04-12 11:29:27 +00:00
Anthony Calosa
4d218e3624 Merge branch 'master' into 'master'
unused import

See merge request core-developers/forge!4482
2021-04-12 10:22:46 +00:00
Anthony Calosa
89fab45eb7 unused import 2021-04-12 18:21:54 +08:00
Anthony Calosa
9bca338cc2 Merge branch 'kevlahnota-master-patch-58825' into 'master'
Update Commander 2021.txt

See merge request core-developers/forge!4481
2021-04-12 10:16:33 +00:00
Anthony Calosa
36c3c43f86 Merge branch 'master' into 'master'
Simplify ManaConversion scripts, remove Flash/Ice Cave technical debt

See merge request core-developers/forge!4479
2021-04-12 10:14:23 +00:00
Anthony Calosa
d9f762c485 Update Commander 2021.txt 2021-04-12 10:04:33 +00:00
John
3d206d1fb1 Update revival_experiment.txt 2021-04-12 09:46:24 +00:00
John
2a959f23d9 Add new file 2021-04-12 09:44:53 +00:00
Hythonia
bb659d39db ManaConvert keyword display 2021-04-12 10:03:03 +02:00
Hythonia
49cdd4d36d DefinedCost and DefinedManaCost 2021-04-12 10:03:02 +02:00
Hythonia
41d55cf780 Simplify ManaConvertion scripts 2021-04-12 10:03:02 +02:00
Michael Kamensky
5c5c948193 Merge branch 'card-fixes' into 'master'
Moritte type fix

See merge request core-developers/forge!4477
2021-04-12 04:19:37 +00:00
Michael Kamensky
0e32dc982e Merge branch 'unset-cards' into 'master'
Just host cards

See merge request core-developers/forge!4460
2021-04-12 04:19:30 +00:00
Michael Kamensky
9fffdb8cad Merge branch 'mannequin' into 'master'
Makeshift Mannequin: Fix duration

See merge request core-developers/forge!4474
2021-04-12 04:17:47 +00:00
Michael Kamensky
76a1da2d0f Merge branch 'emblazoned_golem' into 'master'
Add Emblazoned Golem

See merge request core-developers/forge!4467
2021-04-12 04:17:33 +00:00
Adam Pantel
ba012a4b0e Moritte type fix 2021-04-11 21:54:28 -04:00
Adam Pantel
09fd212e29 Remove // from names 2021-04-11 21:53:01 -04:00
swordshine
54fd4787fd Merge branch 'kaboom' into 'master'
Kaboom!

See merge request core-developers/forge!4472
2021-04-12 01:04:57 +00:00
Hans Mackowiak
023499a6c0 Player: run checkStaticAbilities before Companion to apply CDA 2021-04-12 02:03:59 +02:00
Lyu Zong-Hong
a8ad7f540b Add DeckHas hint 2021-04-12 08:01:20 +09:00
Adam Pantel
b7ee2c696b Some host cards 2021-04-11 18:35:03 -04:00
austinio7116
4528223dee Initial basic fix for LDA module to ensure it builds without errors - also removed some unneeded imports which the Maven checks were blocking on 2021-04-11 23:13:49 +01:00
austinio7116
cf729db0c0 Merge remote-tracking branch 'Core/master' into coremaster 2021-04-11 23:13:14 +01:00
austinio7116
a4deafa238 Merge branch 'khmldabranch' into 'master'
KHM deck generation

See merge request core-developers/forge!4469
2021-04-11 22:12:02 +00:00
austinio7116
5e88eab617 Modern LDA data 2021-04-11 22:46:47 +01:00
austinio7116
950e51e0fe Improved snow land decision making on LDA deck generator 2021-04-11 22:46:43 +01:00
austinio7116
1c1d743638 Fixes for snow covered lands in LDA deck generation 2021-04-11 22:46:38 +01:00
austinio7116
ffa7e39121 Merge remote-tracking branch 'Core/master' into coremaster 2021-04-11 22:46:01 +01:00
austinio7116
84d6a24446 Modern LDA data 2021-04-11 22:45:09 +01:00
tool4EvEr
91c92d4b64 Fix duration 2021-04-11 21:27:50 +02:00
Bug Hunter
056229e8ee Merge branch 'impetus' into 'master'
Psychic Impetus: Fix missing param

See merge request core-developers/forge!4473
2021-04-11 19:22:12 +00:00
tool4EvEr
c4c5f7ab6b Fix missing param 2021-04-11 21:21:39 +02:00
tool4EvEr
c7ab33f3ab Oracle adjustment 2021-04-11 21:12:48 +02:00
Michael Kamensky
3a3850ec4e Merge branch 'Williams-master-patch-09539' into 'master'
C21 Sproutback Trudge

See merge request core-developers/forge!4455
2021-04-11 19:04:23 +00:00
Michael Kamensky
84ed014909 Merge branch 'new-cards-2' into 'master'
Combat Calligrapher

Closes #1795

See merge request core-developers/forge!4462
2021-04-11 19:03:57 +00:00
Michael Kamensky
39563682f5 Merge branch 'phasedAttachment' into 'master'
GameEntity: when attachment is phased out, treat as not attached

See merge request core-developers/forge!4470
2021-04-11 19:03:26 +00:00
Michael Kamensky
53179f6e3f Merge branch 'triggerCounterPlayerAddedAll' into 'master'
TriggerCounterPlayerAddedAll: new Trigger

See merge request core-developers/forge!4454
2021-04-11 19:03:12 +00:00
Michael Kamensky
b1d5ba6787 Merge branch 'zaffai' into 'master'
Zaffai: Fix trigger

See merge request core-developers/forge!4471
2021-04-11 19:02:56 +00:00
John
361a3d0371 Add new file 2021-04-11 19:01:59 +00:00
tool4EvEr
6b5b38535e Zaffai: Fix trigger 2021-04-11 18:14:31 +02:00
austinio7116
8a4539b19a Improved snow land decision making on LDA deck generator 2021-04-11 16:06:53 +01:00
Hans Mackowiak
f6ee232d9e GameEntity: when attachment is phased out, treat as not attached 2021-04-11 17:00:20 +02:00
austinio7116
2562d28f4f Fixes for snow covered lands in LDA deck generation 2021-04-11 15:50:02 +01:00
austinio7116
f89cfa3ec8 KHM deck generation 2021-04-11 15:23:22 +01:00
Hans Mackowiak
d8b665f64f TriggerCounterPlayerAddedAll: new Trigger that triggers when a player puts more counters on one object 2021-04-11 15:53:32 +02:00
Adam Pantel
d473737ca8 Remove triggers in setup game state 2021-04-11 09:28:16 -04:00
John
52e7cc41e2 Update sproutback_trudge.txt 2021-04-11 13:08:32 +00:00
austinio7116
53978333fb KHM deck generation 2021-04-11 13:59:58 +01:00
Michael Kamensky
183209baa3 Merge branch 'arlinn' into 'master'
Fix Arlinn Kord transform

See merge request core-developers/forge!4458
2021-04-11 12:43:34 +00:00
Michael Kamensky
0df4e1147c Merge branch 'yedora' into 'master'
Yedora: allow morph

See merge request core-developers/forge!4466
2021-04-11 12:43:26 +00:00
Michael Kamensky
7c42b1f845 Merge branch 'stxrank' into 'master'
STX draftsim ranks

See merge request core-developers/forge!4463
2021-04-11 12:41:21 +00:00
Michael Kamensky
129e4762dc Merge branch 'new-cards' into 'master'
Nils, Discipline Enforcer (attacking tax computation)

See merge request core-developers/forge!4457
2021-04-11 12:40:38 +00:00
Michael Kamensky
fc1c5ca6e9 Merge branch 'fixlang' into 'master'
CardTranslation: Add fallback for english only cards

See merge request core-developers/forge!4456
2021-04-11 12:40:19 +00:00
Lyu Zong-Hong
8ae916ac28 Add Emblazoned Golem 2021-04-11 18:56:49 +09:00
tool4EvEr
0ccf9c961b Yedora: allow morph 2021-04-11 10:27:34 +02:00
Bug Hunter
b894e65618 Merge branch 'TRT-master-patch-28302' into 'master'
Update forge-gui/res/cardsfolder/upcoming/dream_strix.txt

See merge request core-developers/forge!4465
2021-04-11 08:25:14 +00:00
Bug Hunter
801d31ed29 Update forge-gui/res/cardsfolder/upcoming/dream_strix.txt 2021-04-11 08:25:03 +00:00
Anthony Calosa
34fb9bb865 Merge branch 'kevlahnota-master-patch-52335' into 'master'
Update Commander 2021.txt

See merge request core-developers/forge!4464
2021-04-11 07:18:17 +00:00
Anthony Calosa
b5201e17ff Update forge-gui/res/editions/Commander 2021.txt 2021-04-11 07:17:16 +00:00
Anthony Calosa
204bbfc6c7 Update forge-gui/res/editions/Commander 2021.txt 2021-04-11 07:12:00 +00:00
Anthony Calosa
692fe0c885 Update Commander 2021.txt 2021-04-11 07:00:32 +00:00
John
3a372179d6 Update sproutback_trudge.txt 2021-04-11 06:51:44 +00:00
Adam Pantel
3de8bd49ad Combat Calligrapher 2021-04-11 01:59:36 -04:00
austinio7116
b9c02d5d99 Merge branch 'ldastream' into 'master'
LDA module

See merge request core-developers/forge!4251
2021-04-11 05:51:11 +00:00
Hans Mackowiak
185c4c09c9 Changed card-based deck generation to archetype based making better use of the new LDA models. Decks can now be selected by archetype with names generated from the source decklists. Archetypes are ordered by popularity. 2021-04-11 05:51:11 +00:00
austinio7116
ad709fd8f0 STX draftsim ranks 2021-04-11 06:47:57 +01:00
Adam Pantel
da7a1f34e1 Nils, Discipline Enforcer 2021-04-11 00:40:27 -04:00
Michael Kamensky
47d2f3b6ad Merge branch 'new-cards-2' into 'master'
C21: Stinging Study

See merge request core-developers/forge!4461
2021-04-11 04:22:59 +00:00
Michael Kamensky
673759f17b Merge branch 'city' into 'master'
Fix originally printed property

Closes #1797

See merge request core-developers/forge!4453
2021-04-11 04:20:44 +00:00
Adam Pantel
0e2d6ac196 C21: Stinging Study 2021-04-11 00:16:00 -04:00
Anthony Calosa
eb99915912 Merge branch 'master' into 'master'
update ImageKeys

See merge request core-developers/forge!4459
2021-04-11 02:22:04 +00:00
Anthony Calosa
501df02260 update ImageKeys 2021-04-11 10:03:56 +08:00
tool4EvEr
d74f6dba0a Fix Arlinn Kord transform 2021-04-10 23:28:11 +02:00
tool4EvEr
b590c6b55a CardTranslation: Add fallback for english only cards 2021-04-10 20:00:48 +02:00
John
175f38f114 Add new file 2021-04-10 16:53:05 +00:00
Michael Kamensky
44f69b2b0b Merge branch 'fixX' into 'master'
Fix Thieving Skydiver 0 kicker

See merge request core-developers/forge!4450
2021-04-10 16:28:25 +00:00
Michael Kamensky
02a1b139bf Merge branch 'lib' into 'master'
Sylvan Library: Add ordering choice

See merge request core-developers/forge!4452
2021-04-10 16:27:52 +00:00
Michael Kamensky
326cf81a0f Merge branch 'Williams-master-patch-30398' into 'master'
C21 Suthro 10/04

See merge request core-developers/forge!4448
2021-04-10 16:27:45 +00:00
John
8952ddc9dc Update witchs_clinic.txt 2021-04-10 15:31:37 +00:00
John
dfe2c6d064 Update veinwitch_coven.txt 2021-04-10 15:29:31 +00:00
John
fea2f30a1c Update veinwitch_coven.txt 2021-04-10 15:28:55 +00:00
tool4EvEr
5d8bd256f1 Fix originally printed property 2021-04-10 17:28:18 +02:00
John
d7f41f4a56 Add new file 2021-04-10 15:28:18 +00:00
John
9169ea6ebf Update trudge_garden.txt 2021-04-10 15:24:54 +00:00
John
20314c8952 Update trudge_garden.txt 2021-04-10 15:24:00 +00:00
John
f262075d2a Update pest_infestation.txt 2021-04-10 15:21:52 +00:00
John
37d9ea103b Update gyome_master_chef.txt 2021-04-10 15:21:20 +00:00
John
ef51f23308 Update ezzaroot_channeler.txt 2021-04-10 15:20:46 +00:00
John
11f75176b8 Update blossoming_bogbeast.txt 2021-04-10 15:15:33 +00:00
John
bd676c7c5c Update blight_mound.txt 2021-04-10 15:05:17 +00:00
tool4EvEr
ccf51356a9 Sylvan Library: Add ordering choice 2021-04-10 16:13:07 +02:00
tool4EvEr
ece32cbe70 Join XCantBe0 restrictions from both parts when combining costs 2021-04-10 13:51:16 +02:00
Bug Hunter
574e74350c Merge branch 'fixdispute' into 'master'
Academic Dispute: Fix optional

See merge request core-developers/forge!4451
2021-04-10 10:41:27 +00:00
tool4EvEr
4665f8c6c6 Academic Dispute: Fix optional 2021-04-10 12:40:32 +02:00
tool4EvEr
a1bdd1fc47 Fix Thieving Skydiver 0 kicker 2021-04-10 12:35:47 +02:00
Bug Hunter
cdcb801b50 Merge branch 'TRT-master-patch-77164' into 'master'
Update forge-gui/res/cardsfolder/upcoming/explosive_welcome.txt

See merge request core-developers/forge!4449
2021-04-10 08:39:38 +00:00
Bug Hunter
5f4bfed2b5 Update forge-gui/res/cardsfolder/upcoming/explosive_welcome.txt 2021-04-10 08:39:23 +00:00
John
cffaa6fef8 Add new file 2021-04-10 07:09:15 +00:00
John
bf2e5392f8 Add new file 2021-04-10 07:08:31 +00:00
John
de6c93b849 Add new file 2021-04-10 07:07:47 +00:00
John
3b6bbe4799 Add new file 2021-04-10 07:06:05 +00:00
John
62250cd41f Add new file 2021-04-10 07:05:08 +00:00
John
69a3963de0 Add new file 2021-04-10 07:04:23 +00:00
John
acefebfcd7 Add new file 2021-04-10 07:03:27 +00:00
John
b8f7d084c7 Add new file 2021-04-10 07:02:42 +00:00
John
fd420b3d81 Add new file 2021-04-10 07:01:26 +00:00
John
bf799869ef Add new file 2021-04-10 07:00:43 +00:00
John
8fc3004349 Add new file 2021-04-10 06:58:06 +00:00
Michael Kamensky
13b22e2047 Merge branch 'c21_9a' into 'master'
C21 - 9 April (more)

See merge request core-developers/forge!4446
2021-04-10 04:14:10 +00:00
Michael Kamensky
2a33ba9f93 Merge branch 'fixstuff' into 'master'
Fix some stuff

See merge request core-developers/forge!4442
2021-04-10 04:13:47 +00:00
Michael Kamensky
ad09233d4c Merge branch 'clash' into 'master'
ClashEffect: Fix triggering for both players

See merge request core-developers/forge!4436
2021-04-10 04:13:29 +00:00
Bug Hunter
2be3a38bb0 ClashEffect: Fix triggering for both players 2021-04-10 04:13:29 +00:00
Michael Kamensky
26467221b3 Merge branch '1759-extra-generated-text-on-auras' into 'master'
Resolve "Extra generated text on Auras"

Closes #1759

See merge request core-developers/forge!4443
2021-04-10 04:13:12 +00:00
Michael Kamensky
dd19ad356a Merge branch 'guardian-archon' into 'master'
[C21] Guardian Archon

See merge request core-developers/forge!4445
2021-04-10 04:12:48 +00:00
Michael Kamensky
4fe91e9a6a Merge branch 'fix_psychic_battle' into 'master'
Fix Psychic Battle in multiplayer game

See merge request core-developers/forge!4447
2021-04-10 04:12:13 +00:00
Michael Kamensky
63d660d152 Merge branch 'noAbilities' into 'master'
Fix NoAbilities failing for basic land types

See merge request core-developers/forge!4424
2021-04-10 04:12:07 +00:00
Lyu Zong-Hong
730ff1758f Fix Psychic Battle in multiplayer game 2021-04-10 10:10:21 +09:00
Northmoc
174099dd1d tempting_contract.txt 2021-04-09 19:58:18 -04:00
Northmoc
f7f49b98ff healing_technique.txt (Suthro) 2021-04-09 19:05:23 -04:00
Northmoc
18f0af1803 guardian_archon.txt 2021-04-09 17:54:04 -04:00
Northmoc
af2a4e1ce4 tidy up ChosenPlayer for DefinedKW 2021-04-09 17:53:49 -04:00
Bug Hunter
a4a58d9a06 Merge branch 'TRT-master-patch-75766' into 'master'
Update forge-gui/res/cardsfolder/upcoming/uvilda_dean_of_perfection_nassari_dean_of_expression.txt

See merge request core-developers/forge!4444
2021-04-09 20:08:23 +00:00
Bug Hunter
861bb5f608 Update forge-gui/res/cardsfolder/upcoming/uvilda_dean_of_perfection_nassari_dean_of_expression.txt 2021-04-09 20:07:32 +00:00
Hans Mackowiak
36c7f1244d Card: fix faulty SpellDescriptions, skip BasicSpells 2021-04-09 20:57:36 +02:00
Michael Kamensky
8e4d07163d Merge branch 'twosat-master-patch-58716' into 'master'
Update de-DE.properties

See merge request core-developers/forge!4440
2021-04-09 18:21:29 +00:00
Michael Kamensky
1af3cd40c0 Merge branch 'c21_9' into 'master'
C21 - 9 April

See merge request core-developers/forge!4441
2021-04-09 18:21:09 +00:00
Michael Kamensky
8e0eb9d110 Merge branch 'c21_8' into 'master'
C21 - 8 April

See merge request core-developers/forge!4434
2021-04-09 18:20:40 +00:00
Northmoc
bf7dbe0098 fain_the_broker.txt 2021-04-09 13:38:58 -04:00
Northmoc
f37d27f803 blot_out_the_sky.txt fix TokenScript 2021-04-09 13:34:25 -04:00
Northmoc
b98d2fbaa4 quicksilver_dagger.txt clean up Descs 2021-04-09 13:34:03 -04:00
Northmoc
181539a424 keen_duelist.txt 2021-04-09 13:01:46 -04:00
Hans Mackowiak
6fe299d7c8 StaticAbilityPanharmonicon: use LKI only for ChangeZone Trigger 2021-04-09 18:56:52 +02:00
Andreas Bendel
585eb23b3c Update de-DE.properties
translated lblLearnALesson
2021-04-09 15:35:14 +00:00
Hans Mackowiak
04940fc4db StaticAbilityPanharmonicon: clean up use matchesValidParam 2021-04-09 17:28:33 +02:00
Hans Mackowiak
75fa1b7fbe Merge branch 'master' into 'master'
C21 Veyran & Panharmonicon rework

See merge request core-developers/forge!4408
2021-04-09 15:15:30 +00:00
Hythonia
cc8e8b2dd1 C21 Veyran & Panharmonicon rework 2021-04-09 15:15:29 +00:00
Northmoc
0b3bbad98f incarnation_technique.txt 2021-04-09 10:13:44 -04:00
Northmoc
9da11e05ee strixhaven_stadium.txt add TriggerZones$ 2021-04-09 09:57:36 -04:00
Northmoc
ecca6f8eda cunning_rhetoric.txt 2021-04-09 09:40:47 -04:00
Northmoc
b5c6f32d1f author_of_shadows.txt 2021-04-09 09:40:46 -04:00
Northmoc
f38c083b05 inkshield.txt 2021-04-09 09:40:46 -04:00
Northmoc
598a9e0918 felisa_fang_of_silverquill.txt (Suthro) 2021-04-09 09:40:45 -04:00
Michael Kamensky
c93c582ed5 Merge branch 'master' into 'master'
Net Deck Archive Updates

See merge request core-developers/forge!4437
2021-04-09 07:13:21 +00:00
Michael Kamensky
010cfb6b35 Merge branch 'lands' into 'master'
Improve AI land handling

See merge request core-developers/forge!4435
2021-04-09 07:13:19 +00:00
Michael Kamensky
b1d95a5fa0 Merge branch 'fixes' into 'master'
Fixes

See merge request core-developers/forge!4433
2021-04-09 07:10:27 +00:00
Michael Kamensky
0d4bf2ea2e Merge branch 'sponsor' into 'master'
Scholarship Sponsor [C21]

See merge request core-developers/forge!4438
2021-04-09 07:10:16 +00:00
Michael Kamensky
d4eb451ba6 Merge branch 'psychic_battle' into 'master'
Add Psychic Battle

See merge request core-developers/forge!4439
2021-04-09 07:09:52 +00:00
Lyu Zong-Hong
9435701d10 Fix bug that when AI cast divide damage as you choose spells, human can't change targetcorrectly 2021-04-09 15:12:13 +09:00
Lyu Zong-Hong
7603943c38 Add Psychic Battle 2021-04-09 14:06:51 +09:00
Northmoc
a959f79bcf scholarship_sponsor.txt 2021-04-08 22:33:26 -04:00
Northmoc
9899d72dce witherbloom_command.txt fix Mill NumCards 2021-04-08 21:27:58 -04:00
Churrufli
4263ea6881 Net Deck Archive Updates 2021-04-09 00:07:21 +02:00
Adam Pantel
ab287858b3 Improve land handling 2021-04-08 16:05:54 -04:00
tool4EvEr
eb6d168d50 Update NoAbilities logic 2021-04-08 20:40:23 +02:00
Michael Kamensky
52b8c4ae5e Merge branch 'master' into 'master'
translate learnLesson message.

See merge request core-developers/forge!4432
2021-04-08 17:53:37 +00:00
tool4EvEr
1c4640d69b Fix NoAbilities failing for basic land types 2021-04-08 19:42:18 +02:00
Northmoc
3827459229 - pic line 2021-04-08 13:20:21 -04:00
Northmoc
aaeb9f9e84 ajanis_aid.txt Rep Eff: CombatDamage -> IsCombat 2021-04-08 13:19:25 -04:00
Northmoc
61acb3e42b jeska_thrice_reborn.txt Rep Eff: CombatDamage -> IsCombat 2021-04-08 13:18:32 -04:00
Northmoc
4a8f299bb3 gifts_ungiven.txt clean up 2021-04-08 12:58:31 -04:00
Northmoc
5530ca94f3 add Mandatory to some searches that need it 2021-04-08 12:46:42 -04:00
Northmoc
61a1b888bd - add ChoiceTitle 2021-04-08 12:43:40 -04:00
Northmoc
a583701319 intuition.txt fix targeting/choosing 2021-04-08 12:35:10 -04:00
Northmoc
57623c710a remove bad ) 2021-04-08 11:12:44 -04:00
Northmoc
93488528de add TriggerZones to Ward 2021-04-08 11:07:54 -04:00
Northmoc
e7a5ffdf5f containment_breach.txt fix ValidTgts 2021-04-08 11:07:16 -04:00
Northmoc
dc56cb382b elspeth_conquers_death.txt improved AI tags 2021-04-08 11:06:26 -04:00
Northmoc
bb22ea3c8f AI logic for Elspeth Conquers Death 2021-04-08 11:05:22 -04:00
CCTV-1
b856ad0374 translate learnLesson message. 2021-04-08 20:07:56 +08:00
Anthony Calosa
3aff3c51a8 Merge branch 'kevlahnota-master-patch-34682' into 'master'
Update CEditorConstructed.java

See merge request core-developers/forge!4431
2021-04-08 11:09:38 +00:00
Anthony Calosa
3e096a25f7 Update CEditorConstructed.java 2021-04-08 11:09:16 +00:00
Anthony Calosa
adb21e4a7e Merge branch 'c21_edition_update' into 'master'
Update C21 Edition file

See merge request core-developers/forge!4430
2021-04-08 11:03:38 +00:00
paul_snoops
3ea268aea7 Update C21 Edition file 2021-04-08 10:05:54 +01:00
Michael Kamensky
567a8913e4 Merge branch 'Williams-master-patch-18379' into 'master'
C21 07/04 Suthro

See merge request core-developers/forge!4426
2021-04-08 08:05:48 +00:00
John
fd89b30192 Update spawning_kraken.txt 2021-04-08 06:30:32 +00:00
John
ce3b327cff Update guardian_augmenter.txt 2021-04-08 06:29:55 +00:00
John
4e5ac4a921 Update curiosity_crafter.txt 2021-04-08 06:29:23 +00:00
Michael Kamensky
b44c1478b8 Merge branch 'c21_7' into 'master'
C21 - 7 April

See merge request core-developers/forge!4422
2021-04-08 04:10:42 +00:00
Michael Kamensky
b0890ad4bf Merge branch 'resolvedThisTurn' into 'master'
Card: add AbilityResolvedThisTurn

See merge request core-developers/forge!4427
2021-04-08 04:09:44 +00:00
Anthony Calosa
83d632e24c Merge branch 'master' into 'master'
update preload itempool

See merge request core-developers/forge!4428
2021-04-08 02:35:07 +00:00
Anthony Calosa
1e5874aed5 update preload itempool 2021-04-08 09:57:26 +08:00
Northmoc
e807d9a3e7 support for playerXCount TotalCommanderCastFromCommandZone 2021-04-07 21:22:21 -04:00
Northmoc
17bb74ff25 commanders_insight.txt 2021-04-07 21:19:44 -04:00
Hans Mackowiak
5c9e2839eb Card: add AbilityResolvedThisTurn 2021-04-08 02:13:45 +02:00
John
d670a761d9 Update paradox_zone.txt 2021-04-07 22:47:42 +00:00
John
b6a3f765a7 Update paradox_zone.txt 2021-04-07 22:42:30 +00:00
John
2ca2ceea5a Update sequence_engine.txt 2021-04-07 22:41:58 +00:00
John
01b4ab512c Update spawning_kraken.txt 2021-04-07 22:41:33 +00:00
John
d18a6c3479 Update theoretical_duplication.txt 2021-04-07 22:40:59 +00:00
John
ab931cdcb4 Update curiosity_crafter.txt 2021-04-07 22:40:03 +00:00
John
7252b0e69a Add new file 2021-04-07 22:39:04 +00:00
John
ab0ed23c83 Add new file 2021-04-07 22:38:21 +00:00
John
18c002094f Add new file 2021-04-07 22:37:04 +00:00
John
d5f73cb633 Add new file 2021-04-07 22:36:15 +00:00
John
c738da065e Add new file 2021-04-07 22:35:30 +00:00
John
a9296d67f7 Add new file 2021-04-07 22:34:40 +00:00
John
78cb417cfc Add new file 2021-04-07 22:34:00 +00:00
John
4bec5f6792 Add new file 2021-04-07 22:33:14 +00:00
John
3da03f66e0 Add new file 2021-04-07 22:31:33 +00:00
Northmoc
e379c676d0 muse_vortex.txt 2021-04-07 16:58:29 -04:00
Bug Hunter
6565494781 Merge branch 'TRT-master-patch-82622' into 'master'
Update forge-gui/res/cardsfolder/upcoming/abundant_harvest.txt

See merge request core-developers/forge!4425
2021-04-07 20:45:09 +00:00
Bug Hunter
aafcd25403 Update forge-gui/res/cardsfolder/upcoming/abundant_harvest.txt 2021-04-07 20:44:20 +00:00
Northmoc
72c4c0007f oversimplify.txt fix Descs 2021-04-07 15:55:36 -04:00
Northmoc
fd1ce7a750 geometric_nexus.txt 2021-04-07 15:53:26 -04:00
Northmoc
fbbdec5e4e oversimplify.txt 2021-04-07 15:33:51 -04:00
Northmoc
443b613513 deekah_fractal_theorist.txt remove default params 2021-04-07 15:05:05 -04:00
Michael Kamensky
2fa44fc0f4 Merge branch 'Williams-master-patch-07577' into 'master'
C21 Discord Contributions 06/04

See merge request core-developers/forge!4410
2021-04-07 18:02:04 +00:00
Northmoc
5ed21c3ce3 deekah_fractal_theorist.txt 2021-04-07 13:16:16 -04:00
Northmoc
f1d52fb530 ruxa_patient_professor.txt (Suthro) 2021-04-07 12:59:37 -04:00
Northmoc
d418ad8369 fractal_harness.txt 2021-04-07 12:46:48 -04:00
Bug Hunter
7b58d8e77e Merge branch 'TRT-master-patch-33908' into 'master'
Update forge-gui/res/cardsfolder/upcoming/fractal_summoning.txt

See merge request core-developers/forge!4423
2021-04-07 16:31:09 +00:00
Bug Hunter
38971c2946 Update forge-gui/res/cardsfolder/upcoming/fractal_summoning.txt 2021-04-07 16:30:10 +00:00
Northmoc
6cef7fd6b6 creative_technique.txt 2021-04-07 11:14:38 -04:00
Northmoc
92ffb0b1e5 rionya_fire_dancer.txt 2021-04-07 10:44:21 -04:00
Northmoc
75f03f1fd1 support for SumCMCGraveyard (Inferno Project) 2021-04-07 10:30:01 -04:00
Northmoc
0306817ac8 inferno_project.txt 2021-04-07 10:29:14 -04:00
Tim Mocny
03b5dc046d Merge branch 'cmc' into 'master'
fix cmc necrotic_fumes.txt

See merge request core-developers/forge!4421
2021-04-07 13:32:42 +00:00
Northmoc
bb5cb8a773 necrotic_fumes.txt 2021-04-07 09:31:40 -04:00
Sol
719bd257a6 Merge branch 'retriever_phoenix_fix' into 'master'
Retriever Phoenix fix for the following error

See merge request core-developers/forge!4420
2021-04-07 12:08:59 +00:00
paul_snoops
e21257ab71 Retriever Phoenix fix for the following error
Caused by: java.lang.RuntimeException: AbilityFactory : getAbility -- Retriever Phoenix has no SVar: TrigLearn
2021-04-07 12:39:41 +01:00
Bug Hunter
fc933ba9d5 Merge branch 'TRT-master-patch-18852' into 'master'
Update forge-gui/res/cardsfolder/upcoming/basic_conjuration.txt

See merge request core-developers/forge!4419
2021-04-07 09:32:28 +00:00
Bug Hunter
9f9215cb3f Update forge-gui/res/cardsfolder/upcoming/basic_conjuration.txt 2021-04-07 09:31:36 +00:00
Anthony Calosa
8308c21d39 Merge branch 'kevlahnota-master-patch-67446' into 'master'
Update StaticData.java

See merge request core-developers/forge!4418
2021-04-07 08:00:16 +00:00
Anthony Calosa
1f6ef5f045 Update StaticData.java 2021-04-07 07:59:39 +00:00
Anthony Calosa
7673918178 Merge branch 'master' into 'master'
Update support for Custom Cards

See merge request core-developers/forge!4417
2021-04-07 07:53:23 +00:00
Anthony Calosa
92b0aad937 Update support for Custom Cards 2021-04-07 15:50:24 +08:00
Michael Kamensky
233e126cd3 Merge branch 'Williams-master-patch-47951' into 'master'
Update Judge Gift Cards 2021.txt

See merge request core-developers/forge!4416
2021-04-07 07:33:50 +00:00
John
e9506787d5 Update Judge Gift Cards 2021.txt 2021-04-07 06:51:51 +00:00
John
e061289a26 Update elementalists_palette.txt 2021-04-07 06:47:38 +00:00
Michael Kamensky
7f87164235 Merge branch 'learnEffect' into 'master'
Learn effect

See merge request core-developers/forge!4374
2021-04-07 05:11:55 +00:00
Hans Mackowiak
0627d37f69 - Basic AI for AF Learn
- Some minor tweaks and fixes for AF Learn
2021-04-07 05:11:55 +00:00
Michael Kamensky
68dc49cb88 Merge branch 'secretly' into 'master'
Emissary of Grudges, Stalking Leonin

See merge request core-developers/forge!4413
2021-04-07 05:11:28 +00:00
Michael Kamensky
24cf97ca84 Merge branch 'cultic' into 'master'
Add Cultic Cube

See merge request core-developers/forge!4411
2021-04-07 05:08:13 +00:00
Michael Kamensky
9859cbdca6 Merge branch 'ConditionDefinedSpells' into 'master'
SpellAbilityCondition: extends ConditionDefined to work with SpellAbilities

See merge request core-developers/forge!4412
2021-04-07 05:07:58 +00:00
Michael Kamensky
b819f303bb Merge branch 'new-cards' into 'master'
C21 Cards

See merge request core-developers/forge!4414
2021-04-07 05:07:18 +00:00
Michael Kamensky
5ad4a25c33 Merge branch 'card-fixes' into 'master'
Some card fixes

See merge request core-developers/forge!4415
2021-04-07 05:07:01 +00:00
Adam Pantel
265e5bb122 Golden Ratio fix 2021-04-06 23:35:18 -04:00
Adam Pantel
7964f862c6 Fixes from discord 2021-04-06 22:49:16 -04:00
Adam Pantel
622176d3de Surge to Victory 2021-04-06 22:43:51 -04:00
Adam Pantel
7e793a48e7 Duration fix on Rowan 2021-04-06 22:18:58 -04:00
Adam Pantel
5bdc4615e8 Reinterpret 2021-04-06 19:31:39 -04:00
Adam Pantel
ef04a4376a Emissary of Grudges, Stalking Leonin 2021-04-06 19:29:37 -04:00
John
3c57007765 Update rousing_refrain.txt 2021-04-06 22:08:21 +00:00
John
9965f2e79c Update inspiring_refrain.txt 2021-04-06 22:06:13 +00:00
John
f131997d6a Update sly_instigator.txt 2021-04-06 22:05:02 +00:00
John
08b9e3c46a Update inspiring_refrain.txt 2021-04-06 22:04:08 +00:00
John
8afbaefa6d Update inspiring_refrain.txt 2021-04-06 22:00:21 +00:00
John
88a8336ae7 Update rousing_refrain.txt 2021-04-06 21:59:41 +00:00
John
0c6dface18 Update rousing_refrain.txt 2021-04-06 21:59:01 +00:00
John
9bb2dd3887 Update inspiring_refrain.txt 2021-04-06 21:49:48 +00:00
John
1f3943fe9a Update octavia_living_thesis.txt 2021-04-06 21:48:59 +00:00
John
9502034d58 Update inspiring_refrain.txt 2021-04-06 21:48:37 +00:00
Hans Mackowiak
c6b0b2021c SpellAbilityCondition: extends ConditionDefined to work with SpellAbilities 2021-04-06 22:13:48 +02:00
John
0d889ec773 Add new file 2021-04-06 19:55:33 +00:00
John
3d8f848936 Add new file 2021-04-06 19:54:52 +00:00
Adam Pantel
6cbd108bca Add Cultic Cube 2021-04-06 15:03:26 -04:00
John
03c950f82e Add new file 2021-04-06 17:58:35 +00:00
John
552b061357 Add new file 2021-04-06 17:47:44 +00:00
John
ca98432e20 Add new file 2021-04-06 17:21:44 +00:00
John
6ada1f04da Add new file 2021-04-06 17:20:43 +00:00
John
edf1738e3c Add new file 2021-04-06 17:18:45 +00:00
Michael Kamensky
5b77205b77 Merge branch 'ranking' into 'master'
Add default cube rankings from cubecobra

See merge request core-developers/forge!4409
2021-04-06 17:09:07 +00:00
Michael Kamensky
67128c2fd5 Merge branch 'new-cards' into 'master'
C21 Cards

See merge request core-developers/forge!4406
2021-04-06 17:08:49 +00:00
Michael Kamensky
490ec7ef3e Merge branch 'Williams-master-patch-25580' into 'master'
C21 Suthro 5/4

See merge request core-developers/forge!4401
2021-04-06 17:08:27 +00:00
Michael Kamensky
0ab7ff919b Merge branch 'new-cards-2' into 'master'
Demonstrate keyword and Excavation Technique

See merge request core-developers/forge!4397
2021-04-06 17:08:12 +00:00
Adam Pantel
489ccd9003 Add default cube rankings from cubecobra 2021-04-06 12:28:29 -04:00
Sol
9971eef9b8 Merge branch 'Williams-master-patch-65571' into 'master'
Update STX edition file

See merge request core-developers/forge!4407
2021-04-06 14:43:38 +00:00
John
88e8bed395 Update Standard.txt 2021-04-06 14:29:27 +00:00
John
9304942fd6 Update Pioneer.txt 2021-04-06 14:29:14 +00:00
John
5f3b5cbe26 Update Modern.txt 2021-04-06 14:28:42 +00:00
John
c5dbc8ddca Update Strixhaven School of Mages.txt 2021-04-06 14:07:29 +00:00
John
eaea041e73 Update blocks.txt 2021-04-06 14:04:16 +00:00
John
20b4ae230f Update Strixhaven School of Mages.txt 2021-04-06 14:02:56 +00:00
Adam Pantel
037dce8be8 Wake the Past 2021-04-06 09:59:08 -04:00
Anthony Calosa
3ad6045377 Merge branch 'master' into 'master'
update AssetsDownloader

See merge request core-developers/forge!4405
2021-04-06 12:18:40 +00:00
Anthony Calosa
3f683c3909 update AssetsDownloader 2021-04-06 20:16:45 +08:00
Anthony Calosa
2f9974f6ef Merge branch 'master' into 'master'
fix downloader

See merge request core-developers/forge!4404
2021-04-06 11:25:47 +00:00
Anthony Calosa
d2156a1179 Merge remote-tracking branch 'core/master' 2021-04-06 19:24:33 +08:00
Anthony Calosa
d6a0b92f1e fix downloader 2021-04-06 19:21:45 +08:00
Bug Hunter
15aab58415 Merge branch 'TRT-master-patch-76250' into 'master'
Update forge-gui/res/cardsfolder/upcoming/culling_ritual.txt

See merge request core-developers/forge!4403
2021-04-06 10:40:56 +00:00
Bug Hunter
1e8679bf94 Update forge-gui/res/cardsfolder/upcoming/culling_ritual.txt 2021-04-06 10:39:39 +00:00
Michael Kamensky
4eeb4c515f Merge branch 'fixtarget' into 'master'
DestroyAI: Fix illegal targets

See merge request core-developers/forge!4394
2021-04-06 08:57:17 +00:00
John
76a75942a9 Update triplicate_titan.txt 2021-04-06 08:47:17 +00:00
John
4383c3486a Update digsite_engineer.txt 2021-04-06 08:46:53 +00:00
Anthony Calosa
e629877984 Merge branch 'master' into 'master'
fix filteredCards, fix NPE devmode

See merge request core-developers/forge!4402
2021-04-06 07:53:23 +00:00
Anthony Calosa
fb71ea83f0 Merge remote-tracking branch 'core/master' 2021-04-06 15:50:39 +08:00
Anthony Calosa
d9cf090060 update 2021-04-06 15:49:30 +08:00
Michael Kamensky
a0fd16513a Merge branch 'c21_5' into 'master'
C21 - 5 April

See merge request core-developers/forge!4396
2021-04-06 07:30:26 +00:00
Michael Kamensky
c7c6d4e162 Merge branch 'new-cards' into 'master'
Strict Proctor

See merge request core-developers/forge!4393
2021-04-06 07:27:15 +00:00
Anthony Calosa
9a2eaa7a1d fix filteredCards, fix NPE devmode 2021-04-06 15:27:14 +08:00
Michael Kamensky
3b12843d5f Merge branch 'card-fixes' into 'master'
Typo in Liliana's Scorn

See merge request core-developers/forge!4398
2021-04-06 07:25:20 +00:00
Michael Kamensky
6685649e39 Merge branch 'AIplayCantBeCast' into 'master'
AI: Add missing CantBeCast check from PlayEffect

See merge request core-developers/forge!4391
2021-04-06 07:23:55 +00:00
John
7e63c902d3 Update triplicate_titan.txt 2021-04-06 06:50:50 +00:00
John
c1e1530cd9 Update losheel_clockwork_scholar.txt 2021-04-06 06:49:52 +00:00
John
b1a190093c Update digsite_engineer.txt 2021-04-06 06:49:01 +00:00
Hans Mackowiak
fcc7cf0566 Merge branch 'kevlahnota-master-patch-51370' into 'master'
Update lathiel_the_bounteous_dawn.txt

See merge request core-developers/forge!4400
2021-04-06 06:47:33 +00:00
Anthony Calosa
ca3d51157f Update lathiel_the_bounteous_dawn.txt 2021-04-06 06:47:33 +00:00
John
899a7b2500 Update bronze_guardian.txt 2021-04-06 06:46:58 +00:00
John
d19be1731e Update angel_of_ruins.txt 2021-04-06 06:45:26 +00:00
John
90768b6933 Update alibou_ancient_witness.txt 2021-04-06 06:43:39 +00:00
John
89d6f050d1 Add new file 2021-04-06 06:40:06 +00:00
John
5bfe7a2146 Add new file 2021-04-06 06:39:07 +00:00
John
e54dc157ac Add new file 2021-04-06 06:38:17 +00:00
John
5be7472dd9 Add new file 2021-04-06 06:37:16 +00:00
John
1a2de2bbc2 Add new file 2021-04-06 06:36:01 +00:00
John
0c9935b1b5 Add new file 2021-04-06 06:34:55 +00:00
John
aba00ad17b Add new file 2021-04-06 06:33:04 +00:00
Anthony Calosa
5ca05394d6 Merge branch 'kevlahnota-master-patch-58460' into 'master'
Update ImageCache.java

See merge request core-developers/forge!4399
2021-04-06 03:59:42 +00:00
Anthony Calosa
5165415fb1 Update ImageCache.java
Don't dispose defaultImage
2021-04-06 03:59:21 +00:00
Adam Pantel
1f1060b97c Typo in Liliana's Scorn 2021-04-05 23:50:37 -04:00
Adam Pantel
3e9dabe2a5 Demonstrate keyword and Excavation Technique 2021-04-05 23:19:33 -04:00
Northmoc
86d72b15c5 support for archaeomancers_map.txt 2021-04-05 23:05:56 -04:00
Northmoc
3fd3a3e5aa archaeomancers_map.txt 2021-04-05 23:05:16 -04:00
Northmoc
3fbcf09326 ruin_grinder.txt 2021-04-05 23:04:47 -04:00
Anthony Calosa
d97cd9eabe Merge branch 'STX_edition' into 'master'
STX edition missing cards

See merge request core-developers/forge!4392
2021-04-06 01:32:58 +00:00
Sol
de52bb1036 Merge branch 'card-fixes' into 'master'
MoJhoSto improvements

See merge request core-developers/forge!4395
2021-04-06 01:19:38 +00:00
Adam Pantel
f9a073fd93 Momir script improvement 2021-04-05 19:32:51 -04:00
Adam Pantel
c77f99cf9b MoJhoSto logging 2021-04-05 18:57:36 -04:00
Northmoc
af8c1b2092 cursed_mirror.txt 2021-04-05 18:45:59 -04:00
Adam Pantel
9095dcd9d8 Add infinite loop protection for Stonehewer 2021-04-05 18:21:32 -04:00
Northmoc
08359103da teachings_of_the_archaics.txt only 3 cards 2021-04-05 18:15:36 -04:00
Northmoc
9fe15fd8c2 audacious_reshapers.txt 2021-04-05 18:11:18 -04:00
Northmoc
728afbc887 madcap_experiment.txt tidy up 2021-04-05 18:10:51 -04:00
Adam Pantel
1e10d49d0f Strict Proctor 2021-04-05 17:51:26 -04:00
tool4EvEr
4a81ea0ee4 Fix illegal targets 2021-04-05 23:50:16 +02:00
Northmoc
acf89b1e9e monologue_tax.txt 2021-04-05 17:41:01 -04:00
paul_snoops
1c99b5bbc7 STX edition missing cards 2021-04-05 22:11:33 +01:00
Northmoc
d8d0c67462 study_hall.txt 2021-04-05 16:29:22 -04:00
Michael Kamensky
2e9f050707 Merge branch 'stx_5' into 'master'
STX - Conspiracy Theorist

See merge request core-developers/forge!4387
2021-04-05 19:29:30 +00:00
Michael Kamensky
4b0a3741de Merge branch 'livio' into 'master'
livio_oathsworn_sentinel.txt fixup

See merge request core-developers/forge!4390
2021-04-05 19:28:27 +00:00
Michael Kamensky
c45c5f6e92 Merge branch 'new-cards-2' into 'master'
Dragon's Approach

See merge request core-developers/forge!4363
2021-04-05 19:26:54 +00:00
Michael Kamensky
482515f538 Merge branch 'fixdig' into 'master'
Fix DigMultiple not changing cards when no valid targets

See merge request core-developers/forge!4386
2021-04-05 19:26:16 +00:00
tool4EvEr
42d75bd1ee Add missing CantBeCast check 2021-04-05 21:06:52 +02:00
Northmoc
296d7436ae fix stonehewer_giant_avatar.txt 2021-04-05 12:38:39 -04:00
Northmoc
5ba1466b96 livio_oathsworn_sentinel.txt fixup 2021-04-05 12:02:43 -04:00
Sol
7cdf36df87 Merge branch 'STX_fixes' into 'master'
STX edition fix MDFCs

See merge request core-developers/forge!4389
2021-04-05 14:57:07 +00:00
paul_snoops
02f25f8816 STX edition fix MDFCs 2021-04-05 15:54:13 +01:00
Sol
fa7e6bfa36 Merge branch 'stx_update' into 'master'
STX edition file update

See merge request core-developers/forge!4367
2021-04-05 14:07:13 +00:00
paul_snoops
854890cc77 STX edition file update 2021-04-05 14:57:15 +01:00
Northmoc
5445ec1b8d RememberTriggerCards param support for Conspiracy Theorist 2021-04-05 08:44:40 -04:00
Northmoc
050f11bfab conspiracy_theorist.txt 2021-04-05 08:43:46 -04:00
tool4EvEr
da89b95654 Fix DigMultiple not changing cards when no valid targets 2021-04-05 13:41:46 +02:00
Anthony Calosa
71631ccf0c Merge branch 'churrufli-master-patch-15537' into 'master'
Update text, resource file size

See merge request core-developers/forge!4384
2021-04-05 10:24:05 +00:00
Churrufli
f1c38dd742 Update text 2021-04-05 10:01:34 +00:00
Bug Hunter
0fc7126238 Merge branch 'typofix' into 'master'
Show of Confidence: Fix cost typo

See merge request core-developers/forge!4383
2021-04-05 09:45:53 +00:00
tool4EvEr
41cf366014 Fix cost typo 2021-04-05 11:45:13 +02:00
Anthony Calosa
4288ae3582 Merge branch 'master' into 'master'
[Mobile] Seperate cache for Cards and Other Images

See merge request core-developers/forge!4382
2021-04-05 09:07:03 +00:00
Anthony Calosa
e57d0a4d0e [Mobile] add OtherImageLoader for otherCache 2021-04-05 17:04:40 +08:00
Anthony Calosa
1b6a0188f3 [Mobile] Seperate cache for Cards/Tokens and Icon/Booster/Fatpacks 2021-04-05 15:45:22 +08:00
Michael Kamensky
e225b8a77c Merge branch 'card-fixes' into 'master'
Codie oracle text

See merge request core-developers/forge!4381
2021-04-05 04:12:40 +00:00
Michael Kamensky
e4b1c542db Merge branch 'sheetfix' into 'master'
Sheets / Edition parsing fixes

See merge request core-developers/forge!4377
2021-04-05 04:12:33 +00:00
Adam Pantel
b960460e02 Codie oracle text 2021-04-04 23:33:31 -04:00
Anthony Calosa
c8f39802ae Merge branch 'master' into 'master'
Limit preloading

See merge request core-developers/forge!4380
2021-04-05 03:27:59 +00:00
Anthony Calosa
e3caad856d Limit preloading 2021-04-05 11:25:42 +08:00
Anthony Calosa
97bc512d1c Merge branch 'master' into 'master'
cleanup

See merge request core-developers/forge!4379
2021-04-05 02:07:51 +00:00
Anthony Calosa
5752fe698f cleanup 2021-04-05 10:06:16 +08:00
Anthony Calosa
727ef87a11 Merge branch 'master' into 'master'
[Mobile] Update ImageCache Texture dispose

See merge request core-developers/forge!4378
2021-04-04 22:56:04 +00:00
Anthony Calosa
a92c85a0be [Mobile] Update ImageCache Texture dispose 2021-04-05 06:51:27 +08:00
tool4EvEr
225790cc4b Fix parsing of new edition sections 2021-04-04 23:52:41 +02:00
tool4EvEr
0202d52e49 Printsheets: Update wrong JMP codes 2021-04-04 22:56:09 +02:00
Bug Hunter
397ad4c597 Merge branch 'brawl2' into 'master'
Add commander relocation for brawl

See merge request core-developers/forge!4376
2021-04-04 20:29:12 +00:00
tool4EvEr
ce00f31f0f Add commander relocation for brawl 2021-04-04 22:28:19 +02:00
swordshine
b9dc9e6eb0 Merge branch 'master' into 'master'
Leonin Vanguard fix + Medomai

See merge request core-developers/forge!4372
2021-04-04 15:33:30 +00:00
Anthony Calosa
05884bba0d Merge branch 'master' into 'master'
[Mobile] When generating a lot of image at once (more than 100) in the shop...

See merge request core-developers/forge!4373
2021-04-04 13:40:36 +00:00
Anthony Calosa
d8f37ac6a7 Merge remote-tracking branch 'core/master' 2021-04-04 21:38:01 +08:00
Anthony Calosa
9c89e0b790 [Mobile] When generating a lot of image at once (more than 100) in the shop list/rewards/unlocked sets, clear the cache 2021-04-04 21:35:01 +08:00
Hythonia
b37d695c8b Minor card fixes 2021-04-04 15:06:50 +02:00
Michael Kamensky
263db54a12 Merge branch 'Williams-master-patch-50535' into 'master'
STX Discord Contributions

See merge request core-developers/forge!4362
2021-04-04 11:23:18 +00:00
Michael Kamensky
dc8c25b8bc Merge branch 'master' into 'master'
STX: Last three cards

See merge request core-developers/forge!4365
2021-04-04 11:18:53 +00:00
Anthony Calosa
1d43cae1b1 Merge branch 'kevlahnota-master-patch-75509' into 'master'
unused import

See merge request core-developers/forge!4371
2021-04-04 10:58:35 +00:00
Anthony Calosa
7e02ae518d unused import 2021-04-04 10:58:14 +00:00
Bug Hunter
4afc439eb1 Merge branch 'Williams-master-patch-14604' into 'master'
Update make_your_mark.txt

See merge request core-developers/forge!4369
2021-04-04 10:39:19 +00:00
Anthony Calosa
330d4f006f Merge branch 'kevlahnota-master-patch-65912' into 'master'
update token-images

See merge request core-developers/forge!4370
2021-04-04 10:23:17 +00:00
Anthony Calosa
5929a7c6f9 Update token-images.txt 2021-04-04 10:19:57 +00:00
Anthony Calosa
1b95989b05 Update token-images.txt 2021-04-04 10:14:59 +00:00
John
b284fc590e Update make_your_mark.txt 2021-04-04 10:13:45 +00:00
Anthony Calosa
dd3e21e5d4 Merge branch 'kevlahnota-master-patch-92499' into 'master'
Update ImageCache.java

See merge request core-developers/forge!4368
2021-04-04 09:29:00 +00:00
Anthony Calosa
31c674c3dc Update ImageCache.java 2021-04-04 09:28:15 +00:00
paul_snoops
b3632417a7 STX edition file update (no tokens yet) 2021-04-04 10:24:35 +01:00
Anthony Calosa
bc28919727 Merge branch 'kevlahnota-master-patch-57420' into 'master'
Update borderlessCardList.txt

See merge request core-developers/forge!4366
2021-04-04 09:12:05 +00:00
Anthony Calosa
4333744248 Update borderlessCardList.txt 2021-04-04 09:11:20 +00:00
John
d62599d62c Add new file 2021-04-04 07:31:19 +00:00
John
bad745d2a9 Add new file 2021-04-04 07:30:02 +00:00
Hythonia
0c6aa6afdb STX: Last three cards 2021-04-04 09:29:37 +02:00
John
bb8e68d1b1 Add new file 2021-04-04 07:29:31 +00:00
John
8c6b6e617c Add new file 2021-04-04 07:29:01 +00:00
John
a9e4270ddb Add new file 2021-04-04 07:28:24 +00:00
John
72262a77fd Add new file 2021-04-04 07:27:47 +00:00
John
68c524a75d Add new file 2021-04-04 07:27:08 +00:00
John
64c56288c9 Add new file 2021-04-04 07:26:25 +00:00
John
2996bf7e6b Add new file 2021-04-04 07:25:46 +00:00
John
5520c3778b Add new file 2021-04-04 07:23:42 +00:00
John
4c6bdcb68a Add new file 2021-04-04 07:23:08 +00:00
John
a925786899 Add new file 2021-04-04 07:22:18 +00:00
John
e73a3cbf8c Add new file 2021-04-04 07:21:26 +00:00
John
c86dbb4665 Add new file 2021-04-04 07:20:24 +00:00
John
951c338edd Add new file 2021-04-04 07:19:29 +00:00
John
5a22ad86b8 Add new file 2021-04-04 07:18:25 +00:00
John
6db22d43a6 Add new file 2021-04-04 07:17:26 +00:00
John
6569f59501 Add new file 2021-04-04 07:16:44 +00:00
John
c30b04131b Add new file 2021-04-04 07:15:58 +00:00
John
fb7e6d482a Add new file 2021-04-04 07:15:13 +00:00
John
df252a0051 Add new file 2021-04-04 07:14:08 +00:00
John
443d79f902 Update springmane_cervin.txt 2021-04-04 07:11:38 +00:00
John
7ac9997950 Update emergent_sequence.txt 2021-04-04 07:10:58 +00:00
John
aa59b5b4e3 Update callous_bloodmage.txt 2021-04-04 07:10:29 +00:00
John
2cd0b56f11 Update acess_tunnel.txt 2021-04-04 07:09:45 +00:00
Michael Kamensky
40c8485f02 Merge branch 'fix_illusionary_mask' into 'master'
Copy SA without mana cost will also copy castFaceDown flag. (Fix for Illusionary Mask)

See merge request core-developers/forge!4364
2021-04-04 04:26:17 +00:00
Michael Kamensky
f70e22fa4d Merge branch 'biblio' into 'master'
STX - The Biblioplex

See merge request core-developers/forge!4361
2021-04-04 04:25:56 +00:00
Michael Kamensky
cbaa7028d0 Merge branch 'stx_3' into 'master'
STX - 3 April

See merge request core-developers/forge!4360
2021-04-04 04:25:49 +00:00
Michael Kamensky
d6a4e33035 Merge branch 'new-cards' into 'master'
STX Cards

See merge request core-developers/forge!4353
2021-04-04 04:25:14 +00:00
Adam Pantel
99bae93e07 STX Cards 2021-04-03 23:37:21 -04:00
Lyu Zong-Hong
9097fb6f60 Copy SA without mana cost will also copy castFaceDown flag. (Fix for Illusionary Mask) 2021-04-04 10:43:55 +09:00
John
8e9b91eeb6 Add new file 2021-04-03 23:22:06 +00:00
John
419b46c7cc Add new file 2021-04-03 23:21:14 +00:00
John
e927d76cb5 Add new file 2021-04-03 23:20:19 +00:00
John
966d190b77 Add new file 2021-04-03 23:19:37 +00:00
John
b7b06413d6 Add new file 2021-04-03 23:18:54 +00:00
John
67ba5e21f7 Add new file 2021-04-03 23:17:51 +00:00
John
27e401d3a1 Add new file 2021-04-03 23:17:04 +00:00
John
45c1c40e9f Add new file 2021-04-03 23:16:21 +00:00
John
29cfde5249 Upload New File 2021-04-03 23:00:13 +00:00
John
ffecb391b3 Upload New File 2021-04-03 22:59:44 +00:00
John
c87f8b0986 Add new file 2021-04-03 22:35:13 +00:00
John
8cc4eef916 Add new file 2021-04-03 22:34:05 +00:00
John
46f241f004 Add new file 2021-04-03 22:33:18 +00:00
John
4dbe0bdbcb Add new file 2021-04-03 22:32:09 +00:00
John
ee5e0beb7f Add new file 2021-04-03 22:30:56 +00:00
John
ef89a35de6 Add new file 2021-04-03 22:30:14 +00:00
Adam Pantel
26f94ccf07 Dragon's Approach 2021-04-03 18:29:57 -04:00
John
c7a0b9d31c Add new file 2021-04-03 22:29:13 +00:00
John
e2f91339b4 Add new file 2021-04-03 22:28:23 +00:00
John
155b2723f1 Add new file 2021-04-03 22:27:34 +00:00
John
2b5d5c5c12 Add new file 2021-04-03 22:26:22 +00:00
John
660f854546 Add new file 2021-04-03 22:25:42 +00:00
John
505ae78d14 Add new file 2021-04-03 22:25:02 +00:00
John
0f19934021 Add new file 2021-04-03 22:23:29 +00:00
John
0865e3262f Add new file 2021-04-03 22:22:46 +00:00
John
8d266dbc4c Add new file 2021-04-03 22:22:00 +00:00
John
8801365896 Add new file 2021-04-03 22:21:11 +00:00
John
33249ea526 Add new file 2021-04-03 22:20:26 +00:00
John
d1f3ec25fc Add new file 2021-04-03 22:18:08 +00:00
Northmoc
8ea76ee1b8 pestilent_cauldron_restorative_burst.txt 2021-04-03 15:28:29 -04:00
Michael Kamensky
660ed4c341 Merge branch 'Williams-master-patch-56008' into 'master'
STX Contibutions

See merge request core-developers/forge!4357
2021-04-03 19:00:05 +00:00
Northmoc
ef6f88a129 dramatic_finale.txt 2021-04-03 14:45:46 -04:00
Northmoc
bf072e0802 move TriggerZone param 2021-04-03 14:30:29 -04:00
John
d910521a0b Update zimone_quandrix_prodigy.txt 2021-04-03 18:22:43 +00:00
John
3ceb40741c Update zimone_quandrix_prodigy.txt 2021-04-03 18:22:15 +00:00
John
7d3a192435 Update witherbloom_pledgemage.txt 2021-04-03 18:21:39 +00:00
John
50af4b83c5 Update witherbloom_pledgemage.txt 2021-04-03 18:20:43 +00:00
John
2e4f79f6ef Update twinscroll_shaman.txt 2021-04-03 18:20:17 +00:00
John
7c57be2181 Update test_of_talents.txt 2021-04-03 18:19:52 +00:00
John
2f238789ad Update professors_warning.txt 2021-04-03 18:19:17 +00:00
John
29d4cfe669 Update professor_of_zoomancy.txt 2021-04-03 18:18:51 +00:00
John
df4eaa28b9 Update mila_crafty_companion_lukka_wayward_bonder.txt 2021-04-03 18:18:23 +00:00
John
42dc55c664 Update mila_crafty_companion_lukka_wayward_bonder.txt 2021-04-03 18:16:37 +00:00
John
a82ad37e78 Update mila_crafty_companion_lukka_wayward_bonder.txt 2021-04-03 18:15:45 +00:00
John
5fdc7970cd Update draconic_intervention.txt 2021-04-03 18:15:12 +00:00
John
8f45cc62d2 Update draconic_intervention.txt 2021-04-03 18:14:36 +00:00
Northmoc
b8eaa369c2 add AI hint 2021-04-03 13:57:34 -04:00
Northmoc
fec2a4fcd5 blot_out_the_sky.txt (Suthro) 2021-04-03 13:57:33 -04:00
Northmoc
f40ca2037b rushed_rebirth.txt (Suthro) 2021-04-03 13:57:33 -04:00
Northmoc
7227885e0e silverquill_silencer.txt (Suthro) 2021-04-03 13:57:32 -04:00
Northmoc
4fe66ddca8 add NonLegendary param to copySpellHost (for Double Major) 2021-04-03 13:57:32 -04:00
Northmoc
5472f17c96 double_major.txt 2021-04-03 13:57:32 -04:00
Northmoc
69b072f130 augmenter_pugilist_echoing_equation.txt 2021-04-03 13:57:31 -04:00
Northmoc
640106a78a jadzi_oracle_of_arcavios_journey_to_the_oracle.txt 2021-04-03 13:57:31 -04:00
Northmoc
2e31e1840a improve awkward prompt 2021-04-03 13:11:45 -04:00
Northmoc
09334d2043 support for OrActivationCardsInHand param 2021-04-03 13:10:09 -04:00
Northmoc
b52d22fd76 the_biblioplex.txt 2021-04-03 13:07:38 -04:00
Michael Kamensky
34233bf682 Merge branch 'stx_2' into 'master'
STX - 2 April

See merge request core-developers/forge!4354
2021-04-03 13:53:57 +00:00
John
3ca111f6e0 Update dina_soul_steeper.txt 2021-04-03 13:16:06 +00:00
John
09556cc3ac Update daemogoth_titan.txt 2021-04-03 13:15:38 +00:00
John
ff94d85e53 Update bookwurm.txt 2021-04-03 13:14:52 +00:00
John
dd759d62e9 Update beledros_witherbloom.txt 2021-04-03 13:13:37 +00:00
Northmoc
9739ed6b87 fix Oracle typo 2021-04-03 08:08:01 -04:00
John
3d4c88ae7c Update tempted_by_the_oriq.txt 2021-04-03 11:03:21 +00:00
John
eb5f842e7b Update zimone_quandrix_prodigy.txt 2021-04-03 10:56:22 +00:00
Anthony Calosa
6fb409dcd0 Merge branch 'kevlahnota-master-patch-68671' into 'master'
Update MagicStack.java

See merge request core-developers/forge!4359
2021-04-03 10:35:04 +00:00
Anthony Calosa
b2e36e8ca2 Update MagicStack.java
Should fix conspire (Wort, the Raidmother), replicate (Djiin Illuminatus)
2021-04-03 10:27:14 +00:00
Anthony Calosa
709524b7c9 Merge branch 'master' into 'master'
Fix spell shop items

See merge request core-developers/forge!4358
2021-04-03 09:32:39 +00:00
Anthony Calosa
4444438670 Merge remote-tracking branch 'core/master' 2021-04-03 17:29:53 +08:00
Anthony Calosa
87ada19569 fix spell shop items 2021-04-03 17:28:58 +08:00
John
082fce33c3 Update witherbloom_pledgemage.txt 2021-04-03 08:01:52 +00:00
John
ae93d2028a Update sedgemoor_witch.txt 2021-04-03 08:00:06 +00:00
John
a36719686b Add new file 2021-04-03 07:52:11 +00:00
John
981dc381cf Add new file 2021-04-03 07:51:26 +00:00
John
261241c1ee Add new file 2021-04-03 07:50:49 +00:00
John
7c8d432aa9 Add new file 2021-04-03 07:49:59 +00:00
John
bd82d5d355 Add new file 2021-04-03 07:49:17 +00:00
John
fed9270211 Add new file 2021-04-03 07:48:28 +00:00
John
2fa0ef2572 Add new file 2021-04-03 07:47:42 +00:00
John
02ffbb2b5c Add new file 2021-04-03 07:46:56 +00:00
John
c00197d44a Add new file 2021-04-03 07:46:13 +00:00
John
aa30390674 Add new file 2021-04-03 07:30:16 +00:00
John
c54bf6f730 Add new file 2021-04-03 07:27:42 +00:00
John
a5cc4cc01d Add new file 2021-04-03 07:26:58 +00:00
John
ca02c342d7 Add new file 2021-04-03 07:25:32 +00:00
John
bd58d81bcc Add new file 2021-04-03 07:24:55 +00:00
John
15dfdb5bc1 Add new file 2021-04-03 07:24:12 +00:00
John
36730dd6e2 Add new file 2021-04-03 07:23:26 +00:00
John
0122589138 Add new file 2021-04-03 07:22:41 +00:00
John
e82b1c4524 Add new file 2021-04-03 07:21:40 +00:00
John
8a0ae86b28 Add new file 2021-04-03 07:20:17 +00:00
John
d406277247 Add new file 2021-04-03 07:19:33 +00:00
John
5e5015c552 Add new file 2021-04-03 07:18:49 +00:00
John
22c46a50f0 Add new file 2021-04-03 07:17:13 +00:00
Michael Kamensky
7c9404864a Merge branch 'issue1789' into 'master'
goblin_welder.txt - improve StackDesc

Closes #1789

See merge request core-developers/forge!4355
2021-04-03 04:46:20 +00:00
Michael Kamensky
82752f55d1 Merge branch 'card-fixes' into 'master'
Fix AltCost NPE

See merge request core-developers/forge!4356
2021-04-03 04:44:36 +00:00
Adam Pantel
50a7643610 Fix AltCost NPE 2021-04-02 21:37:38 -04:00
Northmoc
a85c84096f necrotic_fumes.txt remove extra spaces 2021-04-02 20:33:43 -04:00
Northmoc
7edda12548 selfless_glyphweaver_deadly_vanity.txt 2021-04-02 20:32:47 -04:00
Northmoc
d91f1df9d8 blex_vexing_pest_search_for_blex.txt 2021-04-02 20:05:47 -04:00
Northmoc
7c2f9e529c goblin_welder.txt - improve StackDesc 2021-04-02 19:13:03 -04:00
Northmoc
d7ca06c599 CostAdjustment.java support UnlessValidTarget param for Mavinda 2021-04-02 18:41:42 -04:00
Northmoc
f4ee0441ea mavinda_students_advocate.txt 2021-04-02 18:40:21 -04:00
Michael Kamensky
7b28ea1fa3 Merge branch 'Williams-master-patch-83252' into 'master'
STX More Cards 02/04

See merge request core-developers/forge!4345
2021-04-02 19:10:35 +00:00
John
e02da44288 Add new file 2021-04-02 17:58:40 +00:00
John
e836880a39 Add new file 2021-04-02 17:46:34 +00:00
Anthony Calosa
2ef00808b7 Merge branch 'kevlahnota-master-patch-77063' into 'master'
update, add alias to SLD

See merge request core-developers/forge!4352
2021-04-02 16:36:09 +00:00
Anthony Calosa
1dca52e8cd update, add alias to SLD
Should fix some decks that uses "PSLD" set code before (ie. An unsupported card was requested: "Teferi, Time Raveler" from "PSLD". We're sorry, but you cannot use this card yet.)
2021-04-02 16:34:53 +00:00
John
a4f7bd4d2b Add new file 2021-04-02 16:02:33 +00:00
John
5740917a11 Update promising_duskmage.txt 2021-04-02 14:19:26 +00:00
John
0790220a44 Update novice_dissector.txt 2021-04-02 14:18:57 +00:00
John
462459c6c2 Update karok_wrangler.txt 2021-04-02 14:18:29 +00:00
Michael Kamensky
eee160c379 Merge branch 'gorm' into 'master'
Gorm the Great and support

See merge request core-developers/forge!4349
2021-04-02 14:09:30 +00:00
Michael Kamensky
96c1ce5803 Merge branch 'stackdesc' into 'master'
Use StackDescription for AltCosts

See merge request core-developers/forge!4344
2021-04-02 14:09:03 +00:00
Michael Kamensky
6dc705450b Merge branch 'master' into 'master'
Better fix for CloneAi

See merge request core-developers/forge!4351
2021-04-02 14:08:45 +00:00
Michael Kamensky
e9f5039817 - Better fix for CloneAi 2021-04-02 17:07:07 +03:00
Michael Kamensky
4a7d0222a2 Merge branch 'master' into 'master'
Fix AI logic for ETB CloneAi with Choices

See merge request core-developers/forge!4350
2021-04-02 14:02:43 +00:00
Michael Kamensky
63e7803ea4 - Fix AI logic for ETB CloneAi with Choices 2021-04-02 17:01:59 +03:00
John
c522710f7f Add new file 2021-04-02 12:35:47 +00:00
John
d9be2b2437 Add new file 2021-04-02 12:30:26 +00:00
John
caeaefcc6a Add new file 2021-04-02 12:10:33 +00:00
John
6048161924 Add new file 2021-04-02 12:03:44 +00:00
John
4348bc20eb Add new file 2021-04-02 11:55:49 +00:00
tool4EvEr
bff6d43701 Gorm the Great and support 2021-04-02 13:55:36 +02:00
John
944fc70ff4 Add new file 2021-04-02 11:50:49 +00:00
John
a75a148278 Add new file 2021-04-02 10:35:42 +00:00
John
a378eefb7b Add new file 2021-04-02 10:28:31 +00:00
Bug Hunter
9782705e06 Merge branch 'fixed' into 'master'
Fix typos

See merge request core-developers/forge!4348
2021-04-02 10:26:55 +00:00
tool4EvEr
4cd47da3f4 Fix typos 2021-04-02 12:25:54 +02:00
Bug Hunter
65776d8401 Merge branch 'Williams-master-patch-28392' into 'master'
Fix Arrogant Poet

See merge request core-developers/forge!4347
2021-04-02 10:25:45 +00:00
John
1c5c5b3429 Update arrogant_poet.txt 2021-04-02 10:23:33 +00:00
John
284d51918f Update tenured_inkcaster.txt 2021-04-02 10:22:31 +00:00
Anthony Calosa
06a1a49a73 Merge branch 'kevlahnota-master-patch-25321' into 'master'
Update tenured_inkcaster.txt

See merge request core-developers/forge!4346
2021-04-02 10:20:49 +00:00
Anthony Calosa
1c2c72ca7c Update tenured_inkcaster.txt 2021-04-02 10:20:37 +00:00
John
f2a28a34fe Update tome_shredder.txt 2021-04-02 09:33:14 +00:00
John
34e6eedf23 Add new file 2021-04-02 09:12:44 +00:00
John
c1042d15b5 Add new file 2021-04-02 09:04:14 +00:00
John
c40f258725 Add new file 2021-04-02 08:57:31 +00:00
John
8619c180de Add new file 2021-04-02 08:49:24 +00:00
John
1e9e5c5956 Add new file 2021-04-02 08:43:21 +00:00
tool4EvEr
095769b7cd Use StackDescription for AltCosts 2021-04-02 10:20:22 +02:00
Anthony Calosa
0740115724 Merge branch 'master' into 'master'
Remove Unused Import

See merge request core-developers/forge!4343
2021-04-02 07:52:46 +00:00
Anthony Calosa
862f2080ea Remove Unused Import 2021-04-02 15:51:46 +08:00
Anthony Calosa
2ed264b2b1 Merge branch 'new-cards-2' into 'master'
Add SpellCastOrCopy trigger

See merge request core-developers/forge!4338
2021-04-02 07:44:56 +00:00
Anthony Calosa
e9c2ea0e87 Merge branch 'master' into 'master'
Skip loading filtered card

See merge request core-developers/forge!4342
2021-04-02 06:34:41 +00:00
Anthony Calosa
79cea28630 Skip loading filtered card 2021-04-02 14:31:21 +08:00
Adam Pantel
ec6ed8cea5 Remove redundant checks 2021-04-02 00:31:49 -04:00
Michael Kamensky
eab7a03c77 Merge branch 'Williams-master-patch-02407' into 'master'
STX Vanishing Verse

See merge request core-developers/forge!4336
2021-04-02 04:08:48 +00:00
Michael Kamensky
970d82e48e Merge branch 'fuse' into 'master'
Fuse: add keyword to card text

See merge request core-developers/forge!4337
2021-04-02 04:08:27 +00:00
Michael Kamensky
a9ad5169f2 Merge branch 'squirrel' into 'master'
form_of_the_squirrel.txt fixes

See merge request core-developers/forge!4340
2021-04-02 04:08:12 +00:00
Michael Kamensky
27e0b3e62f Merge branch 'stx_1' into 'master'
STX - 1 Apr (no foolin)

See merge request core-developers/forge!4335
2021-04-02 04:07:30 +00:00
Michael Kamensky
3dfa2cd397 Merge branch 'master' into 'master'
update simplified chinese translation

See merge request core-developers/forge!4341
2021-04-02 04:06:11 +00:00
Michael Kamensky
782eca7b95 Merge branch 'new-cards' into 'master'
STX cards

See merge request core-developers/forge!4334
2021-04-02 04:06:00 +00:00
CCTV-1
17c6e46e8d update simplified chinese translation 2021-04-02 11:40:30 +08:00
Northmoc
684b3d2b2b form_of_the_squirrel.txt fixes 2021-04-01 22:25:05 -04:00
Northmoc
e0c4ee102c mastery description updates 2021-04-01 22:17:13 -04:00
Northmoc
0766a4b76d devastating_mastery.txt 2021-04-01 22:16:47 -04:00
Northmoc
e16bd40fd3 verdant_mastery.txt 2021-04-01 22:16:31 -04:00
Hans Mackowiak
479f72f160 AbilityUtils: add Valid to xCount to fix Mana Echoes 2021-04-02 00:53:46 +02:00
John
56bfb32ad4 Add new file 2021-04-01 21:13:20 +00:00
Adam Pantel
45dbb08993 STX cards 2021-04-01 17:09:43 -04:00
Bug Hunter
5030f6e901 Merge branch 'typo' into 'master'
Fix description

See merge request core-developers/forge!4339
2021-04-01 20:44:12 +00:00
tool4EvEr
e64003a22f Fix description 2021-04-01 22:43:54 +02:00
Adam Pantel
3abdeee850 Combine into one class 2021-04-01 16:23:33 -04:00
Adam Pantel
dfec2dc308 Add SpellCastOrCopy trigger 2021-04-01 15:36:51 -04:00
John
abede2f979 Add new file 2021-04-01 19:21:50 +00:00
John
695a0dac40 Add new file 2021-04-01 19:13:14 +00:00
John
266e2e13cf Add new file 2021-04-01 19:07:41 +00:00
Northmoc
79a81c86bb add AI hint 2021-04-01 14:47:29 -04:00
tool4EvEr
20f7bf5ebf Fuse: add keyword to card text 2021-04-01 20:46:31 +02:00
Northmoc
bc3cd33a30 fervent_mastery.txt 2021-04-01 14:41:20 -04:00
John
5eb3902854 Add new file 2021-04-01 18:27:25 +00:00
Northmoc
eb75911d50 baleful_mastery.txt 2021-04-01 12:26:48 -04:00
Michael Kamensky
81b352c888 Merge branch 'ai-multiple-choice' into 'master'
Add basic Multiple Choice AI, tweak Brain in a Jar AI. Also fix Multiple Choice being uncastable.

See merge request core-developers/forge!4330
2021-04-01 15:47:46 +00:00
Michael Kamensky
4b64dff6dd Merge branch 'fixdesc' into 'master'
Some fixes

See merge request core-developers/forge!4333
2021-04-01 15:47:29 +00:00
Michael Kamensky
ea2eb39975 Merge branch 'master' into 'master'
Strixhaven fun with these new cards

See merge request core-developers/forge!4331
2021-04-01 15:46:20 +00:00
Michael Kamensky
c3d64a7d2a Merge branch 'Williams-master-patch-81587' into 'master'
STX Some more cards

See merge request core-developers/forge!4332
2021-04-01 15:46:09 +00:00
Northmoc
4049a66c45 tamiyo_collector_of_tales.txt add ValidCards to +1 2021-04-01 11:33:45 -04:00
John
b06749aff9 Add new file 2021-04-01 13:29:39 +00:00
John
6fa418b43b Add new file 2021-04-01 12:30:26 +00:00
John
831786f55e Add new file 2021-04-01 12:23:41 +00:00
Michael Kamensky
952a9b69e1 Merge branch 'Williams-master-patch-62846' into 'master'
Some STX Cards 01/04

See merge request core-developers/forge!4329
2021-04-01 11:22:59 +00:00
Michael Kamensky
8d367a1167 - Add basic Multiple Choice AI
- Fix Brain in a Jar AI, move it to SpecialCardAi
2021-04-01 14:06:53 +03:00
John
6cb366a978 Add new file 2021-04-01 10:55:51 +00:00
John
aa99373d2d Add new file 2021-04-01 10:47:01 +00:00
John
a6633365ec Add new file 2021-04-01 10:46:11 +00:00
John
cac0f46d1c Add new file 2021-04-01 10:44:47 +00:00
Hythonia
3e3b14779e STX: Golden Ratio, Strixhaven Stadium and more 2021-04-01 11:26:34 +02:00
Michael Kamensky
225767acab Merge branch 'stx_31' into 'master'
STX - 31 Mar

See merge request core-developers/forge!4326
2021-04-01 08:06:10 +00:00
Michael Kamensky
7d1f31247e Merge branch 'stxed' into 'master'
New/updated edition files

See merge request core-developers/forge!4325
2021-04-01 08:04:08 +00:00
Michael Kamensky
5fe1de5f7f Merge branch 'multiple' into 'master'
Multiple Choice

See merge request core-developers/forge!4320
2021-04-01 08:02:39 +00:00
Michael Kamensky
0ac4f98ffa Merge branch 'practical' into 'master'
Practical Research and support

See merge request core-developers/forge!4323
2021-04-01 08:02:02 +00:00
Anthony Calosa
ebd3c72170 Merge branch 'master' into 'master'
fix check

See merge request core-developers/forge!4328
2021-04-01 07:54:41 +00:00
Anthony Calosa
839a91d144 fix check 2021-04-01 15:53:10 +08:00
Anthony Calosa
5c3df787bb Merge branch 'kevlahnota-master-patch-83054' into 'master'
Update blood_of_the_martyr.txt

See merge request core-developers/forge!4327
2021-04-01 06:30:26 +00:00
Anthony Calosa
daeec63b71 Update blood_of_the_martyr.txt 2021-04-01 06:30:09 +00:00
Northmoc
1aea0aa24c archway_commons.txt 2021-03-31 22:08:50 -04:00
Northmoc
7b53957e55 kasmina AI tweak 2021-03-31 22:04:08 -04:00
Northmoc
09c7af4cc4 manifestation_sage.txt 2021-03-31 22:03:49 -04:00
Northmoc
7ed9526828 leonin_lightscribe.txt 2021-03-31 21:53:09 -04:00
Northmoc
27c61f1596 hall_of_oracles.txt 2021-03-31 21:47:45 -04:00
Northmoc
ca8e4d19bf quandrix_campus.txt 2021-03-31 21:27:04 -04:00
Northmoc
938114f942 Strixhaven School of Mages.txt update 2021-03-31 17:10:58 -04:00
Northmoc
b7c61baafb Commander 2021.txt edition file 2021-03-31 17:10:08 -04:00
Northmoc
5850e271c3 moorland_haunt.txt clean up 2021-03-31 16:57:29 -04:00
Northmoc
7ab15776df ecological_appreciation.txt 2021-03-31 16:51:17 -04:00
Anthony Calosa
7f0b819993 Merge branch 'master' into 'master'
[Mobile] preload ItemPool and fix Planar Conquest new game

See merge request core-developers/forge!4324
2021-03-31 18:35:35 +00:00
Anthony Calosa
c0af1fa1eb [Mobile] preload ItemPool and fix Planar Conquest new game 2021-04-01 02:34:14 +08:00
Northmoc
316eb681f6 add AI hint 2021-03-31 10:09:28 -04:00
Northmoc
2a841e4ae2 AI multi UnlessType support 2021-03-31 09:59:54 -04:00
Northmoc
0033de0831 human multi UnlessType support 2021-03-31 09:58:54 -04:00
Northmoc
f70af84e16 practical_research.txt 2021-03-31 09:32:56 -04:00
Michael Kamensky
d9571f3c4d Merge branch 'Williams-master-patch-43202' into 'master'
STX some more cards

See merge request core-developers/forge!4317
2021-03-31 09:59:08 +00:00
Michael Kamensky
fda2f1ff15 Merge branch 'refactor_loadnonlegal' into 'master'
refactor enable non legal cards

See merge request core-developers/forge!4322
2021-03-31 09:58:54 +00:00
John
0d4824b7e0 Update sudden_breakthrough.txt 2021-03-31 07:45:18 +00:00
Hans Mackowiak
0cd376e039 CopySpellAbilityEffect: better description for optional 2021-03-31 09:26:45 +02:00
Hans Mackowiak
101c953020 Culmination of studies: better use of Remembered 2021-03-31 09:18:24 +02:00
John
46bf18cbfa Update resculpt.txt 2021-03-31 07:09:08 +00:00
Anthony Calosa
5348cd3f22 refactor enable non legal cards 2021-03-31 14:01:25 +08:00
Michael Kamensky
9591ed744b Merge branch 'stx_30' into 'master'
STX - 30 Mar

See merge request core-developers/forge!4316
2021-03-31 04:51:15 +00:00
Michael Kamensky
efed8bbd4b Merge branch 'new-cards' into 'master'
STX cards

See merge request core-developers/forge!4319
2021-03-31 04:50:11 +00:00
Michael Kamensky
7e0025d3fc Merge branch 'sym-master' into 'master'
Master Symmetrist

See merge request core-developers/forge!4308
2021-03-31 04:49:11 +00:00
Michael Kamensky
b016b56a30 Merge branch 'typo' into 'master'
typo

See merge request core-developers/forge!4321
2021-03-31 04:48:57 +00:00
Michael Kamensky
167d577f4d Merge branch 'gold' into 'master'
Fix Bound in Gold typos

See merge request core-developers/forge!4318
2021-03-31 04:48:50 +00:00
Northmoc
8888c97e07 multiple_choice.txt 2021-03-30 23:01:05 -04:00
Northmoc
b99e1f7b28 support for second SVar check 2021-03-30 22:59:59 -04:00
Adam Pantel
39b09c5b25 Symmetry Master 2021-03-30 19:31:41 -04:00
Adam Pantel
86842735bf STX cards 2021-03-30 18:37:33 -04:00
Northmoc
05cf67d3e1 prompt support for Wandering Archaic 2021-03-30 18:19:00 -04:00
Northmoc
9f10d1b923 wandering_archaic_explore_the_vastlands.txt 2021-03-30 18:19:00 -04:00
Northmoc
db66c6cde4 culmination_of_studies.txt 2021-03-30 18:19:00 -04:00
Northmoc
0eaa99a91a prismari_campus.txt 2021-03-30 18:18:59 -04:00
Northmoc
11de057064 extus_oriq_overlord_awaken_the_blood_avatar.txt and token 2021-03-30 18:18:59 -04:00
Northmoc
d206ed5c0b tanazir_quandrix.txt 2021-03-30 18:18:58 -04:00
Northmoc
6dd0275e5a galazeth_prismari.txt 2021-03-30 18:18:58 -04:00
Hans Mackowiak
cefaa78832 Semester's End: needs to be ReplacementEffect like Spark Double 2021-03-30 22:52:10 +02:00
John
7253c56086 Add new file 2021-03-30 20:27:39 +00:00
tool4EvEr
38dc4274ff Fix Bound in Gold typos 2021-03-30 21:59:30 +02:00
John
833a5bd974 Update sudden_breakthrough.txt 2021-03-30 19:43:18 +00:00
John
2443772e78 Add new file 2021-03-30 19:41:50 +00:00
John
243a24c7f2 Add new file 2021-03-30 19:34:22 +00:00
Northmoc
1166297dda fetch counter 2021-03-30 15:29:33 -04:00
Anthony Calosa
58bfc4b36c Merge branch 'tweaks' into 'master'
Tweaks

See merge request core-developers/forge!4315
2021-03-30 17:53:20 +00:00
Anthony Calosa
1b07c8838f Merge branch 'kevlahnota-master-patch-57147' into 'master'
fix NPE

See merge request core-developers/forge!4314
2021-03-30 17:28:31 +00:00
Anthony Calosa
6592694778 fix NPE 2021-03-30 17:28:08 +00:00
Northmoc
08d5735af9 one SubAbility per line 2021-03-30 13:22:07 -04:00
Northmoc
4cd69a52f6 better prompt 2021-03-30 13:21:13 -04:00
Michael Kamensky
542bab816f Merge branch 'new-cards' into 'master'
STX cards

See merge request core-developers/forge!4306
2021-03-30 17:19:24 +00:00
Michael Kamensky
357be5296f Merge branch 'Williams-master-patch-83839' into 'master'
STX some cards 30/03

See merge request core-developers/forge!4310
2021-03-30 17:18:19 +00:00
Hans Mackowiak
57816a4f3a fixup DestroyAi use getMaxXValue 2021-03-30 19:08:15 +02:00
John
7486eb640f Add new file 2021-03-30 16:51:31 +00:00
John
e946e23360 Update illustrious_historian.txt 2021-03-30 16:47:00 +00:00
Adam Pantel
e233eed8eb STX cards 2021-03-30 12:44:14 -04:00
Michael Kamensky
80ac8906b3 Merge branch 'stx_29' into 'master'
STX - 29 Mar

See merge request core-developers/forge!4302
2021-03-30 16:24:46 +00:00
Michael Kamensky
ee9cb656fc Merge branch 'master' into 'master'
STX: A couple more cards

See merge request core-developers/forge!4305
2021-03-30 16:23:44 +00:00
Michael Kamensky
cfe9f8e6e6 Merge branch 'master' into 'master'
Fix Heliod's Intervention AI

See merge request core-developers/forge!4313
2021-03-30 16:23:24 +00:00
Michael Kamensky
72f768cca4 - Fix Heliod's Intervention AI 2021-03-30 19:22:46 +03:00
Northmoc
9d0206c432 lorehold_excavation.txt AI fix 2021-03-30 12:19:35 -04:00
Anthony Calosa
53bc91d399 Merge branch 'master' into 'master'
refactor CardDB

See merge request core-developers/forge!4312
2021-03-30 15:44:06 +00:00
Anthony Calosa
ca63763d04 refactor CardDB
use filterentries instead of declaring maps
2021-03-30 23:42:33 +08:00
John
ac7589a3ff Add new file 2021-03-30 15:03:48 +00:00
John
d752442aa3 Add new file 2021-03-30 14:48:53 +00:00
Anthony Calosa
930c27a890 Merge branch 'Williams-master-patch-81518' into 'master'
Update returned_pastcaller.txt

See merge request core-developers/forge!4311
2021-03-30 11:24:28 +00:00
John
2caec7be0e Update returned_pastcaller.txt 2021-03-30 10:44:50 +00:00
John
a7cfe98ca3 Update illustrious_historian.txt 2021-03-30 10:03:10 +00:00
John
bad5f6f431 Update illustrious_historian.txt 2021-03-30 10:02:51 +00:00
John
d0116c852c Add new file 2021-03-30 10:01:56 +00:00
Hythonia
3f3ce39b30 STX: Yet more cards 2021-03-30 10:02:08 +02:00
Bug Hunter
8a5692e9df Merge branch 'TRT-master-patch-00357' into 'master'
ActivateAbilityAi: small revert/align for multiplayer

See merge request core-developers/forge!4309
2021-03-30 07:58:56 +00:00
Bug Hunter
5cf96b1154 Update forge-ai/src/main/java/forge/ai/ability/ActivateAbilityAi.java 2021-03-30 07:56:46 +00:00
Michael Kamensky
2d392de9ef Merge branch 'spellAbilityAdditionalAbilities' into 'master'
SpellAbility: use SpellAbility for additionalAbilities so Trigger can have cost

See merge request core-developers/forge!4303
2021-03-30 04:47:45 +00:00
Hans Mackowiak
f53aff1882 SpellAbility: use SpellAbility for additionalAbilities so Trigger can have cost 2021-03-30 04:47:44 +00:00
Michael Kamensky
0cf6cbf3bc Merge branch 'proliferate' into 'master'
Fix AI killing itself with poison + proliferate

See merge request core-developers/forge!4307
2021-03-30 04:12:46 +00:00
Michael Kamensky
28b428a6f1 Merge branch 'Williams-master-patch-92703' into 'master'
STX more cards

See merge request core-developers/forge!4300
2021-03-30 04:12:10 +00:00
Northmoc
d311ced441 campus_guide.txt 2021-03-29 19:10:17 -04:00
tool4EvEr
3ccce18f23 Fix AI killing itself with poison + proliferate 2021-03-29 23:27:38 +02:00
Northmoc
0ecd719799 curate.txt 2021-03-29 17:12:12 -04:00
Northmoc
5438e20b01 lorehold_campus.txt 2021-03-29 15:10:42 -04:00
Northmoc
b5f72ecc5c hofri_ghostforge.txt 2021-03-29 14:06:35 -04:00
Hythonia
dd20c8caf0 STX: Some more cards 2021-03-29 19:49:27 +02:00
Northmoc
c3b96e4f68 illuminate_history.txt 2021-03-29 13:09:51 -04:00
Northmoc
b3132aa757 reconstruct_history.txt 2021-03-29 13:09:50 -04:00
Northmoc
7685e3233f blade_historian.txt 2021-03-29 13:09:50 -04:00
Northmoc
ee3e66580d lorehold_excavation.txt 2021-03-29 13:09:50 -04:00
Northmoc
26cd2acbcd velomachus_lorehold.txt 2021-03-29 13:09:49 -04:00
Northmoc
0c3b099265 radiant_scrollwielder.txt 2021-03-29 13:09:49 -04:00
Bug Hunter
6971d320ae Merge branch 'fixRemembered' into 'master'
Fix RememberDiscarded

See merge request core-developers/forge!4304
2021-03-29 17:03:48 +00:00
John
2dd6305e54 Add new file 2021-03-29 17:02:42 +00:00
tool4EvEr
baf5479d0b Fix RememberDiscarded 2021-03-29 18:52:13 +02:00
Hythonia
c57be04143 STX: Some cards 2021-03-29 18:40:17 +02:00
John
f1030c2f10 Add new file 2021-03-29 16:31:09 +00:00
John
1f1639e661 Update quintorius_field_historian.txt 2021-03-29 16:10:06 +00:00
John
04d3445834 Add new file 2021-03-29 16:05:58 +00:00
John
1559a14e39 Add new file 2021-03-29 15:58:22 +00:00
John
fbbc6aa005 Update quintorius_field_historian.txt 2021-03-29 15:31:58 +00:00
John
9c573b9bfd Update returned_pastcaller.txt 2021-03-29 15:01:17 +00:00
John
96bbe2c7dc Update returned_pastcaller.txt 2021-03-29 14:59:55 +00:00
John
ec9f6b82d9 Update quintorius_field_historian.txt 2021-03-29 14:58:26 +00:00
John
2c120c3c45 Add new file 2021-03-29 14:58:03 +00:00
John
b911f5c45b Update returned_pastcaller.txt 2021-03-29 14:56:38 +00:00
John
6366a22ff3 Update returned_pastcaller.txt 2021-03-29 14:55:17 +00:00
John
5fc819d8f5 Add new file 2021-03-29 14:43:15 +00:00
John
df9d1ddcbf Add new file 2021-03-29 14:00:44 +00:00
Anthony Calosa
eed7b282d3 Merge branch 'master' into 'master'
Refactor carddb for Deck Editor/Workshop Catalog

Closes #1436

See merge request core-developers/forge!4298
2021-03-29 13:44:55 +00:00
Anthony Calosa
fc6237cd29 update advance filter
support alternate parts limited to card names, keywords, rules text, types and subtypes
2021-03-29 21:26:14 +08:00
John
c473e634e0 Add new file 2021-03-29 12:20:38 +00:00
John
28866fc1dc Add new file 2021-03-29 12:17:00 +00:00
John
8028e8ca12 Add new file 2021-03-29 12:13:42 +00:00
Anthony Calosa
28d1cc2055 Merge remote-tracking branch 'core/master' 2021-03-29 18:30:55 +08:00
Anthony Calosa
cffd99b68d update advance filter 2021-03-29 18:30:09 +08:00
Michael Kamensky
ac92a92090 Merge branch 'targetselection' into 'master'
TargetSelection: Fix stack peeking detection

See merge request core-developers/forge!4295
2021-03-29 08:00:21 +00:00
Bug Hunter
28dbb309cf TargetSelection: Fix stack peeking detection 2021-03-29 08:00:21 +00:00
Michael Kamensky
7b585aaa05 Merge branch 'descriptions' into 'master'
Fix alternate state descriptions when viewed from original

See merge request core-developers/forge!4277
2021-03-29 07:59:43 +00:00
Bug Hunter
82f0c85bd0 Fix alternate state descriptions when viewed from original 2021-03-29 07:59:43 +00:00
Michael Kamensky
1c0a7d8475 Merge branch 'Williams-master-patch-39368' into 'master'
Update adrix_and_nev_twincasters.txt

See merge request core-developers/forge!4296
2021-03-29 07:58:48 +00:00
Michael Kamensky
5f63b5f907 Merge branch 'card-fixes' into 'master'
Inzerva doesn't target

See merge request core-developers/forge!4297
2021-03-29 07:58:38 +00:00
Anthony Calosa
7459cd65d9 unused variable 2021-03-29 15:23:57 +08:00
Anthony Calosa
0b61ddaff7 fix advance filter for adventure and flip 2021-03-29 15:23:03 +08:00
Anthony Calosa
d554a7ee97 add getUniqueCardsNoAlt for mobile 2021-03-29 13:35:20 +08:00
Anthony Calosa
3d1e130b1e Merge remote-tracking branch 'core/master' 2021-03-29 12:47:11 +08:00
Anthony Calosa
a63e008f84 Merge branch 'kevlahnota-master-patch-97416' into 'master'
fix cards

See merge request core-developers/forge!4299
2021-03-29 04:46:29 +00:00
Anthony Calosa
d76ae27611 fix cards 2021-03-29 04:46:29 +00:00
Anthony Calosa
bf00d79e23 refactor carddb for Deck Editor/Workshop Catalog 2021-03-29 11:19:26 +08:00
Adam Pantel
c08775f3e3 Inzerva doesn't target 2021-03-28 22:03:12 -04:00
John
d94ceaf492 Update adrix_and_nev_twincasters.txt 2021-03-28 21:57:32 +00:00
Michael Kamensky
9802cb62d9 Merge branch 'update_word_of_command' into 'master'
Update Word of Command mana payment check conditions

See merge request core-developers/forge!4291
2021-03-28 12:26:30 +00:00
Anthony Calosa
2c4f90b31c Merge branch 'master' into 'master'
Restrict Deck Editor cards

See merge request core-developers/forge!4294
2021-03-28 09:59:21 +00:00
Anthony Calosa
163d95800b Restrict Deck Editor cards
- fix duplicate cards
2021-03-28 17:55:23 +08:00
Michael Kamensky
0662d44a86 Merge branch 'Williams-master-patch-67517' into 'master'
Update Strixhaven Mystical Archive.txt

See merge request core-developers/forge!4293
2021-03-28 09:36:11 +00:00
Michael Kamensky
c3792191a2 Merge branch 'Williams-master-patch-03072' into 'master'
STX Lesson Cards

See merge request core-developers/forge!4289
2021-03-28 09:36:01 +00:00
Michael Kamensky
97ff18c3e2 Merge branch 'uvilda' into 'master'
Uvilda

See merge request core-developers/forge!4288
2021-03-28 09:35:44 +00:00
Michael Kamensky
d069252f76 Merge branch 'new-cards' into 'master'
Zaffai

See merge request core-developers/forge!4290
2021-03-28 09:35:39 +00:00
John
54fda151be Update Strixhaven Mystical Archive.txt 2021-03-28 08:43:02 +00:00
John
04c4cf4426 Update osgir_the_Reconstructor.txt 2021-03-28 08:04:23 +00:00
John
7c81fe3339 Add new file 2021-03-28 08:03:38 +00:00
John
330862e6c0 Update adrix_and_nev_twincasters.txt 2021-03-28 07:41:09 +00:00
John
ce309a384e Update environmental_sciences.txt 2021-03-28 07:38:14 +00:00
Adam Pantel
6bc01c3472 Uvilda 2021-03-28 03:14:37 -04:00
Adam Pantel
9ecf3e11bf Zaffai 2021-03-28 03:12:59 -04:00
Bug Hunter
7da306eb65 Merge branch 'fixDraw' into 'master'
Fix NPE when targeting different players hidden zone

See merge request core-developers/forge!4292
2021-03-28 07:08:44 +00:00
tool4EvEr
3d9d6d45fe Fix NPE when targeting different players hidden zone 2021-03-28 09:05:31 +02:00
Lyu Zong-Hong
7fc85a47be Update Word of Command mana payment check conditions 2021-03-28 15:09:01 +09:00
John
6accf7fd6c Update environmental_sciences.txt 2021-03-27 22:42:37 +00:00
John
f619155667 Update introduction_to_anihilation.txt 2021-03-27 19:34:23 +00:00
John
89d50af521 Update expanded_anatomy.txt 2021-03-27 19:32:18 +00:00
John
fe3a111d6f Update adrix_and_nev_twincasters.txt 2021-03-27 19:31:43 +00:00
John
75b6b6316b Add new file 2021-03-27 19:30:57 +00:00
John
f64822a18b Add new file 2021-03-27 19:29:45 +00:00
John
e566fb2d57 Add new file 2021-03-27 19:29:14 +00:00
John
9f5931509c Add new file 2021-03-27 19:28:39 +00:00
John
deb7ee8c3a Add new file 2021-03-27 19:27:13 +00:00
Hans Mackowiak
6a56e29474 Breena with better Choices 2021-03-27 17:19:39 +01:00
Michael Kamensky
c79ccd7e23 Merge branch 'stx_27' into 'master'
STX - 27 Mar

See merge request core-developers/forge!4287
2021-03-27 15:54:51 +00:00
Michael Kamensky
36e395e3a3 Merge branch 'master' into 'master'
STX cards + Abundant Harvest

See merge request core-developers/forge!4285
2021-03-27 15:54:25 +00:00
Northmoc
c0b106c7ef valentin_dean_of_the_vein_lisette_dean_of_the_root.txt 2021-03-27 11:40:28 -04:00
Hythonia
23c8c5da19 Will fix 2021-03-27 15:37:30 +01:00
Hythonia
883f1a7cf8 DeckHas/DeckHints 2021-03-27 15:35:37 +01:00
Michael Kamensky
b2ff43bd9b Merge branch 'twosat-master-patch-55523' into 'master'
Update de-DE.properties

See merge request core-developers/forge!4284
2021-03-27 14:06:30 +00:00
Michael Kamensky
471bb1cacd Merge branch 'fix_mutate_mdfc' into 'master'
Fix crash when mutate under an MDFC backside creature.

See merge request core-developers/forge!4286
2021-03-27 14:05:08 +00:00
Northmoc
de30329a58 prismari_apprentice.txt 2021-03-27 09:23:51 -04:00
Lyu Zong-Hong
c990c8af56 Fix crash when mutate under an MDFC backside creature. 2021-03-27 22:00:20 +09:00
Hythonia
2dbd099c68 STX: Inkling type 2021-03-27 13:55:54 +01:00
Hythonia
25da363d56 STX: Rowan // Will 2021-03-27 13:50:10 +01:00
Hythonia
a957566fc9 STX: Lorehold Apprentice 2021-03-27 13:28:45 +01:00
Michael Kamensky
97db42427e Merge branch 'shaileEnterUnder' into 'master'
Card: add turnInController to keep track under which control it entered

See merge request core-developers/forge!4282
2021-03-27 12:14:09 +00:00
Hythonia
34fabaab5f STA: Abundant Harvest 2021-03-27 12:33:41 +01:00
Andreas Bendel
11893e244e Update de-DE.properties
some Choose-strings
2021-03-27 11:29:17 +00:00
Hythonia
24f1285a43 Breena and PlayerProperty.java update 2021-03-27 12:02:13 +01:00
Anthony Calosa
2d2e0fd6c0 Merge branch 'kevlahnota-master-patch-21990' into 'master'
set correct imagekey for rollback

See merge request core-developers/forge!4283
2021-03-27 10:54:51 +00:00
Anthony Calosa
5ea2aca8a8 set correct imagekey for rollback 2021-03-27 10:51:53 +00:00
Hans Mackowiak
088b4fa07e Card: add turnInController to keep track under which control it entered 2021-03-27 11:35:52 +01:00
Hans Mackowiak
7dcfbc409c STX: fix Execute$ Execute$ 2021-03-27 10:13:06 +01:00
Michael Kamensky
44bb2ff0f5 Merge branch 'drawing' into 'master'
Move all hidden zone changing after choosing

See merge request core-developers/forge!4256
2021-03-27 04:37:27 +00:00
Bug Hunter
4effb02219 Move all hidden zone changing after choosing 2021-03-27 04:37:27 +00:00
Michael Kamensky
5d7ad6bb81 Merge branch 'master' into 'master'
STX: One Ward, two card

See merge request core-developers/forge!4273
2021-03-27 04:37:13 +00:00
Michael Kamensky
2bef5d7653 Merge branch 'c21_26' into 'master'
some C21 scripts

See merge request core-developers/forge!4274
2021-03-27 04:36:54 +00:00
Michael Kamensky
6b76bec480 Merge branch 'new-cards' into 'master'
Some STX cards

See merge request core-developers/forge!4276
2021-03-27 04:36:31 +00:00
Michael Kamensky
76ab53078e Merge branch 'stx_26' into 'master'
STX - 26 Mar (+Lesson type)

See merge request core-developers/forge!4278
2021-03-27 04:35:36 +00:00
Michael Kamensky
ddf8df9c71 Merge branch 'fixStartDraw' into 'master'
Don't count draws before game started

Closes #1781

See merge request core-developers/forge!4275
2021-03-27 04:34:09 +00:00
Michael Kamensky
b312f28ab6 Merge branch 'add_word_of_command' into 'master'
Add Word of Command

See merge request core-developers/forge!4281
2021-03-27 04:33:42 +00:00
Michael Kamensky
d9e536b789 Merge branch 'shadrix' into 'master'
STX: Shadrix, token, and CharmEffect support

See merge request core-developers/forge!4271
2021-03-27 04:33:01 +00:00
Lyu Zong-Hong
2e768cb77b Add Word of Command 2021-03-27 12:25:20 +09:00
Northmoc
d611dbd386 quandrix_apprentice.txt 2021-03-26 20:30:43 -04:00
Northmoc
8976e238a5 silverquill_apprentice.txt 2021-03-26 20:27:22 -04:00
Northmoc
eb13bb8b6d witherbloom_apprentice.txt 2021-03-26 20:26:05 -04:00
Northmoc
d72eba1602 archmage_emeritus.txt 2021-03-26 20:21:45 -04:00
Northmoc
cd4685c093 eager_first_year.txt 2021-03-26 20:08:19 -04:00
Northmoc
bc3aa56ed9 remove unneeded NoResolvingCheck 2021-03-26 19:50:13 -04:00
Northmoc
903ead9e91 star_pupil.txt 2021-03-26 19:47:33 -04:00
Northmoc
bacbd8baf3 confront_the_past.txt 2021-03-26 19:36:07 -04:00
Bug Hunter
407c742124 Merge branch 'fixNPE' into 'master'
AI: Fix NPE

See merge request core-developers/forge!4280
2021-03-26 21:34:09 +00:00
tool4EvEr
34a91b7e3d Fix NPE 2021-03-26 22:33:23 +01:00
Northmoc
65f4724f87 add Lesson type 2021-03-26 16:56:21 -04:00
Northmoc
afafe46221 pest_summoning.txt and token 2021-03-26 16:55:56 -04:00
Northmoc
ff220113af storm_kiln_artist.txt (script by ?) 2021-03-26 15:28:00 -04:00
Adam Pantel
81c2c482cd Treasure Keeper, Kianne, Plargg 2021-03-26 15:25:10 -04:00
tool4EvEr
afb06baa56 Don't count draws before game started 2021-03-26 20:20:24 +01:00
Northmoc
d25ff61f22 dragonsguard_elite.txt (script by ?) 2021-03-26 15:12:08 -04:00
Northmoc
067f0b3a66 bandaid for CharmEffect.java description builder 2021-03-26 14:44:32 -04:00
Northmoc
0f71efd1e9 willowdusk_essence_seer.txt 2021-03-26 14:39:01 -04:00
Hythonia
351f9c1976 STX: Ward's TriggerDescription 2021-03-26 18:14:27 +01:00
Northmoc
a11eff4d9a add Optional param and prompt to CharmEffect.java 2021-03-26 12:44:11 -04:00
Northmoc
fe6a5a700a add optional Charm prompt 2021-03-26 12:43:34 -04:00
Northmoc
87a994b8ce shadrix_silverquill.txt v2 2021-03-26 12:42:52 -04:00
Hythonia
3f06553604 Merge remote-tracking branch 'origin/master' 2021-03-26 16:34:34 +01:00
Northmoc
e9f765a216 wb_2_1_inkling_flying.txt 2021-03-26 11:29:28 -04:00
Northmoc
532e65f3a0 shadrix_silverquill.txt 2021-03-26 11:29:08 -04:00
Hythonia
b038044347 STX: One Ward, two card 2021-03-26 15:01:59 +01:00
Michael Kamensky
0664d11f55 Merge branch 'master' into 'master'
Basic logic for Professor Onyx

See merge request core-developers/forge!4270
2021-03-26 11:48:04 +00:00
Michael Kamensky
6d00a26530 - Basic logic for Professor Onyx. 2021-03-26 14:38:10 +03:00
Michael Kamensky
a4d28b13ef Merge branch 'AIopponent' into 'master'
AI Multiplayer improvements

See merge request core-developers/forge!4239
2021-03-26 10:58:22 +00:00
Bug Hunter
fd01dc1bb1 AI Multiplayer improvements 2021-03-26 10:58:22 +00:00
Michael Kamensky
fa72d3d0de Merge branch 'master' into 'master'
update simplified chinese translation

See merge request core-developers/forge!4269
2021-03-26 10:57:20 +00:00
Michael Kamensky
f935f57d23 Merge branch 'add_camouflage' into 'master'
Add Camouflage

See merge request core-developers/forge!4268
2021-03-26 10:57:03 +00:00
Michael Kamensky
fbf1e855ad Merge branch 'snarls' into 'master'
STX rare land cycle

See merge request core-developers/forge!4263
2021-03-26 10:56:25 +00:00
Michael Kamensky
50a6037a6d Merge branch 'onyx' into 'master'
professor_onyx.txt

See merge request core-developers/forge!4262
2021-03-26 10:56:10 +00:00
CCTV-1
d15d27defb update simplified chinese translation 2021-03-26 18:40:04 +08:00
Hythonia
7952bdcb48 Revert "ManaEffect rework"
This reverts commit 9db11ce6
2021-03-26 11:37:57 +01:00
Hythonia
dfdf3160d3 Merge remote-tracking branch 'origin/master' 2021-03-26 11:20:36 +01:00
Lyu Zong-Hong
ce4f12179c Add Camouflage 2021-03-26 13:05:04 +09:00
Leandro Doctors
491da787b4 Merge branch 'update-building-docs' into 'master'
doc: update Android building instructions

See merge request core-developers/forge!4265
2021-03-26 02:01:43 +00:00
Leandro Doctors
6dbc7e2704 doc: update Android building instructions 2021-03-25 22:58:15 -03:00
Leandro Doctors
2a39c917ee Merge branch 'mention-hardcoded-value' into 'master'
add FIXME for hardcoded value

See merge request core-developers/forge!4264
2021-03-26 01:51:37 +00:00
Leandro Doctors
3733ba45e2 add FIXME for hardcoded value 2021-03-25 22:50:13 -03:00
Northmoc
2a410b9099 snarls 2021-03-25 20:14:22 -04:00
Northmoc
3ccc7c41e7 professor_onyx.txt v2 2021-03-25 18:13:33 -04:00
Northmoc
d03bc797fc DiscardEffect.java add "RememberDiscardingPlayers" param 2021-03-25 18:13:10 -04:00
Leandro Doctors
5c4f8eb526 Merge branch 'clean-up-pom' into 'master'
Delete obsolete 'developers' tags (pom.xml)

See merge request core-developers/forge!4219
2021-03-25 17:41:42 +00:00
Northmoc
973464f1c4 professor_onyx.txt 2021-03-25 10:56:40 -04:00
Hans Mackowiak
e090ab2825 Merge branch '1779-lingering-hostage-taker-untilhostleavesplay-issue' into 'master'
Resolve "Lingering Hostage Taker (UntilHostLeavesPlay) issue"

Closes #1779

See merge request core-developers/forge!4261
2021-03-25 13:51:07 +00:00
Hans Mackowiak
0413e5ded8 Fix ExiledWith being cleared when successfully added to MagicStack 2021-03-25 14:50:25 +01:00
Michael Kamensky
51c24f6fdf Merge branch 'fix_invasion_plans' into 'master'
Fix Invasion Plans

See merge request core-developers/forge!4260
2021-03-25 13:40:21 +00:00
Lyu Zong-Hong
d1de248894 Fix Invasion Plans 2021-03-25 21:19:28 +09:00
Hans Mackowiak
fdf3e618c2 Merge branch '1778-untilhostleavesplay-missing-a-check-on-hostage-taker' into 'master'
Resolve "UntilHostLeavesPlay missing a check on Hostage Taker"

Closes #1778

See merge request core-developers/forge!4259
2021-03-25 07:52:04 +00:00
Hans Mackowiak
5b0a228f4f SpellAbilityEffect: fix untilHostLeavesPlayCommand to check the until CardCollection 2021-03-25 08:51:35 +01:00
Hythonia
9db11ce690 ManaEffect rework 2021-03-24 20:14:41 +01:00
Leandro Doctors
f9bdf6e181 Delete obsolete 'developers' tags
That information is obsolete. The up to date version can always be obtained form version control.
2021-03-19 11:38:30 -03:00
4610 changed files with 51107 additions and 28503 deletions

2
.gitignore vendored
View File

@@ -32,6 +32,8 @@ bin
gen
*.log
# Ignore macOS Spotlight rubbish
.DS_Store
# TODO: specify what these ignores are for (releasing?)

104
README.md
View File

@@ -1,15 +1,15 @@
# Forge
Gitlab repo is found [here](https://git.cardforge.org/core-developers/forge).
[Official GitLab repo](https://git.cardforge.org/core-developers/forge).
Dev instructions here: [Getting Started](https://www.slightlymagic.net/wiki/Forge:How_to_Get_Started_Developing_Forge) (Somewhat outdated)
Dev instructions here: [Getting Started](https://git.cardforge.org/core-developers/forge/-/wikis/(SM-autoconverted)-how-to-get-started-developing-forge) (Somewhat outdated)
Discord channel [here](https://discordapp.com/channels/267367946135928833/267742313390931968)
# Requirements / Tools
## Requirements / Tools
- Java IDE such as IntelliJ or Eclipse
- Java JDK 8 or later
- you favourite Java IDE (IntelliJ, Eclipse, VSCodium, Emacs, Vi...)
- Java JDK 8 or later (some IDEs such as Eclipse require JDK11+, whereas the Android build currently only works with JDK8)
- Git
- Git client (optional)
- Maven
@@ -18,7 +18,7 @@ Discord channel [here](https://discordapp.com/channels/267367946135928833/267742
- Android SDK (optional: for Android releases)
- RoboVM (optional: for iOS releases) (TBD: Current status of support by libgdx)
# Project Quick Setup
## Project Quick Setup
- Log in to gitlab with your user account and fork the project.
@@ -26,18 +26,18 @@ Discord channel [here](https://discordapp.com/channels/267367946135928833/267742
- Go to the project location on your machine. Run Maven to download all dependencies and build a snapshot. Example for Windows & Linux: `mvn -U -B clean -P windows-linux install`
# Eclipse
## Eclipse
Eclipse includes Maven integration so a separate install is not necessary. For other IDEs, your mileage may vary.
## Project Setup
### Project Setup
- Follow the instructions for cloning from Gitlab. You'll need a Gitlab account setup and an SSH key defined.
- Follow the instructions for cloning from Gitlab. You'll need a Gitlab account setup and an SSH key defined.
If you are on a Windows machine you can use Putty with TortoiseGit for SSH keys. Run puttygen.exe to generate the key -- save the private key and export
the OpenSSH public key. If you just leave the dialog open, you can copy and paste the key from it to your Gitlab profile under
"SSH keys". Run pageant.exe and add the private key generated earlier. TortoiseGit will use this for accessing Gitlab.
- Fork the Forge git repo to your Gitlab account.
- Clone your forked repo to your local machine.
@@ -48,16 +48,16 @@ Eclipse includes Maven integration so a separate install is not necessary. For
- Create a workspace. Go to the workbench. Right-click inside of Package Explorer > Import... > Maven > Existing Maven Projects > Navigate to root path of the local forge repo and
ensure everything is checked > Finish.
- Let Eclipse run through building the project. You may be prompted for resolving any missing Maven plugins -- accept the ones offered. You may see errors appear in the "Problems" tab. These should
be automatically resolved as plug-ins are installed and Eclipse continues the build process. If this is the first time for some plug-in installs, Eclipse may prompt you to restart. Do so. Be patient
for this first time through.
- Once everything builds, all errors should disappear. You can now advance to Project launch.
## Project Launch
### Project Launch
### Desktop
#### Desktop
This is the standard configuration used for releasing to Windows / Linux / MacOS.
@@ -65,7 +65,7 @@ This is the standard configuration used for releasing to Windows / Linux / MacOS
- The familiar Forge splash screen, etc. should appear. Enjoy!
### Mobile (Desktop dev)
#### Mobile (Desktop dev)
This is the configuration used for doing mobile development using the Windows / Linux / MacOS front-end. Knowledge of libgdx is helpful here.
@@ -73,50 +73,49 @@ This is the configuration used for doing mobile development using the Windows /
- A view similar to a mobile phone should appear. Enjoy!
## Eclipse / Android SDK Integration
### Eclipse / Android SDK Integration
Google no longer supports Android SDK releases for Eclipse. That said, it is still possible to build and debug Android platforms.
### Android SDK
#### Android SDK
Reference SO for obtaining a specific release: https://stackoverflow.com/questions/27043522/where-can-i-download-an-older-version-of-the-android-sdk
#### Windows
##### Windows
Download the following archived version of the Android SDK: http://dl-ssl.google.com/android/repository/tools_r25.2.3-windows.zip. Install it somewhere on your machine. This is referenced
in the following instructions as your 'Android SDK Install' path.
#### Linux / Mac OSX
##### Linux / Mac OSX
TBD
### Android Plugin for Eclipse
#### Android Plugin for Eclipse
Google's last plugin release does not work completely with target's running Android 7.0 or later. Download the ADT-24.2.0-20160729.zip plugin
from: https://github.com/khaledev/ADT/releases
Google's last plugin release does not work completely with target's running Android 7.0 or later. Download the ADT-24.2.0-20160729.zip plugin
from: https://github.com/khaledev/ADT/releases
In Eclipse go to: Help > Install New Software... > Add > Name: ADT Update, Click on the "Archive:" button and navigate to the downloaded ADT-24.2.0-20160729.zip file > Add. Install all "Developer Tools". Eclipse
should restart and prompt you to run the SDK Manager. Launch it and continue to the next steps below.
### Android Platform
#### Android Platform
In Eclipse, if the SDK Manager is not already running, go to Window > Android SDK Manager. Install the following options / versions:
- Android SDK Build-tools 26.0.1
- Android 7.1.1 (API 25) SDK Platform
- Google USB Driver 11
- Android 8.0.0 (API 26) SDK Platform
- Google USB Driver (in case your phone is not detected by ADB)
Note that this will populate additional tools in the Android SDK install path extracted above.
### Proguard update
#### Proguard update
The Proguard included with the Android SDK Build-tools is outdated and does not work with Java 1.8. Download Proguard 6.0.3 from https://sourceforge.net/projects/proguard/files/proguard/6.0/.
The Proguard included with the Android SDK Build-tools is outdated and does not work with Java 1.8. Download Proguard 6.0.3 or later (last tested with 7.0.1) from https://github.com/Guardsquare/proguard
- Go to the Android SDK install path. Rename the tools/proguard/ path to tools/proguard-4.7/.
- Go to the Android SDK install path. Rename the tools/proguard/ path to tools/proguard4.7/.
- Extract your Proguard version to the Android SDK install path under tools/. You will need to either rename the dir proguard-<your-version> to proguard/ or, if your filesystem supports it, use a symbolic link (the later is highly recommended), such as `ln -s proguard proguard-<your-version>`.
- Extract Proguard 6.0.3 to the Android SDK install path under tools/. You will need to rename the dir proguard6.0.3/ to proguard/.
### Android Build
#### Android Build
The Eclipse plug-ins do NOT support building things for Android. They do however allow you to use the debugger so you can still set breakpoints and trace
things out. The steps below show how to generate a debug Android build.
@@ -135,7 +134,7 @@ things out. The steps below show how to generate a debug Android build.
Assuming you got this far, you should have an Android forge-android-[version].apk in the forge-gui-android/target path.
### Android Deploy
#### Android Deploy
You'll need to have the Android SDK install path platform-tools/ path in your command search path to easily deploy builds.
@@ -149,37 +148,37 @@ You'll need to have the Android SDK install path platform-tools/ path in your co
- Install the new apk: `adb install forge-android-[version].apk`
### Android Debugging
#### Android Debugging
Assuming the apk is installed, launch it from the device.
In Eclipse, launch the DDMS. Window > Perspective > Open Perspective > Other... > DDMS. You should see the forge app in the list. Highlight the app, click on the green debug button and a
In Eclipse, launch the DDMS. Window > Perspective > Open Perspective > Other... > DDMS. You should see the forge app in the list. Highlight the app, click on the green debug button and a
green debug button should appear next to the app's name. You can now set breakpoints and step through the source code.
## Windows / Linux SNAPSHOT build
### Windows / Linux SNAPSHOT build
SNAPSHOT builds can be built via the Maven integration in Eclipse.
1) Create a Maven build for the forge top-level project. Right-click on the forge project. Run as.. > Maven build...
- On the Main tab, set Goals: clean install, set Profiles: windows-linux
2) Run forge Maven build. If everything built, you should see "BUILD SUCCESS" in the Console View.
The resulting snapshot will be found at: forge-gui-desktop/target/forge-gui-desktop-[version]-SNAPSHOT
# IntelliJ
## IntelliJ
Quick start guide for [setting up the Forge project within IntelliJ](https://git.cardforge.org/core-developers/forge/wikis/intellij-setup).
Quick start guide for [setting up the Forge project within IntelliJ](https://git.cardforge.org/core-developers/forge/-/wikis/Development/intellij-setup).
# Card Scripting
## Card Scripting
Visit [this page](https://www.slightlymagic.net/wiki/Forge_API) for information on scripting.
Visit [this page](https://git.cardforge.org/core-developers/forge/-/wikis/Card-scripting-API/Card-scripting-API) for information on scripting.
Card scripting resources are found in the forge-gui/res/ path.
# General Notes
## General Notes
## Project Hierarchy
### Project Hierarchy
Forge is divided into 4 primary projects with additional projects that target specific platform releases. The primary projects are:
@@ -196,35 +195,34 @@ The platform-specific projects are:
- forge-gui-mobile
- forge-gui-mobile-dev
### forge-ai
#### forge-ai
### forge-core
#### forge-core
### forge-game
#### forge-game
### forge-gui
#### forge-gui
The forge-gui project includes the scripting resource definitions in the res/ path.
### forge-gui-android
#### forge-gui-android
Libgdx-based backend targeting Android. Requires Android SDK and relies on forge-gui-mobile for GUI logic.
### forge-gui-desktop
#### forge-gui-desktop
Java Swing based GUI targeting desktop machines.
Java Swing based GUI targeting desktop machines.
Screen layout and game logic revolving around the GUI is found here. For example, the overlay arrows (when enabled) that indicate attackers and blockers, or the targets of the stack are defined and drawn by this.
### forge-gui-ios
#### forge-gui-ios
Libgdx-based backend targeting iOS. Relies on forge-gui-mobile for GUI logic.
### forge-gui-mobile
#### forge-gui-mobile
Mobile GUI game logic utilizing [libgdx](https://libgdx.badlogicgames.com/) library. Screen layout and game logic revolving around the GUI for the mobile platforms is found here.
### forge-gui-mobile-dev
#### forge-gui-mobile-dev
Libgdx backend for desktop development for mobile backends. Utilizes LWJGL. Relies on forge-gui-mobile for GUI logic.

View File

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

View File

@@ -18,6 +18,7 @@
package forge.ai;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import com.google.common.base.Predicate;
@@ -43,6 +44,7 @@ import forge.game.combat.GlobalAttackRestrictions;
import forge.game.keyword.Keyword;
import forge.game.keyword.KeywordInterface;
import forge.game.player.Player;
import forge.game.player.PlayerPredicates;
import forge.game.spellability.SpellAbility;
import forge.game.trigger.Trigger;
import forge.game.trigger.TriggerType;
@@ -54,7 +56,6 @@ import forge.util.TextUtil;
import forge.util.collect.FCollectionView;
//doesHumanAttackAndWin() uses the global variable AllZone.getComputerPlayer()
/**
* <p>
* ComputerUtil_Attack2 class.
@@ -91,7 +92,7 @@ public class AiAttackController {
public AiAttackController(final Player ai, boolean nextTurn) {
this.ai = ai;
this.defendingOpponent = choosePreferredDefenderPlayer();
this.defendingOpponent = choosePreferredDefenderPlayer(ai);
this.oppList = getOpponentCreatures(this.defendingOpponent);
this.myList = ai.getCreaturesInPlay();
this.attackers = new ArrayList<>();
@@ -107,7 +108,7 @@ public class AiAttackController {
public AiAttackController(final Player ai, Card attacker) {
this.ai = ai;
this.defendingOpponent = choosePreferredDefenderPlayer();
this.defendingOpponent = choosePreferredDefenderPlayer(ai);
this.oppList = getOpponentCreatures(this.defendingOpponent);
this.myList = ai.getCreaturesInPlay();
this.attackers = new ArrayList<>();
@@ -156,13 +157,12 @@ public class AiAttackController {
}
/** Choose opponent for AI to attack here. Expand as necessary. */
private Player choosePreferredDefenderPlayer() {
Player defender = ai.getWeakestOpponent(); //Gets opponent with the least life
public static Player choosePreferredDefenderPlayer(Player ai) {
Player defender = ai.getWeakestOpponent(); //Concentrate on opponent within easy kill range
if (defender.getLife() < 8) { //Concentrate on opponent within easy kill range
return defender;
} else { //Otherwise choose a random opponent to ensure no ganging up on players
defender = ai.getOpponents().get(MyRandom.getRandom().nextInt(ai.getOpponents().size()));
if (defender.getLife() > 8) { //Otherwise choose a random opponent to ensure no ganging up on players
// TODO should we cache the random for each turn? some functions like shouldPumpCard base their decisions on the assumption who will be attacked
return ai.getOpponents().get(MyRandom.getRandom().nextInt(ai.getOpponents().size()));
}
return defender;
}
@@ -393,7 +393,7 @@ public class AiAttackController {
//Calculate the amount of creatures necessary
for (int i = 0; i < list.size(); i++) {
if (!this.doesHumanAttackAndWin(ai, i)) {
if (!doesHumanAttackAndWin(ai, i)) {
blockersNeeded = i;
break;
}
@@ -413,12 +413,10 @@ public class AiAttackController {
final Player opp = this.defendingOpponent;
// Increase the total number of blockers needed by 1 if Finest Hour in
// play
// Increase the total number of blockers needed by 1 if Finest Hour in play
// (human will get an extra first attack with a creature that untaps)
// In addition, if the computer guesses it needs no blockers, make sure
// that
// it won't be surprised by Exalted
// that it won't be surprised by Exalted
final int humanExaltedBonus = opp.countExaltedBonus();
if (humanExaltedBonus > 0) {
@@ -428,8 +426,7 @@ public class AiAttackController {
// total attack = biggest creature + exalted, *2 if Rafiq is in play
int humanBasePower = getAttack(this.oppList.get(0)) + humanExaltedBonus;
if (finestHour) {
// For Finest Hour, one creature could attack and get the
// bonus TWICE
// For Finest Hour, one creature could attack and get the bonus TWICE
humanBasePower = humanBasePower + humanExaltedBonus;
}
final int totalExaltedAttack = opp.isCardInPlay("Rafiq of the Many") ? 2 * humanBasePower
@@ -450,7 +447,6 @@ public class AiAttackController {
return notNeededAsBlockers;
}
// this uses a global variable, which isn't perfect
public final boolean doesHumanAttackAndWin(final Player ai, final int nBlockingCreatures) {
int totalAttack = 0;
int totalPoison = 0;
@@ -624,7 +620,7 @@ public class AiAttackController {
int totalCombatDamage = ComputerUtilCombat.sumDamageIfUnblocked(unblockedAttackers, opp) + trampleDamage;
int totalPoisonDamage = ComputerUtilCombat.sumPoisonIfUnblocked(unblockedAttackers, opp);
if (totalCombatDamage + ComputerUtil.possibleNonCombatDamage(ai) >= opp.getLife()
if (totalCombatDamage + ComputerUtil.possibleNonCombatDamage(ai, opp) >= opp.getLife()
&& !((opp.cantLoseForZeroOrLessLife() || ai.cantWin()) && opp.getLife() < 1)) {
return true;
}
@@ -641,7 +637,7 @@ public class AiAttackController {
if (defs.size() == 1) {
return defs.getFirst();
}
Player prefDefender = (Player) (defs.contains(this.defendingOpponent) ? this.defendingOpponent : defs.get(0));
GameEntity prefDefender = defs.contains(this.defendingOpponent) ? this.defendingOpponent : defs.get(0);
// Attempt to see if there's a defined entity that must be attacked strictly this turn...
GameEntity entity = ai.getMustAttackEntityThisTurn();
@@ -665,7 +661,7 @@ public class AiAttackController {
// 2. attack planeswalkers
List<Card> pwDefending = c.getDefendingPlaneswalkers();
if (!pwDefending.isEmpty()) {
return pwDefending.get(0);
return ComputerUtilCard.getBestPlaneswalkerToDamage(pwDefending);
} else {
return prefDefender;
}
@@ -682,7 +678,6 @@ public class AiAttackController {
* @return a {@link forge.game.combat.Combat} object.
*/
public final void declareAttackers(final Combat combat) {
if (this.attackers.isEmpty()) {
return;
}
@@ -702,14 +697,14 @@ public class AiAttackController {
tradeIfLowerLifePressure = aic.getBooleanProperty(AiProps.RANDOMLY_ATKTRADE_ONLY_ON_LOWER_LIFE_PRESSURE);
}
final boolean bAssault = this.doAssault(ai);
final boolean bAssault = doAssault(ai);
// TODO: detect Lightmine Field by presence of a card with a specific trigger
final boolean lightmineField = ComputerUtilCard.isPresentOnBattlefield(ai.getGame(), "Lightmine Field");
// TODO: detect Season of the Witch by presence of a card with a specific trigger
final boolean seasonOfTheWitch = ComputerUtilCard.isPresentOnBattlefield(ai.getGame(), "Season of the Witch");
// Determine who will be attacked
GameEntity defender = this.chooseDefender(combat, bAssault);
GameEntity defender = chooseDefender(combat, bAssault);
List<Card> attackersLeft = new ArrayList<>(this.attackers);
// TODO probably use AttackConstraints instead of only GlobalAttackRestrictions?
@@ -717,7 +712,7 @@ public class AiAttackController {
int attackMax = restrict.getMax();
if (attackMax == -1) {
// check with the local limitations vs. the chosen defender
attackMax = ComputerUtilCombat.getMaxAttackersFor(defender);
attackMax = restrict.getDefenderMax().get(defender) == null ? -1 : restrict.getDefenderMax().get(defender);
}
if (attackMax == 0) {
@@ -777,7 +772,6 @@ public class AiAttackController {
return;
}
if (bAssault) {
if (LOG_AI_ATTACKS)
System.out.println("Assault");
@@ -851,7 +845,6 @@ public class AiAttackController {
// no more creatures to attack
return;
}
// *******************
// Evaluate the creature forces
@@ -919,7 +912,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)) / candidateUnblockedDamage;
humanLifeToDamageRatio = (double) (opp.getLife() - ComputerUtil.possibleNonCombatDamage(ai, opp)) / candidateUnblockedDamage;
}
// determine if the ai outnumbers the player
@@ -936,12 +929,9 @@ public class AiAttackController {
// *********************
// if outnumber and superior ratio work out whether attritional all out
// attacking will work
// attritional attack will expect some creatures to die but to achieve
// victory by sheer weight
// of numbers attacking turn after turn. It's not calculate very
// carefully, the accuracy
// can probably be improved
// attacking will work attritional attack will expect some creatures to die but to achieve
// victory by sheer weight of numbers attacking turn after turn. It's not calculate very
// carefully, the accuracy can probably be improved
// *********************
boolean doAttritionalAttack = false;
// get list of attackers ordered from low power to high
@@ -975,7 +965,6 @@ public class AiAttackController {
doAttritionalAttack = true;
}
}
// System.out.println(doAttritionalAttack + " = do attritional attack");
// *********************
// end attritional attack calculation
// *********************
@@ -1025,11 +1014,9 @@ public class AiAttackController {
// end see how long until unblockable attackers will be fatal
// *****************
// decide on attack aggression based on a comparison of forces, life
// totals and other considerations
// some bad "magic numbers" here, TODO replace with nice descriptive
// variable names
// totals and other considerations some bad "magic numbers" here
// TODO replace with nice descriptive variable names
if (ratioDiff > 0 && doAttritionalAttack) {
this.aiAggression = 5; // attack at all costs
} else if ((ratioDiff >= 1 && this.attackers.size() > 1 && (humanLifeToDamageRatio < 2 || outNumber > 0))
@@ -1105,17 +1092,17 @@ public class AiAttackController {
// if enough damage: switch to next planeswalker or player
if (damage >= pw.getCounters(CounterEnumType.LOYALTY)) {
List<Card> pwDefending = combat.getDefendingPlaneswalkers();
boolean found = false;
// look for next planeswalker
for (Card walker : pwDefending) {
if (combat.getAttackersOf(walker).isEmpty()) {
defender = walker;
found = true;
break;
for (Card walker : Lists.newArrayList(pwDefending)) {
if (!combat.getAttackersOf(walker).isEmpty()) {
pwDefending.remove(walker);
}
}
if (!found) {
defender = combat.getDefendingPlayers().get(0);
if (pwDefending.isEmpty()) {
defender = Collections.min(Lists.newArrayList(combat.getDefendingPlayers()), PlayerPredicates.compareByLife());
}
else {
defender = ComputerUtilCard.getBestPlaneswalkerToDamage(pwDefending);
}
}
}
@@ -1224,9 +1211,8 @@ public class AiAttackController {
}
// look at the attacker in relation to the blockers to establish a
// number of factors about the attacking
// context that will be relevant to the attackers decision according to
// the selected strategy
// number of factors about the attacking context that will be relevant
// to the attackers decision according to the selected strategy
for (final Card defender : validBlockers) {
// if both isWorthLessThanAllKillers and canKillAllDangerous are false there's nothing more to check
if (isWorthLessThanAllKillers || canKillAllDangerous || numberOfPossibleBlockers < 2) {
@@ -1300,14 +1286,11 @@ public class AiAttackController {
}
if (numberOfPossibleBlockers > 2
|| (numberOfPossibleBlockers >= 1 && CombatUtil.canAttackerBeBlockedWithAmount(attacker, 1, combat))
|| (numberOfPossibleBlockers == 2 && CombatUtil.canAttackerBeBlockedWithAmount(attacker, 2, combat))) {
|| (numberOfPossibleBlockers >= 1 && CombatUtil.canAttackerBeBlockedWithAmount(attacker, 1, this.defendingOpponent))
|| (numberOfPossibleBlockers == 2 && CombatUtil.canAttackerBeBlockedWithAmount(attacker, 2, this.defendingOpponent))) {
canBeBlocked = true;
}
/*System.out.println(attacker + " canBeKilledByOne: " + canBeKilledByOne + " canKillAll: "
+ canKillAll + " isWorthLessThanAllKillers: " + isWorthLessThanAllKillers + " canBeBlocked: " + canBeBlocked);*/
// decide if the creature should attack based on the prevailing strategy
// choice in aiAggression
// decide if the creature should attack based on the prevailing strategy choice in aiAggression
switch (this.aiAggression) {
case 6: // Exalted: expecting to at least kill a creature of equal value or not be blocked
if ((canKillAll && isWorthLessThanAllKillers) || !canBeBlocked) {
@@ -1373,8 +1356,7 @@ public class AiAttackController {
}
// if card has a Exert Trigger which would target,
// but there are no creatures it can target, no need to exert with
// it
// but there are no creatures it can target, no need to exert with it
boolean missTarget = false;
for (Trigger t : c.getTriggers()) {
if (!TriggerType.Exerted.equals(t.getMode())) {
@@ -1442,7 +1424,7 @@ public class AiAttackController {
public String toProtectAttacker(SpellAbility sa) {
//AiAttackController is created with the selected attacker as the only entry in "attackers"
if (sa.getApi() != ApiType.Protection || oppList.isEmpty() || getPossibleBlockers(oppList, attackers).isEmpty()) {
return null; //not protection sa or attacker is already unblockable
return null; //not protection sa or attacker is already unblockable
}
final List<String> choices = ProtectEffect.getProtectionList(sa);
String color = ComputerUtilCard.getMostProminentColor(getPossibleBlockers(oppList, attackers)), artifact = null;
@@ -1452,7 +1434,7 @@ public class AiAttackController {
if (!choices.contains(color)) {
color = null;
}
for (Card c : oppList) { //find a blocker that ignores the currently selected protection
for (Card c : oppList) { //find a blocker that ignores the currently selected protection
if (artifact != null && !c.isArtifact()) {
artifact = null;
}
@@ -1485,7 +1467,7 @@ public class AiAttackController {
break;
}
}
if (color == null && artifact == null) { //nothing can make the attacker unblockable
if (color == null && artifact == null) { //nothing can make the attacker unblockable
return null;
}
}
@@ -1563,4 +1545,4 @@ public class AiAttackController {
return true;
}
} // end class ComputerUtil_Attack2
}

View File

@@ -180,11 +180,9 @@ public class AiBlockController {
// Good Blocks means a good trade or no trade
private void makeGoodBlocks(final Combat combat) {
List<Card> currentAttackers = new ArrayList<>(attackersLeft);
for (final Card attacker : attackersLeft) {
if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT")
|| attacker.hasKeyword("CARDNAME can't be blocked unless all creatures defending player controls block it.")
|| attacker.hasKeyword(Keyword.MENACE)) {
@@ -192,7 +190,6 @@ public class AiBlockController {
}
Card blocker = null;
final List<Card> blockers = getPossibleBlockers(combat, attacker, blockersLeft, true);
final List<Card> safeBlockers = getSafeBlockers(combat, attacker, blockers);
@@ -305,7 +302,6 @@ public class AiBlockController {
}
Card blocker = null;
final List<Card> blockers = getPossibleBlockers(combat, attacker, blockersLeft, true);
for (Card b : blockers) {
@@ -366,10 +362,9 @@ public class AiBlockController {
final int damageNeeded = ComputerUtilCombat.getDamageToKill(attacker)
+ ComputerUtilCombat.predictToughnessBonusOfAttacker(attacker, blocker, combat, false);
// if the total damage of the blockgang was not enough
// without but is enough with this blocker finish the
// blockgang
// without but is enough with this blocker finish the blockgang
if (ComputerUtilCombat.totalFirstStrikeDamageOfBlockers(attacker, blockGang) < damageNeeded
|| CombatUtil.needsBlockers(attacker) > blockGang.size()) {
|| CombatUtil.getMinNumBlockersForAttacker(attacker, ai) > blockGang.size()) {
blockGang.add(blocker);
if (ComputerUtilCombat.totalFirstStrikeDamageOfBlockers(attacker, blockGang) >= damageNeeded) {
currentAttackers.remove(attacker);
@@ -407,7 +402,7 @@ public class AiBlockController {
boolean foundDoubleBlock = false; // if true, a good double block is found
// AI can't handle good blocks with more than three creatures yet
if (CombatUtil.needsBlockers(attacker) > (considerTripleBlock ? 3 : 2)) {
if (CombatUtil.getMinNumBlockersForAttacker(attacker, ai) > (considerTripleBlock ? 3 : 2)) {
continue;
}
@@ -444,7 +439,7 @@ public class AiBlockController {
final int addedValue = ComputerUtilCard.evaluateCreature(blocker);
final int damageNeeded = ComputerUtilCombat.getDamageToKill(attacker)
+ ComputerUtilCombat.predictToughnessBonusOfAttacker(attacker, blocker, combat, false);
if ((damageNeeded > currentDamage || CombatUtil.needsBlockers(attacker) > blockGang.size())
if ((damageNeeded > currentDamage || CombatUtil.getMinNumBlockersForAttacker(attacker, ai) > blockGang.size())
&& !(damageNeeded > currentDamage + additionalDamage)
// The attacker will be killed
&& (absorbedDamage2 + absorbedDamage > attacker.getNetCombatDamage()
@@ -454,8 +449,7 @@ public class AiBlockController {
|| (lifeInDanger && ComputerUtilCombat.lifeInDanger(ai, combat)))
// or life is in danger
&& CombatUtil.canBlock(attacker, blocker, combat)) {
// this is needed for attackers that can't be blocked by
// more than 1
// this is needed for attackers that can't be blocked by more than 1
currentAttackers.remove(attacker);
combat.addBlocker(attacker, blocker);
if (CombatUtil.canBlock(attacker, leader, combat)) {
@@ -496,7 +490,7 @@ public class AiBlockController {
final int addedValue3 = ComputerUtilCard.evaluateCreature(secondBlocker);
final int netCombatDamage = attacker.getNetCombatDamage();
if ((damageNeeded > currentDamage || CombatUtil.needsBlockers(attacker) > blockGang.size())
if ((damageNeeded > currentDamage || CombatUtil.getMinNumBlockersForAttacker(attacker, ai) > blockGang.size())
&& !(damageNeeded > currentDamage + additionalDamage2 + additionalDamage3)
// The attacker will be killed
&& ((absorbedDamage2 + absorbedDamage > netCombatDamage && absorbedDamage3 + absorbedDamage > netCombatDamage
@@ -510,8 +504,7 @@ public class AiBlockController {
// or life is in danger
&& CombatUtil.canBlock(attacker, secondBlocker, combat)
&& CombatUtil.canBlock(attacker, thirdBlocker, combat)) {
// this is needed for attackers that can't be blocked by
// more than 1
// this is needed for attackers that can't be blocked by more than 1
currentAttackers.remove(attacker);
combat.addBlocker(attacker, thirdBlocker);
if (CombatUtil.canBlock(attacker, secondBlocker, combat)) {
@@ -587,12 +580,10 @@ public class AiBlockController {
* @param combat a {@link forge.game.combat.Combat} object.
*/
private void makeTradeBlocks(final Combat combat) {
List<Card> currentAttackers = new ArrayList<>(attackersLeft);
List<Card> killingBlockers;
for (final Card attacker : attackersLeft) {
if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT")
|| attacker.hasKeyword(Keyword.MENACE)
|| attacker.hasKeyword("CARDNAME can't be blocked unless all creatures defending player controls block it.")) {
@@ -628,7 +619,6 @@ public class AiBlockController {
// Chump Blocks (should only be made if life is in danger)
private void makeChumpBlocks(final Combat combat) {
List<Card> currentAttackers = new ArrayList<>(attackersLeft);
makeChumpBlocks(combat, currentAttackers);
@@ -639,7 +629,6 @@ public class AiBlockController {
}
private void makeChumpBlocks(final Combat combat, List<Card> attackers) {
if (attackers.isEmpty() || !ComputerUtilCombat.lifeInDanger(ai, combat)) {
return;
}
@@ -694,11 +683,9 @@ public class AiBlockController {
// Block creatures with "can't be blocked except by two or more creatures"
private void makeMultiChumpBlocks(final Combat combat) {
List<Card> currentAttackers = new ArrayList<>(attackersLeft);
for (final Card attacker : currentAttackers) {
if (!attacker.hasStartOfKeyword("CantBeBlockedByAmount LT")
&& !attacker.hasKeyword(Keyword.MENACE)
&& !attacker.hasKeyword("CARDNAME can't be blocked unless all creatures defending player controls block it.")) {
@@ -730,14 +717,12 @@ public class AiBlockController {
/** Reinforce blockers blocking attackers with trample (should only be made if life is in danger) */
private void reinforceBlockersAgainstTrample(final Combat combat) {
List<Card> chumpBlockers;
List<Card> tramplingAttackers = CardLists.getKeyword(attackers, Keyword.TRAMPLE);
tramplingAttackers = CardLists.filter(tramplingAttackers, Predicates.not(rampagesOrNeedsManyToBlock));
// TODO - should check here for a "rampage-like" trigger that replaced
// the keyword:
// TODO - should check here for a "rampage-like" trigger that replaced the keyword:
// "Whenever CARDNAME becomes blocked, it gets +1/+1 until end of turn for each creature blocking it."
for (final Card attacker : tramplingAttackers) {
@@ -764,7 +749,6 @@ public class AiBlockController {
/** Support blockers not destroying the attacker with more blockers to try to kill the attacker */
private void reinforceBlockersToKill(final Combat combat) {
List<Card> safeBlockers;
List<Card> blockers;
List<Card> targetAttackers = CardLists.filter(blockedButUnkilled, Predicates.not(rampagesOrNeedsManyToBlock));
@@ -1036,27 +1020,21 @@ public class AiBlockController {
} else {
lifeInDanger = false;
}
// if life is still in danger
// Reinforce blockers blocking attackers with trample if life is
// still
// in danger
// Reinforce blockers blocking attackers with trample if life is still in danger
if (lifeInDanger && ComputerUtilCombat.lifeInDanger(ai, combat)) {
reinforceBlockersAgainstTrample(combat);
} else {
lifeInDanger = false;
}
// Support blockers not destroying the attacker with more blockers
// to
// try to kill the attacker
// to try to kill the attacker
if (!lifeInDanger) {
reinforceBlockersToKill(combat);
}
// == 2. If the AI life would still be in danger make a safer
// approach ==
// == 2. If the AI life would still be in danger make a safer approach ==
if (lifeInDanger && ComputerUtilCombat.lifeInDanger(ai, combat)) {
clearBlockers(combat, possibleBlockers); // reset every block
// assignment
clearBlockers(combat, possibleBlockers); // reset every block assignment
makeTradeBlocks(combat); // choose necessary trade blocks
// if life is in danger
makeGoodBlocks(combat);
@@ -1066,8 +1044,7 @@ public class AiBlockController {
} else {
lifeInDanger = false;
}
// Reinforce blockers blocking attackers with trample if life is
// still in danger
// Reinforce blockers blocking attackers with trample if life is still in danger
if (lifeInDanger && ComputerUtilCombat.lifeInDanger(ai, combat)) {
reinforceBlockersAgainstTrample(combat);
} else {
@@ -1077,11 +1054,9 @@ public class AiBlockController {
reinforceBlockersToKill(combat);
}
// == 3. If the AI life would be in serious danger make an even
// safer approach ==
// == 3. If the AI life would be in serious danger make an even safer approach ==
if (lifeInDanger && ComputerUtilCombat.lifeInSeriousDanger(ai, combat)) {
clearBlockers(combat, possibleBlockers); // reset every block
// assignment
clearBlockers(combat, possibleBlockers); // reset every block assignment
makeChumpBlocks(combat); // choose chump blocks
if (ComputerUtilCombat.lifeInDanger(ai, combat)) {
makeTradeBlocks(combat); // choose necessary trade
@@ -1090,15 +1065,13 @@ public class AiBlockController {
if (!ComputerUtilCombat.lifeInDanger(ai, combat)) {
makeGoodBlocks(combat);
}
// Reinforce blockers blocking attackers with trample if life is
// still in danger
// Reinforce blockers blocking attackers with trample if life is still in danger
else {
reinforceBlockersAgainstTrample(combat);
}
makeGangBlocks(combat);
// Support blockers not destroying the attacker with more
// blockers
// to try to kill the attacker
// blockers to try to kill the attacker
reinforceBlockersToKill(combat);
}
}
@@ -1108,7 +1081,7 @@ public class AiBlockController {
chumpBlockers.addAll(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)) {
if (CombatUtil.mustBlockAnAttacker(blocker, combat, null)) {
chumpBlockers.add(blocker);
}
}
@@ -1118,7 +1091,7 @@ public class AiBlockController {
blockers = getPossibleBlockers(combat, attacker, chumpBlockers, false);
for (final Card blocker : blockers) {
if (CombatUtil.canBlock(attacker, blocker, combat) && blockersLeft.contains(blocker)
&& (CombatUtil.mustBlockAnAttacker(blocker, combat)
&& (CombatUtil.mustBlockAnAttacker(blocker, combat, null)
|| blocker.hasKeyword("CARDNAME blocks each turn if able.")
|| blocker.hasKeyword("CARDNAME blocks each combat if able."))) {
combat.addBlocker(attacker, blocker);
@@ -1137,7 +1110,6 @@ public class AiBlockController {
}
}
// 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
if (ai.getController().isAI() && !ComputerUtilCombat.lifeInDanger(ai, combat)) {

View File

@@ -32,7 +32,9 @@ import com.google.common.collect.Lists;
import forge.ai.ability.ChangeZoneAi;
import forge.ai.ability.ExploreAi;
import forge.ai.ability.LearnAi;
import forge.ai.simulation.SpellAbilityPicker;
import forge.card.CardStateName;
import forge.card.MagicColor;
import forge.card.mana.ManaCost;
import forge.deck.CardPool;
@@ -45,6 +47,7 @@ import forge.game.Game;
import forge.game.GameActionUtil;
import forge.game.GameEntity;
import forge.game.GlobalRuleChange;
import forge.game.ability.AbilityKey;
import forge.game.ability.AbilityUtils;
import forge.game.ability.ApiType;
import forge.game.ability.SpellApiBased;
@@ -77,6 +80,7 @@ import forge.game.player.Player;
import forge.game.player.PlayerActionConfirmMode;
import forge.game.replacement.ReplaceMoved;
import forge.game.replacement.ReplacementEffect;
import forge.game.replacement.ReplacementLayer;
import forge.game.replacement.ReplacementType;
import forge.game.spellability.AbilitySub;
import forge.game.spellability.LandAbility;
@@ -88,6 +92,7 @@ import forge.game.spellability.SpellAbilityCondition;
import forge.game.spellability.SpellAbilityPredicates;
import forge.game.spellability.SpellPermanent;
import forge.game.staticability.StaticAbility;
import forge.game.staticability.StaticAbilityMustTarget;
import forge.game.trigger.Trigger;
import forge.game.trigger.TriggerType;
import forge.game.trigger.WrappedAbility;
@@ -305,6 +310,8 @@ public class AiController {
}
exSA.setTrigger(tr);
// need to set TriggeredObject
exSA.setTriggeringObject(AbilityKey.Card, card);
// for trigger test, need to ignore the conditions
SpellAbilityCondition cons = exSA.getConditions();
@@ -430,17 +437,22 @@ public class AiController {
}
}
// don't play the land if it has cycling and enough lands are available
final FCollectionView<SpellAbility> spellAbilities = c.getSpellAbilities();
final CardCollectionView hand = player.getCardsIn(ZoneType.Hand);
CardCollection lands = new CardCollection(battlefield);
lands.addAll(hand);
lands = CardLists.filter(lands, CardPredicates.Presets.LANDS);
int maxCmcInHand = Aggregates.max(hand, CardPredicates.Accessors.fnGetCmc);
for (final SpellAbility sa : spellAbilities) {
if (sa.isCycling()) {
if (lands.size() >= Math.max(maxCmcInHand, 6)) {
if (lands.size() >= Math.max(maxCmcInHand, 6)) {
// don't play MDFC land if other side is spell and enough lands are available
if (!c.isLand() || (c.isModal() && !c.getState(CardStateName.Modal).getType().isLand())) {
return false;
}
// don't play the land if it has cycling and enough lands are available
final FCollectionView<SpellAbility> spellAbilities = c.getSpellAbilities();
for (final SpellAbility sa : spellAbilities) {
if (sa.isCycling()) {
return false;
}
}
@@ -501,20 +513,35 @@ public class AiController {
//try to skip lands that enter the battlefield tapped
if (!nonLandsInHand.isEmpty()) {
CardCollection nonTappeddLands = new CardCollection();
CardCollection nonTappedLands = new CardCollection();
for (Card land : landList) {
// Is this the best way to check if a land ETB Tapped?
if (land.hasSVar("ETBTappedSVar")) {
// check replacement effects if land would enter tapped or not
final Map<AbilityKey, Object> repParams = AbilityKey.mapFromAffected(land);
repParams.put(AbilityKey.Origin, land.getZone().getZoneType());
repParams.put(AbilityKey.Destination, ZoneType.Battlefield);
repParams.put(AbilityKey.Source, land);
boolean foundTapped = false;
for (ReplacementEffect re : player.getGame().getReplacementHandler().getReplacementList(ReplacementType.Moved, repParams, ReplacementLayer.Other)) {
SpellAbility reSA = re.ensureAbility();
if (reSA == null || !ApiType.Tap.equals(reSA.getApi())) {
continue;
}
reSA.setActivatingPlayer(reSA.getHostCard().getController());
if (reSA.metConditions()) {
foundTapped = true;
break;
}
}
if (foundTapped) {
continue;
}
// Glacial Fortress and friends
if (land.hasSVar("ETBCheckSVar") && CardFactoryUtil.xCount(land, land.getSVar("ETBCheckSVar")) == 0) {
continue;
}
nonTappeddLands.add(land);
nonTappedLands.add(land);
}
if (!nonTappeddLands.isEmpty()) {
landList = nonTappeddLands;
if (!nonTappedLands.isEmpty()) {
landList = nonTappedLands;
}
}
@@ -585,8 +612,7 @@ public class AiController {
SpellAbility currentSA = sa;
sa.setActivatingPlayer(player);
// check everything necessary
AiPlayDecision opinion = canPlayAndPayFor(currentSA);
//PhaseHandler ph = game.getPhaseHandler();
// System.out.printf("Ai thinks '%s' of %s @ %s %s >>> \n", opinion, sa, Lang.getPossesive(ph.getPlayerTurn().getName()), ph.getPhase());
@@ -826,8 +852,11 @@ public class AiController {
}
return canPlayFromEffectAI((SpellPermanent)sa, false, true);
}
if (sa.usesTargeting() && !sa.isTargetNumberValid()) {
if (!sa.getTargetRestrictions().hasCandidates(sa, true)) {
if (sa.usesTargeting()) {
if (!sa.isTargetNumberValid() && !sa.getTargetRestrictions().hasCandidates(sa, true)) {
return AiPlayDecision.TargetingFailed;
}
if (!StaticAbilityMustTarget.meetsMustTargetRestriction(sa)) {
return AiPlayDecision.TargetingFailed;
}
}
@@ -1110,12 +1139,18 @@ public class AiController {
final CardCollection discardList = new CardCollection();
int count = 0;
if (sa != null) {
String logic = sa.getParamOrDefault("AILogic", "");
sourceCard = sa.getHostCard();
if ("Always".equals(sa.getParam("AILogic")) && !validCards.isEmpty()) {
if ("Always".equals(logic) && !validCards.isEmpty()) {
min = 1;
} else if ("VolrathsShapeshifter".equals(sa.getParam("AILogic"))) {
} else if (logic.startsWith("UnlessAtLife.")) {
int threshold = AbilityUtils.calculateAmount(sourceCard, logic.substring(logic.indexOf(".") + 1), sa);
if (player.getLife() <= threshold) {
min = 1;
}
} else if ("VolrathsShapeshifter".equals(logic)) {
return SpecialCardAi.VolrathsShapeshifter.targetBestCreature(player, sa);
} else if ("DiscardCMCX".equals(sa.getParam("AILogic"))) {
} else if ("DiscardCMCX".equals(logic)) {
final int cmc = sa.getXManaCostPaid();
CardCollection discards = CardLists.filter(player.getCardsIn(ZoneType.Hand), CardPredicates.hasCMC(cmc));
if (discards.isEmpty()) {
@@ -1290,15 +1325,6 @@ public class AiController {
}
public boolean confirmStaticApplication(Card hostCard, GameEntity affected, String logic, String message) {
if (logic.equalsIgnoreCase("ProtectFriendly")) {
final Player controller = hostCard.getController();
if (affected instanceof Player) {
return !((Player) affected).isOpponentOf(controller);
}
if (affected instanceof Card) {
return !((Card) affected).getController().isOpponentOf(controller);
}
}
return true;
}
@@ -1328,7 +1354,7 @@ public class AiController {
public AiPlayDecision canPlayFromEffectAI(Spell spell, boolean mandatory, boolean withoutPayingManaCost) {
int damage = ComputerUtil.getDamageForPlaying(player, spell);
if (damage >= player.getLife() && !player.cantLoseForZeroOrLessLife() && player.canLoseLife()) {
if (!mandatory && damage >= player.getLife() && !player.cantLoseForZeroOrLessLife() && player.canLoseLife()) {
return AiPlayDecision.CurseEffects;
}
@@ -1397,8 +1423,6 @@ public class AiController {
private List<SpellAbility> singleSpellAbilityList(SpellAbility sa) {
if (sa == null) { return null; }
// System.out.println("Chosen to play: " + sa);
final List<SpellAbility> abilities = Lists.newArrayList();
abilities.add(sa);
return abilities;
@@ -1443,6 +1467,13 @@ public class AiController {
if (!game.getPhaseHandler().is(PhaseType.MAIN1) || !isSafeToHoldLandDropForMain2(land)) {
final List<SpellAbility> abilities = Lists.newArrayList();
// TODO extend this logic to evaluate MDFC with both sides land
// this can only happen if its a MDFC land
if (!land.isLand()) {
land.setState(CardStateName.Modal, true);
land.setBackSide(true);
}
LandAbility la = new LandAbility(land, player, null);
la.setCardState(land.getCurrentState());
if (la.canPlay()) {
@@ -1693,10 +1724,8 @@ public class AiController {
for (int i = 0; i < numToExile; i++) {
Card chosen = null;
for (final Card c : grave) { // Exile noncreatures first in
// case we can revive. Might
// wanna do some additional
// checking here for Flashback
// and the like.
// case we can revive. Might wanna do some additional
// checking here for Flashback and the like.
if (!c.isCreature()) {
chosen = c;
break;
@@ -1735,12 +1764,21 @@ public class AiController {
* @param sa the sa
* @return true, if successful
*/
public final boolean aiShouldRun(final ReplacementEffect effect, final SpellAbility sa) {
public final boolean aiShouldRun(final ReplacementEffect effect, final SpellAbility sa, GameEntity affected) {
Card hostCard = effect.getHostCard();
if (hostCard.hasAlternateState()) {
hostCard = game.getCardState(hostCard);
}
if (effect.hasParam("AILogic") && effect.getParam("AILogic").equalsIgnoreCase("ProtectFriendly")) {
final Player controller = hostCard.getController();
if (affected instanceof Player) {
return !((Player) affected).isOpponentOf(controller);
}
if (affected instanceof Card) {
return !((Card) affected).getController().isOpponentOf(controller);
}
}
if (effect.hasParam("AICheckSVar")) {
System.out.println("aiShouldRun?" + sa);
final String svarToCheck = effect.getParam("AICheckSVar");
@@ -1755,7 +1793,7 @@ public class AiController {
compareTo = Integer.parseInt(strCmpTo);
} catch (final Exception ignored) {
if (sa == null) {
compareTo = CardFactoryUtil.xCount(hostCard, hostCard.getSVar(strCmpTo));
compareTo = AbilityUtils.calculateAmount(hostCard, hostCard.getSVar(strCmpTo), effect);
} else {
compareTo = AbilityUtils.calculateAmount(hostCard, hostCard.getSVar(strCmpTo), sa);
}
@@ -1765,7 +1803,7 @@ public class AiController {
int left = 0;
if (sa == null) {
left = CardFactoryUtil.xCount(hostCard, hostCard.getSVar(svarToCheck));
left = AbilityUtils.calculateAmount(hostCard, svarToCheck, effect);
} else {
left = AbilityUtils.calculateAmount(hostCard, svarToCheck, sa);
}
@@ -1855,11 +1893,13 @@ public class AiController {
} else if ("LowestLoseLife".equals(logic)) {
return MyRandom.getRandom().nextInt(Math.min(player.getLife() / 3, player.getWeakestOpponent().getLife())) + 1;
} else if ("HighestLoseLife".equals(logic)) {
return MyRandom.getRandom().nextInt(Math.max(player.getLife() / 3, player.getWeakestOpponent().getLife())) + 1;
return Math.min(player.getLife() -1,MyRandom.getRandom().nextInt(Math.max(player.getLife() / 3, player.getWeakestOpponent().getLife())) + 1);
} else if ("HighestGetCounter".equals(logic)) {
return MyRandom.getRandom().nextInt(3);
} else if (source.hasSVar("EnergyToPay")) {
return AbilityUtils.calculateAmount(source, source.getSVar("EnergyToPay"), sa);
} else if ("Vermin".equals(logic)) {
return MyRandom.getRandom().nextInt(Math.max(player.getLife() - 5, 0));
}
return max;
}
@@ -1954,7 +1994,6 @@ public class AiController {
return result;
}
// this is where the computer cheats
// changes AllZone.getComputerPlayer().getZone(Zone.Library)
@@ -2089,8 +2128,11 @@ public class AiController {
if (useSimulation) {
return simPicker.chooseCardToHiddenOriginChangeZone(destination, origin, sa, fetchList, player2, decider);
}
if (sa.getApi() == ApiType.Explore) {
return ExploreAi.shouldPutInGraveyard(fetchList, decider);
} else if (sa.getApi() == ApiType.Learn) {
return LearnAi.chooseCardToLearn(fetchList, decider, sa);
} else {
return ChangeZoneAi.chooseCardToHiddenOriginChangeZone(destination, origin, sa, fetchList, player2, decider);
}
@@ -2231,10 +2273,8 @@ public class AiController {
}
}
// AI logic for choosing which replacement effect to apply
// happens here.
// AI logic for choosing which replacement effect to apply happens here.
return Iterables.getFirst(list, null);
}
}

View File

@@ -6,9 +6,11 @@ import java.util.Collections;
import java.util.List;
import java.util.Map;
import forge.game.cost.*;
import org.apache.commons.lang3.ObjectUtils;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import forge.card.CardType;
@@ -22,43 +24,12 @@ import forge.game.card.CardLists;
import forge.game.card.CardPredicates;
import forge.game.card.CounterEnumType;
import forge.game.card.CounterType;
import forge.game.cost.CostAddMana;
import forge.game.cost.CostChooseCreatureType;
import forge.game.cost.CostDamage;
import forge.game.cost.CostDecisionMakerBase;
import forge.game.cost.CostDiscard;
import forge.game.cost.CostDraw;
import forge.game.cost.CostExert;
import forge.game.cost.CostExile;
import forge.game.cost.CostExileFromStack;
import forge.game.cost.CostExiledMoveToGrave;
import forge.game.cost.CostFlipCoin;
import forge.game.cost.CostGainControl;
import forge.game.cost.CostGainLife;
import forge.game.cost.CostMill;
import forge.game.cost.CostPartMana;
import forge.game.cost.CostPayEnergy;
import forge.game.cost.CostPayLife;
import forge.game.cost.CostPutCardToLib;
import forge.game.cost.CostPutCounter;
import forge.game.cost.CostRemoveAnyCounter;
import forge.game.cost.CostRemoveCounter;
import forge.game.cost.CostReturn;
import forge.game.cost.CostReveal;
import forge.game.cost.CostSacrifice;
import forge.game.cost.CostTap;
import forge.game.cost.CostTapType;
import forge.game.cost.CostUnattach;
import forge.game.cost.CostUntap;
import forge.game.cost.CostUntapType;
import forge.game.cost.PaymentDecision;
import forge.game.keyword.Keyword;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.SpellAbilityStackInstance;
import forge.game.zone.ZoneType;
import forge.util.Aggregates;
import forge.util.Localizer;
import forge.util.TextUtil;
import forge.util.collect.FCollectionView;
@@ -92,15 +63,9 @@ public class AiCostDecision extends CostDecisionMakerBase {
@Override
public PaymentDecision visit(CostChooseCreatureType cost) {
Integer amount = cost.convertAmount();
Iterable<String> choices = player.getController().chooseSomeType(
Localizer.getInstance().getMessage("lblCreature"), ability, amount, amount, Lists.newArrayList(CardType.getAllCreatureTypes()));
if (choices == null || Iterables.isEmpty(choices)) {
return null;
}
return PaymentDecision.types(choices);
String choice = player.getController().chooseSomeType("Creature", ability, CardType.getAllCreatureTypes(),
Lists.newArrayList());
return PaymentDecision.type(choice);
}
@Override
@@ -583,6 +548,11 @@ public class AiCostDecision extends CostDecisionMakerBase {
return PaymentDecision.card(aic.getCardsToDiscard(c, type.split(";"), ability));
}
@Override
public PaymentDecision visit(CostRevealChosenPlayer cost) {
return PaymentDecision.number(1);
}
protected int removeCounter(GameEntityCounterTable table, List<Card> prefs, CounterEnumType cType, int stillToRemove) {
int removed = 0;
if (!prefs.isEmpty() && stillToRemove > 0) {
@@ -596,7 +566,7 @@ public class AiCostDecision extends CostDecisionMakerBase {
int thisRemove = Math.min(prefCard.getCounters(cType), stillToRemove);
if (thisRemove > 0) {
removed += thisRemove;
table.put(prefCard, CounterType.get(cType), thisRemove);
table.put(null, prefCard, CounterType.get(cType), thisRemove);
}
}
}
@@ -607,7 +577,7 @@ public class AiCostDecision extends CostDecisionMakerBase {
public PaymentDecision visit(CostRemoveAnyCounter cost) {
final String amount = cost.getAmount();
final int c = AbilityUtils.calculateAmount(source, amount, ability);
final Card originalHost = ability.getOriginalOrHost();
final Card originalHost = ObjectUtils.defaultIfNull(ability.getOriginalHost(), source);
if (c <= 0) {
return null;
@@ -662,7 +632,7 @@ public class AiCostDecision extends CostDecisionMakerBase {
int thisRemove = Math.min(card.getCounters(ctype), c - toRemove);
if (thisRemove > 0) {
toRemove += thisRemove;
table.put(card, ctype, thisRemove);
table.put(null, card, ctype, thisRemove);
}
}
}
@@ -690,7 +660,7 @@ public class AiCostDecision extends CostDecisionMakerBase {
int over = Math.min(e.getValue(), c - toRemove);
if (over > 0) {
toRemove += over;
table.put(crd, e.getKey(), over);
table.put(null, crd, e.getKey(), over);
}
}
}
@@ -720,7 +690,7 @@ public class AiCostDecision extends CostDecisionMakerBase {
int over = Math.min(e.getValue(), c - toRemove);
if (over > 0) {
toRemove += over;
table.put(crd, e.getKey(), over);
table.put(null, crd, e.getKey(), over);
}
}
}
@@ -760,7 +730,7 @@ public class AiCostDecision extends CostDecisionMakerBase {
int over = Math.min(crd.getCounters(CounterEnumType.QUEST) - e, c - toRemove);
if (over > 0) {
toRemove += over;
table.put(crd, CounterType.get(CounterEnumType.QUEST), over);
table.put(null, crd, CounterType.get(CounterEnumType.QUEST), over);
}
}
}
@@ -784,7 +754,7 @@ public class AiCostDecision extends CostDecisionMakerBase {
int thisRemove = Math.min(card.getCounters(cost.counter), c - toRemove);
if (thisRemove > 0) {
toRemove += thisRemove;
table.put(card, cost.counter, thisRemove);
table.put(null, card, cost.counter, thisRemove);
}
}
}
@@ -798,7 +768,7 @@ public class AiCostDecision extends CostDecisionMakerBase {
int thisRemove = Math.min(e.getValue(), c - toRemove);
if (thisRemove > 0) {
toRemove += thisRemove;
table.put(card, e.getKey(), thisRemove);
table.put(null, card, e.getKey(), thisRemove);
}
}
}

View File

@@ -188,7 +188,6 @@ public class ComputerUtil {
final Card source = sa.getHostCard();
final TargetRestrictions tgt = sa.getTargetRestrictions();
// Play higher costing spells first?
final Cost cost = sa.getPayCosts();
@@ -213,7 +212,8 @@ public class ComputerUtil {
if (unless != null && !unless.endsWith(">")) {
final int amount = AbilityUtils.calculateAmount(source, unless, sa);
final int usableManaSources = ComputerUtilMana.getAvailableManaSources(ComputerUtil.getOpponentFor(ai), true).size();
// this is enough as long as the AI is only smart enough to target top of stack
final int usableManaSources = ComputerUtilMana.getAvailableManaSources(ComputerUtilAbility.getTopSpellAbilityOnStack(ai.getGame(), sa).getActivatingPlayer(), true).size();
// If the Unless isn't enough, this should be less likely to be used
if (amount > usableManaSources) {
@@ -280,7 +280,7 @@ public class ComputerUtil {
SpellAbility newSA = sa.copyWithNoManaCost();
newSA.setActivatingPlayer(ai);
if (!CostPayment.canPayAdditionalCosts(newSA.getPayCosts(), newSA)) {
if (!CostPayment.canPayAdditionalCosts(newSA.getPayCosts(), newSA) || !ComputerUtilMana.canPayManaCost(newSA, ai, 0)) {
return false;
}
@@ -1068,9 +1068,6 @@ public class ComputerUtil {
return true;
}
}
if (card.isEquipment() && buffedcard.isCreature() && CombatUtil.canAttack(buffedcard, ComputerUtil.getOpponentFor(ai))) {
return true;
}
if (card.isCreature()) {
if (buffedcard.hasKeyword(Keyword.SOULBOND) && !buffedcard.isPaired()) {
return true;
@@ -1093,8 +1090,8 @@ public class ComputerUtil {
} // BuffedBy
// get all cards the human controls with AntiBuffedBy
final CardCollectionView antibuffed = ComputerUtil.getOpponentFor(ai).getCardsIn(ZoneType.Battlefield);
// there's a good chance AI will attack weak target
final CardCollectionView antibuffed = ai.getWeakestOpponent().getCardsIn(ZoneType.Battlefield);
for (Card buffedcard : antibuffed) {
if (buffedcard.hasSVar("AntiBuffedBy")) {
final String buffedby = buffedcard.getSVar("AntiBuffedBy");
@@ -1128,7 +1125,7 @@ public class ComputerUtil {
creatures2.add(creatures.get(i));
}
}
if (((creatures2.size() + CardUtil.getThisTurnCast("Creature.YouCtrl", vengevines.get(0)).size()) > 1)
if (((creatures2.size() + CardUtil.getThisTurnCast("Creature.YouCtrl", vengevines.get(0), null).size()) > 1)
&& card.isCreature() && card.getManaCost().getCMC() <= 3) {
return true;
}
@@ -1142,27 +1139,16 @@ public class ComputerUtil {
* @return true if it's OK to cast this Card for less than the max targets
*/
public static boolean shouldCastLessThanMax(final Player ai, final Card source) {
boolean ret = true;
if (source.getManaCost().countX() > 0) {
// If TargetMax is MaxTgts (i.e., an "X" cost), this is fine because AI is limited by mana available.
return ret;
} else {
// Otherwise, if life is possibly in danger, then this is fine.
Combat combat = new Combat(ComputerUtil.getOpponentFor(ai));
CardCollectionView attackers = ComputerUtil.getOpponentFor(ai).getCreaturesInPlay();
for (Card att : attackers) {
if (ComputerUtilCombat.canAttackNextTurn(att, ai)) {
combat.addAttacker(att, ComputerUtil.getOpponentFor(att.getController()));
}
}
AiBlockController aiBlock = new AiBlockController(ai);
aiBlock.assignBlockersForCombat(combat);
if (!ComputerUtilCombat.lifeInDanger(ai, combat)) {
// Otherwise, return false. Do not play now.
ret = false;
}
if (source.getXManaCostPaid() > 0) {
// If TargetMax is MaxTgts (i.e., an "X" cost), this is fine because AI is limited by payment resources available.
return true;
}
return ret;
if (aiLifeInDanger(ai, false, 0)) {
// Otherwise, if life is possibly in danger, then this is fine.
return true;
}
// do not play now.
return false;
}
/**
@@ -1266,8 +1252,8 @@ public class ComputerUtil {
}
}
// get all cards the human controls with AntiBuffedBy
final CardCollectionView antibuffed = ComputerUtil.getOpponentFor(ai).getCardsIn(ZoneType.Battlefield);
// there's a good chance AI will attack weak target
final CardCollectionView antibuffed = ai.getWeakestOpponent().getCardsIn(ZoneType.Battlefield);
for (Card buffedcard : antibuffed) {
if (buffedcard.hasSVar("AntiBuffedBy")) {
final String buffedby = buffedcard.getSVar("AntiBuffedBy");
@@ -1463,7 +1449,7 @@ public class ComputerUtil {
return false;
}
public static int possibleNonCombatDamage(Player ai) {
public static int possibleNonCombatDamage(Player ai, Player enemy) {
int damage = 0;
final CardCollection all = new CardCollection(ai.getCardsIn(ZoneType.Battlefield));
all.addAll(ai.getCardsActivableInExternalZones(true));
@@ -1483,7 +1469,6 @@ public class ComputerUtil {
if (tgt == null) {
continue;
}
final Player enemy = ComputerUtil.getOpponentFor(ai);
if (!sa.canTarget(enemy)) {
continue;
}
@@ -2256,42 +2241,40 @@ public class ComputerUtil {
return getCardsToDiscardFromOpponent(aiChooser, p, sa, validCards, min, max);
}
public static List<String> chooseSomeType(Player ai, String kindOfType, String logic, int min, int max, Collection<String> validTypes) {
public static String chooseSomeType(Player ai, String kindOfType, String logic, Collection<String> validTypes, List<String> invalidTypes) {
if (invalidTypes == null) {
invalidTypes = ImmutableList.of();
}
if (validTypes == null) {
validTypes = ImmutableList.of();
}
final Game game = ai.getGame();
List<String> chosenList = Lists.newArrayList();
String chosen = "";
if (kindOfType.equals("Card")) {
String chosen = "";
// TODO
// computer will need to choose a type
// based on whether it needs a creature or land,
// otherwise, lib search for most common type left
// then, reveal chosenType to Human
// computer will need to choose a type based on whether it needs a creature or land,
// otherwise, lib search for most common type left then, reveal chosenType to Human
if (game.getPhaseHandler().is(PhaseType.UNTAP) && logic == null) { // Storage Matrix
double amount = 0;
for (String type : validTypes) {
CardCollection list = CardLists.filter(ai.getCardsIn(ZoneType.Battlefield), CardPredicates.isType(type), Presets.TAPPED);
double i = type.equals("Creature") ? list.size() * 1.5 : list.size();
if (i > amount) {
amount = i;
chosen = type;
for (String type : CardType.getAllCardTypes()) {
if (!invalidTypes.contains(type)) {
CardCollection list = CardLists.filter(ai.getCardsIn(ZoneType.Battlefield), CardPredicates.isType(type), Presets.TAPPED);
double i = type.equals("Creature") ? list.size() * 1.5 : list.size();
if (i > amount) {
amount = i;
chosen = type;
}
}
}
} else if (logic == "MostProminentInComputerDeck") {
chosen = ComputerUtilCard.getMostProminentType(ai.getAllCards(), validTypes);
}
if (StringUtils.isEmpty(chosen)) {
chosen = validTypes.isEmpty() ? "Creature" : Aggregates.random(validTypes);
}
chosenList.add(chosen);
} else if (kindOfType.equals("Creature")) {
String chosen = "";
if (logic != null) {
List <String> valid = Lists.newArrayList(validTypes);
List <String> valid = Lists.newArrayList(CardType.getAllCreatureTypes());
valid.removeAll(invalidTypes);
if (logic.equals("MostProminentOnBattlefield")) {
chosen = ComputerUtilCard.getMostProminentType(game.getCardsIn(ZoneType.Battlefield), valid);
@@ -2302,7 +2285,7 @@ public class ComputerUtil {
else if (logic.equals("MostProminentOppControls")) {
CardCollection list = CardLists.filterControlledBy(game.getCardsIn(ZoneType.Battlefield), ai.getOpponents());
chosen = ComputerUtilCard.getMostProminentType(list, valid);
if (!CardType.isACreatureType(chosen) || !validTypes.contains(chosen)) {
if (!CardType.isACreatureType(chosen) || invalidTypes.contains(chosen)) {
list = CardLists.filterControlledBy(game.getCardsInGame(), ai.getOpponents());
chosen = ComputerUtilCard.getMostProminentType(list, valid);
}
@@ -2314,17 +2297,16 @@ public class ComputerUtil {
chosen = ComputerUtilCard.getMostProminentType(ai.getCardsIn(ZoneType.Graveyard), valid);
}
}
if (!CardType.isACreatureType(chosen) || !validTypes.contains(chosen)) {
chosen = Iterables.getFirst(validTypes, null);
if (!CardType.isACreatureType(chosen) || invalidTypes.contains(chosen)) {
chosen = "Sliver";
}
chosenList.add(chosen);
} else if (kindOfType.equals("Basic Land")) {
String chosen = "";
if (logic != null) {
if (logic.equals("MostProminentOppControls")) {
CardCollection list = CardLists.filterControlledBy(game.getCardsIn(ZoneType.Battlefield), ai.getOpponents());
List<String> valid = Lists.newArrayList(validTypes);
List<String> valid = Lists.newArrayList(CardType.getBasicTypes());
valid.removeAll(invalidTypes);
chosen = ComputerUtilCard.getMostProminentType(list, valid);
} else if (logic.equals("MostNeededType")) {
@@ -2347,9 +2329,9 @@ public class ComputerUtil {
}
}
else if (logic.equals("ChosenLandwalk")) {
for (Card c : ai.getWeakestOpponent().getLandsInPlay()) {
for (Card c : AiAttackController.choosePreferredDefenderPlayer(ai).getLandsInPlay()) {
for (String t : c.getType()) {
if (validTypes.contains(t) && CardType.isABasicLandType(t)) {
if (!invalidTypes.contains(t) && CardType.isABasicLandType(t)) {
chosen = t;
break;
}
@@ -2358,28 +2340,16 @@ public class ComputerUtil {
}
}
if (!CardType.isABasicLandType(chosen) || !validTypes.contains(chosen)) {
chosen = Iterables.getFirst(validTypes, null);
}
chosenList.add(chosen);
// the only one with choosing two types is Illusionary Terrain
// and it needs other AI logic
while (chosenList.size() < min) {
validTypes.remove(chosen);
chosen = Iterables.getFirst(validTypes, null);
if (chosen == null) {
return Lists.newArrayList();
}
chosenList.add(chosen);
if (!CardType.isABasicLandType(chosen) || invalidTypes.contains(chosen)) {
chosen = "Island";
}
}
else if (kindOfType.equals("Land")) {
String chosen = "";
if (logic != null) {
if (logic.equals("ChosenLandwalk")) {
for (Card c : ai.getWeakestOpponent().getLandsInPlay()) {
for (Card c : AiAttackController.choosePreferredDefenderPlayer(ai).getLandsInPlay()) {
for (String t : c.getType().getLandTypes()) {
if (validTypes.contains(t)) {
if (!invalidTypes.contains(t)) {
chosen = t;
break;
}
@@ -2388,11 +2358,10 @@ public class ComputerUtil {
}
}
if (StringUtils.isEmpty(chosen)) {
chosen = Iterables.getFirst(validTypes, null);
chosen = "Island";
}
chosenList.add(chosen);
}
return chosenList;
return chosen;
}
public static Object vote(Player ai, List<Object> options, SpellAbility sa, Multimap<Object, Player> votes, Player forPlayer) {
@@ -2413,23 +2382,26 @@ public class ComputerUtil {
case "Torture":
return "Torture";
case "GraceOrCondemnation":
return ai.getCreaturesInPlay().size() > ComputerUtil.getOpponentFor(ai).getCreaturesInPlay().size() ? "Grace"
: "Condemnation";
List<ZoneType> graceZones = new ArrayList<ZoneType>();
graceZones.add(ZoneType.Battlefield);
graceZones.add(ZoneType.Graveyard);
CardCollection graceCreatures = CardLists.getType(sa.getHostCard().getGame().getCardsIn(graceZones), "Creature");
int humanGrace = CardLists.filterControlledBy(graceCreatures, ai.getOpponents()).size();
int aiGrace = CardLists.filterControlledBy(graceCreatures, ai).size();
return aiGrace > humanGrace ? "Grace" : "Condemnation";
case "CarnageOrHomage":
CardCollection cardsInPlay = CardLists
.getNotType(sa.getHostCard().getGame().getCardsIn(ZoneType.Battlefield), "Land");
CardCollection cardsInPlay = CardLists.getNotType(sa.getHostCard().getGame().getCardsIn(ZoneType.Battlefield), "Land");
CardCollection humanlist = CardLists.filterControlledBy(cardsInPlay, ai.getOpponents());
CardCollection computerlist = CardLists.filterControlledBy(cardsInPlay, ai);
return (ComputerUtilCard.evaluatePermanentList(computerlist) + 3) < ComputerUtilCard
.evaluatePermanentList(humanlist) ? "Carnage" : "Homage";
CardCollection computerlist = ai.getCreaturesInPlay();
return (ComputerUtilCard.evaluatePermanentList(computerlist) + 3) < ComputerUtilCard.evaluatePermanentList(humanlist) ? "Carnage" : "Homage";
case "Judgment":
if (votes.isEmpty()) {
CardCollection list = new CardCollection();
for (Object o : options) {
if (o instanceof Card) {
list.add((Card) o);
}
}
}
return ComputerUtilCard.getBestAI(list);
} else {
return Iterables.getFirst(votes.keySet(), null);
@@ -2460,8 +2432,7 @@ public class ComputerUtil {
if (!source.canReceiveCounters(p1p1Type)) {
return opponent ? "Feather" : "Quill";
}
// if source is not on the battlefield anymore, choose +1/+1
// ones
// if source is not on the battlefield anymore, choose +1/+1 ones
if (!game.getCardState(source).isInZone(ZoneType.Battlefield)) {
return opponent ? "Feather" : "Quill";
}
@@ -2503,8 +2474,7 @@ public class ComputerUtil {
return opponent ? "Numbers" : "Strength";
}
// TODO check for ETB to +1/+1 counters
// or over another trigger like lifegain
// TODO check for ETB to +1/+1 counters or over another trigger like lifegain
int tokenScore = ComputerUtilCard.evaluateCreature(token);
@@ -2582,8 +2552,7 @@ public class ComputerUtil {
return "Taxes";
} else {
// ai is first voter or ally of controller
// both are not affected, but if opponents controll creatures,
// sacrifice is worse
// both are not affected, but if opponents control creatures, sacrifice is worse
return controller.getOpponents().getCreaturesInPlay().isEmpty() ? "Taxes" : "Death";
}
default:
@@ -2667,7 +2636,7 @@ public class ComputerUtil {
continue;
}
if (trigger.hasParam("ValidCard")) {
if (!card.isValid(trigger.getParam("ValidCard"), source.getController(), source, sa)) {
if (!card.isValid(trigger.getParam("ValidCard").split(","), source.getController(), source, sa)) {
continue;
}
}
@@ -2880,7 +2849,6 @@ public class ComputerUtil {
}
public static boolean lifegainNegative(final Player player, final Card source, final int n) {
if (!player.canGainLife()) {
return false;
}
@@ -2948,23 +2916,6 @@ public class ComputerUtil {
return true;
}
@Deprecated
public static final Player getOpponentFor(final Player player) {
// This method is deprecated and currently functions as a synonym for player.getWeakestOpponent
// until it can be replaced everywhere in the code.
// Consider replacing calls to this method either with a multiplayer-friendly determination of
// opponent that contextually makes the most sense, or with a direct call to player.getWeakestOpponent
// where that is applicable and makes sense from the point of view of multiplayer AI logic.
Player opponent = player.getWeakestOpponent();
if (opponent != null) {
return opponent;
}
throw new IllegalStateException("No opponents left ingame for " + player);
}
public static int countUsefulCreatures(Player p) {
CardCollection creats = p.getCreaturesInPlay();
int count = 0;
@@ -3047,31 +2998,32 @@ public class ComputerUtil {
// call this to determine if it's safe to use a life payment spell
// or trigger "emergency" strategies such as holding mana for Spike Weaver of Counterspell.
public static boolean aiLifeInDanger(Player ai, boolean serious, int payment) {
Player opponent = ComputerUtil.getOpponentFor(ai);
// test whether the human can kill the ai next turn
Combat combat = new Combat(opponent);
boolean containsAttacker = false;
for (Card att : opponent.getCreaturesInPlay()) {
if (ComputerUtilCombat.canAttackNextTurn(att, ai)) {
combat.addAttacker(att, ai);
containsAttacker = true;
for (Player opponent: ai.getOpponents()) {
// test whether the human can kill the ai next turn
Combat combat = new Combat(opponent);
boolean containsAttacker = false;
for (Card att : opponent.getCreaturesInPlay()) {
if (ComputerUtilCombat.canAttackNextTurn(att, ai)) {
combat.addAttacker(att, ai);
containsAttacker = true;
}
}
}
if (!containsAttacker) {
return false;
}
AiBlockController block = new AiBlockController(ai);
block.assignBlockersForCombat(combat);
if (!containsAttacker) {
return false;
}
AiBlockController block = new AiBlockController(ai);
block.assignBlockersForCombat(combat);
// TODO predict other, noncombat sources of damage and add them to the "payment" variable.
// examples : Black Vise, The Rack, known direct damage spells in enemy hand, etc
// If added, might need a parameter to define whether we want to check all threats or combat threats.
// TODO predict other, noncombat sources of damage and add them to the "payment" variable.
// examples : Black Vise, The Rack, known direct damage spells in enemy hand, etc
// If added, might need a parameter to define whether we want to check all threats or combat threats.
if ((serious) && (ComputerUtilCombat.lifeInSeriousDanger(ai, combat, payment))) {
return true;
}
if ((!serious) && (ComputerUtilCombat.lifeInDanger(ai, combat, payment))) {
return true;
if ((serious) && (ComputerUtilCombat.lifeInSeriousDanger(ai, combat, payment))) {
return true;
}
if ((!serious) && (ComputerUtilCombat.lifeInDanger(ai, combat, payment))) {
return true;
}
}
return false;

View File

@@ -171,8 +171,12 @@ public class ComputerUtilAbility {
return sa;
}
public static Card getAbilitySource(SpellAbility sa) {
return sa.getOriginalHost() != null ? sa.getOriginalHost() : sa.getHostCard();
}
public static String getAbilitySourceName(SpellAbility sa) {
final Card c = sa.getOriginalOrHost();
final Card c = getAbilitySource(sa);
return c != null ? c.getName() : "";
}

View File

@@ -47,6 +47,7 @@ import forge.game.combat.Combat;
import forge.game.combat.CombatUtil;
import forge.game.cost.Cost;
import forge.game.cost.CostPayEnergy;
import forge.game.cost.CostRemoveCounter;
import forge.game.keyword.Keyword;
import forge.game.keyword.KeywordCollection;
import forge.game.keyword.KeywordInterface;
@@ -76,7 +77,7 @@ public class ComputerUtilCard {
}
});
}
return ComputerUtilCard.getMostExpensivePermanentAI(all);
return getMostExpensivePermanentAI(all);
}
/**
@@ -137,6 +138,56 @@ public class ComputerUtilCard {
return Aggregates.itemWithMin(all, CardPredicates.Accessors.fnGetCmc);
}
public static Card getBestPlaneswalkerToDamage(final List<Card> pws) {
Card bestTgt = null;
// As of right now, ranks planeswalkers by their Current Loyalty * 10 + Big buff if close to "Ultimate"
int bestScore = 0;
for (Card pw : pws) {
int curLoyalty = pw.getCounters(CounterEnumType.LOYALTY);
int pwScore = curLoyalty * 10;
for (SpellAbility sa : pw.getSpellAbilities()) {
if (sa.hasParam("Ultimate")) {
Integer loyaltyCost = 0;
CostRemoveCounter remLoyalty = sa.getPayCosts().getCostPartByType(CostRemoveCounter.class);
if (remLoyalty != null) {
// if remLoyalty is null, generally there's an AddCounter<0/LOYALTY> cost, like for Gideon Jura.
loyaltyCost = remLoyalty.convertAmount();
}
if (loyaltyCost != null && loyaltyCost != 0 && loyaltyCost - curLoyalty <= 1) {
// Will ultimate soon
pwScore += 10000;
}
if (pwScore > bestScore) {
bestScore = pwScore;
bestTgt = pw;
}
}
}
}
return bestTgt;
}
public static Card getWorstPlaneswalkerToDamage(final List<Card> pws) {
Card bestTgt = null;
int bestScore = Integer.MAX_VALUE;
for (Card pw : pws) {
int curLoyalty = pw.getCounters(CounterEnumType.LOYALTY);
if (curLoyalty < bestScore) {
bestScore = curLoyalty;
bestTgt = pw;
}
}
return bestTgt;
}
// The AI doesn't really pick the best enchantment, just the most expensive.
/**
* <p>
@@ -265,16 +316,14 @@ public class ComputerUtilCard {
* @return a {@link forge.game.card.Card} object.
*/
public static Card getBestAI(final Iterable<Card> list) {
// Get Best will filter by appropriate getBest list if ALL of the list
// is of that type
// Get Best will filter by appropriate getBest list if ALL of the list is of that type
if (Iterables.all(list, CardPredicates.Presets.CREATURES)) {
return ComputerUtilCard.getBestCreatureAI(list);
}
if (Iterables.all(list, CardPredicates.Presets.LANDS)) {
return getBestLandAI(list);
}
// TODO - Once we get an EvaluatePermanent this should call
// getBestPermanent()
// TODO - Once we get an EvaluatePermanent this should call getBestPermanent()
return ComputerUtilCard.getMostExpensivePermanentAI(list);
}
@@ -383,7 +432,7 @@ public class ComputerUtilCard {
}
if (biasLand && Iterables.any(list, CardPredicates.Presets.LANDS)) {
return ComputerUtilCard.getWorstLand(CardLists.filter(list, CardPredicates.Presets.LANDS));
return getWorstLand(CardLists.filter(list, CardPredicates.Presets.LANDS));
}
final boolean hasCreatures = Iterables.any(list, CardPredicates.Presets.CREATURES);
@@ -393,7 +442,7 @@ public class ComputerUtilCard {
List<Card> lands = CardLists.filter(list, CardPredicates.Presets.LANDS);
if (lands.size() > 6) {
return ComputerUtilCard.getWorstLand(lands);
return getWorstLand(lands);
}
if (hasEnchantmants || hasArtifacts) {
@@ -410,8 +459,7 @@ public class ComputerUtilCard {
return getWorstCreatureAI(CardLists.filter(list, CardPredicates.Presets.CREATURES));
}
// Planeswalkers fall through to here, lands will fall through if there
// aren't very many
// Planeswalkers fall through to here, lands will fall through if there aren't very many
return getCheapestPermanentAI(list, null, false);
}
@@ -444,7 +492,7 @@ public class ComputerUtilCard {
public static final Comparator<Card> EvaluateCreatureComparator = new Comparator<Card>() {
@Override
public int compare(final Card a, final Card b) {
return ComputerUtilCard.evaluateCreature(b) - ComputerUtilCard.evaluateCreature(a);
return evaluateCreature(b) - evaluateCreature(a);
}
};
@@ -550,7 +598,7 @@ public class ComputerUtilCard {
*/
public static CardCollectionView getLikelyBlockers(final Player ai, final CardCollectionView blockers) {
AiBlockController aiBlk = new AiBlockController(ai);
final Player opp = ai.getWeakestOpponent();
final Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
Combat combat = new Combat(opp);
//Use actual attackers if available, else consider all possible attackers
Combat currentCombat = ai.getGame().getCombat();
@@ -882,9 +930,9 @@ public class ComputerUtilCard {
public static final Predicate<Deck> AI_KNOWS_HOW_TO_PLAY_ALL_CARDS = new Predicate<Deck>() {
@Override
public boolean apply(Deck d) {
for(Entry<DeckSection, CardPool> cp: d) {
for(Entry<PaperCard, Integer> e : cp.getValue()) {
if ( e.getKey().getRules().getAiHints().getRemAIDecks() )
for (Entry<DeckSection, CardPool> cp: d) {
for (Entry<PaperCard, Integer> e : cp.getValue()) {
if (e.getKey().getRules().getAiHints().getRemAIDecks())
return false;
}
}
@@ -1198,7 +1246,7 @@ public class ComputerUtilCard {
}
String kws = params.get("AddKeyword");
if (kws != null) {
bonusPT += 4 * (1 + StringUtils.countMatches(kws, "&")); //treat each added keyword as a +2/+2 for now
bonusPT += 4 * (1 + StringUtils.countMatches(kws, "&")); //treat each added keyword as a +2/+2 for now
}
if (bonusPT > 0) {
threat = bonusPT * (1 + opp.getCreaturesInPlay().size()) / 10.0f;
@@ -1212,7 +1260,7 @@ public class ComputerUtilCard {
}
final float valueNow = Math.max(valueTempo, threat);
if (valueNow < 0.2) { //hard floor to reduce ridiculous odds for instants over time
if (valueNow < 0.2) { //hard floor to reduce ridiculous odds for instants over time
return false;
}
final float chance = MyRandom.getRandom().nextFloat();
@@ -1310,7 +1358,7 @@ public class ComputerUtilCard {
threat *= 2;
}
if (c.getNetPower() == 0 && c == sa.getHostCard() && power > 0 ) {
threat *= 4; //over-value self +attack for 0 power creatures which may be pumped further after attacking
threat *= 4; //over-value self +attack for 0 power creatures which may be pumped further after attacking
}
chance += threat;
@@ -1633,7 +1681,7 @@ public class ComputerUtilCard {
pumped.addChangedCardKeywords(kws, null, false, false, timestamp);
Set<CounterType> types = c.getCounters().keySet();
for(CounterType ct : types) {
pumped.addCounterFireNoEvents(ct, c.getCounters(ct), ai, true, null);
pumped.addCounterFireNoEvents(ct, c.getCounters(ct), ai, sa, true, null);
}
//Copies tap-state and extra keywords (auras, equipment, etc.)
if (c.isTapped()) {
@@ -1644,11 +1692,12 @@ public class ComputerUtilCard {
copiedKeywords.insertAll(pumped.getKeywords());
List<KeywordInterface> toCopy = Lists.newArrayList();
for (KeywordInterface k : c.getKeywords()) {
if (!copiedKeywords.contains(k.getOriginal())) {
if (k.getHidden()) {
pumped.addHiddenExtrinsicKeyword(k);
KeywordInterface copiedKI = k.copy(c, true);
if (!copiedKeywords.contains(copiedKI.getOriginal())) {
if (copiedKI.getHidden()) {
pumped.addHiddenExtrinsicKeyword(copiedKI);
} else {
toCopy.add(k);
toCopy.add(copiedKI);
}
}
}
@@ -1678,37 +1727,27 @@ public class ComputerUtilCard {
// remove old boost that might be copied
for (final StaticAbility stAb : c.getStaticAbilities()) {
vCard.removePTBoost(c.getTimestamp(), stAb.getId());
final Map<String, String> params = stAb.getMapParams();
if (!params.get("Mode").equals("Continuous")) {
if (!stAb.getParam("Mode").equals("Continuous")) {
continue;
}
if (!params.containsKey("Affected")) {
if (!stAb.hasParam("Affected")) {
continue;
}
if (!params.containsKey("AddPower") && !params.containsKey("AddToughness")) {
if (!stAb.hasParam("AddPower") && !stAb.hasParam("AddToughness")) {
continue;
}
final String valid = params.get("Affected");
if (!vCard.isValid(valid, c.getController(), c, null)) {
if (!vCard.isValid(stAb.getParam("Affected").split(","), c.getController(), c, stAb)) {
continue;
}
int att = 0;
if (params.containsKey("AddPower")) {
String addP = params.get("AddPower");
if (addP.equals("AffectedX")) {
att = CardFactoryUtil.xCount(vCard, AbilityUtils.getSVar(stAb, addP));
} else {
att = AbilityUtils.calculateAmount(c, addP, stAb);
}
if (stAb.hasParam("AddPower")) {
String addP = stAb.getParam("AddPower");
att = AbilityUtils.calculateAmount(addP.startsWith("Affected") ? vCard : c, addP, stAb, true);
}
int def = 0;
if (params.containsKey("AddToughness")) {
String addT = params.get("AddToughness");
if (addT.equals("AffectedY")) {
def = CardFactoryUtil.xCount(vCard, AbilityUtils.getSVar(stAb, addT));
} else {
def = AbilityUtils.calculateAmount(c, addT, stAb);
}
if (stAb.hasParam("AddToughness")) {
String addT = stAb.getParam("AddToughness");
def = AbilityUtils.calculateAmount(addT.startsWith("Affected") ? vCard : c, addT, stAb, true);
}
vCard.addPTBoost(att, def, c.getTimestamp(), stAb.getId());
}

View File

@@ -33,7 +33,6 @@ import forge.game.ability.ApiType;
import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardCollectionView;
import forge.game.card.CardFactoryUtil;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates;
import forge.game.card.CardUtil;
@@ -97,34 +96,39 @@ public class ComputerUtilCombat {
* canAttackNextTurn.
* </p>
*
* @param atacker
* @param attacker
* a {@link forge.game.card.Card} object.
* @param defender
* the defending {@link GameEntity}.
* @return a boolean.
*/
public static boolean canAttackNextTurn(final Card atacker, final GameEntity defender) {
if (!atacker.isCreature()) {
public static boolean canAttackNextTurn(final Card attacker, final GameEntity defender) {
if (!attacker.isCreature()) {
return false;
}
if (!CombatUtil.canAttackNextTurn(atacker, defender)) {
if (!CombatUtil.canAttackNextTurn(attacker, defender)) {
return false;
}
for (final KeywordInterface inst : atacker.getKeywords()) {
for (final KeywordInterface inst : attacker.getKeywords()) {
final String keyword = inst.getOriginal();
if (keyword.startsWith("CARDNAME attacks specific player each combat if able")) {
final String defined = keyword.split(":")[1];
final Player player = AbilityUtils.getDefinedPlayers(atacker, defined, null).get(0);
final Player player = AbilityUtils.getDefinedPlayers(attacker, defined, null).get(0);
if (!defender.equals(player)) {
return false;
}
}
}
// TODO this should be a factor but needs some alignment with AttachAi
//boolean leavesPlay = !ComputerUtilCard.hasActiveUndyingOrPersist(attacker)
// && ((attacker.hasKeyword(Keyword.VANISHING) && attacker.getCounters(CounterEnumType.TIME) == 1)
// || (attacker.hasKeyword(Keyword.FADING) && attacker.getCounters(CounterEnumType.FADE) == 0)
// || attacker.hasSVar("EndOfTurnLeavePlay"));
// The creature won't untap next turn
return !atacker.isTapped() || Untap.canUntap(atacker);
} // canAttackNextTurn(Card, GameEntity)
return !attacker.isTapped() || Untap.canUntap(attacker);
}
/**
* <p>
@@ -330,7 +334,7 @@ public class ComputerUtilCombat {
*/
public static int resultingPoison(final Player ai, final Combat combat) {
// ai can't get poision counters, so the value can't change
// ai can't get poison counters, so the value can't change
if (!ai.canReceiveCounters(CounterEnumType.POISON)) {
return ai.getPoisonCounters();
}
@@ -408,7 +412,6 @@ public class ComputerUtilCombat {
return false;
}
// check for creatures that must be blocked
final List<Card> attackers = combat.getAttackersOf(ai);
@@ -488,8 +491,7 @@ public class ComputerUtilCombat {
}
public static boolean lifeInSeriousDanger(final Player ai, final Combat combat, final int payment) {
// life in danger only cares about the player's life. Not about a
// Planeswalkers life
// life in danger only cares about the player's life. Not about a Planeswalkers life
if (ai.cantLose() || combat == null) {
return false;
}
@@ -564,8 +566,7 @@ public class ComputerUtilCombat {
return damage;
}
// This calculates the amount of damage a blocker in a blockgang can deal to
// the attacker
// This calculates the amount of damage a blocker in a blockgang can deal to the attacker
/**
* <p>
* dealsDamageAsBlocker.
@@ -578,7 +579,6 @@ public class ComputerUtilCombat {
* @return a int.
*/
public static int dealsDamageAsBlocker(final Card attacker, final Card defender) {
int defenderDamage = predictDamageByBlockerWithoutDoubleStrike(attacker, defender);
if (defender.hasKeyword(Keyword.DOUBLE_STRIKE)) {
@@ -643,7 +643,6 @@ public class ComputerUtilCombat {
* @return a int.
*/
public static int totalShieldDamage(final Card attacker, final List<Card> defenders) {
int defenderDefense = 0;
for (final Card defender : defenders) {
@@ -667,7 +666,6 @@ public class ComputerUtilCombat {
* @return a int.
*/
public static int shieldDamage(final Card attacker, final Card blocker) {
if (ComputerUtilCombat.canDestroyBlockerBeforeFirstStrike(blocker, attacker, false)) {
return 0;
}
@@ -772,7 +770,6 @@ public class ComputerUtilCombat {
public static boolean combatTriggerWillTrigger(final Card attacker, final Card defender, final Trigger trigger,
Combat combat, final List<Card> plannedAttackers) {
final Game game = attacker.getGame();
final Map<String, String> trigParams = trigger.getMapParams();
boolean willTrigger = false;
final Card source = trigger.getHostCard();
if (combat == null) {
@@ -795,29 +792,27 @@ public class ComputerUtilCombat {
if (combat.isAttacking(attacker)) {
return false; // The trigger should have triggered already
}
if (trigParams.containsKey("ValidCard")) {
if (!trigger.matchesValid(attacker, trigParams.get("ValidCard").split(","))
&& !(combat.isAttacking(source) && trigger.matchesValid(source,
trigParams.get("ValidCard").split(","))
&& !trigParams.containsKey("Alone"))) {
if (trigger.hasParam("ValidCard")) {
if (!trigger.matchesValidParam("ValidCard", attacker)
&& !(combat.isAttacking(source) && trigger.matchesValidParam("ValidCard", source)
&& !trigger.hasParam("Alone"))) {
return false;
}
}
if (trigParams.containsKey("Attacked")) {
if (combat.isAttacking(attacker)) {
GameEntity attacked = combat.getDefenderByAttacker(attacker);
if (!trigger.matchesValid(attacked, trigParams.get("Attacked").split(","))) {
return false;
}
} else {
if ("You,Planeswalker.YouCtrl".equals(trigParams.get("Attacked"))) {
if (source.getController() == attacker.getController()) {
return false;
}
}
}
if (trigger.hasParam("Attacked")) {
if (combat.isAttacking(attacker)) {
if (!trigger.matchesValidParam("Attacked", combat.getDefenderByAttacker(attacker))) {
return false;
}
} else {
if ("You,Planeswalker.YouCtrl".equals(trigger.getParam("Attacked"))) {
if (source.getController() == attacker.getController()) {
return false;
}
}
}
}
if (trigParams.containsKey("Alone") && plannedAttackers != null && plannedAttackers.size() != 1) {
if (trigger.hasParam("Alone") && plannedAttackers != null && plannedAttackers.size() != 1) {
return false; // won't trigger since the AI is planning to attack with more than one creature
}
}
@@ -825,10 +820,8 @@ public class ComputerUtilCombat {
// defender == null means unblocked
if ((defender == null) && mode == TriggerType.AttackerUnblocked) {
willTrigger = true;
if (trigParams.containsKey("ValidCard")) {
if (!trigger.matchesValid(attacker, trigParams.get("ValidCard").split(","))) {
return false;
}
if (!trigger.matchesValidParam("ValidCard", attacker)) {
return false;
}
}
@@ -838,8 +831,8 @@ public class ComputerUtilCombat {
if (mode == TriggerType.Blocks) {
willTrigger = true;
if (trigParams.containsKey("ValidBlocked")) {
String validBlocked = trigParams.get("ValidBlocked");
if (trigger.hasParam("ValidBlocked")) {
String validBlocked = trigger.getParam("ValidBlocked");
if (validBlocked.contains(".withLesserPower")) {
// Have to check this restriction here as triggering objects aren't set yet, so
// ValidBlocked$Creature.powerLTX where X:TriggeredBlocker$CardPower crashes with NPE
@@ -852,8 +845,8 @@ public class ComputerUtilCombat {
return false;
}
}
if (trigParams.containsKey("ValidCard")) {
String validBlocker = trigParams.get("ValidCard");
if (trigger.hasParam("ValidCard")) {
String validBlocker = trigger.getParam("ValidCard");
if (validBlocker.contains(".withLesserPower")) {
// Have to check this restriction here as triggering objects aren't set yet, so
// ValidCard$Creature.powerLTX where X:TriggeredAttacker$CardPower crashes with NPE
@@ -868,29 +861,23 @@ public class ComputerUtilCombat {
}
} else if (mode == TriggerType.AttackerBlocked || mode == TriggerType.AttackerBlockedByCreature) {
willTrigger = true;
if (trigParams.containsKey("ValidBlocker")) {
if (!trigger.matchesValid(defender, trigParams.get("ValidBlocker").split(","))) {
return false;
}
if (!trigger.matchesValidParam("ValidBlocker", defender)) {
return false;
}
if (trigParams.containsKey("ValidCard")) {
if (!trigger.matchesValid(attacker, trigParams.get("ValidCard").split(","))) {
return false;
}
if (!trigger.matchesValidParam("ValidCard", attacker)) {
return false;
}
} else if (mode == TriggerType.DamageDone) {
willTrigger = true;
if (trigParams.containsKey("ValidSource")) {
if (!(trigger.matchesValid(defender, trigParams.get("ValidSource").split(","))
if (trigger.hasParam("ValidSource")) {
if (!(trigger.matchesValidParam("ValidSource", defender)
&& defender.getNetCombatDamage() > 0
&& (!trigParams.containsKey("ValidTarget")
|| trigger.matchesValid(attacker, trigParams.get("ValidTarget").split(","))))) {
&& trigger.matchesValidParam("ValidTarget", attacker))) {
return false;
}
if (!(trigger.matchesValid(attacker, trigParams.get("ValidSource").split(","))
if (!(trigger.matchesValidParam("ValidSource", attacker)
&& attacker.getNetCombatDamage() > 0
&& (!trigParams.containsKey("ValidTarget")
|| trigger.matchesValid(defender, trigParams.get("ValidTarget").split(","))))) {
&& trigger.matchesValidParam("ValidTarget", defender))) {
return false;
}
}
@@ -939,30 +926,22 @@ public class ComputerUtilCombat {
}
final Game game = attacker.getGame();
// look out for continuous static abilities that only care for blocking
// creatures
// look out for continuous static abilities that only care for blocking creatures
final CardCollectionView cardList = CardCollection.combine(game.getCardsIn(ZoneType.Battlefield), game.getCardsIn(ZoneType.Command));
for (final Card card : cardList) {
for (final StaticAbility stAb : card.getStaticAbilities()) {
final Map<String, String> params = stAb.getMapParams();
if (!params.get("Mode").equals("Continuous")) {
if (!stAb.getParam("Mode").equals("Continuous")) {
continue;
}
if (!params.containsKey("Affected") || !params.get("Affected").contains("blocking")) {
if (!stAb.hasParam("Affected") || !stAb.getParam("Affected").contains("blocking")) {
continue;
}
final String valid = TextUtil.fastReplace(params.get("Affected"), "blocking", "Creature");
if (!blocker.isValid(valid, card.getController(), card, null)) {
final String valid = TextUtil.fastReplace(stAb.getParam("Affected"), "blocking", "Creature");
if (!blocker.isValid(valid, card.getController(), card, stAb)) {
continue;
}
if (params.containsKey("AddPower")) {
if (params.get("AddPower").equals("X")) {
power += CardFactoryUtil.xCount(card, card.getSVar("X"));
} else if (params.get("AddPower").equals("Y")) {
power += CardFactoryUtil.xCount(card, card.getSVar("Y"));
} else {
power += Integer.valueOf(params.get("AddPower"));
}
if (stAb.hasParam("AddPower")) {
power += AbilityUtils.calculateAmount(card, stAb.getParam("AddPower"), stAb);
}
}
}
@@ -1247,31 +1226,23 @@ public class ComputerUtilCombat {
theTriggers.addAll(blocker.getTriggers());
}
// look out for continuous static abilities that only care for attacking
// creatures
// look out for continuous static abilities that only care for attacking creatures
if (!withoutCombatStaticAbilities) {
final CardCollectionView cardList = CardCollection.combine(game.getCardsIn(ZoneType.Battlefield), game.getCardsIn(ZoneType.Command));
for (final Card card : cardList) {
for (final StaticAbility stAb : card.getStaticAbilities()) {
final Map<String, String> params = stAb.getMapParams();
if (!params.get("Mode").equals("Continuous")) {
if (!stAb.getParam("Mode").equals("Continuous")) {
continue;
}
if (!params.containsKey("Affected") || !params.get("Affected").contains("attacking")) {
if (!stAb.hasParam("Affected") || !stAb.getParam("Affected").contains("attacking")) {
continue;
}
final String valid = TextUtil.fastReplace(params.get("Affected"), "attacking", "Creature");
if (!attacker.isValid(valid, card.getController(), card, null)) {
final String valid = TextUtil.fastReplace(stAb.getParam("Affected"), "attacking", "Creature");
if (!attacker.isValid(valid, card.getController(), card, stAb)) {
continue;
}
if (params.containsKey("AddPower")) {
if (params.get("AddPower").equals("X")) {
power += CardFactoryUtil.xCount(card, card.getSVar("X"));
} else if (params.get("AddPower").equals("Y")) {
power += CardFactoryUtil.xCount(card, card.getSVar("Y"));
} else {
power += Integer.valueOf(params.get("AddPower"));
}
if (stAb.hasParam("AddPower")) {
power += AbilityUtils.calculateAmount(card, stAb.getParam("AddPower"), stAb);
}
}
}
@@ -1345,7 +1316,7 @@ public class ComputerUtilCombat {
} else if (bonus.contains("TriggeredAttacker$CardToughness")) {
bonus = TextUtil.fastReplace(bonus, "TriggeredAttacker$CardToughness", TextUtil.concatNoSpace("Number$", String.valueOf(attacker.getNetToughness())));
}
power += CardFactoryUtil.xCount(source, bonus);
power += AbilityUtils.calculateAmount(source, bonus, sa);
}
}
@@ -1442,8 +1413,7 @@ public class ComputerUtilCombat {
theTriggers.addAll(blocker.getTriggers());
}
// look out for continuous static abilities that only care for attacking
// creatures
// look out for continuous static abilities that only care for attacking creatures
if (!withoutCombatStaticAbilities) {
final CardCollectionView cardList = game.getCardsIn(ZoneType.Battlefield);
for (final Card card : cardList) {
@@ -1535,7 +1505,7 @@ public class ComputerUtilCombat {
} else if (bonus.contains("TriggeredPlayersDefenders$Amount")) { // for Melee
bonus = TextUtil.fastReplace(bonus, "TriggeredPlayersDefenders$Amount", "Number$1");
}
toughness += CardFactoryUtil.xCount(source, bonus);
toughness += AbilityUtils.calculateAmount(source, bonus, sa);
}
} else if (ApiType.PumpAll.equals(sa.getApi())) {
@@ -1568,7 +1538,7 @@ public class ComputerUtilCombat {
} else if (bonus.contains("TriggeredPlayersDefenders$Amount")) { // for Melee
bonus = TextUtil.fastReplace(bonus, "TriggeredPlayersDefenders$Amount", "Number$1");
}
toughness += CardFactoryUtil.xCount(source, bonus);
toughness += AbilityUtils.calculateAmount(source, bonus, sa);
}
}
}
@@ -1633,8 +1603,8 @@ public class ComputerUtilCombat {
//Check triggers that deal damage or shrink the attacker
if (ComputerUtilCombat.getDamageToKill(attacker)
+ ComputerUtilCombat.predictToughnessBonusOfAttacker(attacker, blocker, combat, withoutAbilities) <= 0) {
return true;
+ ComputerUtilCombat.predictToughnessBonusOfAttacker(attacker, blocker, combat, withoutAbilities) <= 0) {
return true;
}
// check Destroy triggers (Cockatrice and friends)
@@ -2251,11 +2221,12 @@ public class ComputerUtilCombat {
* @return a int.
*/
public final static int getDamageToKill(final Card c) {
int killDamage = c.getLethalDamage() + c.getPreventNextDamageTotalShields();
int damageShield = c.getPreventNextDamageTotalShields();
int killDamage = c.getLethalDamage() + damageShield;
if ((killDamage > c.getPreventNextDamageTotalShields())
if ((killDamage > damageShield)
&& c.hasSVar("DestroyWhenDamaged")) {
killDamage = 1 + c.getPreventNextDamageTotalShields();
killDamage = 1 + damageShield;
}
return killDamage;
@@ -2277,7 +2248,6 @@ public class ComputerUtilCombat {
*/
public final static int predictDamageTo(final Player target, final int damage, final Card source, final boolean isCombat) {
final Game game = target.getGame();
int restDamage = damage;
@@ -2286,39 +2256,38 @@ public class ComputerUtilCombat {
// Predict replacement effects
for (final Card ca : game.getCardsIn(ZoneType.STATIC_ABILITIES_SOURCE_ZONES)) {
for (final ReplacementEffect re : ca.getReplacementEffects()) {
Map<String, String> params = re.getMapParams();
if (!re.getMode().equals(ReplacementType.DamageDone) || !params.containsKey("PreventionEffect")) {
if (!re.getMode().equals(ReplacementType.DamageDone) ||
(!re.hasParam("PreventionEffect") && !re.hasParam("Prevent"))) {
continue;
}
// Immortal Coil prevents the damage but has a similar negative effect
if ("Immortal Coil".equals(ca.getName())) {
continue;
}
if (params.containsKey("ValidSource")
&& !source.isValid(params.get("ValidSource"), ca.getController(), ca, null)) {
if (!re.matchesValidParam("ValidSource", source)) {
continue;
}
if (params.containsKey("ValidTarget")
&& !target.isValid(params.get("ValidTarget"), ca.getController(), ca, null)) {
if (!re.matchesValidParam("ValidTarget", source)) {
continue;
}
if (params.containsKey("IsCombat")) {
if (params.get("IsCombat").equals("True")) {
if (!isCombat) {
continue;
}
} else {
if (isCombat) {
continue;
}
if (re.hasParam("IsCombat")) {
if (re.getParam("IsCombat").equals("True") != isCombat) {
continue;
}
}
if (re.hasParam("Prevent")) {
return 0;
} else if (re.getOverridingAbility() != null) {
SpellAbility repSA = re.getOverridingAbility();
if (repSA.getApi() == ApiType.ReplaceDamage) {
return Math.max(0, restDamage - AbilityUtils.calculateAmount(ca, repSA.getParam("Amount"), repSA));
}
}
return 0;
}
}
restDamage = target.staticDamagePrevention(restDamage, source, isCombat, true);
restDamage = target.staticDamagePrevention(restDamage, 0, source, isCombat);
return restDamage;
}
@@ -2339,13 +2308,7 @@ public class ComputerUtilCombat {
// This function helps the AI calculate the actual amount of damage an
// effect would deal
public final static int predictDamageTo(final Card target, final int damage, final Card source, final boolean isCombat) {
int restDamage = damage;
restDamage = target.staticReplaceDamage(restDamage, source, isCombat);
restDamage = target.staticDamagePrevention(restDamage, source, isCombat, true);
return restDamage;
return predictDamageTo(target, damage, 0, source, isCombat);
}
@@ -2367,7 +2330,6 @@ public class ComputerUtilCombat {
* @return a int.
*/
public final static int predictDamageTo(final Card target, final int damage, final int possiblePrevention, final Card source, final boolean isCombat) {
int restDamage = damage;
restDamage = target.staticReplaceDamage(restDamage, source, isCombat);
@@ -2377,7 +2339,6 @@ public class ComputerUtilCombat {
}
public final static boolean dealsFirstStrikeDamage(final Card combatant, final boolean withoutAbilities, final Combat combat) {
if (combatant.hasKeyword(Keyword.DOUBLE_STRIKE) || combatant.hasKeyword(Keyword.FIRST_STRIKE)) {
return true;
}
@@ -2485,7 +2446,14 @@ public class ComputerUtilCombat {
List<ReplacementEffect> list = game.getReplacementHandler().getReplacementList(
ReplacementType.DamageDone, repParams, ReplacementLayer.Other);
return !list.isEmpty();
for (final ReplacementEffect re : list) {
Map<String, String> params = re.getMapParams();
if (params.containsKey("Prevent") ||
(re.getOverridingAbility() != null && re.getOverridingAbility().getApi() != ApiType.ReplaceDamage && re.getOverridingAbility().getApi() != ApiType.ReplaceEffect)) {
return true;
}
}
return false;
}
public static boolean attackerHasThreateningAfflict(Card attacker, Player aiDefender) {
@@ -2494,20 +2462,6 @@ public class ComputerUtilCombat {
return afflictDmg > attacker.getNetPower() || afflictDmg >= aiDefender.getLife();
}
public static int getMaxAttackersFor(final GameEntity defender) {
if (defender instanceof Player) {
for (final Card card : ((Player) defender).getCardsIn(ZoneType.Battlefield)) {
if (card.hasKeyword("No more than one creature can attack you each combat.")) {
return 1;
} else if (card.hasKeyword("No more than two creatures can attack you each combat.")) {
return 2;
}
}
}
return -1;
}
public static List<Card> categorizeAttackersByEvasion(List<Card> attackers) {
List<Card> categorizedAttackers = Lists.newArrayList();
@@ -2597,5 +2551,3 @@ public class ComputerUtilCombat {
return false;
}
}

View File

@@ -3,6 +3,7 @@ package forge.ai;
import java.util.List;
import java.util.Set;
import forge.game.ability.ApiType;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
@@ -137,9 +138,8 @@ public class ComputerUtilCost {
* the source
* @return true, if successful
*/
public static boolean checkDiscardCost(final Player ai, final Cost cost, final Card source, SpellAbility sa) {
if (cost == null || source.hasSVar("AISkipDiscardCostCheck") /* FIXME: should not be needed! */ ) {
if (cost == null) {
return true;
}
@@ -157,7 +157,7 @@ public class ComputerUtilCost {
if (typeList.size() > ai.getMaxHandSize()) {
continue;
}
int num = AbilityUtils.calculateAmount(source, disc.getAmount(), null);
int num = AbilityUtils.calculateAmount(source, disc.getAmount(), sa);
for (int i = 0; i < num; i++) {
Card pref = ComputerUtil.getCardPreference(ai, source, "DiscardCost", typeList);
@@ -338,17 +338,17 @@ public class ComputerUtilCost {
}
public static boolean isSacrificeSelfCost(final Cost cost) {
if (cost == null) {
return false;
}
for (final CostPart part : cost.getCostParts()) {
if (part instanceof CostSacrifice) {
if ("CARDNAME".equals(part.getType())) {
return true;
}
}
}
return false;
if (cost == null) {
return false;
}
for (final CostPart part : cost.getCostParts()) {
if (part instanceof CostSacrifice) {
if ("CARDNAME".equals(part.getType())) {
return true;
}
}
}
return false;
}
/**
@@ -368,13 +368,13 @@ public class ComputerUtilCost {
if (part instanceof CostTapType) {
/*
* Only crew with creatures weaker than vehicle
*
*
* Possible improvements:
* - block against evasive (flyers, intimidate, etc.)
* - break board stall by racing with evasive vehicle
*/
if (sa.hasParam("Crew")) {
Card vehicle = AnimateAi.becomeAnimated(source, sa);
Card vehicle = AnimateAi.becomeAnimated(source, sa);
final int vehicleValue = ComputerUtilCard.evaluateCreature(vehicle);
String type = part.getType();
String totalP = type.split("withTotalPowerGE")[1];
@@ -391,7 +391,7 @@ public class ComputerUtilCost {
return ComputerUtil.chooseTapTypeAccumulatePower(ai, type, sa, true,
Integer.parseInt(totalP), exclude) != null;
}
return false;
return false;
}
}
return true;
@@ -479,9 +479,9 @@ public class ComputerUtilCost {
}
}
for (Card c : player.getCardsIn(ZoneType.Command)) {
if (cannotBeCountered) {
continue;
}
if (cannotBeCountered) {
continue;
}
final String snem = c.getSVar("SpellsNeedExtraManaEffect");
if (!StringUtils.isBlank(snem)) {
if (StringUtils.isNumeric(snem)) {
@@ -549,7 +549,7 @@ public class ComputerUtilCost {
}
return ComputerUtilMana.canPayManaCost(sa, player, extraManaNeeded)
&& CostPayment.canPayAdditionalCosts(sa.getPayCosts(), sa);
&& CostPayment.canPayAdditionalCosts(sa.getPayCosts(), sa);
} // canPayCost()
public static boolean willPayUnlessCost(SpellAbility sa, Player payer, Cost cost, boolean alreadyPaid, FCollectionView<Player> payers) {
@@ -558,7 +558,6 @@ public class ComputerUtilCost {
boolean payForOwnOnly = "OnlyOwn".equals(aiLogic);
boolean payOwner = sa.hasParam("UnlessAI") && aiLogic.startsWith("Defined");
boolean payNever = "Never".equals(aiLogic);
boolean shockland = "Shockland".equals(aiLogic);
boolean isMine = sa.getActivatingPlayer().equals(payer);
if (payNever) { return false; }
@@ -573,26 +572,6 @@ public class ComputerUtilCost {
if (sa.getHostCard() == null || payer.equals(sa.getHostCard().getController())) {
return false;
}
} else if (shockland) {
if (payer.getLife() > 3 && payer.canPayLife(2)) {
final int landsize = payer.getLandsInPlay().size() + 1;
for (Card c : payer.getCardsIn(ZoneType.Hand)) {
// if the new land size would equal the CMC of a card in AIs hand, consider playing it untapped,
// otherwise don't bother running other checks
if (landsize != c.getCMC()) {
continue;
}
// try to determine in the AI is actually planning to play a spell ability from the card
boolean willPlay = ComputerUtil.hasReasonToPlayCardThisTurn(payer, c);
// try to determine if the mana shards provided by the lands would be applicable to pay the mana cost
boolean canPay = c.getManaCost().canBePaidWithAvaliable(ColorSet.fromNames(getAvailableManaColors(payer, source)).getColor());
if (canPay && willPlay) {
return true;
}
}
}
return false;
} else if ("Paralyze".equals(aiLogic)) {
final Card c = source.getEnchantingCard();
if (c == null || c.isUntapped()) {
@@ -610,10 +589,10 @@ public class ComputerUtilCost {
return false;
}
} else if (aiLogic != null && aiLogic.startsWith("LifeLE")) {
// if payer can't lose life its no need to pay unless
if (!payer.canLoseLife())
return false;
else if (payer.getLife() <= Integer.valueOf(aiLogic.substring(6))) {
// if payer can't lose life its no need to pay unless
if (!payer.canLoseLife())
return false;
else if (payer.getLife() <= AbilityUtils.calculateAmount(source, aiLogic.substring(6), sa)) {
return true;
}
} else if ("WillAttack".equals(aiLogic)) {
@@ -630,6 +609,29 @@ public class ComputerUtilCost {
return false;
}
// Check for shocklands and similar ETB replacement effects
if (sa.hasParam("ETB") && sa.getApi().equals(ApiType.Tap)) {
for (final CostPart part : cost.getCostParts()) {
if (part instanceof CostPayLife) {
final CostPayLife lifeCost = (CostPayLife) part;
Integer amount = lifeCost.convertAmount();
if (payer.getLife() > (amount + 1) && payer.canPayLife(amount)) {
final int landsize = payer.getLandsInPlay().size() + 1;
for (Card c : payer.getCardsIn(ZoneType.Hand)) {
// Check if the AI has enough lands to play the card
if (landsize != c.getCMC()) {
continue;
}
// Check if the AI intends to play the card and if it can pay for it with the mana it has
boolean willPlay = ComputerUtil.hasReasonToPlayCardThisTurn(payer, c);
boolean canPay = c.getManaCost().canBePaidWithAvaliable(ColorSet.fromNames(getAvailableManaColors(payer, source)).getColor());
return canPay && willPlay;
}
}
}
}
}
// AI will only pay when it's not already payed and only opponents abilities
if (alreadyPaid || (payers.size() > 1 && (isMine && !payForOwnOnly))) {
return false;
@@ -639,13 +641,13 @@ public class ComputerUtilCost {
// Didn't have any of the data on the original SA to pay dependant costs
return checkLifeCost(payer, cost, source, 4, sa)
&& checkDamageCost(payer, cost, source, 4)
&& (isMine || checkSacrificeCost(payer, cost, source, sa))
&& (isMine || checkDiscardCost(payer, cost, source, sa))
&& (!source.getName().equals("Tyrannize") || payer.getCardsIn(ZoneType.Hand).size() > 2)
&& (!source.getName().equals("Perplex") || payer.getCardsIn(ZoneType.Hand).size() < 2)
&& (!source.getName().equals("Breaking Point") || payer.getCreaturesInPlay().size() > 1)
&& (!source.getName().equals("Chain of Vapor") || (payer.getWeakestOpponent().getCreaturesInPlay().size() > 0 && payer.getLandsInPlay().size() > 3));
&& checkDamageCost(payer, cost, source, 4)
&& (isMine || checkSacrificeCost(payer, cost, source, sa))
&& (isMine || checkDiscardCost(payer, cost, source, sa))
&& (!source.getName().equals("Tyrannize") || payer.getCardsIn(ZoneType.Hand).size() > 2)
&& (!source.getName().equals("Perplex") || payer.getCardsIn(ZoneType.Hand).size() < 2)
&& (!source.getName().equals("Breaking Point") || payer.getCreaturesInPlay().size() > 1)
&& (!source.getName().equals("Chain of Vapor") || (payer.getWeakestOpponent().getCreaturesInPlay().size() > 0 && payer.getLandsInPlay().size() > 3));
}
public static Set<String> getAvailableManaColors(Player ai, Card additionalLand) {
@@ -685,14 +687,14 @@ public class ComputerUtilCost {
public static int getMaxXValue(SpellAbility sa, Player ai) {
final Card source = sa.getHostCard();
final SpellAbility root = sa.getRootAbility();
final Cost abCost = sa.getPayCosts();
final Cost abCost = root.getPayCosts();
if (abCost == null || !abCost.hasXInAnyCostPart()) {
return 0;
}
Integer val = null;
if (sa.costHasManaX()) {
if (root.costHasManaX()) {
val = ComputerUtilMana.determineLeftoverMana(root, ai);
}

View File

@@ -319,8 +319,8 @@ public class ComputerUtilMana {
// Exception: when paying generic mana with Cavern of Souls, prefer the colored mana producing ability
// to attempt to make the spell uncounterable when possible.
if (ComputerUtilAbility.getAbilitySourceName(ma).equals("Cavern of Souls") && ma.hasChosenType()
&& sa.getHostCard().getType().getCreatureTypes().contains(ma.getChosenType(0))) {
if (ComputerUtilAbility.getAbilitySourceName(ma).equals("Cavern of Souls")
&& sa.getHostCard().getType().getCreatureTypes().contains(ma.getHostCard().getChosenType())) {
if (toPay == ManaCostShard.COLORLESS && cost.getUnpaidShards().contains(ManaCostShard.GENERIC)) {
// Deprioritize Cavern of Souls, try to pay generic mana with it instead to use the NoCounter ability
continue;
@@ -341,7 +341,7 @@ public class ComputerUtilMana {
continue;
}
if (canPayShardWithSpellAbility(toPay, ai, paymentChoice, sa, checkCosts)) {
if (canPayShardWithSpellAbility(toPay, ai, paymentChoice, sa, checkCosts, cost.getXManaCostPaidByColor())) {
return paymentChoice;
}
}
@@ -414,6 +414,7 @@ public class ComputerUtilMana {
// then apply this one
if (!replaceType.isEmpty()) {
for (SpellAbility saMana : replaceAmount) {
Card card = saMana.getHostCard();
if (saMana.hasParam("ReplaceType")) {
// replace color and colorless
String color = saMana.getParam("ReplaceType");
@@ -435,8 +436,8 @@ public class ComputerUtilMana {
// replace color
String color = saMana.getParam("ReplaceColor");
if ("Chosen".equals(color)) {
if (saMana.hasChosenColor()) {
color = MagicColor.toShortString(saMana.getChosenColor());
if (card.hasChosenColor()) {
color = MagicColor.toShortString(card.getChosenColor());
}
}
if (saMana.hasParam("ReplaceOnly")) {
@@ -488,7 +489,7 @@ public class ComputerUtilMana {
int pAmount = AbilityUtils.calculateAmount(trSA.getHostCard(), trSA.getParamOrDefault("Amount", "1"), trSA);
String produced = trSA.getParam("Produced");
if (produced.equals("Chosen")) {
produced = MagicColor.toShortString(trSA.getHostCard().getChosenColor(trSA));
produced = MagicColor.toShortString(trSA.getHostCard().getChosenColor());
}
manaProduced += " " + StringUtils.repeat(produced, pAmount);
} else if (ApiType.ManaReflected.equals(trSA.getApi())) {
@@ -546,7 +547,7 @@ public class ComputerUtilMana {
}
// get a mana of this type from floating, bail if none available
final Mana mana = getMana(ai, part, sa, cost.getSourceRestriction(), (byte) -1);
final Mana mana = getMana(ai, part, sa, cost.getSourceRestriction(), (byte) -1, cost.getXManaCostPaidByColor());
if (mana != null) {
if (ai.getManaPool().tryPayCostWithMana(sa, cost, mana, false)) {
manaSpentToPay.add(0, mana);
@@ -608,7 +609,6 @@ public class ComputerUtilMana {
String manaProduced = predictManafromSpellAbility(saPayment, ai, toPay);
//System.out.println(manaProduced);
payMultipleMana(cost, manaProduced, ai);
// remove from available lists
@@ -935,7 +935,7 @@ public class ComputerUtilMana {
}
// get a mana of this type from floating, bail if none available
final Mana mana = getMana(ai, part, sa, cost.getSourceRestriction(), hasConverge ? cost.getColorsPaid() : -1);
final Mana mana = getMana(ai, part, sa, cost.getSourceRestriction(), hasConverge ? cost.getColorsPaid() : -1, cost.getXManaCostPaidByColor());
if (mana != null) {
if (ai.getManaPool().tryPayCostWithMana(sa, cost, mana, test)) {
manaSpentToPay.add(0, mana);
@@ -964,8 +964,10 @@ public class ComputerUtilMana {
* a {@link forge.game.spellability.SpellAbility} object.
* @return a {@link forge.game.mana.Mana} object.
*/
private static Mana getMana(final Player ai, final ManaCostShard shard, final SpellAbility saBeingPaidFor, String restriction, final byte colorsPaid) {
final List<Pair<Mana, Integer>> weightedOptions = selectManaToPayFor(ai.getManaPool(), shard, saBeingPaidFor, restriction, colorsPaid);
private static Mana getMana(final Player ai, final ManaCostShard shard, final SpellAbility saBeingPaidFor,
String restriction, final byte colorsPaid, Map<String, Integer> xManaCostPaidByColor) {
final List<Pair<Mana, Integer>> weightedOptions = selectManaToPayFor(ai.getManaPool(), shard,
saBeingPaidFor, restriction, colorsPaid, xManaCostPaidByColor);
// Exclude border case
if (weightedOptions.isEmpty()) {
@@ -1014,9 +1016,13 @@ public class ComputerUtilMana {
}
private static List<Pair<Mana, Integer>> selectManaToPayFor(final ManaPool manapool, final ManaCostShard shard,
final SpellAbility saBeingPaidFor, String restriction, final byte colorsPaid) {
final SpellAbility saBeingPaidFor, String restriction, final byte colorsPaid, Map<String, Integer> xManaCostPaidByColor) {
final List<Pair<Mana, Integer>> weightedOptions = new ArrayList<>();
for (final Mana thisMana : manapool) {
if (shard == ManaCostShard.COLORED_X && !ManaCostBeingPaid.canColoredXShardBePaidByColor(MagicColor.toShortString(thisMana.getColor()), xManaCostPaidByColor)) {
continue;
}
if (!manapool.canPayForShardWithColor(shard, thisMana.getColor())) {
continue;
}
@@ -1092,7 +1098,7 @@ public class ComputerUtilMana {
}
}
private static boolean canPayShardWithSpellAbility(ManaCostShard toPay, Player ai, SpellAbility ma, SpellAbility sa, boolean checkCosts) {
private static boolean canPayShardWithSpellAbility(ManaCostShard toPay, Player ai, SpellAbility ma, SpellAbility sa, boolean checkCosts, Map<String, Integer> xManaCostPaidByColor) {
final Card sourceCard = ma.getHostCard();
if (isManaSourceReserved(ai, sourceCard, sa)) {
@@ -1130,6 +1136,10 @@ public class ComputerUtilMana {
if (m.isComboMana()) {
for (String s : m.getComboColors().split(" ")) {
if (toPay == ManaCostShard.COLORED_X && !ManaCostBeingPaid.canColoredXShardBePaidByColor(s, xManaCostPaidByColor)) {
continue;
}
if ("Any".equals(s) || ai.getManaPool().canPayForShardWithColor(toPay, ManaAtom.fromName(s)))
return true;
}
@@ -1140,6 +1150,9 @@ public class ComputerUtilMana {
Set<String> reflected = CardUtil.getReflectableManaColors(ma);
for (byte c : MagicColor.WUBRG) {
if (toPay == ManaCostShard.COLORED_X && !ManaCostBeingPaid.canColoredXShardBePaidByColor(MagicColor.toShortString(c), xManaCostPaidByColor)) {
continue;
}
if (ai.getManaPool().canPayForShardWithColor(toPay, c) && reflected.contains(MagicColor.toLongString(c))) {
m.setExpressChoice(MagicColor.toShortString(c));
return true;
@@ -1147,6 +1160,16 @@ public class ComputerUtilMana {
}
return false;
}
if (toPay == ManaCostShard.COLORED_X) {
for (String s : m.mana().split(" ")) {
if (ManaCostBeingPaid.canColoredXShardBePaidByColor(s, xManaCostPaidByColor)) {
return true;
}
}
return false;
}
return true;
}
@@ -1433,17 +1456,26 @@ public class ComputerUtilMana {
// Tack xMana Payments into mana here if X is a set value
if (cost.getXcounter() > 0 || extraMana > 0) {
int manaToAdd = 0;
int xCounter = cost.getXcounter();
if (test && extraMana > 0) {
final int multiplicator = Math.max(cost.getXcounter(), 1);
final int multiplicator = Math.max(xCounter, 1);
manaToAdd = extraMana * multiplicator;
} else {
manaToAdd = AbilityUtils.calculateAmount(card, "X", sa) * cost.getXcounter();
manaToAdd = AbilityUtils.calculateAmount(card, "X", sa) * xCounter;
}
cost.increaseShard(ManaCostShard.parseNonGeneric(sa.getParamOrDefault("XColor", "1")), manaToAdd);
String xColor = sa.getParamOrDefault("XColor", "1");
if (card.hasKeyword("Spend only colored mana on X. No more than one mana of each color may be spent this way.")) {
xColor = "WUBRGX";
}
if (xCounter > 0) {
cost.setXManaCostPaid(manaToAdd / xCounter, xColor);
} else {
cost.increaseShard(ManaCostShard.parseNonGeneric(xColor), manaToAdd);
}
if (!test) {
sa.setXManaCostPaid(manaToAdd / cost.getXcounter());
sa.setXManaCostPaid(manaToAdd / xCounter);
}
}
@@ -1530,7 +1562,7 @@ public class ComputerUtilMana {
public boolean apply(final Card c) {
for (final SpellAbility am : getAIPlayableMana(c)) {
am.setActivatingPlayer(ai);
if (!checkPlayable || am.canPlay()) {
if (!checkPlayable || (am.canPlay() && am.checkRestrictions(ai))) {
return true;
}
}

View File

@@ -162,9 +162,9 @@ public class CreatureEvaluator implements Function<Card, Integer> {
value -= subValue(10, "must-attack");
} else if (c.hasStartOfKeyword("CARDNAME attacks specific player each combat if able")) {
value -= subValue(10, "must-attack-player");
} else if (c.hasKeyword("CARDNAME can block only creatures with flying.")) {
}/* else if (c.hasKeyword("CARDNAME can block only creatures with flying.")) {
value -= subValue(toughness * 5, "reverse-reach");
}
}//*/
if (c.hasSVar("DestroyWhenDamaged")) {
value -= subValue((toughness - 1) * 9, "dies-to-dmg");

View File

@@ -3,23 +3,12 @@ package forge.ai;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.*;
import java.util.Map.Entry;
import java.util.Set;
import com.google.common.collect.*;
import org.apache.commons.lang3.StringUtils;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import forge.StaticData;
import forge.card.CardStateName;
import forge.card.MagicColor;
@@ -27,7 +16,6 @@ import forge.game.Game;
import forge.game.GameEntity;
import forge.game.GameObject;
import forge.game.ability.AbilityFactory;
import forge.game.ability.AbilityKey;
import forge.game.ability.effects.DetachedCardEffect;
import forge.game.card.Card;
import forge.game.card.CardCloneStates;
@@ -44,9 +32,7 @@ import forge.game.mana.ManaPool;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.AbilityManaPart;
import forge.game.spellability.AbilitySub;
import forge.game.spellability.SpellAbility;
import forge.game.trigger.TriggerType;
import forge.game.zone.PlayerZone;
import forge.game.zone.ZoneType;
import forge.item.IPaperCard;
@@ -89,7 +75,8 @@ public abstract class GameState {
private final Map<Card, Integer> markedDamage = new HashMap<>();
private final Map<Card, List<String>> cardToChosenClrs = new HashMap<>();
private final Map<Card, CardCollection> cardToChosenCards = new HashMap<>();
private final Map<Card, List<String>> cardToChosenTypes = new HashMap<>();
private final Map<Card, String> cardToChosenType = new HashMap<>();
private final Map<Card, String> cardToChosenType2 = new HashMap<>();
private final Map<Card, List<String>> cardToRememberedId = new HashMap<>();
private final Map<Card, List<String>> cardToImprintedId = new HashMap<>();
private final Map<Card, List<String>> cardToMergedCards = new HashMap<>();
@@ -340,16 +327,15 @@ public abstract class GameState {
newText.append("|Damage:").append(c.getDamage());
}
SpellAbility first = c.getFirstSpellAbility();
if (first != null) {
if (first.hasChosenColor()) {
newText.append("|ChosenColor:").append(TextUtil.join(first.getChosenColors(), ","));
}
if (first.hasChosenType()) {
newText.append("|ChosenType:").append(TextUtil.join(first.getChosenType(), ","));
}
if (!c.getChosenColor().isEmpty()) {
newText.append("|ChosenColor:").append(TextUtil.join(c.getChosenColors(), ","));
}
if (!c.getChosenType().isEmpty()) {
newText.append("|ChosenType:").append(c.getChosenType());
}
if (!c.getChosenType2().isEmpty()) {
newText.append("|ChosenType2:").append(c.getChosenType2());
}
if (!c.getNamedCard().isEmpty()) {
newText.append("|NamedCard:").append(c.getNamedCard());
}
@@ -638,7 +624,8 @@ public abstract class GameState {
markedDamage.clear();
cardToChosenClrs.clear();
cardToChosenCards.clear();
cardToChosenTypes.clear();
cardToChosenType.clear();
cardToChosenType2.clear();
cardToMergedCards.clear();
cardToScript.clear();
cardAttackMap.clear();
@@ -733,7 +720,7 @@ public abstract class GameState {
if (persistent) {
produced.put("PersistentMana", "True");
}
final AbilityManaPart abMana = new AbilityManaPart(dummy, null, produced);
final AbilityManaPart abMana = new AbilityManaPart(dummy, produced);
game.getAction().invoke(new Runnable() {
@Override
public void run() {
@@ -769,23 +756,10 @@ public abstract class GameState {
}
game.fireEvent(new GameEventAttackersDeclared(attackingPlayer, attackersMap));
if (!combat.getAttackers().isEmpty()) {
List<GameEntity> attackedTarget = Lists.newArrayList();
for (final Card c : combat.getAttackers()) {
attackedTarget.add(combat.getDefenderByAttacker(c));
}
final Map<AbilityKey, Object> runParams = Maps.newEnumMap(AbilityKey.class);
runParams.put(AbilityKey.Attackers, combat.getAttackers());
runParams.put(AbilityKey.AttackingPlayer, combat.getAttackingPlayer());
runParams.put(AbilityKey.AttackedTarget, attackedTarget);
game.getTriggerHandler().runTrigger(TriggerType.AttackersDeclared, runParams, false);
}
for (final Card c : combat.getAttackers()) {
CombatUtil.checkDeclaredAttacker(game, c, combat);
CombatUtil.checkDeclaredAttacker(game, c, combat, false);
}
game.getTriggerHandler().resetActiveTriggers();
game.updateCombatForView();
game.fireEvent(new GameEventCombatChanged());
@@ -828,7 +802,6 @@ public abstract class GameState {
Card exiledWith = idToCard.get(Integer.parseInt(id));
c.setExiledWith(exiledWith);
c.setExiledBy(exiledWith.getController());
exiledWith.addExiledWith(c);
}
}
@@ -1087,13 +1060,19 @@ public abstract class GameState {
Card c = entry.getKey();
List<String> colors = entry.getValue();
c.setChosenColors(colors, c.getFirstSpellAbility());
c.setChosenColors(colors);
}
// Chosen type
for (Entry<Card, List<String>> entry : cardToChosenTypes.entrySet()) {
for (Entry<Card, String> entry : cardToChosenType.entrySet()) {
Card c = entry.getKey();
c.setChosenType(entry.getValue(), c.getFirstSpellAbility());
c.setChosenType(entry.getValue());
}
// Chosen type 2
for (Entry<Card, String> entry : cardToChosenType2.entrySet()) {
Card c = entry.getKey();
c.setChosenType2(entry.getValue());
}
// Named card
@@ -1196,7 +1175,7 @@ public abstract class GameState {
String[] allCounterStrings = counterString.split(",");
for (final String counterPair : allCounterStrings) {
String[] pair = counterPair.split("=", 2);
entity.addCounter(CounterType.getType(pair[0]), Integer.parseInt(pair[1]), null, false, false, null);
entity.addCounter(CounterType.getType(pair[0]), Integer.parseInt(pair[1]), null, null, false, false, null);
}
}
@@ -1236,8 +1215,7 @@ public abstract class GameState {
boolean tapped = c.isTapped();
boolean sickness = c.hasSickness();
Map<CounterType, Integer> counters = c.getCounters();
// Note: Not clearCounters() since we want to keep the counters
// var as-is.
// Note: Not clearCounters() since we want to keep the counters var as-is.
c.setCounters(Maps.newHashMap());
if (c.isAura()) {
// dummy "enchanting" to indicate that the card will be force-attached elsewhere
@@ -1340,7 +1318,7 @@ public abstract class GameState {
}
else if (info.startsWith("OnAdventure")) {
String abAdventure = "DB$ Effect | RememberObjects$ Self | StaticAbilities$ Play | ExileOnMoved$ Exile | Duration$ Permanent | ConditionDefined$ Self | ConditionPresent$ Card.nonCopiedSpell";
AbilitySub saAdventure = (AbilitySub)AbilityFactory.getAbility(abAdventure, c);
SpellAbility saAdventure = AbilityFactory.getAbility(abAdventure, c);
StringBuilder sbPlay = new StringBuilder();
sbPlay.append("Mode$ Continuous | MayPlay$ True | EffectZone$ Command | Affected$ Card.IsRemembered+nonAdventure");
sbPlay.append(" | AffectedZone$ Exile | Description$ You may cast the card.");
@@ -1379,7 +1357,9 @@ public abstract class GameState {
} else if (info.startsWith("ChosenColor:")) {
cardToChosenClrs.put(c, Arrays.asList(info.substring(info.indexOf(':') + 1).split(",")));
} else if (info.startsWith("ChosenType:")) {
cardToChosenTypes.put(c, Arrays.asList(info.substring(info.indexOf(':') + 1).split(",")));
cardToChosenType.put(c, info.substring(info.indexOf(':') + 1));
} else if (info.startsWith("ChosenType2:")) {
cardToChosenType2.put(c, info.substring(info.indexOf(':') + 1));
} else if (info.startsWith("ChosenCards:")) {
CardCollection chosen = new CardCollection();
String[] idlist = info.substring(info.indexOf(':') + 1).split(",");

View File

@@ -8,6 +8,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.Pair;
@@ -79,9 +80,10 @@ import forge.util.MyRandom;
import forge.util.collect.FCollection;
import forge.util.collect.FCollectionView;
/**
/**
* A prototype for player controller class
*
*
* Handles phase skips for now.
*/
public class PlayerControllerAi extends PlayerController {
@@ -92,11 +94,11 @@ public class PlayerControllerAi extends PlayerController {
brains = new AiController(p, game);
}
public void allowCheatShuffle(boolean value){
brains.allowCheatShuffle(value);
}
public void setUseSimulation(boolean value) {
brains.setUseSimulation(value);
}
@@ -129,6 +131,12 @@ public class PlayerControllerAi extends PlayerController {
return ComputerUtilCombat.distributeAIDamage(attacker, blockers, damageDealt, defender, overrideOrder);
}
@Override
public Map<GameEntity, Integer> divideShield(Card effectSource, Map<GameEntity, Integer> affected, int shieldAmount) {
// TODO: AI current can't use this so this is not implemented.
return new HashMap<>();
}
@Override
public Integer announceRequirements(SpellAbility ability, String announce) {
// For now, these "announcements" are made within the AI classes of the appropriate SA effects
@@ -155,7 +163,7 @@ public class PlayerControllerAi extends PlayerController {
case BidLife:
return 0;
default:
return null;
return null;
}
}
return null; // return incorrect value to indicate that
@@ -238,7 +246,7 @@ public class PlayerControllerAi extends PlayerController {
public boolean confirmAction(SpellAbility sa, PlayerActionConfirmMode mode, String message) {
return getAi().confirmAction(sa, mode, message);
}
@Override
public boolean confirmBidAction(SpellAbility sa, PlayerActionConfirmMode mode, String string,
int bid, Player winner) {
@@ -528,7 +536,14 @@ public class PlayerControllerAi extends PlayerController {
@Override
public CardCollectionView chooseCardsToDiscardUnlessType(int num, CardCollectionView hand, String uType, SpellAbility sa) {
final CardCollectionView cardsOfType = CardLists.getType(hand, uType);
String [] splitUTypes = uType.split(",");
CardCollection cardsOfType = new CardCollection();
for (String part : splitUTypes) {
CardCollection partCards = CardLists.getType(hand, part);
if (!partCards.isEmpty()) {
cardsOfType.addAll(partCards);
}
}
if (!cardsOfType.isEmpty()) {
Card toDiscard = Aggregates.itemWithMin(cardsOfType, CardPredicates.Accessors.fnGetCmc);
return new CardCollection(toDiscard);
@@ -543,12 +558,13 @@ public class PlayerControllerAi extends PlayerController {
}
@Override
public List<String> chooseSomeType(String kindOfType, SpellAbility sa, int min, int max, List<String> validTypes) {
List<String> chosen = ComputerUtil.chooseSomeType(player, kindOfType, sa.getParam("AILogic"), min, max, validTypes);
if (chosen.isEmpty()) {
return validTypes.subList(0, min);
public String chooseSomeType(String kindOfType, SpellAbility sa, Collection<String> validTypes, List<String> invalidTypes, boolean isOptional) {
String chosen = ComputerUtil.chooseSomeType(player, kindOfType, sa.getParam("AILogic"), validTypes, invalidTypes);
if (StringUtils.isBlank(chosen) && !validTypes.isEmpty()) {
chosen = validTypes.iterator().next();
System.err.println("AI has no idea how to choose " + kindOfType +", defaulting to arbitrary element: chosen");
}
getGame().getAction().notifyOfValue(sa, player, chosen.toString(), player);
getGame().getAction().notifyOfValue(sa, player, chosen, player);
return chosen;
}
@@ -558,8 +574,8 @@ public class PlayerControllerAi extends PlayerController {
}
@Override
public boolean confirmReplacementEffect(ReplacementEffect replacementEffect, SpellAbility effectSA, String question) {
return brains.aiShouldRun(replacementEffect, effectSA);
public boolean confirmReplacementEffect(ReplacementEffect replacementEffect, SpellAbility effectSA, GameEntity affected, String question) {
return brains.aiShouldRun(replacementEffect, effectSA, affected);
}
@Override
@@ -636,10 +652,9 @@ public class PlayerControllerAi extends PlayerController {
public List<SpellAbility> chooseSpellAbilityToPlay() {
return brains.chooseSpellAbilityToPlay();
}
@Override
public boolean playChosenSpellAbility(SpellAbility sa) {
// System.out.println("Playing sa: " + sa);
if (sa instanceof LandAbility) {
if (sa.canPlay()) {
sa.resolve();
@@ -649,7 +664,7 @@ public class PlayerControllerAi extends PlayerController {
ComputerUtil.handlePlayingSpellAbility(player, sa, getGame());
}
return true;
}
}
@Override
public CardCollection chooseCardsToDiscardToMaximumHandSize(int numDiscard) {
@@ -702,7 +717,7 @@ public class PlayerControllerAi extends PlayerController {
public int chooseNumber(SpellAbility sa, String title, int min, int max) {
return brains.chooseNumber(sa, title, min, max);
}
@Override
public int chooseNumber(SpellAbility sa, String string, int min, int max, Map<String, Object> params) {
ApiType api = sa.getApi();
@@ -778,7 +793,8 @@ public class PlayerControllerAi extends PlayerController {
case "BetterTgtThanRemembered":
if (source.getRememberedCount() > 0) {
Card rem = (Card) source.getFirstRemembered();
if (!rem.isInZone(ZoneType.Battlefield)) {
// avoid pumping opponent creature
if (!rem.isInZone(ZoneType.Battlefield) || rem.getController().isOpponentOf(source.getController())) {
return true;
}
for (Card c : source.getController().getCreaturesInPlay()) {
@@ -804,7 +820,7 @@ public class PlayerControllerAi extends PlayerController {
/*
* (non-Javadoc)
*
*
* @see
* forge.game.player.PlayerController#chooseBinary(forge.game.spellability.
* SpellAbility, java.lang.String,
@@ -845,7 +861,7 @@ public class PlayerControllerAi extends PlayerController {
}
return sa.getChosenList();
}
@Override
public byte chooseColorAllowColorless(String message, Card card, ColorSet colors) {
final String c = ComputerUtilCard.getMostProminentColor(player.getCardsIn(ZoneType.Hand));
@@ -891,7 +907,7 @@ public class PlayerControllerAi extends PlayerController {
/*
* (non-Javadoc)
*
*
* @see forge.game.player.PlayerController#chooseCounterType(java.util.List,
* forge.game.spellability.SpellAbility, java.lang.String, java.util.Map)
*/
@@ -907,7 +923,7 @@ public class PlayerControllerAi extends PlayerController {
@Override
public boolean confirmPayment(CostPart costPart, String prompt, SpellAbility sa) {
return brains.confirmPayment(costPart); // AI is expected to know what it is paying for at the moment (otherwise add another parameter to this method)
return brains.confirmPayment(costPart); // AI is expected to know what it is paying for at the moment (otherwise add another parameter to this method)
}
@Override
@@ -988,6 +1004,7 @@ public class PlayerControllerAi extends PlayerController {
emptyAbility.setActivatingPlayer(player);
emptyAbility.setTriggeringObjects(sa.getTriggeringObjects());
emptyAbility.setSVars(sa.getSVars());
emptyAbility.setXManaCostPaid(sa.getRootAbility().getXManaCostPaid());
if (ComputerUtilCost.willPayUnlessCost(sa, player, cost, alreadyPaid, allPayers) && ComputerUtilCost.canPayCost(emptyAbility, player)) {
ComputerUtil.playNoStack(player, emptyAbility, getGame()); // AI needs something to resolve to pay that cost
return true;
@@ -1019,7 +1036,7 @@ public class PlayerControllerAi extends PlayerController {
*/
if (sa.isMayChooseNewTargets() && !sa.setupTargets()) {
if (sa.isSpell()) {
sa.getHostCard().ceaseToExist();
player.getGame().getAction().ceaseToExist(sa.getHostCard(), false);
}
continue;
}
@@ -1053,14 +1070,13 @@ public class PlayerControllerAi extends PlayerController {
boolean noManaCost = tgtSA.hasParam("WithoutManaCost");
if (tgtSA instanceof Spell) { // Isn't it ALWAYS a spell?
Spell spell = (Spell) tgtSA;
if (brains.canPlayFromEffectAI(spell, !optional, noManaCost) == AiPlayDecision.WillPlay || !optional) {
if (tgtSA.checkRestrictions(brains.getPlayer()) && (brains.canPlayFromEffectAI(spell, !optional, noManaCost) == AiPlayDecision.WillPlay || !optional)) {
if (noManaCost) {
return ComputerUtil.playSpellAbilityWithoutPayingManaCost(player, tgtSA, getGame());
} else {
return ComputerUtil.playStack(tgtSA, player, getGame());
}
} else
return false; // didn't play spell
return ComputerUtil.playStack(tgtSA, player, getGame());
}
return false; // didn't play spell
}
return true;
}
@@ -1083,7 +1099,6 @@ public class PlayerControllerAi extends PlayerController {
boolean allCreatures = Iterables.all(Iterables.concat(pile1, pile2), CardPredicates.Presets.CREATURES);
int cmc1 = allCreatures ? ComputerUtilCard.evaluateCreatureList(pile1) : ComputerUtilCard.evaluatePermanentList(pile1);
int cmc2 = allCreatures ? ComputerUtilCard.evaluateCreatureList(pile2) : ComputerUtilCard.evaluatePermanentList(pile2);
System.out.println("value:" + cmc1 + " " + cmc2);
// for now, this assumes that the outcome will be bad
// TODO: This should really have a ChooseLogic param to
@@ -1096,7 +1111,7 @@ public class PlayerControllerAi extends PlayerController {
public void revealAnte(String message, Multimap<Player, PaperCard> removedAnteCards) {
// Ai won't understand that anyway
}
@Override
public Collection<? extends PaperCard> complainCardsCantPlayWell(Deck myDeck) {
return brains.complainCardsCantPlayWell(myDeck);
@@ -1144,7 +1159,7 @@ public class PlayerControllerAi extends PlayerController {
return new HashMap<>();
}
}
//Do not convoke potential blockers until after opponent's attack
final CardCollectionView blockers = ComputerUtilCard.getLikelyBlockers(ai, null);
if ((ph.isPlayerTurn(ai) && ph.getPhase().isAfter(PhaseType.COMBAT_BEGIN)) ||

View File

@@ -39,7 +39,6 @@ import forge.game.ability.ApiType;
import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardCollectionView;
import forge.game.card.CardFactoryUtil;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates;
import forge.game.card.CardUtil;
@@ -112,6 +111,63 @@ public class SpecialCardAi {
}
}
// Brain in a Jar
public static class BrainInAJar {
public static boolean consider(final Player ai, final SpellAbility sa) {
final Card source = sa.getHostCard();
int counterNum = source.getCounters(CounterEnumType.CHARGE);
// no need for logic
if (counterNum == 0) {
return false;
}
int libsize = ai.getCardsIn(ZoneType.Library).size();
final CardCollection hand = CardLists.filter(ai.getCardsIn(ZoneType.Hand), Predicates.or(
CardPredicates.isType("Instant"), CardPredicates.isType("Sorcery")));
if (!hand.isEmpty()) {
// has spell that can be cast in hand with put ability
if (!CardLists.filter(hand, CardPredicates.hasCMC(counterNum + 1)).isEmpty()) {
return false;
}
// has spell that can be cast if one counter is removed
if (!CardLists.filter(hand, CardPredicates.hasCMC(counterNum)).isEmpty()) {
sa.setXManaCostPaid(1);
return true;
}
}
final CardCollection library = CardLists.filter(ai.getCardsIn(ZoneType.Library), Predicates.or(
CardPredicates.isType("Instant"), CardPredicates.isType("Sorcery")));
if (!library.isEmpty()) {
// get max cmc of instant or sorceries in the libary
int maxCMC = 0;
for (final Card c : library) {
int v = c.getCMC();
if (c.isSplitCard()) {
v = Math.max(c.getCMC(Card.SplitCMCMode.LeftSplitCMC), c.getCMC(Card.SplitCMCMode.RightSplitCMC));
}
if (v > maxCMC) {
maxCMC = v;
}
}
// there is a spell with more CMC, no need to remove counter
if (counterNum + 1 < maxCMC) {
return false;
}
int maxToRemove = counterNum - maxCMC + 1;
// no Scry 0, even if its catched from later stuff
if (maxToRemove <= 0) {
return false;
}
sa.setXManaCostPaid(maxToRemove);
} else {
// no Instant or Sorceries anymore, just scry
sa.setXManaCostPaid(Math.min(counterNum, libsize));
}
return true;
}
}
// Chain of Acid
public static class ChainOfAcid {
public static boolean consider(final Player ai, final SpellAbility sa) {
@@ -159,10 +215,9 @@ public class SpecialCardAi {
final PhaseHandler ph = ai.getGame().getPhaseHandler();
final Combat combat = ai.getGame().getCombat();
Card animated = AnimateAi.becomeAnimated(sa.getHostCard(), sa);
animated.addType("Creature");
Card animated = AnimateAi.becomeAnimated(sa.getHostCard(), sa.getSubAbility());
if (sa.getHostCard().canReceiveCounters(CounterEnumType.P1P1)) {
animated.addCounter(CounterEnumType.P1P1, 2, ai, false, null);
animated.addCounter(CounterEnumType.P1P1, 2, ai, sa.getSubAbility(), false, null);
}
boolean isOppEOT = ph.is(PhaseType.END_OF_TURN) && ph.getNextTurn() == ai;
boolean isValuableAttacker = ph.is(PhaseType.MAIN1, ai) && ComputerUtilCard.doesSpecifiedCreatureAttackAI(ai, animated);
@@ -170,10 +225,6 @@ public class SpecialCardAi {
return isOppEOT || isValuableAttacker || isValuableBlocker;
}
public static SpellAbility considerAnimating(final Player ai, final SpellAbility sa, final List<SpellAbility> options) {
return ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2) ? options.get(0) : options.get(1);
}
}
// Cursed Scroll
@@ -327,7 +378,7 @@ public class SpecialCardAi {
}
// Do not activate if damage will be prevented
if (source.staticDamagePrevention(predictedPT.getLeft(), source, true, true) == 0) {
if (source.staticDamagePrevention(predictedPT.getLeft(), 0, source, true) == 0) {
return false;
}
@@ -911,6 +962,39 @@ public class SpecialCardAi {
}
}
// Multiple Choice
public static class MultipleChoice {
public static boolean consider(final Player ai, final SpellAbility sa) {
int maxX = ComputerUtilCost.getMaxXValue(sa, ai);
if (maxX == 0) {
return false;
}
boolean canScryDraw = maxX >= 1 && ai.getCardsIn(ZoneType.Library).size() >= 3; // TODO: generalize / use profile values
boolean canBounce = maxX >= 2 && !ai.getOpponents().getCreaturesInPlay().isEmpty();
boolean shouldBounce = canBounce && ComputerUtilCard.evaluateCreature(ComputerUtilCard.getWorstCreatureAI(ai.getOpponents().getCreaturesInPlay())) > 210; // 180 is the level of a 4/4 token creature
boolean canMakeToken = maxX >= 3;
boolean canDoAll = maxX >= 4 && canScryDraw && shouldBounce;
if (canDoAll) {
sa.setXManaCostPaid(4);
return true;
} else if (canMakeToken) {
sa.setXManaCostPaid(3);
return true;
} else if (shouldBounce) {
sa.setXManaCostPaid(2);
return true;
} else if (canScryDraw) {
sa.setXManaCostPaid(1);
return true;
}
return false;
}
}
// Necropotence
public static class Necropotence {
public static boolean consider(final Player ai, final SpellAbility sa) {

View File

@@ -58,6 +58,11 @@ public abstract class SpellAbilityAi {
final Card source = sa.getHostCard();
final Cost cost = sa.getPayCosts();
if (sa.hasParam("AICheckCanPlayWithDefinedX")) {
// FIXME: can this somehow be simplified without the need for an extra AI hint?
sa.setXManaCostPaid(ComputerUtilCost.getMaxXValue(sa, ai));
}
if (!checkConditions(ai, sa, sa.getConditions())) {
SpellAbility sub = sa.getSubAbility();
if (sub != null && !checkConditions(ai, sub, sub.getConditions())) {
@@ -157,7 +162,7 @@ public abstract class SpellAbilityAi {
if (ComputerUtil.preventRunAwayActivations(sa)) {
return false; // prevent infinite loop
}
return MyRandom.getRandom().nextFloat() < .8f; // random success
return MyRandom.getRandom().nextFloat() < .8f; // random success
}
public final boolean doTriggerAI(final Player aiPlayer, final SpellAbility sa, final boolean mandatory) {
@@ -188,7 +193,7 @@ public abstract class SpellAbilityAi {
* Handles the AI decision to play a triggered SpellAbility
*/
protected boolean doTriggerAINoCost(final Player aiPlayer, final SpellAbility sa, final boolean mandatory) {
if (canPlayWithoutRestrict(aiPlayer, sa)) {
if (canPlayWithoutRestrict(aiPlayer, sa) && (!mandatory || sa.isTargetNumberValid())) {
return true;
}
@@ -299,7 +304,7 @@ public abstract class SpellAbilityAi {
final AbilitySub subAb = ab.getSubAbility();
return SpellApiToAi.Converter.get(ab.getApi()).chkAIDrawback(ab, aiPlayer) && (subAb == null || chkDrawbackWithSubs(aiPlayer, subAb));
}
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
System.err.println("Warning: default (ie. inherited from base class) implementation of confirmAction is used by " + sa.getHostCard().getName() + " for " + this.getClass().getName() + ". Consider declaring an overloaded method");
return true;

View File

@@ -34,6 +34,7 @@ public enum SpellApiToAi {
.put(ApiType.BidLife, BidLifeAi.class)
.put(ApiType.Bond, BondAi.class)
.put(ApiType.Branch, AlwaysPlayAi.class)
.put(ApiType.Camouflage, ChooseCardAi.class)
.put(ApiType.ChangeCombatants, ChangeCombatantsAi.class)
.put(ApiType.ChangeTargets, ChangeTargetsAi.class)
.put(ApiType.ChangeX, AlwaysPlayAi.class)
@@ -94,8 +95,10 @@ public enum SpellApiToAi {
.put(ApiType.Haunt, HauntAi.class)
.put(ApiType.ImmediateTrigger, ImmediateTriggerAi.class)
.put(ApiType.Investigate, InvestigateAi.class)
.put(ApiType.Learn, LearnAi.class)
.put(ApiType.LoseLife, LifeLoseAi.class)
.put(ApiType.LosesGame, GameLossAi.class)
.put(ApiType.MakeCard, AlwaysPlayAi.class)
.put(ApiType.Mana, ManaEffectAi.class)
.put(ApiType.ManaReflected, CannotPlayAi.class)
.put(ApiType.Manifest, ManifestAi.class)

View File

@@ -21,8 +21,7 @@ public class ActivateAbilityAi extends SpellAbilityAi {
final TargetRestrictions tgt = sa.getTargetRestrictions();
final Card source = sa.getHostCard();
final Player opp = ai.getWeakestOpponent();
boolean randomReturn = MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
final Player opp = ai.getStrongestOpponent();
List<Card> list = CardLists.getType(opp.getCardsIn(ZoneType.Battlefield), sa.getParamOrDefault("Type", "Card"));
if (list.isEmpty()) {
@@ -40,12 +39,13 @@ public class ActivateAbilityAi extends SpellAbilityAi {
sa.getTargets().add(opp);
}
boolean randomReturn = MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
return randomReturn;
}
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
final Player opp = ai.getWeakestOpponent();
final Player opp = ai.getStrongestOpponent();
final TargetRestrictions tgt = sa.getTargetRestrictions();
final Card source = sa.getHostCard();

View File

@@ -30,6 +30,7 @@ import forge.game.cost.CostPutCounter;
import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.player.PlayerActionConfirmMode;
import forge.game.spellability.SpellAbility;
import forge.game.staticability.StaticAbility;
import forge.game.staticability.StaticAbilityContinuous;
@@ -99,30 +100,30 @@ public class AnimateAi extends SpellAbilityAi {
}
// Don't use instant speed animate abilities before AI's COMBAT_BEGIN
if (!ph.is(PhaseType.COMBAT_BEGIN) && ph.isPlayerTurn(ai) && !SpellAbilityAi.isSorcerySpeed(sa)
&& !sa.hasParam("ActivationPhases") && !sa.hasParam("Permanent")) {
&& !sa.hasParam("ActivationPhases") && !"Permanent".equals(sa.getParam("Duration"))) {
return false;
}
// Don't use instant speed animate abilities outside human's
// COMBAT_DECLARE_ATTACKERS or if no attackers
if (ph.getPlayerTurn().isOpponentOf(ai) && !sa.hasParam("Permanent")
if (ph.getPlayerTurn().isOpponentOf(ai) && !"Permanent".equals(sa.getParam("Duration"))
&& (!ph.is(PhaseType.COMBAT_DECLARE_ATTACKERS)
|| game.getCombat() != null && game.getCombat().getAttackersOf(ai).isEmpty())) {
return false;
}
// Don't activate during MAIN2 unless this effect is permanent
if (ph.is(PhaseType.MAIN2) && !sa.hasParam("Permanent") && !sa.hasParam("UntilYourNextTurn")) {
if (ph.is(PhaseType.MAIN2) && !"Permanent".equals(sa.getParam("Duration")) && !"UntilYourNextTurn".equals(sa.getParam("Duration"))) {
return false;
}
// Don't animate if the AI won't attack anyway or use as a potential blocker
Player opponent = ai.getWeakestOpponent();
// Activating as a potential blocker is only viable if it's an ability activated from a permanent, otherwise
// the AI will waste resources
boolean activateAsPotentialBlocker = sa.hasParam("UntilYourNextTurn")
boolean activateAsPotentialBlocker = "UntilYourNextTurn".equals(sa.getParam("Duration"))
&& ai.getGame().getPhaseHandler().getNextTurn() != ai
&& source.isPermanent();
if (ph.isPlayerTurn(ai) && ai.getLife() < 6 && opponent.getLife() > 6
&& Iterables.any(opponent.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.CREATURES)
&& !sa.hasParam("AILogic") && !sa.hasParam("Permanent") && !activateAsPotentialBlocker) {
&& !sa.hasParam("AILogic") && !"Permanent".equals(sa.getParam("Duration")) && !activateAsPotentialBlocker) {
return false;
}
return true;
@@ -155,7 +156,7 @@ public class AnimateAi extends SpellAbilityAi {
&& !c.isEquipping();
// for creatures that could be improved (like Figure of Destiny)
if (!bFlag && c.isCreature() && (sa.hasParam("Permanent") || (!c.isTapped() && !c.isSick()))) {
if (!bFlag && c.isCreature() && ("Permanent".equals(sa.getParam("Duration")) || (!c.isTapped() && !c.isSick()))) {
int power = -5;
if (sa.hasParam("Power")) {
power = AbilityUtils.calculateAmount(c, sa.getParam("Power"), sa);
@@ -178,7 +179,7 @@ public class AnimateAi extends SpellAbilityAi {
}
}
if (!SpellAbilityAi.isSorcerySpeed(sa) && !sa.hasParam("Permanent")) {
if (!SpellAbilityAi.isSorcerySpeed(sa) && !"Permanent".equals(sa.getParam("Duration"))) {
if (sa.hasParam("Crew") && c.isCreature()) {
// Do not try to crew a vehicle which is already a creature
return false;
@@ -243,6 +244,11 @@ public class AnimateAi extends SpellAbilityAi {
return true;
}
@Override
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
return player.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2);
}
private boolean animateTgtAI(final SpellAbility sa) {
final Player ai = sa.getActivatingPlayer();
final PhaseHandler ph = ai.getGame().getPhaseHandler();
@@ -272,7 +278,7 @@ public class AnimateAi extends SpellAbilityAi {
Map<Card, Integer> data = Maps.newHashMap();
for (final Card c : list) {
// don't use Permanent animate on something that would leave the field
if (c.hasSVar("EndOfTurnLeavePlay") && sa.hasParam("Permanent")) {
if (c.hasSVar("EndOfTurnLeavePlay") && "Permanent".equals(sa.getParam("Duration"))) {
continue;
}
@@ -306,9 +312,9 @@ public class AnimateAi extends SpellAbilityAi {
// if its player turn,
// check if its Permanent or that creature would attack
if (ph.isPlayerTurn(ai)) {
if (!sa.hasParam("Permanent")
if (!"Permanent".equals(sa.getParam("Duration"))
&& !ComputerUtilCard.doesSpecifiedCreatureAttackAI(ai, animatedCopy)
&& !sa.hasParam("UntilHostLeavesPlay")) {
&& !"UntilHostLeavesPlay".equals(sa.getParam("Duration"))) {
continue;
}
}
@@ -354,8 +360,7 @@ public class AnimateAi extends SpellAbilityAi {
// This is reasonable for now. Kamahl, Fist of Krosa and a sorcery or
// two are the only things
// that animate a target. Those can just use AI:RemoveDeck:All until
// this can do a reasonably
// good job of picking a good target
// this can do a reasonably good job of picking a good target
return false;
}
@@ -401,7 +406,7 @@ public class AnimateAi extends SpellAbilityAi {
// allow ChosenType - overrides anything else specified
if (types.hasSubtype("ChosenType")) {
types.clear();
types.addAll(sa.getChosenType());
types.add(source.getChosenType());
}
final List<String> keywords = Lists.newArrayList();
@@ -432,7 +437,7 @@ public class AnimateAi extends SpellAbilityAi {
if (sa.hasParam("Colors")) {
final String colors = sa.getParam("Colors");
if (colors.equals("ChosenColor")) {
tmpDesc = CardUtil.getShortColorsString(sa.getChosenColors());
tmpDesc = CardUtil.getShortColorsString(source.getChosenColors());
} else {
tmpDesc = CardUtil.getShortColorsString(Lists.newArrayList(Arrays.asList(colors.split(","))));
}

View File

@@ -6,6 +6,7 @@ import java.util.Iterator;
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.Lists;
@@ -554,12 +555,7 @@ public class AttachAi extends SpellAbilityAi {
if (!evenBetterList.isEmpty()) {
betterList = evenBetterList;
}
evenBetterList = CardLists.filter(betterList, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return c.isUntapped();
}
});
evenBetterList = CardLists.filter(betterList, CardPredicates.Presets.UNTAPPED);
if (!evenBetterList.isEmpty()) {
betterList = evenBetterList;
}
@@ -733,17 +729,15 @@ public class AttachAi extends SpellAbilityAi {
*/
private static Card attachAISpecificCardPreference(final SpellAbility sa, final List<Card> list, final boolean mandatory,
final Card attachSource) {
// I know this isn't much better than Hardcoding, but some cards need it for now
final Player ai = sa.getActivatingPlayer();
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
Card chosen = null;
if ("Guilty Conscience".equals(sourceName)) {
chosen = SpecialCardAi.GuiltyConscience.getBestAttachTarget(ai, sa, list);
} else if ("Bonds of Faith".equals(sourceName)) {
chosen = doPumpOrCurseAILogic(ai, sa, list, "Human");
} else if ("Clutch of Undeath".equals(sourceName)) {
chosen = doPumpOrCurseAILogic(ai, sa, list, "Zombie");
} else if (sa.hasParam("AIValid")) {
// TODO: Make the AI recognize which cards to pump based on the card's abilities alone
chosen = doPumpOrCurseAILogic(ai, sa, list, sa.getParam("AIValid"));
}
// If Mandatory (brought directly into play without casting) gotta
@@ -767,7 +761,7 @@ public class AttachAi extends SpellAbilityAi {
int powerBuff = 0;
for (StaticAbility stAb : sa.getHostCard().getStaticAbilities()) {
if ("Card.EquippedBy".equals(stAb.getParam("Affected")) && stAb.hasParam("AddPower")) {
powerBuff = AbilityUtils.calculateAmount(sa.getHostCard(), stAb.getParam("AddPower"), null);
powerBuff = AbilityUtils.calculateAmount(sa.getHostCard(), stAb.getParam("AddPower"), stAb);
}
}
if (combat != null && combat.isAttacking(equipped) && ph.is(PhaseType.COMBAT_DECLARE_BLOCKERS, sa.getActivatingPlayer())) {
@@ -1129,8 +1123,7 @@ public class AttachAi extends SpellAbilityAi {
for (Card target : list) {
for (Trigger t : target.getTriggers()) {
if (t.getMode() == TriggerType.SpellCast) {
final Map<String, String> params = t.getMapParams();
if ("Card.Self".equals(params.get("TargetsValid")) && "You".equals(params.get("ValidActivatingPlayer"))) {
if ("Card.Self".equals(t.getParam("TargetsValid")) && "You".equals(t.getParam("ValidActivatingPlayer"))) {
magnetList.add(target);
break;
}
@@ -1646,7 +1639,7 @@ public class AttachAi extends SpellAbilityAi {
return ComputerUtilCombat.canAttackNextTurn(card) && card.getNetCombatDamage() >= 1;
} else if (keyword.endsWith("CARDNAME attacks each turn if able.") || keyword.endsWith("CARDNAME attacks each combat if able.")) {
return ComputerUtilCombat.canAttackNextTurn(card) && CombatUtil.canBlock(card, true) && !ai.getCreaturesInPlay().isEmpty();
} else if (keyword.endsWith("CARDNAME can't block.") || keyword.contains("CantBlock")) {
} else if (keyword.endsWith("CARDNAME can't block.")) {
return CombatUtil.canBlock(card, true);
} else if (keyword.endsWith("CARDNAME's activated abilities can't be activated.")) {
for (SpellAbility ability : card.getSpellAbilities()) {
@@ -1699,7 +1692,7 @@ public class AttachAi extends SpellAbilityAi {
if (!c.getController().equals(ai)) {
return false;
}
return c.getType().hasCreatureType(type);
return c.isValid(type, ai, sa.getHostCard(), sa);
}
});
List<Card> oppNonType = CardLists.filter(list, new Predicate<Card>() {
@@ -1709,7 +1702,7 @@ public class AttachAi extends SpellAbilityAi {
if (c.getController().equals(ai)) {
return false;
}
return !c.getType().hasCreatureType(type) && !ComputerUtilCard.isUselessCreature(ai, c);
return !c.isValid(type, ai, sa.getHostCard(), sa) && !ComputerUtilCard.isUselessCreature(ai, c);
}
});

View File

@@ -13,18 +13,21 @@ public class BalanceAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
String logic = sa.getParam("AILogic");
int diff = 0;
// TODO Add support for multiplayer logic
final Player opp = aiPlayer.getWeakestOpponent();
final CardCollectionView humPerms = opp.getCardsIn(ZoneType.Battlefield);
Player opp = aiPlayer.getWeakestOpponent();
final CardCollectionView compPerms = aiPlayer.getCardsIn(ZoneType.Battlefield);
for (Player min : aiPlayer.getOpponents()) {
if (min.getCardsIn(ZoneType.Battlefield).size() < opp.getCardsIn(ZoneType.Battlefield).size()) {
opp = min;
}
}
final CardCollectionView humPerms = opp.getCardsIn(ZoneType.Battlefield);
if ("BalanceCreaturesAndLands".equals(logic)) {
// Copied over from hardcoded Balance. We should be checking value of the lands/creatures not just counting
// TODO Copied over from hardcoded Balance. We should be checking value of the lands/creatures for each opponent, not just counting
diff += CardLists.filter(humPerms, CardPredicates.Presets.LANDS).size() -
CardLists.filter(compPerms, CardPredicates.Presets.LANDS).size();
diff += 1.5 * ( CardLists.filter(humPerms, CardPredicates.Presets.CREATURES).size() -
diff += 1.5 * (CardLists.filter(humPerms, CardPredicates.Presets.CREATURES).size() -
CardLists.filter(compPerms, CardPredicates.Presets.CREATURES).size());
}
else if ("BalancePermanents".equals(logic)) {

View File

@@ -2,6 +2,7 @@ package forge.ai.ability;
import java.util.List;
import forge.ai.AiAttackController;
import forge.ai.ComputerUtilCard;
import forge.ai.SpellAbilityAi;
import forge.game.Game;
@@ -24,7 +25,7 @@ public class BidLifeAi extends SpellAbilityAi {
if (tgt != null) {
sa.resetTargets();
if (tgt.canTgtCreature()) {
List<Card> list = CardLists.getTargetableCards(aiPlayer.getWeakestOpponent().getCardsIn(ZoneType.Battlefield), sa);
List<Card> list = CardLists.getTargetableCards(AiAttackController.choosePreferredDefenderPlayer(aiPlayer).getCardsIn(ZoneType.Battlefield), sa);
list = CardLists.getValidCards(list, tgt.getValidTgts(), source.getController(), source, sa);
if (list.isEmpty()) {
return false;

View File

@@ -10,6 +10,7 @@ import forge.game.card.Card;
import forge.game.mana.ManaCostBeingPaid;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetChoices;
public class ChangeTargetsAi extends SpellAbilityAi {
@@ -40,18 +41,20 @@ public class ChangeTargetsAi extends SpellAbilityAi {
// nothing on stack, so nothing to target
return false;
}
final TargetChoices topTargets = topSa.getTargets();
final Card topHost = topSa.getHostCard();
if (sa.getTargets().size() != 0) {
if (sa.getTargets().size() != 0 && sa.isTrigger()) {
// something was already chosen before (e.g. in response to a trigger - Mizzium Meddler), so just proceed
return true;
}
if (!topSa.usesTargeting() || topSa.getTargets().getTargetCards().contains(sa.getHostCard())) {
if (!topSa.usesTargeting() || topTargets.getTargetCards().contains(sa.getHostCard())) {
// if this does not target at all or already targets host, no need to redirect it again
return false;
}
for (Card tgt : topSa.getTargets().getTargetCards()) {
for (Card tgt : topTargets.getTargetCards()) {
if (ComputerUtilAbility.getAbilitySourceName(sa).equals(tgt.getName()) && tgt.getController().equals(aiPlayer)) {
// We are already targeting at least one card with the same name (e.g. in presence of 2+ Spellskites),
// no need to retarget again to another one
@@ -59,7 +62,7 @@ public class ChangeTargetsAi extends SpellAbilityAi {
}
}
if (topSa.getHostCard() != null && !topSa.getHostCard().getController().isOpponentOf(aiPlayer)) {
if (topHost != null && !topHost.getController().isOpponentOf(aiPlayer)) {
// make sure not to redirect our own abilities
return false;
}
@@ -71,7 +74,7 @@ public class ChangeTargetsAi extends SpellAbilityAi {
// don't try retargeting a spell that the current card can't legally retarget (e.g. Muck Drubb + Lightning Bolt to the face)
return false;
}
if (sa.getPayCosts().getCostMana() != null && sa.getPayCosts().getCostMana().getMana().hasPhyrexian()) {
ManaCost manaCost = sa.getPayCosts().getCostMana().getMana();
int payDamage = manaCost.getPhyrexianCount() * 2;
@@ -80,12 +83,22 @@ public class ChangeTargetsAi extends SpellAbilityAi {
ManaCost normalizedMana = manaCost.getNormalizedMana();
boolean canPay = ComputerUtilMana.canPayManaCost(new ManaCostBeingPaid(normalizedMana), sa, aiPlayer);
if (potentialDmg != -1 && potentialDmg <= payDamage && !canPay
&& topSa.getTargets().contains(aiPlayer)) {
&& topTargets.contains(aiPlayer)) {
// do not pay Phyrexian mana if the spell is a damaging one but it deals less damage or the same damage as we'll pay life
return false;
}
}
Card firstCard = topTargets.getFirstTargetedCard();
// if we're not the target don't intervene unless we can steal a buff
if (firstCard != null && !aiPlayer.equals(firstCard.getController()) && !topHost.getController().equals(firstCard.getController()) && !topHost.getController().getAllies().contains(firstCard.getController())) {
return false;
}
Player firstPlayer = topTargets.getFirstTargetedPlayer();
if (firstPlayer != null && !aiPlayer.equals(firstPlayer)) {
return false;
}
sa.resetTargets();
sa.getTargets().add(topSa);
return true;

View File

@@ -15,6 +15,7 @@ import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import forge.ai.AiAttackController;
import forge.ai.AiBlockController;
import forge.ai.AiCardMemory;
import forge.ai.AiController;
@@ -57,6 +58,7 @@ import forge.game.player.PlayerActionConfirmMode;
import forge.game.spellability.AbilitySub;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.game.staticability.StaticAbilityMustTarget;
import forge.game.zone.ZoneType;
import forge.util.MyRandom;
@@ -74,11 +76,9 @@ public class ChangeZoneAi extends SpellAbilityAi {
@Override
protected boolean checkAiLogic(final Player ai, final SpellAbility sa, final String aiLogic) {
Card host = sa.getHostCard();
if (host != null && host.hasSVar("AIPreferenceOverride")) {
if (sa.getHostCard() != null && sa.getHostCard().hasSVar("AIPreferenceOverride")) {
// currently used by SacAndUpgrade logic, might need simplification
host.removeSVar("AIPreferenceOverride");
sa.getHostCard().removeSVar("AIPreferenceOverride");
}
if (aiLogic.equals("BeforeCombat")) {
@@ -92,7 +92,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
} else if (aiLogic.equals("PriorityOptionalCost")) {
boolean highPriority = false;
// if we have more than one of these in hand, might not be worth waiting for optional cost payment on the additional copy
highPriority |= CardLists.filter(ai.getCardsIn(ZoneType.Hand), CardPredicates.nameEquals(host.getName())).size() > 1;
highPriority |= CardLists.filter(ai.getCardsIn(ZoneType.Hand), CardPredicates.nameEquals(sa.getHostCard().getName())).size() > 1;
// if we are in danger in combat, no need to wait to pay the optional cost
highPriority |= ai.getGame().getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)
&& ai.getGame().getCombat() != null && ComputerUtilCombat.lifeInDanger(ai, ai.getGame().getCombat());
@@ -127,40 +127,6 @@ public class ChangeZoneAi extends SpellAbilityAi {
return true;
} else if (aiLogic.equals("Pongify")) {
return SpecialAiLogic.doPongifyLogic(ai, sa);
} else if (aiLogic.equals("Ashiok")) {
final int loyalty = host.getCurrentLoyalty() - 1;
CardCollectionView choices = new CardCollection();
for (int i = loyalty; i >= 0; i--) {
sa.setXManaCostPaid(i);
choices = ai.getGame().getCardsIn(ZoneType.listValueOf(sa.getParam("Origin")));
choices = CardLists.getValidCards(choices, sa.getParam("ChangeType"), host.getController(), host, sa);
if (!choices.isEmpty()) {
return true;
}
}
return false;
} else if (aiLogic.equals("BestCard")) {
CardCollectionView choices = ai.getGame().getCardsIn(ZoneType.listValueOf(sa.getParam("Origin")));
choices = CardLists.getValidCards(choices, sa.getParam("ChangeType"), host.getController(), host, sa);
if (!choices.isEmpty()) {
return true;
}
} else if (aiLogic.startsWith("DiscardAllAndRetExiled")) {
int numExiledWithSrc = CardLists.filter(ai.getCardsIn(ZoneType.Exile), CardPredicates.isExiledWith(host)).size();
int curHandSize = ai.getCardsIn(ZoneType.Hand).size();
// minimum card advantage unless the hand will be fully reloaded
int minAdv = aiLogic.contains(".minAdv") ? Integer.parseInt(aiLogic.substring(aiLogic.indexOf(".minAdv") + 7)) : 0;
if (numExiledWithSrc > curHandSize) {
if (ComputerUtil.predictThreatenedObjects(ai, sa, true).contains(host)) {
// Try to gain some card advantage if the card will die anyway
// TODO: ideally, should evaluate the hand value and not discard good hands to it
return true;
}
}
return (curHandSize + minAdv - 1 < numExiledWithSrc) || (numExiledWithSrc >= ai.getMaxHandSize());
}
return super.checkAiLogic(ai, sa, aiLogic);
@@ -197,16 +163,9 @@ public class ChangeZoneAi extends SpellAbilityAi {
return SpecialCardAi.MazesEnd.consider(aiPlayer, sa);
} else if (aiLogic.equals("Pongify")) {
return sa.isTargetNumberValid(); // Pre-targeted in checkAiLogic
} else if (aiLogic.equals("Ashiok")) {
return true; // If checkAiLogic returns true, then we should be good to go
} else if (aiLogic.equals("BestCard")) {
return true; // If checkAiLogic returns true, then we should be good to go
} else if (aiLogic.startsWith("DiscardAllAndRetExiled")) {
return true; // If checkAiLogic returns true, then we should be good to go
}
}
if (isHidden(sa)) {
if (sa.isHidden()) {
return hiddenOriginCanPlayAI(aiPlayer, sa);
}
return knownOriginCanPlayAI(aiPlayer, sa);
@@ -223,21 +182,12 @@ public class ChangeZoneAi extends SpellAbilityAi {
*/
@Override
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
if (isHidden(sa)) {
if (sa.isHidden()) {
return hiddenOriginPlayDrawbackAI(aiPlayer, sa);
}
return knownOriginPlayDrawbackAI(aiPlayer, sa);
}
private static boolean isHidden(SpellAbility sa) {
boolean hidden = sa.hasParam("Hidden");
if (!hidden && sa.hasParam("Origin")) {
hidden = ZoneType.isHidden(sa.getParam("Origin"));
}
return hidden;
}
/**
* <p>
* changeZoneTriggerAINoCost.
@@ -273,7 +223,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
return delta <= 0;
}
if (isHidden(sa)) {
if (sa.isHidden()) {
return hiddenTriggerAI(aiPlayer, sa, mandatory);
}
return knownOriginTriggerAI(aiPlayer, sa, mandatory);
@@ -306,7 +256,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
final Card source = sa.getHostCard();
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
ZoneType origin = null;
final Player opponent = ai.getWeakestOpponent();
final Player opponent = AiAttackController.choosePreferredDefenderPlayer(ai);
boolean activateForCost = ComputerUtil.activateForCost(sa, ai);
if (sa.hasParam("Origin")) {
@@ -514,7 +464,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
// if putting cards from hand to library and parent is drawing cards
// make sure this will actually do something:
final TargetRestrictions tgt = sa.getTargetRestrictions();
final Player opp = aiPlayer.getWeakestOpponent();
final Player opp = AiAttackController.choosePreferredDefenderPlayer(aiPlayer);
if (tgt != null && tgt.canTgtPlayer()) {
boolean isCurse = sa.isCurse();
if (isCurse && sa.canTarget(opp)) {
@@ -573,7 +523,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
Iterable<Player> pDefined;
final TargetRestrictions tgt = sa.getTargetRestrictions();
if ((tgt != null) && tgt.canTgtPlayer()) {
final Player opp = ai.getWeakestOpponent();
final Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
if (sa.isCurse()) {
if (sa.canTarget(opp)) {
sa.getTargets().add(opp);
@@ -829,7 +779,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
return ph.getNextTurn().equals(ai) && ph.is(PhaseType.END_OF_TURN);
}
if (isHidden(sa)) {
if (sa.isHidden()) {
return true;
}
@@ -935,7 +885,6 @@ public class ChangeZoneAi extends SpellAbilityAi {
// TODO need to set XManaCostPaid for targets, maybe doesn't need PayX anymore?
sa.setXManaCostPaid(xPay);
// TODO since change of PayX. the shouldCastLessThanMax logic might be faulty
}
CardCollection list = CardLists.getTargetableCards(game.getCardsIn(origin), sa);
@@ -956,9 +905,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
if (sa.isSpell()) {
list.remove(source); // spells can't target their own source, because it's actually in the stack zone
}
//System.out.println("isPreferredTarget " + list);
if (sa.hasParam("AttachedTo")) {
//System.out.println("isPreferredTarget att " + list);
list = CardLists.filter(list, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
@@ -970,7 +917,6 @@ public class ChangeZoneAi extends SpellAbilityAi {
return false;
}
});
//System.out.println("isPreferredTarget ok " + list);
}
if (list.size() < sa.getMinTargets()) {
@@ -1206,6 +1152,10 @@ public class ChangeZoneAi extends SpellAbilityAi {
if (!list.isEmpty()) {
if (destination.equals(ZoneType.Battlefield) || origin.contains(ZoneType.Battlefield)) {
// filter by MustTarget requirement
CardCollection originalList = new CardCollection(list);
boolean mustTargetFiltered = StaticAbilityMustTarget.filterMustTargetCards(ai, list, sa);
final Card mostExpensive = ComputerUtilCard.getMostExpensivePermanentAI(list, sa, false);
if (mostExpensive.isCreature()) {
// if a creature is most expensive take the best one
@@ -1234,6 +1184,11 @@ public class ChangeZoneAi extends SpellAbilityAi {
return false;
}
}
// Restore original list for next loop if filtered by MustTarget requirement
if (mustTargetFiltered) {
list = originalList;
}
} else if (destination.equals(ZoneType.Hand) || destination.equals(ZoneType.Library)) {
List<Card> nonLands = CardLists.getNotType(list, "Land");
// Prefer to pull a creature, generally more useful for AI.
@@ -1352,8 +1307,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
private static Card canBouncePermanent(final Player ai, SpellAbility sa, CardCollectionView list) {
Game game = ai.getGame();
// filter out untargetables
CardCollectionView aiPermanents = CardLists
.filterControlledBy(list, ai);
CardCollectionView aiPermanents = CardLists.filterControlledBy(list, ai);
CardCollection aiPlaneswalkers = CardLists.filter(aiPermanents, Presets.PLANESWALKERS);
// Felidar Guardian + Saheeli Rai combo support
@@ -1525,9 +1479,11 @@ public class ChangeZoneAi extends SpellAbilityAi {
if ("DeathgorgeScavenger".equals(logic)) {
return SpecialCardAi.DeathgorgeScavenger.consider(ai, sa);
} else if ("ExtraplanarLens".equals(logic)) {
}
if ("ExtraplanarLens".equals(logic)) {
return SpecialCardAi.ExtraplanarLens.consider(ai, sa);
} else if ("ExileCombatThreat".equals(logic)) {
}
if ("ExileCombatThreat".equals(logic)) {
return doExileCombatThreatLogic(ai, sa);
}
@@ -1904,23 +1860,25 @@ public class ChangeZoneAi extends SpellAbilityAi {
}));
}
// JAVA 1.8 use Map.Entry.comparingByValue() somehow
Map.Entry<Player, Map.Entry<String, Integer>> max = Collections.max(data.entrySet(), new Comparator<Map.Entry<Player, Map.Entry<String, Integer>>>() {
@Override
public int compare(Map.Entry<Player, Map.Entry<String, Integer>> o1, Map.Entry<Player, Map.Entry<String, Integer>> o2) {
return o1.getValue().getValue() - o2.getValue().getValue();
if (!data.isEmpty()) {
// JAVA 1.8 use Map.Entry.comparingByValue() somehow
Map.Entry<Player, Map.Entry<String, Integer>> max = Collections.max(data.entrySet(), new Comparator<Map.Entry<Player, Map.Entry<String, Integer>>>() {
@Override
public int compare(Map.Entry<Player, Map.Entry<String, Integer>> o1, Map.Entry<Player, Map.Entry<String, Integer>> o2) {
return o1.getValue().getValue() - o2.getValue().getValue();
}
});
// filter list again by the opponent and a creature of the wanted name that can be targeted
list = CardLists.filter(CardLists.filterControlledBy(list, max.getKey()),
CardPredicates.nameEquals(max.getValue().getKey()), CardPredicates.isTargetableBy(sa));
// list should contain only one element or zero
sa.resetTargets();
for (Card c : list) {
sa.getTargets().add(c);
return true;
}
});
// filter list again by the opponent and a creature of the wanted name that can be targeted
list = CardLists.filter(CardLists.filterControlledBy(list, max.getKey()),
CardPredicates.nameEquals(max.getValue().getKey()), CardPredicates.isTargetableBy(sa));
// list should contain only one element or zero
sa.resetTargets();
for (Card c : list) {
sa.getTargets().add(c);
return true;
}
return false;
@@ -2021,17 +1979,12 @@ public class ChangeZoneAi extends SpellAbilityAi {
boolean setPayX = false;
if (unlessCost.equals("X") && sa.getSVar(unlessCost).equals("Count$xPaid")) {
setPayX = true;
// TODO use ComputerUtilCost.getMaxXValue if able
toPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
toPay = ComputerUtilCost.getMaxXValue(sa, ai);
} else {
toPay = AbilityUtils.calculateAmount(source, unlessCost, sa);
}
if (toPay == 0) {
canBeSaved.add(potentialTgt);
}
if (toPay <= usableManaSources) {
if (toPay == 0 || toPay <= usableManaSources) {
canBeSaved.add(potentialTgt);
}

View File

@@ -245,8 +245,25 @@ public class ChangeZoneAllAi extends SpellAbilityAi {
&& !ComputerUtil.isPlayingReanimator(ai);
}
} else if (origin.equals(ZoneType.Exile)) {
// TODO: nothing to do here at the moment
return false;
String logic = sa.getParam("AILogic");
if (logic != null && logic.startsWith("DiscardAllAndRetExiled")) {
int numExiledWithSrc = CardLists.filter(ai.getCardsIn(ZoneType.Exile), CardPredicates.isExiledWith(source)).size();
int curHandSize = ai.getCardsIn(ZoneType.Hand).size();
// minimum card advantage unless the hand will be fully reloaded
int minAdv = logic.contains(".minAdv") ? Integer.parseInt(logic.substring(logic.indexOf(".minAdv") + 7)) : 0;
if (numExiledWithSrc > curHandSize) {
if (ComputerUtil.predictThreatenedObjects(ai, sa, true).contains(source)) {
// Try to gain some card advantage if the card will die anyway
// TODO: ideally, should evaluate the hand value and not discard good hands to it
return true;
}
}
return (curHandSize + minAdv - 1 < numExiledWithSrc) || (numExiledWithSrc >= ai.getMaxHandSize());
}
} else if (origin.equals(ZoneType.Stack)) {
// time stop can do something like this:
// Origin$ Stack | Destination$ Exile | SubAbility$ DBSkip

View File

@@ -227,7 +227,7 @@ public class CharmAi extends SpellAbilityAi {
if (AiPlayDecision.WillPlay == aic.canPlaySa(sub)) {
chosenList.add(sub);
if (chosenList.size() == min) {
break; // enough choices
break; // enough choices
}
}
}

View File

@@ -9,6 +9,7 @@ import com.google.common.base.Predicates;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import forge.ai.AiAttackController;
import forge.ai.ComputerUtilAbility;
import forge.ai.ComputerUtilCard;
import forge.ai.ComputerUtilCombat;
@@ -20,6 +21,7 @@ import forge.game.card.CardCollectionView;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates;
import forge.game.card.CardPredicates.Presets;
import forge.game.card.CounterEnumType;
import forge.game.combat.Combat;
import forge.game.keyword.Keyword;
import forge.game.phase.PhaseType;
@@ -73,10 +75,8 @@ public class ChooseCardAi extends SpellAbilityAi {
return !choices.isEmpty();
} else if (aiLogic.equals("AtLeast2") || aiLogic.equals("BestBlocker")) {
return choices.size() >= 2;
} else if (aiLogic.equals("Clone") || aiLogic.equals("Vesuva")) {
final String filter = aiLogic.equals("Clone") ? "Permanent.YouDontCtrl,Permanent.nonLegendary"
: "Permanent.YouDontCtrl+notnamedVesuva,Permanent.nonLegendary+notnamedVesuva";
} else if (aiLogic.equals("Clone")) {
final String filter = "Permanent.YouDontCtrl,Permanent.nonLegendary";
choices = CardLists.getValidCards(choices, filter, host.getController(), host, sa);
return !choices.isEmpty();
} else if (aiLogic.equals("Never")) {
@@ -96,12 +96,24 @@ public class ChooseCardAi extends SpellAbilityAi {
return ComputerUtilCombat.damageIfUnblocked(c, ai, combat, true) > ref;
}
});
return !choices.isEmpty();
} else if (aiLogic.equals("Ashiok")) {
final int loyalty = host.getCounters(CounterEnumType.LOYALTY) - 1;
for (int i = loyalty; i >= 0; i--) {
sa.setXManaCostPaid(i);
choices = ai.getGame().getCardsIn(choiceZone);
choices = CardLists.getValidCards(choices, sa.getParam("Choices"), host.getController(), host, sa);
if (!choices.isEmpty()) {
return true;
}
}
return !choices.isEmpty();
} else if (aiLogic.equals("RandomNonLand")) {
return !CardLists.getValidCards(choices, "Card.nonLand", host.getController(), host, sa).isEmpty();
} else if (aiLogic.equals("Duneblast")) {
CardCollection aiCreatures = ai.getCreaturesInPlay();
CardCollection oppCreatures = ai.getWeakestOpponent().getCreaturesInPlay();
CardCollection oppCreatures = AiAttackController.choosePreferredDefenderPlayer(ai).getCreaturesInPlay();
aiCreatures = CardLists.getNotKeyword(aiCreatures, Keyword.INDESTRUCTIBLE);
oppCreatures = CardLists.getNotKeyword(oppCreatures, Keyword.INDESTRUCTIBLE);
@@ -159,18 +171,13 @@ public class ChooseCardAi extends SpellAbilityAi {
options = CardLists.filter(options, Presets.UNTAPPED);
}
choice = ComputerUtilCard.getBestCreatureAI(options);
} else if (logic.equals("Clone") || logic.equals("Vesuva")) {
final String filter = logic.equals("Clone") ? "Permanent.YouDontCtrl,Permanent.nonLegendary" :
"Permanent.YouDontCtrl+notnamedVesuva,Permanent.nonLegendary+notnamedVesuva";
} else if (logic.equals("Clone")) {
final String filter = "Permanent.YouDontCtrl,Permanent.nonLegendary";
CardCollection newOptions = CardLists.getValidCards(options, filter.split(","), ctrl, host, sa);
if (!newOptions.isEmpty()) {
options = newOptions;
}
choice = ComputerUtilCard.getBestAI(options);
if (logic.equals("Vesuva") && "Vesuva".equals(choice.getName())) {
choice = null;
}
} else if ("RandomNonLand".equals(logic)) {
options = CardLists.getValidCards(options, "Card.nonLand", host.getController(), host, sa);
choice = Aggregates.random(options);

View File

@@ -7,6 +7,7 @@ import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import forge.StaticData;
import forge.ai.AiAttackController;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCard;
import forge.ai.SpecialCardAi;
@@ -34,9 +35,7 @@ public class ChooseCardNameAi extends SpellAbilityAi {
}
String logic = sa.getParam("AILogic");
if (logic.equals("MomirAvatar")) {
return SpecialCardAi.MomirVigAvatar.consider(ai, sa);
} else if (logic.equals("CursedScroll")) {
if (logic.equals("CursedScroll")) {
return SpecialCardAi.CursedScroll.consider(ai, sa);
}
@@ -44,7 +43,7 @@ public class ChooseCardNameAi extends SpellAbilityAi {
if (tgt != null) {
sa.resetTargets();
if (tgt.canOnlyTgtOpponent()) {
sa.getTargets().add(ai.getWeakestOpponent());
sa.getTargets().add(AiAttackController.choosePreferredDefenderPlayer(ai));
} else {
sa.getTargets().add(ai);
}

View File

@@ -1,5 +1,6 @@
package forge.ai.ability;
import forge.ai.AiAttackController;
import forge.ai.SpellAbilityAi;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
@@ -16,7 +17,7 @@ public class ChooseEvenOddAi extends SpellAbilityAi {
TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null) {
sa.resetTargets();
Player opp = aiPlayer.getWeakestOpponent();
Player opp = AiAttackController.choosePreferredDefenderPlayer(aiPlayer);
if (sa.canTarget(opp)) {
sa.getTargets().add(opp);
} else {

View File

@@ -8,7 +8,6 @@ import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import forge.ai.ComputerUtilAbility;
import forge.ai.ComputerUtilCard;
import forge.ai.ComputerUtilCost;
@@ -32,7 +31,6 @@ import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.AbilitySub;
import forge.game.spellability.SpellAbility;
import forge.game.staticability.StaticAbilityCantBeCast;
import forge.game.zone.ZoneType;
import forge.util.Aggregates;
import forge.util.collect.FCollection;
@@ -54,8 +52,6 @@ public class ChooseGenericEffectAi extends SpellAbilityAi {
}
} else if ("GideonBlackblade".equals(aiLogic)) {
return SpecialCardAi.GideonBlackblade.consider(ai, sa);
} else if ("SoulEcho".equals(aiLogic)) {
return doTriggerAINoCost(ai, sa, true);
} else if ("Always".equals(aiLogic)) {
return true;
}
@@ -72,7 +68,7 @@ public class ChooseGenericEffectAi extends SpellAbilityAi {
*/
@Override
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
return checkApiLogic(aiPlayer, sa);
return sa.isTrigger() ? doTriggerAINoCost(aiPlayer, sa, sa.isMandatory()) : checkApiLogic(aiPlayer, sa);
}
@Override
@@ -160,6 +156,22 @@ public class ChooseGenericEffectAi extends SpellAbilityAi {
}
}
return others;
} else if ("Counters".equals(logic)) {
// TODO: this code will need generalization if this logic is used for cards other
// than Elspeth Conquers Death with different choice parameters
SpellAbility p1p1 = null, loyalty = null;
for (final SpellAbility sp : spells) {
if (("P1P1").equals(sp.getParam("CounterType"))) {
p1p1 = sp;
} else {
loyalty = sp;
}
}
if (sa.getParent().getTargetCard() != null && sa.getParent().getTargetCard().getType().isPlaneswalker()) {
return loyalty;
} else {
return p1p1;
}
} else if ("Fatespinner".equals(logic)) {
SpellAbility skipDraw = null, /*skipMain = null,*/ skipCombat = null;
for (final SpellAbility sp : spells) {
@@ -230,10 +242,11 @@ public class ChooseGenericEffectAi extends SpellAbilityAi {
return allow;
}
SpellAbility firstSpell = imprinted.getFirstSpellAbility();
// check if something would prevent it from casting
if (firstSpell == null || StaticAbilityCantBeCast.cantBeCastAbility(firstSpell, imprinted, owner)) {
return allow;
//if Iona does prevent from casting, allow it to draw
for (final Card io : player.getCardsIn(ZoneType.Battlefield, "Iona, Shield of Emeria")) {
if (CardUtil.getColors(imprinted).hasAnyColor(MagicColor.fromName(io.getChosenColor()))) {
return allow;
}
}
if (dmg == 0) {
@@ -364,8 +377,6 @@ public class ChooseGenericEffectAi extends SpellAbilityAi {
} else if ("Riot".equals(logic)) {
SpellAbility counterSA = spells.get(0), hasteSA = spells.get(1);
return preferHasteForRiot(sa, player) ? hasteSA : counterSA;
} else if ("CrawlingBarrens".equals(logic)) {
return SpecialCardAi.CrawlingBarrens.considerAnimating(player, sa, spells);
}
return spells.get(0); // return first choice if no logic found
}

View File

@@ -1,5 +1,6 @@
package forge.ai.ability;
import forge.ai.AiAttackController;
import forge.ai.SpellAbilityAi;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
@@ -16,7 +17,7 @@ public class ChooseNumberAi extends SpellAbilityAi {
TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null) {
sa.resetTargets();
Player opp = aiPlayer.getWeakestOpponent();
Player opp = AiAttackController.choosePreferredDefenderPlayer(aiPlayer);
if (sa.canTarget(opp)) {
sa.getTargets().add(opp);
} else {

View File

@@ -8,6 +8,7 @@ import com.google.common.base.Predicates;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import forge.ai.AiAttackController;
import forge.ai.ComputerUtilCard;
import forge.ai.ComputerUtilCombat;
import forge.ai.SpellAbilityAi;
@@ -54,7 +55,7 @@ public class ChooseSourceAi extends SpellAbilityAi {
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null) {
sa.resetTargets();
Player opp = ai.getWeakestOpponent();
Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
if (sa.canTarget(opp)) {
sa.getTargets().add(opp);
} else {

View File

@@ -3,13 +3,17 @@ package forge.ai.ability;
import java.util.List;
import java.util.Map;
import com.google.common.base.Predicates;
import forge.ai.ComputerUtilCard;
import forge.ai.SpellAbilityAi;
import forge.game.Game;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardCollectionView;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates;
import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
@@ -52,7 +56,7 @@ public class CloneAi extends SpellAbilityAi {
bFlag |= (!c.isCreature() && !c.isTapped() && !(c.getTurnInZone() == phase.getTurn()));
// for creatures that could be improved (like Figure of Destiny)
if (c.isCreature() && (sa.hasParam("Permanent") || (!c.isTapped() && !c.isSick()))) {
if (c.isCreature() && (!sa.hasParam("Duration") || (!c.isTapped() && !c.isSick()))) {
int power = -5;
if (sa.hasParam("Power")) {
power = AbilityUtils.calculateAmount(source, sa.getParam("Power"), sa);
@@ -68,8 +72,7 @@ public class CloneAi extends SpellAbilityAi {
}
if (!bFlag) { // All of the defined stuff is cloned, not very
// useful
if (!bFlag) { // All of the defined stuff is cloned, not very useful
return false;
}
} else {
@@ -95,11 +98,18 @@ public class CloneAi extends SpellAbilityAi {
@Override
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
Card host = sa.getHostCard();
boolean chance = true;
if (sa.usesTargeting()) {
chance = cloneTgtAI(sa);
} else {
if (sa.hasParam("Choices")) {
CardCollectionView choices = CardLists.getValidCards(host.getGame().getCardsIn(ZoneType.Battlefield),
sa.getParam("Choices"), host.getController(), host, sa);
chance = !choices.isEmpty();
}
}
// Improve AI for triggers. If source is a creature with:
@@ -171,18 +181,18 @@ public class CloneAi extends SpellAbilityAi {
@Override
protected Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional,
Player targetedPlayer, Map<String, Object> params) {
final Card host = sa.getHostCard();
final String name = host.getName();
final Player ctrl = host.getController();
final Card cloneTarget = getCloneTarget(sa);
final boolean isOpp = cloneTarget.getController().isOpponentOf(sa.getActivatingPlayer());
final boolean isVesuva = "Vesuva".equals(host.getName());
final boolean isVesuva = "Vesuva".equals(name) || "Sculpting Steel".equals(name);
final boolean canCloneLegendary = "True".equalsIgnoreCase(sa.getParam("NonLegendary"));
String filter = !isVesuva ? "Permanent.YouDontCtrl,Permanent.nonLegendary"
: "Permanent.YouDontCtrl+notnamedVesuva,Permanent.nonLegendary+notnamedVesuva";
: "Permanent.YouDontCtrl+notnamed" + name + ",Permanent.nonLegendary+notnamed" + name;
// TODO: rewrite this block so that this is done somehow more elegantly
if (canCloneLegendary) {
@@ -201,12 +211,13 @@ public class CloneAi extends SpellAbilityAi {
}
}
Card choice = isOpp ? ComputerUtilCard.getWorstAI(options) : ComputerUtilCard.getBestAI(options);
if (isVesuva && "Vesuva".equals(choice.getName())) {
choice = null;
// prevent loop of choosing copy of same card
if (isVesuva) {
options = CardLists.filter(options, Predicates.not(CardPredicates.sharesNameWith(host)));
}
Card choice = isOpp ? ComputerUtilCard.getWorstAI(options) : ComputerUtilCard.getBestAI(options);
return choice;
}
@@ -234,7 +245,7 @@ public class CloneAi extends SpellAbilityAi {
// Combat_Begin step
if (!ph.is(PhaseType.COMBAT_BEGIN)
&& ph.isPlayerTurn(ai) && !SpellAbilityAi.isSorcerySpeed(sa)
&& !sa.hasParam("ActivationPhases") && !sa.hasParam("Permanent")) {
&& !sa.hasParam("ActivationPhases") && sa.hasParam("Duration")) {
return false;
}
@@ -245,6 +256,6 @@ public class CloneAi extends SpellAbilityAi {
}
// don't activate during main2 unless this effect is permanent
return !ph.is(PhaseType.MAIN2) || sa.hasParam("Permanent");
return !ph.is(PhaseType.MAIN2) || !sa.hasParam("Duration");
}
}

View File

@@ -3,6 +3,7 @@ package forge.ai.ability;
import com.google.common.base.Predicate;
import com.google.common.collect.Lists;
import forge.ai.AiAttackController;
import forge.ai.ComputerUtilCard;
import forge.ai.SpellAbilityAi;
import forge.game.ability.AbilityUtils;
@@ -29,9 +30,8 @@ public class ControlExchangeAi extends SpellAbilityAi {
sa.resetTargets();
CardCollection list =
CardLists.getValidCards(ai.getWeakestOpponent().getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), ai, sa.getHostCard(), sa);
// AI won't try to grab cards that are filtered out of AI decks on
// purpose
CardLists.getValidCards(AiAttackController.choosePreferredDefenderPlayer(ai).getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), ai, sa.getHostCard(), sa);
// AI won't try to grab cards that are filtered out of AI decks on purpose
list = CardLists.filter(list, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {

View File

@@ -39,6 +39,7 @@ import forge.game.player.PlayerCollection;
import forge.game.player.PlayerPredicates;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.game.staticability.StaticAbilityMustTarget;
import forge.game.zone.ZoneType;
import forge.util.Aggregates;
@@ -109,8 +110,7 @@ public class ControlGainAi extends SpellAbilityAi {
}
}
// Don't steal something if I can't Attack without, or prevent it from
// blocking at least
// Don't steal something if I can't Attack without, or prevent it from blocking at least
if (lose.contains("EOT")
&& ai.getGame().getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)
&& !sa.isTrigger()) {
@@ -210,7 +210,12 @@ public class ControlGainAi extends SpellAbilityAi {
}
}
// TODO check life of controller and consider stealing from another opponent so the risk of your army disappearing is spread out
while (t == null) {
// filter by MustTarget requirement
CardCollection originalList = new CardCollection(list);
boolean mustTargetFiltered = StaticAbilityMustTarget.filterMustTargetCards(ai, list, sa);
if (planeswalkers > 0) {
t = ComputerUtilCard.getBestPlaneswalkerAI(list);
} else if (creatures > 0) {
@@ -238,6 +243,11 @@ public class ControlGainAi extends SpellAbilityAi {
enchantments--;
}
// Restore original list for next loop if filtered by MustTarget requirement
if (mustTargetFiltered) {
list = originalList;
}
if (!sa.canTarget(t)) {
list.remove(t);
t = null;
@@ -254,7 +264,6 @@ public class ControlGainAi extends SpellAbilityAi {
}
return true;
}
@Override

View File

@@ -44,7 +44,9 @@ public class CopyPermanentAi extends SpellAbilityAi {
return false;
}
if ("MimicVat".equals(aiLogic)) {
if ("MomirAvatar".equals(aiLogic)) {
return SpecialCardAi.MomirVigAvatar.consider(aiPlayer, sa);
} else if ("MimicVat".equals(aiLogic)) {
return SpecialCardAi.MimicVat.considerCopy(aiPlayer, sa);
} else if ("AtEOT".equals(aiLogic)) {
return ph.is(PhaseType.END_OF_TURN);

View File

@@ -113,8 +113,7 @@ public class CounterAi extends SpellAbilityAi {
boolean setPayX = false;
if (unlessCost.equals("X") && sa.getSVar(unlessCost).equals("Count$xPaid")) {
setPayX = true;
// TODO use ComputerUtilCost.getMaxXValue
toPay = Math.min(ComputerUtilMana.determineLeftoverMana(sa, ai), usableManaSources + 1);
toPay = Math.min(ComputerUtilCost.getMaxXValue(sa, ai), usableManaSources + 1);
} else {
toPay = AbilityUtils.calculateAmount(source, unlessCost, sa);
}
@@ -124,8 +123,7 @@ public class CounterAi extends SpellAbilityAi {
}
if (toPay <= usableManaSources) {
// If this is a reusable Resource, feel free to play it most of
// the time
// If this is a reusable Resource, feel free to play it most of the time
if (!SpellAbilityAi.playReusable(ai,sa)) {
return false;
}

View File

@@ -372,54 +372,57 @@ public class CountersMoveAi extends SpellAbilityAi {
}
}
List<Card> aiList = CardLists.filterControlledBy(tgtCards, ai);
if (!aiList.isEmpty()) {
List<Card> best = CardLists.filter(aiList, new Predicate<Card>() {
Card lki = CardUtil.getLKICopy(src);
lki.clearCounters();
// go for opponent when value implies debuff
if (ComputerUtilCard.evaluateCreature(src) > ComputerUtilCard.evaluateCreature(lki)) {
List<Card> aiList = CardLists.filterControlledBy(tgtCards, ai);
if (!aiList.isEmpty()) {
List<Card> best = CardLists.filter(aiList, new Predicate<Card>() {
@Override
public boolean apply(Card card) {
// gain from useless
if (ComputerUtilCard.isUselessCreature(ai, card)) {
return false;
}
// source would leave the game
if (card.hasSVar("EndOfTurnLeavePlay")) {
return false;
}
if (cType != null) {
if (cType.is(CounterEnumType.P1P1) && card.hasKeyword(Keyword.UNDYING)) {
return false;
}
if (cType.is(CounterEnumType.M1M1) && card.hasKeyword(Keyword.PERSIST)) {
@Override
public boolean apply(Card card) {
// gain from useless
if (ComputerUtilCard.isUselessCreature(ai, card)) {
return false;
}
if (!card.canReceiveCounters(cType)) {
// source would leave the game
if (card.hasSVar("EndOfTurnLeavePlay")) {
return false;
}
if (cType != null) {
if (cType.is(CounterEnumType.P1P1) && card.hasKeyword(Keyword.UNDYING)) {
return false;
}
if (cType.is(CounterEnumType.M1M1)) {
return false;
}
if (!card.canReceiveCounters(cType)) {
return false;
}
}
return true;
}
});
if (best.isEmpty()) {
best = aiList;
}
Card card = ComputerUtilCard.getBestCreatureAI(best);
if (card != null) {
sa.getTargets().add(card);
return true;
}
});
if (best.isEmpty()) {
best = aiList;
}
Card card = ComputerUtilCard.getBestCreatureAI(best);
if (card != null) {
sa.getTargets().add(card);
return true;
}
}
// move counter to opponents creature but only if you can not steal
// them
// try to move to something useless or something that would leave
// play
// move counter to opponents creature but only if you can not steal them
// try to move to something useless or something that would leave play
List<Card> oppList = CardLists.filterControlledBy(tgtCards, ai.getOpponents());
if (!oppList.isEmpty()) {
List<Card> best = CardLists.filter(oppList, new Predicate<Card>() {
@@ -441,7 +444,7 @@ public class CountersMoveAi extends SpellAbilityAi {
});
if (best.isEmpty()) {
best = aiList;
best = oppList;
}
Card card = ComputerUtilCard.getBestCreatureAI(best);
@@ -455,7 +458,7 @@ public class CountersMoveAi extends SpellAbilityAi {
}
}
// used for multiple sources -> defied
// used for multiple sources -> defined
// or for source -> multiple defined
@Override
protected Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional,

View File

@@ -120,8 +120,7 @@ public class CountersMultiplyAi extends SpellAbilityAi {
CardCollection list = CardLists.getTargetableCards(game.getCardsIn(ZoneType.Battlefield), sa);
// pre filter targetable cards with counters and can receive one of
// them
// pre filter targetable cards with counters and can receive one of them
list = CardLists.filter(list, new Predicate<Card>() {
@Override

View File

@@ -8,8 +8,10 @@ import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import forge.ai.AiProps;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCard;
import forge.ai.PlayerControllerAi;
import forge.ai.SpellAbilityAi;
import forge.game.GameEntity;
import forge.game.card.Card;
@@ -121,6 +123,7 @@ public class CountersProliferateAi extends SpellAbilityAi {
final CounterType poison = CounterType.get(CounterEnumType.POISON);
boolean aggroAI = (((PlayerControllerAi) ai.getController()).getAi()).getBooleanProperty(AiProps.PLAY_AGGRO);
// because countertype can't be chosen anymore, only look for posion counters
for (final Player p : Iterables.filter(options, Player.class)) {
if (p.isOpponentOf(ai)) {
@@ -128,7 +131,8 @@ public class CountersProliferateAi extends SpellAbilityAi {
return (T)p;
}
} else {
if (p.getCounters(poison) <= 5 || p.canReceiveCounters(poison)) {
// poison is risky, should not proliferate them in most cases
if ((p.getCounters(poison) <= 5 && aggroAI && p.getCounters(CounterEnumType.EXPERIENCE) + p.getCounters(CounterEnumType.ENERGY) >= 1) || !p.canReceiveCounters(poison)) {
return (T)p;
}
}

View File

@@ -47,6 +47,8 @@ import forge.game.player.PlayerCollection;
import forge.game.player.PlayerPredicates;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.game.trigger.Trigger;
import forge.game.trigger.TriggerType;
import forge.game.zone.ZoneType;
import forge.util.Aggregates;
import forge.util.MyRandom;
@@ -313,7 +315,12 @@ public class CountersPutAi extends SpellAbilityAi {
} else if (logic.startsWith("MoveCounter")) {
return doMoveCounterLogic(ai, sa, ph);
} else if (logic.equals("CrawlingBarrens")) {
return SpecialCardAi.CrawlingBarrens.consider(ai, sa);
boolean willActivate = SpecialCardAi.CrawlingBarrens.consider(ai, sa);
if (willActivate && ph.getPhase().isBefore(PhaseType.MAIN2)) {
// don't use this for mana until after combat
AiCardMemory.rememberCard(ai, source, AiCardMemory.MemorySet.HELD_MANA_SOURCES_FOR_MAIN2);
}
return willActivate;
}
if (!sa.metConditions() && sa.getSubAbility() == null) {
@@ -401,19 +408,37 @@ public class CountersPutAi extends SpellAbilityAi {
}
if ("Polukranos".equals(logic)) {
boolean found = false;
for (Trigger tr : source.getTriggers()) {
if (!tr.getMode().equals(TriggerType.BecomeMonstrous)) {
continue;
}
SpellAbility oa = tr.ensureAbility();
if (oa == null) {
continue;
}
CardCollection targets = CardLists.getTargetableCards(ai.getOpponents().getCreaturesInPlay(), sa);
// need to set Activating player
oa.setActivatingPlayer(ai);
CardCollection targets = CardLists.getTargetableCards(ai.getOpponents().getCreaturesInPlay(), oa);
if (!targets.isEmpty()){
boolean canSurvive = false;
for (Card humanCreature : targets) {
if (!FightAi.canKill(humanCreature, source, 0)){
canSurvive = true;
if (!targets.isEmpty()){
boolean canSurvive = false;
for (Card humanCreature : targets) {
if (!FightAi.canKill(humanCreature, source, 0)){
canSurvive = true;
break;
}
}
}
if (!canSurvive){
return false;
}
if (!canSurvive){
return false;
}
};
found = true;
break;
}
if (!found) {
return false;
}
}
@@ -841,17 +866,16 @@ public class CountersPutAi extends SpellAbilityAi {
choice = Aggregates.random(list);
}
}
if (choice != null && divided) {
int alloc = Math.max(amount / totalTargets, 1);
if (sa.getTargets().size() == Math.min(totalTargets, sa.getMaxTargets()) - 1) {
sa.addDividedAllocation(choice, left);
} else {
sa.addDividedAllocation(choice, alloc);
left -= alloc;
}
}
if (choice != null && divided) {
int alloc = Math.max(amount / totalTargets, 1);
if (sa.getTargets().size() == Math.min(totalTargets, sa.getMaxTargets()) - 1) {
sa.addDividedAllocation(choice, left);
} else {
sa.addDividedAllocation(choice, alloc);
left -= alloc;
}
}
if (choice != null) {
sa.getTargets().add(choice);
list.remove(choice);

View File

@@ -14,7 +14,7 @@ import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.trigger.Trigger;
import forge.game.trigger.TriggerDamageDone;
import forge.game.trigger.TriggerType;
import forge.game.zone.ZoneType;
import forge.util.MyRandom;
@@ -56,7 +56,7 @@ public abstract class DamageAiBase extends SpellAbilityAi {
boolean dmgByCardsInHand = false;
if ("X".equals(sa.getParam("NumDmg")) && sa.getHostCard() != null && sa.hasSVar(sa.getParam("NumDmg")) &&
sa.getHostCard().getSVar(sa.getParam("NumDmg")).equals("TargetedPlayer$CardsInHand")) {
sa.getSVar(sa.getParam("NumDmg")).equals("TargetedPlayer$CardsInHand")) {
dmgByCardsInHand = true;
}
// Not sure if type choice implemented for the AI yet but it should at least recognize this spell hits harder on larger enemy hand size
@@ -75,7 +75,7 @@ public abstract class DamageAiBase extends SpellAbilityAi {
// If has triggered ability on dealing damage to an opponent, go for it!
Card hostcard = sa.getHostCard();
for (Trigger trig : hostcard.getTriggers()) {
if (trig instanceof TriggerDamageDone) {
if (trig.getMode() == TriggerType.DamageDone) {
if (("Opponent".equals(trig.getParam("ValidTarget")))
&& (!"True".equals(trig.getParam("CombatDamage")))) {
return true;
@@ -111,8 +111,7 @@ public abstract class DamageAiBase extends SpellAbilityAi {
final CardCollectionView hand = comp.getCardsIn(ZoneType.Hand);
if ((enemy.getLife() - restDamage) < 5) {
// drop the human to less than 5
// life
// drop the human to less than 5 life
return true;
}
@@ -147,12 +146,12 @@ public abstract class DamageAiBase extends SpellAbilityAi {
}
}
}
if (value > 0) { //more likely to burn with larger hand
if (value > 0) { //more likely to burn with larger hand
for (int i = 3; i < hand.size(); i++) {
value *= 1.1f;
}
}
if (value < 0.2f) { //hard floor to reduce ridiculous odds for instants over time
if (value < 0.2f) { //hard floor to reduce ridiculous odds for instants over time
return false;
} else {
final float chance = MyRandom.getRandom().nextFloat();

View File

@@ -30,22 +30,20 @@ import forge.game.ability.AbilityUtils;
import forge.game.ability.ApiType;
import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardFactoryUtil;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates;
import forge.game.card.CounterEnumType;
import forge.game.cost.Cost;
import forge.game.cost.CostPartMana;
import forge.game.cost.CostRemoveCounter;
import forge.game.keyword.Keyword;
import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.player.PlayerPredicates;
import forge.game.spellability.AbilitySub;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetChoices;
import forge.game.spellability.TargetRestrictions;
import forge.game.staticability.StaticAbilityMustTarget;
import forge.game.zone.ZoneType;
import forge.util.Aggregates;
import forge.util.MyRandom;
@@ -131,7 +129,7 @@ public class DamageDealAi extends DamageAiBase {
// Set PayX here to maximum value. It will be adjusted later depending on the target.
sa.setXManaCostPaid(dmg);
} else if (sa.getSVar(damage).contains("InYourHand") && source.isInZone(ZoneType.Hand)) {
dmg = CardFactoryUtil.xCount(source, sa.getSVar(damage)) - 1; // the card will be spent casting the spell, so actual damage is 1 less
dmg = AbilityUtils.calculateAmount(source, damage, sa) - 1; // the card will be spent casting the spell, so actual damage is 1 less
} else if (sa.getSVar(damage).equals("TargetedPlayer$CardsInHand")) {
// cards that deal damage by the number of cards in target player's hand, e.g. Sudden Impact
if (sa.getTargetRestrictions().canTgtPlayer()) {
@@ -343,6 +341,9 @@ public class DamageDealAi extends DamageAiBase {
final Game game = source.getGame();
List<Card> hPlay = getTargetableCards(ai, sa, pl, tgt, activator, source, game);
// Filter MustTarget requirements
StaticAbilityMustTarget.filterMustTargetCards(ai, hPlay, sa);
CardCollection killables = CardLists.filter(hPlay, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
@@ -437,63 +438,12 @@ public class DamageDealAi extends DamageAiBase {
// We can hurt a planeswalker, so rank the one which is the best target
if (!hPlay.isEmpty() && pl.isOpponentOf(ai) && activator.equals(ai)) {
return getBestPlaneswalkerToDamage(hPlay);
return ComputerUtilCard.getBestPlaneswalkerToDamage(hPlay);
}
return null;
}
private Card getBestPlaneswalkerToDamage(final List<Card> pws) {
Card bestTgt = null;
// As of right now, ranks planeswalkers by their Current Loyalty * 10 + Big buff if close to "Ultimate"
int bestScore = 0;
for (Card pw : pws) {
int curLoyalty = pw.getCounters(CounterEnumType.LOYALTY);
int pwScore = curLoyalty * 10;
for (SpellAbility sa : pw.getSpellAbilities()) {
if (sa.hasParam("Ultimate")) {
Integer loyaltyCost = 0;
CostRemoveCounter remLoyalty = sa.getPayCosts().getCostPartByType(CostRemoveCounter.class);
if (remLoyalty != null) {
// if remLoyalty is null, generally there's an AddCounter<0/LOYALTY> cost, like for Gideon Jura.
loyaltyCost = remLoyalty.convertAmount();
}
if (loyaltyCost != null && loyaltyCost != 0 && loyaltyCost - curLoyalty <= 1) {
// Will ultimate soon
pwScore += 10000;
}
if (pwScore > bestScore) {
bestScore = pwScore;
bestTgt = pw;
}
}
}
}
return bestTgt;
}
private Card getWorstPlaneswalkerToDamage(final List<Card> pws) {
Card bestTgt = null;
int bestScore = Integer.MAX_VALUE;
for (Card pw : pws) {
int curLoyalty = pw.getCounters(CounterEnumType.LOYALTY);
if (curLoyalty < bestScore) {
bestScore = curLoyalty;
bestTgt = pw;
}
}
return bestTgt;
}
private List<Card> getTargetableCards(Player ai, SpellAbility sa, Player pl, TargetRestrictions tgt, Player activator, Card source, Game game) {
List<Card> hPlay = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), activator, source, sa);
@@ -730,12 +680,10 @@ public class DamageDealAi extends DamageAiBase {
}
// When giving priority to targeting Creatures for mandatory
// triggers
// feel free to add the Human after we run out of good targets
// triggers feel free to add the Human after we run out of good targets
// TODO: add check here if card is about to die from something
// on the stack
// or from taking combat damage
// on the stack or from taking combat damage
final Cost abCost = sa.getPayCosts();
boolean freePing = immediately || abCost == null
@@ -792,8 +740,7 @@ public class DamageDealAi extends DamageAiBase {
}
}
}
// TODO: Improve Damage, we shouldn't just target the player just
// because we can
// TODO: Improve Damage, we shouldn't just target the player just because we can
if (sa.canTarget(enemy) && tcs.size() < tgt.getMaxTargets(source, sa)) {
if (((phase.is(PhaseType.END_OF_TURN) && phase.getNextTurn().equals(ai))
|| (SpellAbilityAi.isSorcerySpeed(sa) && phase.is(PhaseType.MAIN2))
@@ -951,7 +898,7 @@ public class DamageDealAi extends DamageAiBase {
}
else if (tgt.canTgtPlaneswalker()) {
// Second pass for planeswalkers: choose AI's worst planeswalker
final Card c = getWorstPlaneswalkerToDamage(CardLists.filter(ai.getCardsIn(ZoneType.Battlefield), Predicates.and(CardPredicates.Presets.PLANESWALKERS), CardPredicates.isTargetableBy(sa)));
final Card c = ComputerUtilCard.getWorstPlaneswalkerToDamage(CardLists.filter(ai.getCardsIn(ZoneType.Battlefield), Predicates.and(CardPredicates.Presets.PLANESWALKERS), CardPredicates.isTargetableBy(sa)));
if (c != null) {
sa.getTargets().add(c);
if (divided) {
@@ -981,7 +928,6 @@ public class DamageDealAi extends DamageAiBase {
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
final Card source = sa.getHostCard();
final String damage = sa.getParam("NumDmg");
int dmg = AbilityUtils.calculateAmount(source, damage, sa);
@@ -1038,7 +984,7 @@ public class DamageDealAi extends DamageAiBase {
saTgt = saTgt.getParent();
}
Player opponent = ai.getOpponents().min(PlayerPredicates.compareByLife());
Player opponent = ai.getWeakestOpponent();
// TODO: somehow account for the possible cost reduction?
int dmg = ComputerUtilMana.determineLeftoverMana(sa, ai, saTgt.getParam("XColor"));
@@ -1081,7 +1027,7 @@ public class DamageDealAi extends DamageAiBase {
saTgt.resetTargets();
saTgt.getTargets().add(tgtCreature != null && dmg < opponent.getLife() ? tgtCreature : opponent);
sa.setXManaCostPaid(dmg);
saTgt.setXManaCostPaid(dmg);
return true;
}

View File

@@ -8,6 +8,7 @@ import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import forge.ai.AiAttackController;
import forge.ai.ComputerUtilCard;
import forge.ai.ComputerUtilCost;
import forge.ai.SpellAbilityAi;
@@ -26,9 +27,6 @@ import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
public class DebuffAi extends SpellAbilityAi {
// *************************************************************************
// ***************************** Debuff ************************************
// *************************************************************************
@Override
protected boolean canPlayAI(final Player ai, final SpellAbility sa) {
@@ -140,7 +138,6 @@ public class DebuffAi extends SpellAbilityAi {
while (sa.getTargets().size() < tgt.getMaxTargets(sa.getHostCard(), sa)) {
Card t = null;
// boolean goodt = false;
if (list.isEmpty()) {
if ((sa.getTargets().size() < tgt.getMinTargets(sa.getHostCard(), sa)) || (sa.getTargets().size() == 0)) {
@@ -176,19 +173,18 @@ public class DebuffAi extends SpellAbilityAi {
* @return a CardCollection.
*/
private CardCollection getCurseCreatures(final Player ai, final SpellAbility sa, final List<String> kws) {
final Player opp = ai.getWeakestOpponent();
final Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
CardCollection list = CardLists.getTargetableCards(opp.getCreaturesInPlay(), sa);
if (!list.isEmpty()) {
list = CardLists.filter(list, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return c.hasAnyKeyword(kws); // don't add duplicate negative
// keywords
return c.hasAnyKeyword(kws); // don't add duplicate negative keywords
}
});
}
return list;
} // getCurseCreatures()
}
/**
* <p>
@@ -216,7 +212,7 @@ public class DebuffAi extends SpellAbilityAi {
list.remove(c);
}
final CardCollection pref = CardLists.filterControlledBy(list, ai.getWeakestOpponent());
final CardCollection pref = CardLists.filterControlledBy(list, ai.getOpponents());
final CardCollection forced = CardLists.filterControlledBy(list, ai);
final Card source = sa.getHostCard();
@@ -242,8 +238,7 @@ public class DebuffAi extends SpellAbilityAi {
break;
}
// TODO - if forced targeting, just pick something without the given
// keyword
// TODO - if forced targeting, just pick something without the given keyword
Card c;
if (CardLists.getNotType(forced, "Creature").size() == 0) {
c = ComputerUtilCard.getWorstCreatureAI(forced);

View File

@@ -2,15 +2,7 @@ package forge.ai.ability;
import com.google.common.base.Predicate;
import forge.ai.AiController;
import forge.ai.AiProps;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCard;
import forge.ai.ComputerUtilCost;
import forge.ai.PlayerControllerAi;
import forge.ai.SpecialAiLogic;
import forge.ai.SpecialCardAi;
import forge.ai.SpellAbilityAi;
import forge.ai.*;
import forge.game.ability.AbilityUtils;
import forge.game.ability.ApiType;
import forge.game.card.Card;
@@ -26,6 +18,7 @@ import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.staticability.StaticAbilityMustTarget;
import forge.game.zone.ZoneType;
public class DestroyAi extends SpellAbilityAi {
@@ -122,17 +115,21 @@ public class DestroyAi extends SpellAbilityAi {
CardCollection list;
if (ComputerUtil.preventRunAwayActivations(sa)) {
return false;
}
// Targeting
if (sa.usesTargeting()) {
// If there's X in payment costs and it's tied to targeting, make sure we set the XManaCostPaid first
// (e.g. Heliod's Intervention)
if ("X".equals(sa.getTargetRestrictions().getMinTargets()) && sa.getSVar("X").equals("Count$xPaid")) {
int xPay = ComputerUtilCost.getMaxXValue(sa, ai);
sa.getRootAbility().setXManaCostPaid(xPay);
}
// Assume there where already enough targets chosen by AI Logic Above
if (!sa.canAddMoreTarget() && sa.isTargetNumberValid()) {
if (sa.hasParam("AILogic") && !sa.canAddMoreTarget() && sa.isTargetNumberValid()) {
return true;
}
@@ -140,11 +137,11 @@ public class DestroyAi extends SpellAbilityAi {
sa.resetTargets();
int maxTargets;
if (sa.costHasManaX()) {
if (sa.getRootAbility().costHasManaX()) {
// TODO: currently the AI will maximize mana spent on X, trying to maximize damage. This may need improvement.
maxTargets = ComputerUtilCost.getMaxXValue(sa, ai);
// need to set XPaid to get the right number for
sa.setXManaCostPaid(maxTargets);
sa.getRootAbility().setXManaCostPaid(maxTargets);
// need to check for maxTargets
maxTargets = Math.min(maxTargets, sa.getMaxTargets());
} else {
@@ -226,6 +223,10 @@ public class DestroyAi extends SpellAbilityAi {
// target loop
// TODO use can add more Targets
while (sa.getTargets().size() < maxTargets) {
// filter by MustTarget requirement
CardCollection originalList = new CardCollection(list);
boolean mustTargetFiltered = StaticAbilityMustTarget.filterMustTargetCards(ai, list, sa);
if (list.isEmpty()) {
if (!sa.isMinTargetChosen() || sa.isZeroTargets()) {
sa.resetTargets();
@@ -286,6 +287,12 @@ public class DestroyAi extends SpellAbilityAi {
}
}
}
// Restore original list for next loop if filtered by MustTarget requirement
if (mustTargetFiltered) {
list = originalList;
}
list.remove(choice);
sa.getTargets().add(choice);
}

View File

@@ -70,12 +70,12 @@ public class DestroyAllAi extends SpellAbilityAi {
return doMassRemovalLogic(ai, sa);
}
public boolean doMassRemovalLogic(Player ai, SpellAbility sa) {
public static boolean doMassRemovalLogic(Player ai, SpellAbility sa) {
final Card source = sa.getHostCard();
final String logic = sa.getParamOrDefault("AILogic", "");
Player opponent = ai.getWeakestOpponent(); // TODO: how should this AI logic work for multiplayer and getOpponents()?
final int CREATURE_EVAL_THRESHOLD = 200;
// if we hit the whole board, the other opponents who are not the reason to cast this probably still suffer a bit too
final int CREATURE_EVAL_THRESHOLD = 200 / (!sa.usesTargeting() ? ai.getOpponents().size() : 1);
if (logic.equals("Always")) {
return true; // e.g. Tetzimoc, Primal Death, where we want to cast the permanent even if the removal trigger does nothing
@@ -93,99 +93,101 @@ public class DestroyAllAi extends SpellAbilityAi {
valid = valid.replace("X", Integer.toString(xPay));
}
CardCollection opplist = CardLists.getValidCards(opponent.getCardsIn(ZoneType.Battlefield),
valid.split(","), source.getController(), source, sa);
CardCollection ailist = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), valid.split(","),
source.getController(), source, sa);
opplist = CardLists.filter(opplist, predicate);
ailist = CardLists.filter(ailist, predicate);
if (opplist.isEmpty()) {
return false;
}
// TODO should probably sort results when targeted to use on biggest threat instead of first match
for (Player opponent: ai.getOpponents()) {
CardCollection opplist = CardLists.getValidCards(opponent.getCardsIn(ZoneType.Battlefield), valid.split(","), source.getController(), source, sa);
CardCollection ailist = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), valid.split(","), source.getController(), source, sa);
if (sa.usesTargeting()) {
sa.resetTargets();
if (sa.canTarget(opponent)) {
sa.getTargets().add(opponent);
ailist.clear();
} else {
opplist = CardLists.filter(opplist, predicate);
ailist = CardLists.filter(ailist, predicate);
if (opplist.isEmpty()) {
return false;
}
}
// Special handling for Raiding Party
if (logic.equals("RaidingParty")) {
int numAiCanSave = Math.min(CardLists.filter(ai.getCreaturesInPlay(), Predicates.and(CardPredicates.isColor(MagicColor.WHITE), CardPredicates.Presets.UNTAPPED)).size() * 2, ailist.size());
int numOppsCanSave = Math.min(CardLists.filter(ai.getOpponents().getCreaturesInPlay(), Predicates.and(CardPredicates.isColor(MagicColor.WHITE), CardPredicates.Presets.UNTAPPED)).size() * 2, opplist.size());
return numOppsCanSave < opplist.size() && (ailist.size() - numAiCanSave < opplist.size() - numOppsCanSave);
}
// If effect is destroying creatures and AI is about to lose, activate effect anyway no matter what!
if ((!CardLists.getType(opplist, "Creature").isEmpty()) && (ai.getGame().getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS))
&& (ai.getGame().getCombat() != null && ComputerUtilCombat.lifeInSeriousDanger(ai, ai.getGame().getCombat()))) {
return true;
}
// If effect is destroying creatures and AI is about to get low on life, activate effect anyway if difference in lost permanents not very much
if ((!CardLists.getType(opplist, "Creature").isEmpty()) && (ai.getGame().getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS))
&& (ai.getGame().getCombat() != null && ComputerUtilCombat.lifeInDanger(ai, ai.getGame().getCombat()))
&& ((ComputerUtilCard.evaluatePermanentList(ailist) - 6) >= ComputerUtilCard.evaluatePermanentList(opplist))) {
return true;
}
// if only creatures are affected evaluate both lists and pass only if
// human creatures are more valuable
if (CardLists.getNotType(opplist, "Creature").isEmpty() && CardLists.getNotType(ailist, "Creature").isEmpty()) {
if (ComputerUtilCard.evaluateCreatureList(ailist) + CREATURE_EVAL_THRESHOLD < ComputerUtilCard.evaluateCreatureList(opplist)) {
return true;
}
if (ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2)) {
return false;
}
// test whether the human can kill the ai next turn
Combat combat = new Combat(opponent);
boolean containsAttacker = false;
for (Card att : opponent.getCreaturesInPlay()) {
if (ComputerUtilCombat.canAttackNextTurn(att, ai)) {
combat.addAttacker(att, ai);
containsAttacker = containsAttacker | opplist.contains(att);
}
}
if (!containsAttacker) {
return false;
}
AiBlockController block = new AiBlockController(ai);
block.assignBlockersForCombat(combat);
if (ComputerUtilCombat.lifeInSeriousDanger(ai, combat)) {
return true;
}
return false;
} // only lands involved
else if (CardLists.getNotType(opplist, "Land").isEmpty() && CardLists.getNotType(ailist, "Land").isEmpty()) {
if (ai.isCardInPlay("Crucible of Worlds") && !opponent.isCardInPlay("Crucible of Worlds") && !opplist.isEmpty()) {
return true;
}
// evaluate the situation with creatures on the battlefield separately, as that's where the AI typically makes mistakes
CardCollection aiCreatures = ai.getCreaturesInPlay();
CardCollection oppCreatures = opponent.getCreaturesInPlay();
if (!oppCreatures.isEmpty()) {
if (ComputerUtilCard.evaluateCreatureList(aiCreatures) < ComputerUtilCard.evaluateCreatureList(oppCreatures) + CREATURE_EVAL_THRESHOLD) {
if (sa.usesTargeting()) {
sa.resetTargets();
if (sa.canTarget(opponent)) {
sa.getTargets().add(opponent);
ailist.clear();
} else {
return false;
}
}
// check if the AI would lose more lands than the opponent would
if (ComputerUtilCard.evaluatePermanentList(ailist) > ComputerUtilCard.evaluatePermanentList(opplist) + 1) {
// Special handling for Raiding Party
if (logic.equals("RaidingParty")) {
int numAiCanSave = Math.min(CardLists.filter(ai.getCreaturesInPlay(), Predicates.and(CardPredicates.isColor(MagicColor.WHITE), CardPredicates.Presets.UNTAPPED)).size() * 2, ailist.size());
int numOppsCanSave = Math.min(CardLists.filter(ai.getOpponents().getCreaturesInPlay(), Predicates.and(CardPredicates.isColor(MagicColor.WHITE), CardPredicates.Presets.UNTAPPED)).size() * 2, opplist.size());
return numOppsCanSave < opplist.size() && (ailist.size() - numAiCanSave < opplist.size() - numOppsCanSave);
}
// If effect is destroying creatures and AI is about to lose, activate effect anyway no matter what!
if ((!CardLists.getType(opplist, "Creature").isEmpty()) && (ai.getGame().getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS))
&& (ai.getGame().getCombat() != null && ComputerUtilCombat.lifeInSeriousDanger(ai, ai.getGame().getCombat()))) {
return true;
}
// If effect is destroying creatures and AI is about to get low on life, activate effect anyway if difference in lost permanents not very much
if ((!CardLists.getType(opplist, "Creature").isEmpty()) && (ai.getGame().getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS))
&& (ai.getGame().getCombat() != null && ComputerUtilCombat.lifeInDanger(ai, ai.getGame().getCombat()))
&& ((ComputerUtilCard.evaluatePermanentList(ailist) - 6) >= ComputerUtilCard.evaluatePermanentList(opplist))) {
return true;
}
// if only creatures are affected evaluate both lists and pass only if human creatures are more valuable
if (CardLists.getNotType(opplist, "Creature").isEmpty() && CardLists.getNotType(ailist, "Creature").isEmpty()) {
if (ComputerUtilCard.evaluateCreatureList(ailist) + CREATURE_EVAL_THRESHOLD < ComputerUtilCard.evaluateCreatureList(opplist)) {
return true;
}
if (ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2)) {
return false;
}
// test whether the human can kill the ai next turn
Combat combat = new Combat(opponent);
boolean containsAttacker = false;
for (Card att : opponent.getCreaturesInPlay()) {
if (ComputerUtilCombat.canAttackNextTurn(att, ai)) {
combat.addAttacker(att, ai);
containsAttacker = containsAttacker | opplist.contains(att);
}
}
if (!containsAttacker) {
return false;
}
AiBlockController block = new AiBlockController(ai);
block.assignBlockersForCombat(combat);
if (ComputerUtilCombat.lifeInSeriousDanger(ai, combat)) {
return true;
}
return false;
} // only lands involved
else if (CardLists.getNotType(opplist, "Land").isEmpty() && CardLists.getNotType(ailist, "Land").isEmpty()) {
if (ai.isCardInPlay("Crucible of Worlds") && !opponent.isCardInPlay("Crucible of Worlds")) {
return true;
}
// evaluate the situation with creatures on the battlefield separately, as that's where the AI typically makes mistakes
CardCollection aiCreatures = ai.getCreaturesInPlay();
CardCollection oppCreatures = opponent.getCreaturesInPlay();
if (!oppCreatures.isEmpty()) {
if (ComputerUtilCard.evaluateCreatureList(aiCreatures) < ComputerUtilCard.evaluateCreatureList(oppCreatures) + CREATURE_EVAL_THRESHOLD) {
return false;
}
}
// check if the AI would lose more lands than the opponent would
if (ComputerUtilCard.evaluatePermanentList(ailist) > ComputerUtilCard.evaluatePermanentList(opplist) + 1) {
return false;
}
} // otherwise evaluate both lists by CMC and pass only if human permanents are more valuable
else if ((ComputerUtilCard.evaluatePermanentList(ailist) + 3) >= ComputerUtilCard.evaluatePermanentList(opplist)) {
return false;
}
} // otherwise evaluate both lists by CMC and pass only if human permanents are more valuable
else if ((ComputerUtilCard.evaluatePermanentList(ailist) + 3) >= ComputerUtilCard.evaluatePermanentList(opplist)) {
return false;
return true;
}
return true;
return false;
}
}
}

View File

@@ -5,6 +5,7 @@ import java.util.Map;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import forge.ai.AiAttackController;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilAbility;
import forge.ai.ComputerUtilCard;
@@ -32,7 +33,7 @@ public class DigAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
final Game game = ai.getGame();
Player opp = ai.getWeakestOpponent();
Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
final Card host = sa.getHostCard();
Player libraryOwner = ai;
@@ -44,9 +45,8 @@ public class DigAi extends SpellAbilityAi {
sa.resetTargets();
if (!opp.canBeTargetedBy(sa)) {
return false;
} else {
sa.getTargets().add(opp);
}
sa.getTargets().add(opp);
libraryOwner = opp;
}
@@ -120,7 +120,7 @@ public class DigAi extends SpellAbilityAi {
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
final SpellAbility root = sa.getRootAbility();
final Player opp = ai.getWeakestOpponent();
final Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
if (sa.usesTargeting()) {
sa.resetTargets();
if (mandatory && sa.canTarget(opp)) {

View File

@@ -1,5 +1,6 @@
package forge.ai.ability;
import forge.ai.AiAttackController;
import forge.ai.ComputerUtil;
import forge.ai.SpellAbilityAi;
import forge.game.Game;
@@ -19,7 +20,7 @@ public class DigMultipleAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
final Game game = ai.getGame();
Player opp = ai.getWeakestOpponent();
Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
final Card host = sa.getHostCard();
Player libraryOwner = ai;
@@ -27,9 +28,8 @@ public class DigMultipleAi extends SpellAbilityAi {
sa.resetTargets();
if (!opp.canBeTargetedBy(sa)) {
return false;
} else {
sa.getTargets().add(opp);
}
sa.getTargets().add(opp);
libraryOwner = opp;
}
@@ -77,7 +77,7 @@ public class DigMultipleAi extends SpellAbilityAi {
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
final Player opp = ai.getWeakestOpponent();
final Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
if (sa.usesTargeting()) {
sa.resetTargets();
if (mandatory && sa.canTarget(opp)) {

View File

@@ -2,6 +2,7 @@ package forge.ai.ability;
import java.util.List;
import forge.ai.AiAttackController;
import forge.ai.ComputerUtilCost;
import forge.ai.SpellAbilityAi;
import forge.game.card.Card;
@@ -31,10 +32,8 @@ public class DigUntilAi extends SpellAbilityAi {
chance = 1;
}
final boolean randomReturn = MyRandom.getRandom().nextFloat() <= Math.pow(chance, sa.getActivationsThisTurn() + 1);
Player libraryOwner = ai;
Player opp = ai.getWeakestOpponent();
Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
if ("DontMillSelf".equals(logic)) {
// A card that digs for specific things and puts everything revealed before it into graveyard
@@ -61,9 +60,8 @@ public class DigUntilAi extends SpellAbilityAi {
sa.resetTargets();
if (!sa.canTarget(opp)) {
return false;
} else {
sa.getTargets().add(opp);
}
sa.getTargets().add(opp);
libraryOwner = opp;
} else {
if (sa.hasParam("Valid")) {
@@ -92,12 +90,12 @@ public class DigUntilAi extends SpellAbilityAi {
return false;
}
final boolean randomReturn = MyRandom.getRandom().nextFloat() <= Math.pow(chance, sa.getActivationsThisTurn() + 1);
return randomReturn;
}
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
if (sa.usesTargeting()) {
sa.resetTargets();
if (sa.isCurse()) {

View File

@@ -2,6 +2,7 @@ package forge.ai.ability;
import java.util.List;
import forge.ai.AiAttackController;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilAbility;
import forge.ai.ComputerUtilCost;
@@ -60,11 +61,9 @@ public class DiscardAi extends SpellAbilityAi {
if (players.get(0) == ai) {
// the ai should only be using something like this if he has
// few cards in hand,
// cards like this better have a good drawback to be in the
// AIs deck
// cards like this better have a good drawback to be in the AIs deck
} else {
// defined to the human, so that's fine as long the human
// has cards
// defined to the human, so that's fine as long the human has cards
if (!humanHasHand) {
return false;
}
@@ -170,7 +169,7 @@ public class DiscardAi extends SpellAbilityAi {
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null) {
Player opp = ai.getWeakestOpponent();
Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
if (!discardTargetAI(ai, sa)) {
if (mandatory && sa.canTarget(opp)) {
sa.getTargets().add(opp);

View File

@@ -23,8 +23,7 @@ public class DrainManaAi extends SpellAbilityAi {
if (tgt == null) {
// assume we are looking to tap human's stuff
// TODO - check for things with untap abilities, and don't tap
// those.
// TODO - check for things with untap abilities, and don't tap those.
final List<Player> defined = AbilityUtils.getDefinedPlayers(source, sa.getParam("Defined"), sa);
if (!defined.contains(opp)) {

View File

@@ -257,7 +257,7 @@ public class DrawAi extends SpellAbilityAi {
} else {
numCards = ComputerUtilCost.getMaxXValue(sa, ai);
// try not to overdraw
int safeDraw = Math.min(computerMaxHandSize - computerHandSize, computerLibrarySize - 3);
int safeDraw = Math.abs(Math.min(computerMaxHandSize - computerHandSize, computerLibrarySize - 3));
if (sa.getHostCard().isInstant() || sa.getHostCard().isSorcery()) { safeDraw++; } // card will be spent
numCards = Math.min(numCards, safeDraw);
@@ -377,7 +377,7 @@ public class DrawAi extends SpellAbilityAi {
// checks what the ai prevent from casting it on itself
// if spell is not mandatory
if (aiTarget && !ai.cantLose()) {
if (numCards >= computerLibrarySize) {
if (numCards >= computerLibrarySize - 3) {
if (xPaid) {
numCards = computerLibrarySize - 1;
if (numCards <= 0 && !mandatory) {
@@ -422,8 +422,7 @@ public class DrawAi extends SpellAbilityAi {
}
root.setXManaCostPaid(numCards);
} else {
// Don't draw too many cards and then risk discarding
// cards at EOT
// Don't draw too many cards and then risk discarding cards at EOT
if (!drawback && !mandatory) {
return false;
}
@@ -441,7 +440,7 @@ public class DrawAi extends SpellAbilityAi {
continue;
}
// use xPaid abilties only for itself
// use xPaid abilities only for itself
if (xPaid) {
continue;
}
@@ -493,7 +492,7 @@ public class DrawAi extends SpellAbilityAi {
// TODO: consider if human is the defined player
// ability is not targeted
if (numCards >= computerLibrarySize) {
if (numCards >= computerLibrarySize - 3) {
if (ai.isCardInPlay("Laboratory Maniac")) {
return true;
}
@@ -509,8 +508,7 @@ public class DrawAi extends SpellAbilityAi {
&& game.getPhaseHandler().isPlayerTurn(ai)
&& !sa.isTrigger()
&& !assumeSafeX) {
// Don't draw too many cards and then risk discarding cards at
// EOT
// Don't draw too many cards and then risk discarding cards at EOT
if (!drawback) {
return false;
}

View File

@@ -79,7 +79,7 @@ public class EffectAi extends SpellAbilityAi {
if (!game.getStack().isEmpty()) {
return false;
}
if (game.getPhaseHandler().isPreventCombatDamageThisTurn()) {
if (game.getReplacementHandler().isPreventCombatDamageThisTurn()) {
return false;
}
if (!ComputerUtilCombat.lifeInDanger(ai, game.getCombat())) {

View File

@@ -19,6 +19,7 @@ import forge.game.keyword.Keyword;
import forge.game.player.Player;
import forge.game.spellability.AbilitySub;
import forge.game.spellability.SpellAbility;
import forge.game.staticability.StaticAbilityMustTarget;
import forge.game.trigger.Trigger;
import forge.game.trigger.TriggerType;
import forge.util.MyRandom;
@@ -50,6 +51,9 @@ public class FightAi extends SpellAbilityAi {
aiCreatures = ComputerUtil.getSafeTargets(ai, sa, aiCreatures);
List<Card> humCreatures = ai.getOpponents().getCreaturesInPlay();
humCreatures = CardLists.getTargetableCards(humCreatures, sa);
// Filter MustTarget requirements
StaticAbilityMustTarget.filterMustTargetCards(ai, humCreatures, sa);
if (humCreatures.isEmpty())
return false; //prevent IndexOutOfBoundsException on MOJHOSTO variant

View File

@@ -16,14 +16,32 @@ public class FlipACoinAi extends SpellAbilityAi {
protected boolean canPlayAI(Player ai, SpellAbility sa) {
if (sa.hasParam("AILogic")) {
String AILogic = sa.getParam("AILogic");
if (AILogic.equals("Never")) {
String ailogic = sa.getParam("AILogic");
if (ailogic.equals("Never")) {
return false;
} else if (AILogic.equals("PhaseOut")) {
} else if (ailogic.equals("PhaseOut")) {
if (!ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), sa).contains(sa.getHostCard())) {
return false;
}
} else if (AILogic.equals("KillOrcs")) {
} else if (ailogic.equals("Bangchuckers")) {
if (ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.END_OF_TURN) ) {
return false;
}
sa.resetTargets();
for (Player o : ai.getOpponents()) {
if (sa.canTarget(o) && o.canLoseLife() && !o.cantLose()) {
sa.getTargets().add(o);
return true;
}
}
for (Card c : ai.getOpponents().getCreaturesInPlay()) {
if (sa.canTarget(c)) {
sa.getTargets().add(c);
return true;
}
}
return false;
} else if (ailogic.equals("KillOrcs")) {
if (ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.END_OF_TURN) ) {
return false;
}

View File

@@ -29,7 +29,7 @@ public class FogAi extends SpellAbilityAi {
final Card hostCard = sa.getHostCard();
// Don't cast it, if the effect is already in place
if (game.getPhaseHandler().isPreventCombatDamageThisTurn()) {
if (game.getReplacementHandler().isPreventCombatDamageThisTurn()) {
return false;
}

View File

@@ -13,8 +13,7 @@ public class GameLossAi extends SpellAbilityAi {
return false;
}
// Only one SA Lose the Game card right now, which is Door to
// Nothingness
// Only one SA Lose the Game card right now, which is Door to Nothingness
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null) {
@@ -29,20 +28,23 @@ public class GameLossAi extends SpellAbilityAi {
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
Player loser = ai;
// Phage the Untouchable
// (Final Fortune would need to attach it's delayed trigger to a
// specific turn, which can't be done yet)
Player opp = ai.getWeakestOpponent();
if (ai.getGame().getCombat() != null) {
loser = ai.getGame().getCombat().getDefenderPlayerByAttacker(sa.getHostCard());
}
if (!mandatory && opp.cantLose()) {
if (!mandatory && loser.cantLose()) {
return false;
}
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null) {
sa.resetTargets();
sa.getTargets().add(opp);
sa.getTargets().add(loser);
}
return true;

View File

@@ -0,0 +1,56 @@
package forge.ai.ability;
import forge.ai.ComputerUtilCard;
import forge.ai.PlayerControllerAi;
import forge.ai.SpellAbilityAi;
import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates;
import forge.game.player.Player;
import forge.game.player.PlayerActionConfirmMode;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
public class LearnAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
// For the time being, Learn is treated as universally positive due to being optional
return true;
}
@Override
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
return mandatory || canPlayAI(aiPlayer, sa);
}
@Override
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
return canPlayAI(aiPlayer, sa);
}
@Override
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
return true;
}
public static Card chooseCardToLearn(CardCollection options, Player ai, SpellAbility sa) {
CardCollection sideboard = CardLists.filter(options, CardPredicates.inZone(ZoneType.Sideboard));
CardCollection hand = CardLists.filter(options, CardPredicates.inZone(ZoneType.Hand));
hand.remove(sa.getHostCard()); // this card will be used in the process, don't consider it for discard
CardCollection lessons = CardLists.filter(sideboard, CardPredicates.isType("Lesson"));
CardCollection goodDiscards = ((PlayerControllerAi)ai.getController()).getAi().getCardsToDiscard(1, 1, hand, sa);
if (!lessons.isEmpty()) {
return ComputerUtilCard.getBestAI(lessons);
} else if (!goodDiscards.isEmpty()) {
return ComputerUtilCard.getWorstAI(goodDiscards);
}
// Don't choose anything if there's no good option
return null;
}
}

View File

@@ -1,5 +1,6 @@
package forge.ai.ability;
import forge.ai.AiAttackController;
import forge.ai.SpellAbilityAi;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
@@ -19,7 +20,7 @@ public class LifeExchangeAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
final int myLife = aiPlayer.getLife();
Player opponent = aiPlayer.getWeakestOpponent();
Player opponent = AiAttackController.choosePreferredDefenderPlayer(aiPlayer);
final int hLife = opponent.getLife();
if (!aiPlayer.canGainLife()) {
@@ -75,7 +76,7 @@ public class LifeExchangeAi extends SpellAbilityAi {
final boolean mandatory) {
final TargetRestrictions tgt = sa.getTargetRestrictions();
Player opp = ai.getWeakestOpponent();
Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
if (tgt != null) {
sa.resetTargets();
if (sa.canTarget(opp) && (mandatory || ai.getLife() < opp.getLife())) {

View File

@@ -1,5 +1,6 @@
package forge.ai.ability;
import forge.ai.AiAttackController;
import forge.ai.AiProps;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilAbility;
@@ -154,7 +155,7 @@ public class LifeExchangeVariantAi extends SpellAbilityAi {
final boolean mandatory) {
final TargetRestrictions tgt = sa.getTargetRestrictions();
Player opp = ai.getWeakestOpponent();
Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
if (tgt != null) {
sa.resetTargets();
if (sa.canTarget(opp) && (mandatory || ai.getLife() < opp.getLife())) {

View File

@@ -266,8 +266,7 @@ public class LifeGainAi extends SpellAbilityAi {
}
if (!hasTgt && mandatory) {
// need to target something but its neither negative against
// opponents,
// nor posive against allies
// opponents, nor positive against allies
// hurting ally is probably better than healing opponent
// look for Lifegain not Negative (case of lifegain negated)
@@ -295,8 +294,7 @@ public class LifeGainAi extends SpellAbilityAi {
sa.getTargets().add(ally);
hasTgt = true;
}
// better heal opponent which most life then the one with the
// lowest
// better heal opponent which most life then the one with the lowest
if (!hasTgt) {
Player opp = opps.max(PlayerPredicates.compareByLife());
sa.getTargets().add(opp);

View File

@@ -28,7 +28,6 @@ public class LifeLoseAi extends SpellAbilityAi {
*/
@Override
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
final PlayerCollection tgtPlayers = getPlayers(ai, sa);
final Card source = sa.getHostCard();
@@ -60,7 +59,6 @@ public class LifeLoseAi extends SpellAbilityAi {
return true;
}
/*
* (non-Javadoc)
*
@@ -123,12 +121,12 @@ public class LifeLoseAi extends SpellAbilityAi {
return false;
}
final PlayerCollection tgtPlayers = getPlayers(ai, sa);
if (ComputerUtil.playImmediately(ai, sa)) {
return true;
}
final PlayerCollection tgtPlayers = getPlayers(ai, sa);
// TODO: check against the amount we could obtain when multiple activations are possible
PlayerCollection filteredPlayer = tgtPlayers
.filter(Predicates.and(PlayerPredicates.isOpponentOf(ai), PlayerPredicates.lifeLessOrEqualTo(amount)));
// killing opponents asap

View File

@@ -17,7 +17,7 @@ public class LifeSetAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
final int myLife = ai.getLife();
final Player opponent = ai.getWeakestOpponent();
final Player opponent = ai.getStrongestOpponent();
final int hlife = opponent.getLife();
final String amountStr = sa.getParam("LifeAmount");
@@ -36,8 +36,7 @@ public class LifeSetAi extends SpellAbilityAi {
return false;
}
// TODO handle proper calculation of X values based on Cost and what
// would be paid
// TODO handle proper calculation of X values based on Cost and what would be paid
int amount;
// we shouldn't have to worry too much about PayX for SetLife
if (amountStr.equals("X") && sa.getSVar(amountStr).equals("Count$xPaid")) {
@@ -58,11 +57,9 @@ public class LifeSetAi extends SpellAbilityAi {
if (tgt.canOnlyTgtOpponent()) {
sa.getTargets().add(opponent);
// if we can only target the human, and the Human's life
// would
// go up, don't play it.
// would go up, don't play it.
// possibly add a combo here for Magister Sphinx and
// Higedetsu's
// (sp?) Second Rite
// Higedetsu's (sp?) Second Rite
if ((amount > hlife) || !opponent.canLoseLife()) {
return false;
}
@@ -81,8 +78,7 @@ public class LifeSetAi extends SpellAbilityAi {
if (sa.getParam("Defined").equals("Player")) {
if (amount == 0) {
return false;
} else if (myLife > amount) { // will decrease computer's
// life
} else if (myLife > amount) { // will decrease computer's life
if ((myLife < 5) || ((myLife - amount) > (hlife - amount))) {
return false;
}
@@ -104,7 +100,7 @@ public class LifeSetAi extends SpellAbilityAi {
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
final int myLife = ai.getLife();
final Player opponent = ai.getWeakestOpponent();
final Player opponent = ai.getStrongestOpponent();
final int hlife = opponent.getLife();
final Card source = sa.getHostCard();
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
@@ -133,8 +129,7 @@ public class LifeSetAi extends SpellAbilityAi {
}
// If the Target is gaining life, target self.
// if the Target is modifying how much life is gained, this needs to
// be handled better
// if the Target is modifying how much life is gained, this needs to be handled better
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null) {
sa.resetTargets();

View File

@@ -21,6 +21,7 @@ import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.player.PlayerActionConfirmMode;
import forge.game.player.PlayerPredicates;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
@@ -214,13 +215,7 @@ public class MillAi extends SpellAbilityAi {
}
// get targeted or defined Player with largest library
// TODO in Java 8 find better way
final Player m = Collections.max(list, new Comparator<Player>() {
@Override
public int compare(Player arg0, Player arg1) {
return arg0.getCardsIn(ZoneType.Library).size() - arg1.getCardsIn(ZoneType.Library).size();
}
});
final Player m = Collections.max(list, PlayerPredicates.compareByZoneSize(ZoneType.Library));
int cardsToDiscard = m.getCardsIn(ZoneType.Library).size();

View File

@@ -297,7 +297,7 @@ public class PermanentAi extends SpellAbilityAi {
if (!checkPhaseRestrictions(ai, sa, ai.getGame().getPhaseHandler())) {
return false;
}
return checkApiLogic(ai, sa);
return checkApiLogic(ai, sa) || mandatory;
}
}

View File

@@ -8,7 +8,6 @@ import forge.game.card.CardCollection;
import forge.game.card.CardLists;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
/**
@@ -40,10 +39,8 @@ public class PermanentNoncreatureAi extends PermanentAi {
if (host.hasSVar("OblivionRing")) {
SpellAbility effectExile = AbilityFactory.getAbility(host.getSVar("TrigExile"), host);
final ZoneType origin = ZoneType.listValueOf(effectExile.getParam("Origin")).get(0);
final TargetRestrictions tgt = effectExile.getTargetRestrictions();
final CardCollection list = CardLists.getValidCards(game.getCardsIn(origin), tgt.getValidTgts(), ai, host,
effectExile);
CardCollection targets = CardLists.getTargetableCards(list, sa);
effectExile.setActivatingPlayer(ai);
CardCollection targets = CardLists.getTargetableCards(game.getCardsIn(origin), effectExile);
if (sourceName.equals("Suspension Field")
|| sourceName.equals("Detention Sphere")) {
// existing "exile until leaves" enchantments only target

View File

@@ -28,7 +28,6 @@ import forge.game.card.CardPredicates;
import forge.game.cost.Cost;
import forge.game.keyword.Keyword;
import forge.game.player.Player;
import forge.game.player.PlayerActionConfirmMode;
import forge.game.spellability.Spell;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.SpellAbilityPredicates;
@@ -158,16 +157,7 @@ public class PlayAi extends SpellAbilityAi {
return true;
}
/* (non-Javadoc)
* @see forge.card.ability.SpellAbilityAi#confirmAction(forge.game.player.Player, forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String)
*/
@Override
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
// as called from PlayEffect:173
return true;
}
/* (non-Javadoc)
* @see forge.card.ability.SpellAbilityAi#chooseSingleCard(forge.game.player.Player, forge.card.spellability.SpellAbility, java.util.List, boolean)
*/
@@ -202,7 +192,7 @@ public class PlayAi extends SpellAbilityAi {
spell = (Spell) spell.copyWithDefinedCost(abCost);
}
if( AiPlayDecision.WillPlay == ((PlayerControllerAi)ai.getController()).getAi().canPlayFromEffectAI(spell, !isOptional, true)) {
if (AiPlayDecision.WillPlay == ((PlayerControllerAi)ai.getController()).getAi().canPlayFromEffectAI(spell, !isOptional, true)) {
// Before accepting, see if the spell has a valid number of targets (it should at this point).
// Proceeding past this point if the spell is not correctly targeted will result
// in "Failed to add to stack" error and the card disappearing from the game completely.

View File

@@ -30,13 +30,12 @@ public class PowerExchangeAi extends SpellAbilityAi {
sa.resetTargets();
List<Card> list =
CardLists.getValidCards(ai.getWeakestOpponent().getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), ai, sa.getHostCard(), sa);
// AI won't try to grab cards that are filtered out of AI decks on
// purpose
CardLists.getValidCards(ai.getGame().getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), ai, sa.getHostCard(), sa);
// AI won't try to grab cards that are filtered out of AI decks on purpose
list = CardLists.filter(list, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return !ComputerUtilCard.isCardRemAIDeck(c) && c.canBeTargetedBy(sa);
return !ComputerUtilCard.isCardRemAIDeck(c) && c.canBeTargetedBy(sa) && c.getController() != ai;
}
});
CardLists.sortByPowerAsc(list);

View File

@@ -400,7 +400,6 @@ public class PumpAi extends PumpAiBase {
if (ComputerUtilCard.shouldPumpCard(ai, sa, card, defense, attack, keywords, false)) {
return true;
} else if (containsUsefulKeyword(ai, keywords, card, sa, attack)) {
Card pumped = ComputerUtilCard.getPumpedCreature(ai, sa, card, 0, 0, keywords);
if (game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_ATTACKERS, ai)
|| game.getPhaseHandler().is(PhaseType.COMBAT_BEGIN, ai)) {
@@ -438,7 +437,7 @@ public class PumpAi extends PumpAiBase {
&& game.getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS)
&& !(sa.isCurse() && defense < 0)
&& !containsNonCombatKeyword(keywords)
&& !sa.hasParam("UntilYourNextTurn")
&& !"UntilYourNextTurn".equals(sa.getParam("Duration"))
&& !"Snapcaster".equals(sa.getParam("AILogic"))
&& !isFight) {
return false;

View File

@@ -6,6 +6,7 @@ import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.Iterables;
import forge.ai.AiAttackController;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCard;
import forge.ai.ComputerUtilCombat;
@@ -43,7 +44,6 @@ public abstract class PumpAiBase extends SpellAbilityAi {
return false;
}
public boolean grantsUsefulExtraBlockOpts(final Player ai, final SpellAbility sa, final Card card, List<String> keywords) {
PhaseHandler ph = ai.getGame().getPhaseHandler();
Card pumped = ComputerUtilCard.getPumpedCreature(ai, sa, card, 0, 0, keywords);
@@ -109,7 +109,7 @@ public abstract class PumpAiBase extends SpellAbilityAi {
&& (card.getNetCombatDamage() > 0)
&& !ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS);
} else if (keyword.endsWith("CARDNAME can't attack or block.")) {
if (sa.hasParam("UntilYourNextTurn")) {
if ("UntilYourNextTurn".equals(sa.getParam("Duration"))) {
return CombatUtil.canAttack(card, ai) || CombatUtil.canBlock(card, true);
}
if (!ph.isPlayerTurn(ai)) {
@@ -153,14 +153,6 @@ public abstract class PumpAiBase extends SpellAbilityAi {
}
});
return CombatUtil.canBlockAtLeastOne(card, attackers);
} else if (keyword.endsWith("CantBlockCardUIDSource")) { // can't block CARDNAME this turn
if (!ph.isPlayerTurn(ai) || ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS)
|| ph.getPhase().isBefore(PhaseType.MAIN1) || !CombatUtil.canBlock(sa.getHostCard(), card)) {
return false;
}
// target needs to be a creature, controlled by the player which is attacked
return !sa.getHostCard().isTapped() || (combat != null && combat.isAttacking(sa.getHostCard())
&& card.getController().equals(combat.getDefenderPlayerByAttacker(sa.getHostCard())));
} else if (keyword.endsWith("This card doesn't untap during your next untap step.")) {
return !ph.getPhase().isBefore(PhaseType.MAIN2) && !card.isUntapped() && ph.isPlayerTurn(ai)
&& Untap.canUntap(card);
@@ -201,7 +193,7 @@ public abstract class PumpAiBase extends SpellAbilityAi {
final Game game = ai.getGame();
final Combat combat = game.getCombat();
final PhaseHandler ph = game.getPhaseHandler();
final Player opp = ai.getWeakestOpponent();
final Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
final int newPower = card.getNetCombatDamage() + attack;
//int defense = getNumDefense(sa);
if (!CardUtil.isStackingKeyword(keyword) && card.hasKeyword(keyword)) {
@@ -479,7 +471,7 @@ public abstract class PumpAiBase extends SpellAbilityAi {
}
}); // leaves all creatures that will be destroyed
} // -X/-X end
else if (attack < 0 && !game.getPhaseHandler().isPreventCombatDamageThisTurn()) {
else if (attack < 0 && !game.getReplacementHandler().isPreventCombatDamageThisTurn()) {
// spells that give -X/0
boolean isMyTurn = game.getPhaseHandler().isPlayerTurn(ai);
if (isMyTurn) {
@@ -513,7 +505,6 @@ public abstract class PumpAiBase extends SpellAbilityAi {
else {
final boolean addsKeywords = !keywords.isEmpty();
if (addsKeywords) {
// If the keyword can prevent a creature from attacking, see if there's some kind of viable prioritization
if (keywords.contains("CARDNAME can't attack.") || keywords.contains("CARDNAME can't attack or block.")
|| keywords.contains("HIDDEN CARDNAME can't attack.") || keywords.contains("HIDDEN CARDNAME can't attack or block.")) {
@@ -539,8 +530,7 @@ public abstract class PumpAiBase extends SpellAbilityAi {
protected boolean containsNonCombatKeyword(final List<String> keywords) {
for (final String keyword : keywords) {
// since most keywords are combat relevant check for those that are
// not
// since most keywords are combat relevant check for those that are not
if (keyword.endsWith("This card doesn't untap during your next untap step.")
|| keyword.endsWith("Shroud") || keyword.endsWith("Hexproof")) {
return true;

View File

@@ -48,12 +48,6 @@ public class PumpAllAi extends PumpAiBase {
}
}
final int power = AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("NumAtt"), sa);
final int defense = AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("NumDef"), sa);
final List<String> keywords = sa.hasParam("KW") ? Arrays.asList(sa.getParam("KW").split(" & ")) : new ArrayList<>();
final PhaseType phase = game.getPhaseHandler().getPhase();
if (ComputerUtil.preventRunAwayActivations(sa)) {
return false;
}
@@ -63,16 +57,10 @@ public class PumpAllAi extends PumpAiBase {
return false;
}
}
if (sa.hasParam("ValidCards")) {
valid = sa.getParam("ValidCards");
}
final Player opp = ai.getWeakestOpponent();
CardCollection comp = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), valid, source.getController(), source, sa);
CardCollection human = CardLists.getValidCards(opp.getCardsIn(ZoneType.Battlefield), valid, source.getController(), source, sa);
final TargetRestrictions tgt = sa.getTargetRestrictions();
final Player opp = ai.getStrongestOpponent();
if (tgt != null && sa.canTarget(opp) && sa.hasParam("IsCurse")) {
sa.resetTargets();
sa.getTargets().add(opp);
@@ -85,6 +73,18 @@ public class PumpAllAi extends PumpAiBase {
return true;
}
final int power = AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("NumAtt"), sa);
final int defense = AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("NumDef"), sa);
final List<String> keywords = sa.hasParam("KW") ? Arrays.asList(sa.getParam("KW").split(" & ")) : new ArrayList<>();
final PhaseType phase = game.getPhaseHandler().getPhase();
if (sa.hasParam("ValidCards")) {
valid = sa.getParam("ValidCards");
}
CardCollection comp = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), valid, source.getController(), source, sa);
CardCollection human = CardLists.getValidCards(opp.getCardsIn(ZoneType.Battlefield), valid, source.getController(), source, sa);
if (!game.getStack().isEmpty() && !sa.isCurse()) {
return pumpAgainstRemoval(ai, sa, comp);
}
@@ -113,7 +113,7 @@ public class PumpAllAi extends PumpAiBase {
if (phase.isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS)
|| phase.isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)
|| game.getPhaseHandler().isPlayerTurn(sa.getActivatingPlayer())
|| game.getPhaseHandler().isPreventCombatDamageThisTurn()) {
|| game.getReplacementHandler().isPreventCombatDamageThisTurn()) {
return false;
}
int totalPower = 0;
@@ -139,8 +139,7 @@ public class PumpAllAi extends PumpAiBase {
return true;
}
// evaluate both lists and pass only if human creatures are more
// valuable
// evaluate both lists and pass only if human creatures are more valuable
return (ComputerUtilCard.evaluateCreatureList(comp) + 200) < ComputerUtilCard.evaluateCreatureList(human);
} // end Curse
@@ -152,6 +151,12 @@ public class PumpAllAi extends PumpAiBase {
return true;
}
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
// important to call canPlay first so targets are added if needed
return canPlayAI(ai, sa) || mandatory;
}
boolean pumpAgainstRemoval(Player ai, SpellAbility sa, List<Card> comp) {
final List<GameObject> objects = ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), sa, true);
for (final Card c : comp) {

View File

@@ -49,14 +49,6 @@ import forge.game.zone.ZoneType;
*/
public class RegenerateAi extends SpellAbilityAi {
// Ex: A:SP$Regenerate | Cost$W | ValidTgts$ Creature | TgtPrompt$ Select target creature | SpellDescription$Regenerate
// target creature.
// http://www.slightlymagic.net/wiki/Forge_AbilityFactory#Regenerate
// **************************************************************
// ********************* Regenerate ****************************
// **************************************************************
@Override
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
final Game game = ai.getGame();
@@ -65,8 +57,7 @@ public class RegenerateAi extends SpellAbilityAi {
boolean chance = false;
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt == null) {
// As far as I can tell these Defined Cards will only have one of
// them
// As far as I can tell these Defined Cards will only have one of them
final List<Card> list = AbilityUtils.getDefinedCards(hostCard, sa.getParam("Defined"), sa);
if (!game.getStack().isEmpty()) {
@@ -105,8 +96,7 @@ public class RegenerateAi extends SpellAbilityAi {
}
if (!game.getStack().isEmpty()) {
// check stack for something on the stack will kill anything i
// control
// check stack for something on the stack will kill anything i control
final List<GameObject> objects = ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), sa, true);
final List<Card> threatenedTargets = new ArrayList<>();
@@ -191,8 +181,7 @@ public class RegenerateAi extends SpellAbilityAi {
}
}
// TODO see if something on the stack is about to kill something i
// can target
// TODO see if something on the stack is about to kill something i can target
// choose my best X without regen
if (CardLists.getNotType(compTargetables, "Creature").isEmpty()) {

View File

@@ -1,6 +1,7 @@
package forge.ai.ability;
import forge.ai.AiAttackController;
import forge.ai.AiController;
import forge.ai.ComputerUtilCost;
import forge.ai.PlayerControllerAi;
@@ -14,10 +15,10 @@ public class RepeatAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
final Player opp = ai.getWeakestOpponent();
final Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
if (sa.usesTargeting()) {
if (!opp.canBeTargetedBy(sa)) {
if (!sa.canTarget(opp)) {
return false;
}
sa.resetTargets();
@@ -44,9 +45,8 @@ public class RepeatAi extends SpellAbilityAi {
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
if (sa.usesTargeting()) {
final Player opp = ai.getWeakestOpponent();
final Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
if (sa.canTarget(opp)) {
sa.resetTargets();
sa.getTargets().add(opp);

View File

@@ -16,7 +16,6 @@ import forge.game.card.CardPredicates.Presets;
import forge.game.card.CardUtil;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.AbilitySub;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
import forge.util.TextUtil;
@@ -67,7 +66,7 @@ public class RepeatEachAi extends SpellAbilityAi {
return false;
} else if ("AllPlayerLoseLife".equals(logic)) {
final Card source = sa.getHostCard();
AbilitySub repeat = sa.getAdditionalAbility("RepeatSubAbility");
SpellAbility repeat = sa.getAdditionalAbility("RepeatSubAbility");
String svar = repeat.getSVar(repeat.getParam("LifeAmount"));
// replace RememberedPlayerCtrl with YouCtrl

View File

@@ -20,9 +20,6 @@ import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
public class SacrificeAi extends SpellAbilityAi {
// **************************************************************
// *************************** Sacrifice ***********************
// **************************************************************
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
@@ -48,8 +45,7 @@ public class SacrificeAi extends SpellAbilityAi {
}
// Improve AI for triggers. If source is a creature with:
// When ETB, sacrifice a creature. Check to see if the AI has something
// to sacrifice
// When ETB, sacrifice a creature. Check to see if the AI has something to sacrifice
// Eventually, we can call the trigger of ETB abilities with not
// mandatory as part of the checks to cast something
@@ -58,12 +54,11 @@ public class SacrificeAi extends SpellAbilityAi {
}
private boolean sacrificeTgtAI(final Player ai, final SpellAbility sa) {
final Card source = sa.getHostCard();
final TargetRestrictions tgt = sa.getTargetRestrictions();
final boolean destroy = sa.hasParam("Destroy");
Player opp = ai.getWeakestOpponent();
Player opp = ai.getStrongestOpponent();
if (tgt != null) {
sa.resetTargets();
@@ -109,8 +104,7 @@ public class SacrificeAi extends SpellAbilityAi {
sa.setXManaCostPaid(Math.min(ComputerUtilCost.getMaxXValue(sa, ai), amount));
}
final int half = (amount / 2) + (amount % 2); // Half of amount
// rounded up
final int half = (amount / 2) + (amount % 2); // Half of amount rounded up
// If the Human has at least half rounded up of the amount to be
// sacrificed, cast the spell
@@ -130,8 +124,7 @@ public class SacrificeAi extends SpellAbilityAi {
// If Sacrifice hits both players:
// Only cast it if Human has the full amount of valid
// Only cast it if AI doesn't have the full amount of Valid
// TODO: Cast if the type is favorable: my "worst" valid is
// worse than his "worst" valid
// TODO: Cast if the type is favorable: my "worst" valid is worse than his "worst" valid
final String num = sa.hasParam("Amount") ? sa.getParam("Amount") : "1";
int amount = AbilityUtils.calculateAmount(source, num, sa);

View File

@@ -2,17 +2,12 @@ package forge.ai.ability;
import forge.ai.ComputerUtilCard;
import forge.ai.ComputerUtilCost;
import forge.ai.ComputerUtilMana;
import forge.ai.SpellAbilityAi;
import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardLists;
import forge.game.cost.Cost;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
import forge.util.MyRandom;
import forge.util.TextUtil;
public class SacrificeAllAi extends SpellAbilityAi {
@@ -22,22 +17,7 @@ public class SacrificeAllAi extends SpellAbilityAi {
// based on what the expected targets could be
final Cost abCost = sa.getPayCosts();
final Card source = sa.getHostCard();
String valid = "";
if (sa.hasParam("ValidCards")) {
valid = sa.getParam("ValidCards");
}
if (valid.contains("X") && sa.getSVar("X").equals("Count$xPaid")) {
// Set PayX here to maximum value.
final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
valid = TextUtil.fastReplace(valid, "X", Integer.toString(xPay));
}
CardCollection humanlist =
CardLists.getValidCards(ai.getWeakestOpponent().getCardsIn(ZoneType.Battlefield), valid.split(","), source.getController(), source, sa);
CardCollection computerlist =
CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), valid.split(","), source.getController(), source, sa);
final String logic = sa.getParamOrDefault("AILogic", "");
if (abCost != null) {
// AI currently disabled for some costs
@@ -45,30 +25,20 @@ public class SacrificeAllAi extends SpellAbilityAi {
return false;
}
}
if (logic.equals("HellionEruption")) {
if (ai.getCreaturesInPlay().size() < 5 || ai.getCreaturesInPlay().size() * 150 < ComputerUtilCard.evaluateCreatureList(ai.getCreaturesInPlay())) {
return false;
}
}
if (!DestroyAllAi.doMassRemovalLogic(ai, sa)) {
return false;
}
// prevent run-away activations - first time will always return true
boolean chance = MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
// if only creatures are affected evaluate both lists and pass only if
// human creatures are more valuable
if ((CardLists.getNotType(humanlist, "Creature").size() == 0) && (CardLists.getNotType(computerlist, "Creature").size() == 0)) {
if ((ComputerUtilCard.evaluateCreatureList(computerlist) + 200) >= ComputerUtilCard
.evaluateCreatureList(humanlist)) {
return false;
}
} // only lands involved
else if ((CardLists.getNotType(humanlist, "Land").size() == 0) && (CardLists.getNotType(computerlist, "Land").size() == 0)) {
if ((ComputerUtilCard.evaluatePermanentList(computerlist) + 1) >= ComputerUtilCard
.evaluatePermanentList(humanlist)) {
return false;
}
} // otherwise evaluate both lists by CMC and pass only if human
// permanents are more valuable
else if ((ComputerUtilCard.evaluatePermanentList(computerlist) + 3) >= ComputerUtilCard
.evaluatePermanentList(humanlist)) {
return false;
}
return ((MyRandom.getRandom().nextFloat() < .9667) && chance);
}

View File

@@ -3,14 +3,12 @@ package forge.ai.ability;
import com.google.common.base.Predicates;
import forge.ai.ComputerUtilMana;
import forge.ai.SpecialCardAi;
import forge.ai.SpellAbilityAi;
import forge.game.ability.ApiType;
import forge.game.card.Card;
import forge.game.card.Card.SplitCMCMode;
import forge.game.card.CardCollection;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates;
import forge.game.card.CounterEnumType;
import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
@@ -47,6 +45,11 @@ public class ScryAi extends SpellAbilityAi {
*/
@Override
protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, final PhaseHandler ph) {
// For Brain in a Jar, avoid competing against the other ability in the opponent's EOT.
if ("BrainJar".equals(sa.getParam("AILogic"))) {
return ph.getPhase().isAfter(PhaseType.MAIN2);
}
// if the Scry ability requires tapping and has a mana cost, it's best done at the end of opponent's turn
// and right before the beginning of AI's turn, if possible, to avoid mana locking the AI and also to
// try to scry right before drawing a card. Also, avoid tapping creatures in the AI's turn, if possible,
@@ -102,56 +105,9 @@ public class ScryAi extends SpellAbilityAi {
if ("Never".equals(aiLogic)) {
return false;
} else if ("BrainJar".equals(aiLogic)) {
final Card source = sa.getHostCard();
int counterNum = source.getCounters(CounterEnumType.CHARGE);
// no need for logic
if (counterNum == 0) {
return false;
}
int libsize = ai.getCardsIn(ZoneType.Library).size();
final CardCollection hand = CardLists.filter(ai.getCardsIn(ZoneType.Hand), Predicates.or(
CardPredicates.isType("Instant"), CardPredicates.isType("Sorcery")));
if (!hand.isEmpty()) {
// has spell that can be cast in hand with put ability
if (!CardLists.filter(hand, CardPredicates.hasCMC(counterNum + 1)).isEmpty()) {
return false;
}
// has spell that can be cast if one counter is removed
if (!CardLists.filter(hand, CardPredicates.hasCMC(counterNum)).isEmpty()) {
sa.setXManaCostPaid(1);
return true;
}
}
final CardCollection library = CardLists.filter(ai.getCardsIn(ZoneType.Library), Predicates.or(
CardPredicates.isType("Instant"), CardPredicates.isType("Sorcery")));
if (!library.isEmpty()) {
// get max cmc of instant or sorceries in the libary
int maxCMC = 0;
for (final Card c : library) {
int v = c.getCMC();
if (c.isSplitCard()) {
v = Math.max(c.getCMC(SplitCMCMode.LeftSplitCMC), c.getCMC(SplitCMCMode.RightSplitCMC));
}
if (v > maxCMC) {
maxCMC = v;
}
}
// there is a spell with more CMC, no need to remove counter
if (counterNum + 1 < maxCMC) {
return false;
}
int maxToRemove = counterNum - maxCMC + 1;
// no Scry 0, even if its catched from later stuff
if (maxToRemove <= 0) {
return false;
}
sa.setXManaCostPaid(maxToRemove);
} else {
// no Instant or Sorceries anymore, just scry
sa.setXManaCostPaid(Math.min(counterNum, libsize));
}
return SpecialCardAi.BrainInAJar.consider(ai, sa);
} else if ("MultipleChoice".equals(aiLogic)) {
return SpecialCardAi.MultipleChoice.consider(ai, sa);
}
return true;
}
@@ -163,11 +119,9 @@ public class ScryAi extends SpellAbilityAi {
return false;
}
double chance = .4; // 40 percent chance of milling with instant speed
// stuff
double chance = .4; // 40 percent chance of milling with instant speed stuff
if (SpellAbilityAi.isSorcerySpeed(sa)) {
chance = .667; // 66.7% chance for sorcery speed (since it will
// never activate EOT)
chance = .667; // 66.7% chance for sorcery speed (since it will never activate EOT)
}
boolean randomReturn = MyRandom.getRandom().nextFloat() <= Math.pow(chance, sa.getActivationsThisTurn() + 1);

View File

@@ -52,8 +52,7 @@ public class SetStateAi extends SpellAbilityAi {
@Override
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
// Gross generalization, but this always considers alternate
// states more powerful
// Gross generalization, but this always considers alternate states more powerful
return !sa.getHostCard().isInAlternateState();
}

View File

@@ -17,8 +17,7 @@ public class ShuffleAi extends SpellAbilityAi {
return aiPlayer.getGame().getPhaseHandler().is(PhaseType.MAIN2, aiPlayer);
}
// not really sure when the compy would use this; maybe only after a
// human
// not really sure when the compy would use this; maybe only after a human
// deliberately put a card on top of their library
return false;
/*

View File

@@ -66,7 +66,6 @@ public class TapAi extends TapAiBase {
// Set PayX here to maximum value.
// TODO need to set XManaCostPaid for targets, maybe doesn't need PayX anymore?
sa.setXManaCostPaid(ComputerUtilCost.getMaxXValue(sa, ai));
// TODO since change of PayX. the shouldCastLessThanMax logic might be faulty
}
sa.resetTargets();

View File

@@ -5,11 +5,13 @@ import java.util.List;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import forge.ai.AiAttackController;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilAbility;
import forge.ai.ComputerUtilCard;
import forge.ai.SpellAbilityAi;
import forge.game.Game;
import forge.game.ability.AbilityUtils;
import forge.game.ability.ApiType;
import forge.game.card.Card;
import forge.game.card.CardCollection;
@@ -109,7 +111,7 @@ 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 = ai.getWeakestOpponent();
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);
@@ -308,9 +310,8 @@ public abstract class TapAiBase extends SpellAbilityAi {
return true;
}
// TODO: use Defined to determine, if this is an unfavorable result
return true;
final List<Card> pDefined = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("Defined"), sa);
return pDefined.isEmpty() || (pDefined.get(0).isUntapped() && pDefined.get(0).getController() != ai);
} else {
sa.resetTargets();
if (tapPrefTargeting(ai, source, sa, mandatory)) {

View File

@@ -12,7 +12,7 @@ import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.card.CardCollectionView;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates.Presets;
import forge.game.card.CardPredicates;
import forge.game.combat.CombatUtil;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
@@ -49,7 +49,7 @@ public class TapAllAi extends SpellAbilityAi {
}
validTappables = CardLists.getValidCards(validTappables, valid, source.getController(), source, sa);
validTappables = CardLists.filter(validTappables, Presets.UNTAPPED);
validTappables = CardLists.filter(validTappables, CardPredicates.Presets.UNTAPPED);
if (sa.hasParam("AILogic")) {
String logic = sa.getParam("AILogic");
@@ -69,18 +69,8 @@ public class TapAllAi extends SpellAbilityAi {
return false;
}
final List<Card> human = CardLists.filter(validTappables, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return c.getController().equals(opp);
}
});
final List<Card> compy = CardLists.filter(validTappables, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return c.getController().equals(ai);
}
});
final List<Card> human = CardLists.filterControlledBy(validTappables, opp);
final List<Card> compy = CardLists.filterControlledBy(validTappables, ai);
if (human.size() <= compy.size()) {
return false;
}
@@ -102,7 +92,7 @@ public class TapAllAi extends SpellAbilityAi {
final Game game = source.getGame();
CardCollectionView tmpList = game.getCardsIn(ZoneType.Battlefield);
tmpList = CardLists.getValidCards(tmpList, valid, source.getController(), source, sa);
tmpList = CardLists.filter(tmpList, Presets.UNTAPPED);
tmpList = CardLists.filter(tmpList, CardPredicates.Presets.UNTAPPED);
return tmpList;
}

View File

@@ -19,8 +19,7 @@ public class TapOrUntapAi extends TapAiBase {
if (!sa.usesTargeting()) {
// assume we are looking to tap human's stuff
// TODO - check for things with untap abilities, and don't tap
// those.
// TODO - check for things with untap abilities, and don't tap those.
boolean bFlag = false;
for (final Card c : AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa)) {
@@ -40,6 +39,4 @@ public class TapOrUntapAi extends TapAiBase {
return randomReturn;
}
}

View File

@@ -2,6 +2,7 @@ package forge.ai.ability;
import java.util.List;
import forge.ai.AiAttackController;
import forge.ai.SpellAbilityAi;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
@@ -28,7 +29,7 @@ public class TwoPilesAi extends SpellAbilityAi {
valid = sa.getParam("ValidCards");
}
final Player opp = ai.getWeakestOpponent();
final Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null) {

View File

@@ -56,7 +56,6 @@ public class UnattachAllAi extends SpellAbilityAi {
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
final Card card = sa.getHostCard();
final Player opp = ai.getWeakestOpponent();
// Check if there are any valid targets
List<GameObject> targets = new ArrayList<>();
final TargetRestrictions tgt = sa.getTargetRestrictions();
@@ -66,8 +65,8 @@ public class UnattachAllAi extends SpellAbilityAi {
if (!mandatory && card.isEquipment() && !targets.isEmpty()) {
Card newTarget = (Card) targets.get(0);
//don't equip human creatures
if (newTarget.getController().equals(opp)) {
//don't equip opponent creatures
if (!newTarget.getController().equals(ai)) {
return false;
}

View File

@@ -6,6 +6,7 @@ import java.util.Map;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import forge.ai.AiAttackController;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilAbility;
import forge.ai.ComputerUtilCard;
@@ -65,7 +66,7 @@ public class UntapAi extends SpellAbilityAi {
if (!sa.usesTargeting()) {
final List<Card> pDefined = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa);
return pDefined == null || !pDefined.get(0).isUntapped() || pDefined.get(0).getController() != ai;
return pDefined.isEmpty() || (pDefined.get(0).isTapped() && pDefined.get(0).getController() == ai);
} else {
return untapPrefTargeting(ai, sa, false);
}
@@ -81,9 +82,8 @@ public class UntapAi extends SpellAbilityAi {
return false;
}
// TODO: use Defined to determine, if this is an unfavorable result
final List<Card> pDefined = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("Defined"), sa);
return pDefined == null || !pDefined.get(0).isUntapped() || pDefined.get(0).getController() != ai;
return pDefined.isEmpty() || (pDefined.get(0).isTapped() && pDefined.get(0).getController() == ai);
} else {
if (untapPrefTargeting(ai, sa, mandatory)) {
return true;
@@ -130,7 +130,8 @@ public class UntapAi extends SpellAbilityAi {
Player targetController = ai;
if (sa.isCurse()) {
targetController = ai.getWeakestOpponent();
// TODO search through all opponents, may need to check if different controllers allowed
targetController = AiAttackController.choosePreferredDefenderPlayer(ai);
}
CardCollection list = CardLists.getTargetableCards(targetController.getCardsIn(ZoneType.Battlefield), sa);
@@ -149,8 +150,7 @@ public class UntapAi extends SpellAbilityAi {
}
CardCollection untapList = targetUntapped ? list : CardLists.filter(list, Presets.TAPPED);
// filter out enchantments and planeswalkers, their tapped state doesn't
// matter.
// filter out enchantments and planeswalkers, their tapped state doesn't matter.
final String[] tappablePermanents = {"Creature", "Land", "Artifact"};
untapList = CardLists.getValidCards(untapList, tappablePermanents, source.getController(), source, sa);

View File

@@ -8,6 +8,7 @@ import forge.game.card.CardLists;
import forge.game.card.CardPredicates;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.player.PlayerCollection;
import forge.game.spellability.AbilitySub;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
@@ -29,6 +30,10 @@ public class UntapAllAi extends SpellAbilityAi {
valid = sa.getParam("ValidCards");
}
list = CardLists.getValidCards(list, valid.split(","), source.getController(), source, sa);
// don't untap if only opponent benefits
PlayerCollection goodControllers = aiPlayer.getAllies();
goodControllers.add(aiPlayer);
list = CardLists.filter(list, CardPredicates.isControlledByAnyOf(goodControllers));
return !list.isEmpty();
}
return false;

View File

@@ -55,15 +55,15 @@ public class GameCopier {
public GameCopier(Game origGame) {
this.origGame = origGame;
}
public Game getOriginalGame() {
return origGame;
}
public Game getCopiedGame() {
return gameObjectMap.getGame();
}
public Game makeCopy() {
return makeCopy(null);
}
@@ -88,7 +88,6 @@ public class GameCopier {
newPlayer.setCounters(Maps.newHashMap(origPlayer.getCounters()));
newPlayer.setLifeLostLastTurn(origPlayer.getLifeLostLastTurn());
newPlayer.setLifeLostThisTurn(origPlayer.getLifeLostThisTurn());
newPlayer.setPreventNextDamage(origPlayer.getPreventNextDamage());
newPlayer.getManaPool().add(origPlayer.getManaPool());
newPlayer.setCommanders(origPlayer.getCommanders()); // will be fixed up below
playerMap.put(origPlayer, newPlayer);
@@ -101,7 +100,7 @@ public class GameCopier {
for (Player p : newGame.getPlayers()) {
((PlayerZoneBattlefield) p.getZone(ZoneType.Battlefield)).setTriggers(false);
}
copyGameState(newGame);
for (Player p : newGame.getPlayers()) {
@@ -124,6 +123,7 @@ public class GameCopier {
System.err.println(c + " Remembered: " + o + "/" + o.getClass());
c.addRemembered(o);
}
}
}
for (SpellAbility sa : c.getSpellAbilities()) {
@@ -152,7 +152,7 @@ public class GameCopier {
if (advanceToPhase != null) {
newGame.getPhaseHandler().devAdvanceToPhase(advanceToPhase);
}
return newGame;
}
@@ -179,7 +179,7 @@ public class GameCopier {
}
newGame.getStack().add(newSa);
}
}
}
}
private RegisteredPlayer clonePlayer(RegisteredPlayer p) {
@@ -226,7 +226,7 @@ public class GameCopier {
// TODO: Verify that the above relationships are preserved bi-directionally or not.
}
}
private static final boolean USE_FROM_PAPER_CARD = true;
private Card createCardCopy(Game newGame, Player newOwner, Card c) {
if (c.isToken() && !c.isEmblem()) {
@@ -276,7 +276,7 @@ public class GameCopier {
// TODO: Controllers' list with timestamps should be copied.
zoneOwner = playerMap.get(c.getController());
newCard.setController(zoneOwner, 0);
int setPower = c.getSetPower();
int setToughness = c.getSetToughness();
if (setPower != Integer.MAX_VALUE || setToughness != Integer.MAX_VALUE) {
@@ -285,7 +285,7 @@ public class GameCopier {
}
newCard.setPTBoost(c.getPTBoostTable());
newCard.setDamage(c.getDamage());
newCard.setChangedCardTypes(c.getChangedCardTypesMap());
newCard.setChangedCardKeywords(c.getChangedCardKeywords());
newCard.setChangedCardNames(c.getChangedCardNames());
@@ -334,16 +334,15 @@ public class GameCopier {
if (c.getChosenPlayer() != null) {
newCard.setChosenPlayer(playerMap.get(c.getChosenPlayer()));
}
SpellAbility first = c.getFirstSpellAbility();
if (first != null && first.hasChosenColor()) {
newCard.setChosenColors(Lists.newArrayList(first.getChosenColors()), newCard.getFirstSpellAbility());
if (!c.getChosenType().isEmpty()) {
newCard.setChosenType(c.getChosenType());
}
if (first != null && first.hasChosenType()) {
newCard.setChosenColors(Lists.newArrayList(first.getChosenType()), newCard.getFirstSpellAbility());
if (!c.getChosenType2().isEmpty()) {
newCard.setChosenType2(c.getChosenType2());
}
if (c.getChosenColors() != null) {
newCard.setChosenColors(Lists.newArrayList(c.getChosenColors()));
}
if (!c.getNamedCard().isEmpty()) {
newCard.setNamedCard(c.getNamedCard());
}
@@ -359,7 +358,7 @@ public class GameCopier {
zoneOwner.getZone(zone).add(newCard);
}
}
private static SpellAbility findSAInCard(SpellAbility sa, Card c) {
String saDesc = sa.getDescription();
for (SpellAbility cardSa : c.getAllSpellAbilities()) {
@@ -387,7 +386,7 @@ public class GameCopier {
return find(o);
}
}
public GameObject find(GameObject o) {
GameObject result = cardMap.get(o);
if (result != null)

View File

@@ -11,6 +11,7 @@ import forge.ai.ComputerUtilAbility;
import forge.ai.ComputerUtilCost;
import forge.ai.ability.ChangeZoneAi;
import forge.ai.ability.ExploreAi;
import forge.ai.ability.LearnAi;
import forge.ai.simulation.GameStateEvaluator.Score;
import forge.game.Game;
import forge.game.ability.ApiType;
@@ -423,6 +424,8 @@ public class SpellAbilityPicker {
}
if (sa.getApi() == ApiType.Explore) {
return ExploreAi.shouldPutInGraveyard(fetchList, decider);
} else if (sa.getApi() == ApiType.Learn) {
return LearnAi.chooseCardToLearn(fetchList, decider, sa);
} else {
return ChangeZoneAi.chooseCardToHiddenOriginChangeZone(destination, origin, sa, fetchList, player2, decider);
}

View File

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

View File

@@ -121,6 +121,9 @@ public final class ImageKeys {
// if there's a 1st art variant try without it for .fullborder images
file = findFile(dir, TextUtil.fastReplace(fullborderFile, "1.fullborder", ".fullborder"));
if (file != null) { return file; }
// if there's a 1st art variant try with it for .fullborder images
file = findFile(dir, fullborderFile.replaceAll("[0-9]*.fullborder", "1.fullborder"));
if (file != null) { return file; }
// if there's an art variant try without it for .full images
file = findFile(dir, filename.replaceAll("[0-9].full",".full"));
if (file != null) { return file; }

View File

@@ -2,7 +2,6 @@ package forge;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.List;
@@ -79,16 +78,30 @@ public class StaticData {
this.customEditions = new CardEdition.Collection(new CardEdition.Reader(new File(customEditionsFolder)));
this.prefferedArt = prefferedArt;
lastInstance = this;
List<String> funnyCards = new ArrayList<>();
List<String> filtered = new ArrayList<>();
{
final Map<String, CardRules> regularCards = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
final Map<String, CardRules> variantsCards = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
final Map<String, CardRules> customizedCards = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
for (CardEdition e : editions) {
if (e.getType() == CardEdition.Type.FUNNY || e.getBorderColor() == CardEdition.BorderColor.SILVER) {
for (CardEdition.CardInSet cis : e.getAllCardsInSet()) {
funnyCards.add(cis.name);
}
}
}
for (CardRules card : cardReader.loadCards()) {
if (null == card) continue;
final String cardName = card.getName();
if (!loadNonLegalCards && !card.getType().isBasicLand() && funnyCards.contains(cardName))
filtered.add(cardName);
if (card.isVariant()) {
variantsCards.put(cardName, card);
} else {
@@ -104,15 +117,18 @@ public class StaticData {
}
}
if (!filtered.isEmpty()) {
Collections.sort(filtered);
}
commonCards = new CardDb(regularCards, editions);
variantCards = new CardDb(variantsCards, editions);
customCards = new CardDb(customizedCards, customEditions);
commonCards = new CardDb(regularCards, editions, filtered);
variantCards = new CardDb(variantsCards, editions, filtered);
customCards = new CardDb(customizedCards, customEditions, filtered);
//must initialize after establish field values for the sake of card image logic
commonCards.initialize(false, false, enableUnknownCards, loadNonLegalCards);
variantCards.initialize(false, false, enableUnknownCards, loadNonLegalCards);
customCards.initialize(false, false, enableUnknownCards, loadNonLegalCards);
commonCards.initialize(false, false, enableUnknownCards);
variantCards.initialize(false, false, enableUnknownCards);
customCards.initialize(false, false, enableUnknownCards);
}
{
@@ -125,14 +141,6 @@ public class StaticData {
}
allTokens = new TokenDb(tokens, editions);
}
{
if (customCards.getAllCards().size() > 0) {
Collection<PaperCard> paperCards = customCards.getAllCards();
for(PaperCard p: paperCards)
commonCards.addCard(p);
}
}
}
public static StaticData instance() {
@@ -143,6 +151,11 @@ public class StaticData {
return this.editions;
}
public final CardEdition.Collection getCustomEditions(){
return this.customEditions;
}
private List<CardEdition> sortedEditions;
public final List<CardEdition> getSortedEditions() {
if (sortedEditions == null) {
@@ -150,12 +163,24 @@ public class StaticData {
for (CardEdition set : editions) {
sortedEditions.add(set);
}
if (customEditions.size() > 0){
for (CardEdition set : customEditions) {
sortedEditions.add(set);
}
}
Collections.sort(sortedEditions);
Collections.reverse(sortedEditions); //put newer sets at the top
}
return sortedEditions;
}
public CardEdition getCardEdition(String setCode){
CardEdition edition = this.editions.get(setCode);
if (edition == null) // try custom editions
edition = this.customEditions.get(setCode);
return edition;
}
public PaperCard getOrLoadCommonCard(String cardName, String setCode, int artIndex, boolean foil) {
PaperCard card = commonCards.getCard(cardName, setCode, artIndex);
boolean isCustom = false;

View File

@@ -6,7 +6,6 @@ package forge.card;
*/
public class CardAiHints {
private final boolean isRemovedFromAIDecks;
private final boolean isRemovedFromRandomDecks;
private final boolean isRemovedFromNonCommanderDecks;
@@ -15,7 +14,6 @@ public class CardAiHints {
private final DeckHints deckNeeds;
private final DeckHints deckHas;
public CardAiHints(boolean remAi, boolean remRandom, boolean remUnlessCommander, DeckHints dh, DeckHints dn, DeckHints has) {
isRemovedFromAIDecks = remAi;
isRemovedFromRandomDecks = remRandom;
@@ -90,5 +88,4 @@ public class CardAiHints {
}
}
}

View File

@@ -32,6 +32,7 @@ import java.util.Set;
import java.util.TreeMap;
import forge.StaticData;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
@@ -55,6 +56,8 @@ import forge.util.TextUtil;
public final class CardDb implements ICardDatabase, IDeckGenPool {
public final static String foilSuffix = "+";
public final static char NameSetSeparator = '|';
private final String exlcudedCardName = "Concentrate";
private final String exlcudedCardSet = "DS0";
// need this to obtain cardReference by name+set+artindex
private final ListMultimap<String, PaperCard> allCardsByName = Multimaps.newListMultimap(new TreeMap<>(String.CASE_INSENSITIVE_ORDER), CollectionSuppliers.arrayLists());
@@ -66,8 +69,8 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
private final Map<String, String> alternateName = Maps.newTreeMap(String.CASE_INSENSITIVE_ORDER);
private final Map<String, Integer> artIds = new HashMap<>();
private final Collection<PaperCard> roAllCards = Collections.unmodifiableCollection(allCardsByName.values());
private final CardEdition.Collection editions;
private List<String> filtered;
public enum SetPreference {
Latest(false),
@@ -134,12 +137,15 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
}
}
public CardDb(Map<String, CardRules> rules, CardEdition.Collection editions0) {
public CardDb(Map<String, CardRules> rules, CardEdition.Collection editions0, List<String> filteredCards) {
this.filtered = filteredCards;
this.rulesByName = rules;
this.editions = editions0;
// create faces list from rules
for (final CardRules rule : rules.values() ) {
if (filteredCards.contains(rule.getName()) && !exlcudedCardName.equalsIgnoreCase(rule.getName()))
continue;
final ICardFace main = rule.getMainPart();
facesByName.put(main.getName(), main);
if (main.getAltName() != null) {
@@ -155,6 +161,10 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
}
}
private ListMultimap<String, PaperCard> getAllCardsByName() {
return allCardsByName;
}
private void addSetCard(CardEdition e, CardInSet cis, CardRules cr) {
int artIdx = 1;
String key = e.getCode() + "/" + cis.name;
@@ -182,33 +192,24 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
reIndex();
}
public void initialize(boolean logMissingPerEdition, boolean logMissingSummary, boolean enableUnknownCards, boolean loadNonLegalCards) {
public void initialize(boolean logMissingPerEdition, boolean logMissingSummary, boolean enableUnknownCards) {
Set<String> allMissingCards = new LinkedHashSet<>();
List<String> missingCards = new ArrayList<>();
CardEdition upcomingSet = null;
Date today = new Date();
List<String> skippedCardName = new ArrayList<>();
for (CardEdition e : editions.getOrderedEditions()) {
boolean coreOrExpSet = e.getType() == CardEdition.Type.CORE || e.getType() == CardEdition.Type.EXPANSION;
boolean isCoreExpSet = coreOrExpSet || e.getType() == CardEdition.Type.REPRINT;
//todo sets with nonlegal cards should have tags in them so we don't need to specify the code here
boolean skip = !loadNonLegalCards && (e.getType() == CardEdition.Type.FUNNY || e.getBorderColor() == CardEdition.BorderColor.SILVER);
if (logMissingPerEdition && isCoreExpSet) {
System.out.print(e.getName() + " (" + e.getAllCardsInSet().size() + " cards)");
}
if (coreOrExpSet && e.getDate().after(today) && upcomingSet == null) {
if (skip)
upcomingSet = e;
upcomingSet = e;
}
for (CardEdition.CardInSet cis : e.getAllCardsInSet()) {
CardRules cr = rulesByName.get(cis.name);
if (cr != null && !cr.getType().isBasicLand() && skip) {
skippedCardName.add(cis.name);
continue;
}
if (cr != null) {
addSetCard(e, cis, cr);
}
@@ -244,7 +245,7 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
if (!contains(cr.getName())) {
if (upcomingSet != null) {
addCard(new PaperCard(cr, upcomingSet.getCode(), CardRarity.Unknown, 1));
} else if(enableUnknownCards && !skippedCardName.contains(cr.getName())) {
} else if(enableUnknownCards) {
System.err.println("The card " + cr.getName() + " was not assigned to any set. Adding it to UNKNOWN set... to fix see res/editions/ folder. ");
addCard(new PaperCard(cr, CardEdition.UNKNOWN.getCode(), CardRarity.Special, 1));
}
@@ -255,6 +256,9 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
}
public void addCard(PaperCard paperCard) {
if (excludeCard(paperCard.getName(), paperCard.getEdition()))
return;
allCardsByName.put(paperCard.getName(), paperCard);
if (paperCard.getRules().getSplitType() == CardSplitType.None) { return; }
@@ -268,11 +272,22 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
allCardsByName.put(paperCard.getRules().getMainPart().getName(), paperCard);
}
}
private boolean excludeCard(String cardName, String cardEdition) {
if (filtered.isEmpty())
return false;
if (filtered.contains(cardName)) {
if (exlcudedCardSet.equalsIgnoreCase(cardEdition) && exlcudedCardName.equalsIgnoreCase(cardName))
return true;
else if (!exlcudedCardName.equalsIgnoreCase(cardName))
return true;
}
return false;
}
private void reIndex() {
uniqueCardsByName.clear();
for (Entry<String, Collection<PaperCard>> kv : allCardsByName.asMap().entrySet()) {
uniqueCardsByName.put(kv.getKey(), getFirstWithImage(kv.getValue()));
for (Entry<String, Collection<PaperCard>> kv : getAllCardsByName().asMap().entrySet()) {
PaperCard pc = getFirstWithImage(kv.getValue());
uniqueCardsByName.put(kv.getKey(), pc);
}
}
@@ -518,6 +533,8 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
@Override
public int getPrintCount(String cardName, String edition) {
int cnt = 0;
if (edition == null || cardName == null)
return cnt;
for (PaperCard pc : getAllCards(cardName)) {
if (pc.getEdition().equals(edition)) {
cnt++;
@@ -529,6 +546,8 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
@Override
public int getMaxPrintCount(String cardName) {
int max = -1;
if (cardName == null)
return max;
for (PaperCard pc : getAllCards(cardName)) {
if (max < pc.getArtIndex()) {
max = pc.getArtIndex();
@@ -540,6 +559,8 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
@Override
public int getArtCount(String cardName, String setName) {
int cnt = 0;
if (cardName == null || setName == null)
return cnt;
Collection<PaperCard> cards = getAllCards(cardName);
if (null == cards) {
@@ -561,6 +582,17 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
return uniqueCardsByName.values();
}
public Collection<PaperCard> getUniqueCardsNoAlt() {
return Maps.filterEntries(this.uniqueCardsByName, new Predicate<Entry<String, PaperCard>>() {
@Override
public boolean apply(Entry<String, PaperCard> e) {
if (e == null)
return false;
return e.getKey().equals(e.getValue().getName());
}
}).values();
}
public PaperCard getUniqueByName(final String name) {
return uniqueCardsByName.get(getName(name));
}
@@ -575,11 +607,20 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
@Override
public Collection<PaperCard> getAllCards() {
return roAllCards;
return Collections.unmodifiableCollection(getAllCardsByName().values());
}
public Collection<PaperCard> getAllCardsNoAlt() {
return Multimaps.filterEntries(getAllCardsByName(), new Predicate<Entry<String, PaperCard>>() {
@Override
public boolean apply(Entry<String, PaperCard> entry) {
return entry.getKey().equals(entry.getValue().getName());
}
}).values();
}
public Collection<PaperCard> getAllNonPromoCards() {
return Lists.newArrayList(Iterables.filter(this.roAllCards, new Predicate<PaperCard>() {
return Lists.newArrayList(Iterables.filter(getAllCards(), new Predicate<PaperCard>() {
@Override
public boolean apply(final PaperCard paperCard) {
CardEdition edition = null;
@@ -593,6 +634,23 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
}));
}
public Collection<PaperCard> getAllNonPromosNonReprintsNoAlt() {
return Lists.newArrayList(Iterables.filter(getAllCardsNoAlt(), new Predicate<PaperCard>() {
@Override
public boolean apply(final PaperCard paperCard) {
CardEdition edition = null;
try {
edition = editions.getEditionByCodeOrThrow(paperCard.getEdition());
if (edition.getType() == Type.PROMOS||edition.getType() == Type.REPRINT)
return false;
} catch (Exception ex) {
return false;
}
return true;
}
}));
}
public String getName(final String cardName) {
if (alternateName.containsKey(cardName)) {
return alternateName.get(cardName);
@@ -602,13 +660,27 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
@Override
public List<PaperCard> getAllCards(String cardName) {
return allCardsByName.get(getName(cardName));
return getAllCardsByName().get(getName(cardName));
}
public List<PaperCard> getAllCardsNoAlt(String cardName) {
return Lists.newArrayList(Multimaps.filterEntries(getAllCardsByName(), new Predicate<Entry<String, PaperCard>>() {
@Override
public boolean apply(Entry<String, PaperCard> entry) {
return entry.getKey().equals(entry.getValue().getName());
}
}).get(getName(cardName)));
}
/** Returns a modifiable list of cards matching the given predicate */
@Override
public List<PaperCard> getAllCards(Predicate<PaperCard> predicate) {
return Lists.newArrayList(Iterables.filter(this.roAllCards, predicate));
return Lists.newArrayList(Iterables.filter(getAllCards(), predicate));
}
/** Returns a modifiable list of cards matching the given predicate */
public List<PaperCard> getAllCardsNoAlt(Predicate<PaperCard> predicate) {
return Lists.newArrayList(Iterables.filter(getAllCardsNoAlt(), predicate));
}
// Do I want a foiled version of these cards?
@@ -630,12 +702,12 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
@Override
public boolean contains(String name) {
return allCardsByName.containsKey(getName(name));
return getAllCardsByName().containsKey(getName(name));
}
@Override
public Iterator<PaperCard> iterator() {
return this.roAllCards.iterator();
return getAllCards().iterator();
}
public Predicate<? super PaperCard> wasPrintedInSets(List<String> setCodes) {
@@ -700,7 +772,6 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
}
public PaperCard createUnsupportedCard(String cardName) {
CardRequest request = CardRequest.fromString(cardName);
CardEdition cardEdition = CardEdition.UNKNOWN;
CardRarity cardRarity = CardRarity.Unknown;
@@ -741,7 +812,6 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
}
return new PaperCard(CardRules.getUnsupportedCardNamed(request.cardName), cardEdition.getCode(), cardRarity, 1);
}
private final Editor editor = new Editor();

View File

@@ -19,7 +19,6 @@ package forge.card;
import java.io.File;
import java.io.FilenameFilter;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
@@ -71,7 +70,9 @@ import forge.util.storage.StorageReaderFolder;
* @author Forge
* @version $Id: CardSet.java 9708 2011-08-09 19:34:12Z jendave $
*/
public final class CardEdition implements Comparable<CardEdition> { // immutable
public final class CardEdition implements Comparable<CardEdition> {
// immutable
public enum Type {
UNKNOWN,
@@ -116,18 +117,22 @@ public final class CardEdition implements Comparable<CardEdition> { // immutable
}
// reserved names of sections inside edition files, that are not parsed as cards
private static final List<String> reservedSectionNames = ImmutableList.of("metadata", "tokens");
private static final List<String> reservedSectionNames = ImmutableList.of("metadata", "tokens", "other");
// commonly used printsheets with collector number
public enum EditionSectionWithCollectorNumbers {
CARDS("cards"),
SPECIAL_SLOT("special slot"), //to help with convoluted boosters
PRECON_PRODUCT("precon product"),
BORDERLESS("borderless"),
SHOWCASE("showcase"),
EXTENDED_ART("extended art"),
ALTERNATE_ART("alternate art"),
ALTERNATE_FRAME("alternate frame"),
BUY_A_BOX("buy a box"),
PROMO("promo");
PROMO("promo"),
BUNDLE("bundle"),
BOX_TOPPER("box topper");
private final String name;
@@ -147,7 +152,7 @@ public final class CardEdition implements Comparable<CardEdition> { // immutable
}
}
public static class CardInSet {
public static class CardInSet implements Comparable<CardInSet> {
public final CardRarity rarity;
public final String collectorNumber;
public final String name;
@@ -171,6 +176,56 @@ public final class CardEdition implements Comparable<CardEdition> { // immutable
sb.append(name);
return sb.toString();
}
/**
* This method implements the main strategy to allow for natural ordering of collectorNumber
* (i.e. "1" < "10"), overloading the default lexicographic order (i.e. "10" < "1").
* Any non-numerical parts in the input collectorNumber will be also accounted for, and attached to the
* resulting sorting key, accordingly.
*
* @param collectorNumber: Input collectorNumber tro transform in a Sorting Key
* @return A 5-digits zero-padded collector number + any non-numerical parts attached.
*/
public static String getSortableCollectorNumber(final String collectorNumber){
String sortableCollNr = collectorNumber;
if (sortableCollNr == null || sortableCollNr.length() == 0)
sortableCollNr = "50000"; // very big number of 5 digits to have them in last positions
// Now, for proper sorting, let's zero-pad the collector number (if integer)
int collNr;
try {
collNr = Integer.parseInt(sortableCollNr);
sortableCollNr = String.format("%05d", collNr);
} catch (NumberFormatException ex) {
String nonNumeric = sortableCollNr.replaceAll("[0-9]", "");
String onlyNumeric = sortableCollNr.replaceAll("[^0-9]", "");
try {
collNr = Integer.parseInt(onlyNumeric);
} catch (NumberFormatException exon) {
collNr = 0; // this is the case of ONLY-letters collector numbers
}
if ((collNr > 0) && (sortableCollNr.startsWith(onlyNumeric))) // e.g. 12a, 37+, 2018f,
sortableCollNr = String.format("%05d", collNr) + nonNumeric;
else // e.g. WS6, S1
sortableCollNr = nonNumeric + String.format("%05d", collNr);
}
return sortableCollNr;
}
@Override
public int compareTo(CardInSet o) {
final int nameCmp = name.compareToIgnoreCase(o.name);
if (0 != nameCmp) {
return nameCmp;
}
String thisCollNr = getSortableCollectorNumber(collectorNumber);
String othrCollNr = getSortableCollectorNumber(o.collectorNumber);
final int collNrCmp = thisCollNr.compareTo(othrCollNr);
if (0 != collNrCmp) {
return collNrCmp;
}
return rarity.compareTo(o.rarity);
}
}
private final static SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");
@@ -181,6 +236,8 @@ public final class CardEdition implements Comparable<CardEdition> { // immutable
private String code;
private String code2;
private String mciCode;
private String scryfallCode;
private String cardsLanguage;
private Type type;
private String name;
private String alias = null;
@@ -205,6 +262,7 @@ public final class CardEdition implements Comparable<CardEdition> { // immutable
private String[] chaosDraftThemes = new String[0];
private final ListMultimap<String, CardInSet> cardMap;
private final List<CardInSet> cardsInSet;
private final Map<String, Integer> tokenNormalized;
// custom print sheets that will be loaded lazily
private final Map<String, List<String>> customPrintSheetsToParse;
@@ -214,13 +272,18 @@ public final class CardEdition implements Comparable<CardEdition> { // immutable
private CardEdition(ListMultimap<String, CardInSet> cardMap, Map<String, Integer> tokens, Map<String, List<String>> customPrintSheetsToParse) {
this.cardMap = cardMap;
this.cardsInSet = new ArrayList<>(cardMap.values());
Collections.sort(cardsInSet);
this.tokenNormalized = tokens;
this.customPrintSheetsToParse = customPrintSheetsToParse;
}
private CardEdition(CardInSet[] cards, Map<String, Integer> tokens) {
List<CardInSet> cardsList = Arrays.asList(cards);
this.cardMap = ArrayListMultimap.create();
this.cardMap.replaceValues("cards", Arrays.asList(cards));
this.cardMap.replaceValues("cards", cardsList);
this.cardsInSet = new ArrayList<>(cardsList);
Collections.sort(cardsInSet);
this.tokenNormalized = tokens;
this.customPrintSheetsToParse = new HashMap<>();
}
@@ -255,7 +318,7 @@ public final class CardEdition implements Comparable<CardEdition> { // immutable
date = date + "-01";
try {
return formatter.parse(date);
} catch (ParseException e) {
} catch (Exception e) {
return new Date();
}
}
@@ -264,6 +327,8 @@ public final class CardEdition implements Comparable<CardEdition> { // immutable
public String getCode() { return code; }
public String getCode2() { return code2; }
public String getMciCode() { return mciCode; }
public String getScryfallCode() { return scryfallCode.toLowerCase(); }
public String getCardsLangCode() { return cardsLanguage.toLowerCase(); }
public Type getType() { return type; }
public String getName() { return name; }
public String getAlias() { return alias; }
@@ -286,7 +351,7 @@ public final class CardEdition implements Comparable<CardEdition> { // immutable
public List<CardInSet> getCards() { return cardMap.get("cards"); }
public List<CardInSet> getAllCardsInSet() {
return Lists.newArrayList(cardMap.values());
return cardsInSet;
}
public boolean isModern() { return getDate().after(parseDate("2003-07-27")); } //8ED and above are modern except some promo cards and others
@@ -305,7 +370,10 @@ public final class CardEdition implements Comparable<CardEdition> { // immutable
if (o == null) {
return 1;
}
return date.compareTo(o.date);
int dateComp = date.compareTo(o.date);
if (0 != dateComp)
return dateComp;
return name.compareTo(o.name);
}
@Override
@@ -339,7 +407,7 @@ public final class CardEdition implements Comparable<CardEdition> { // immutable
}
public boolean isLargeSet() {
return getAllCardsInSet().size() > 200 && !smallSetOverride;
return this.cardsInSet.size() > 200 && !smallSetOverride;
}
public int getCntBoosterPictures() {
@@ -413,7 +481,8 @@ public final class CardEdition implements Comparable<CardEdition> { // immutable
* rarity - grouping #4
* name - grouping #5
*/
"(^([0-9]+.?) )?(([SCURML]) )?(.*)$"
// "(^(.?[0-9A-Z]+.?))?(([SCURML]) )?(.*)$"
"(^(.?[0-9A-Z]+\\S?[A-Z]*)\\s)?(([SCURML])\\s)?(.*)$"
);
ListMultimap<String, CardInSet> cardMap = ArrayListMultimap.create();
@@ -479,6 +548,14 @@ public final class CardEdition implements Comparable<CardEdition> { // immutable
if (res.mciCode == null) {
res.mciCode = res.code2.toLowerCase();
}
res.scryfallCode = section.get("ScryfallCode");
if (res.scryfallCode == null){
res.scryfallCode = res.code;
}
res.cardsLanguage = section.get("CardLang");
if (res.cardsLanguage == null){
res.cardsLanguage = "en";
}
res.boosterArts = section.getInt("BoosterCovers", 1);
String boosterDesc = section.get("Booster");

View File

@@ -17,7 +17,7 @@
*/
package forge.card;
import java.util.StringTokenizer;
import java.util.*;
import org.apache.commons.lang3.StringUtils;
@@ -28,6 +28,9 @@ import forge.card.mana.ManaCost;
import forge.card.mana.ManaCostShard;
import forge.util.TextUtil;
import static forge.card.MagicColor.Constant.*;
import static org.apache.commons.lang3.StringUtils.containsIgnoreCase;
/**
* A collection of methods containing full
* meta and gameplay properties of a card.
@@ -42,6 +45,7 @@ public final class CardRules implements ICardCharacteristics {
private ICardFace otherPart;
private CardAiHints aiHints;
private ColorSet colorIdentity;
private ColorSet deckbuildingColors;
private String meldWith;
private String partnerWith;
@@ -80,6 +84,12 @@ public final class CardRules implements ICardCharacteristics {
boolean isReminder = false;
boolean isSymbol = false;
String oracleText = face.getOracleText();
// CR 903.4 colors defined by its characteristic-defining abilities
for (String staticAbility : face.getStaticAbilities()) {
if (staticAbility.contains("CharacteristicDefining$ True") && staticAbility.contains("SetColor$ All")) {
res |= MagicColor.ALL_COLORS;
}
}
int len = oracleText.length();
for(int i = 0; i < len; i++) {
char c = oracleText.charAt(i); // This is to avoid needless allocations performed by toCharArray()
@@ -384,7 +394,6 @@ public final class CardRules implements ICardCharacteristics {
this.removedFromNonCommanderDecks = "NonCommander".equalsIgnoreCase(value);
}
} else if ("AlternateMode".equals(key)) {
//System.out.println(faces[curFace].getName());
this.altMode = CardSplitType.smartValueOf(value);
} else if ("ALTERNATE".equals(key)) {
this.curFace = 1;
@@ -535,7 +544,6 @@ public final class CardRules implements ICardCharacteristics {
@Override
public final ManaCostShard next() {
final String unparsed = st.nextToken();
// System.out.println(unparsed);
if (StringUtils.isNumeric(unparsed)) {
this.genericCost += Integer.parseInt(unparsed);
return null;
@@ -551,7 +559,7 @@ public final class CardRules implements ICardCharacteristics {
*/
@Override
public void remove() {
} // unsuported
} // unsupported
}
}
@@ -581,4 +589,25 @@ public final class CardRules implements ICardCharacteristics {
}
return null;
}
public ColorSet getDeckbuildingColors() {
if (deckbuildingColors == null) {
byte colors = 0;
if (mainPart.getType().isLand()) {
colors = getColorIdentity().getColor();
for (int i = 0; i < 5; i++) {
if (containsIgnoreCase(mainPart.getOracleText(), BASIC_LANDS.get(i))) {
colors |= 1 << i;
}
}
} else {
colors = getColor().getColor();
if (getOtherPart() != null) {
colors |= getOtherPart().getManaCost().getColorProfile();
}
}
deckbuildingColors = ColorSet.fromMask(colors);
}
return deckbuildingColors;
}
}

View File

@@ -91,6 +91,7 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
public enum Supertype {
Basic,
Elite,
Host,
Legendary,
Snow,
Ongoing,

View File

@@ -138,23 +138,11 @@ public final class MagicColor {
public static final ImmutableList<String> BASIC_LANDS = ImmutableList.of("Plains", "Island", "Swamp", "Mountain", "Forest");
public static final ImmutableList<String> SNOW_LANDS = ImmutableList.of("Snow-Covered Plains", "Snow-Covered Island", "Snow-Covered Swamp", "Snow-Covered Mountain", "Snow-Covered Forest");
public static final ImmutableMap<String, String> ANY_COLOR_CONVERSION = new ImmutableMap.Builder<String, String>()
.put("ManaColorConversion", "Additive")
.put("WhiteConversion", "Color")
.put("BlueConversion", "Color")
.put("BlackConversion", "Color")
.put("RedConversion", "Color")
.put("GreenConversion", "Color")
.put("ColorlessConversion", "Color")
.put("ManaConversion", "AnyType->AnyColor")
.build();
public static final ImmutableMap<String, String> ANY_TYPE_CONVERSION = new ImmutableMap.Builder<String, String>()
.put("ManaColorConversion", "Additive")
.put("WhiteConversion", "Type")
.put("BlueConversion", "Type")
.put("BlackConversion", "Type")
.put("RedConversion", "Type")
.put("GreenConversion", "Type")
.put("ColorlessConversion", "Type")
.put("ManaConversion", "AnyType->AnyType")
.build();
/**
* Private constructor to prevent instantiation.

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