mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-15 02:08:00 +00:00
Compare commits
2218 Commits
exiledWith
...
forge-1.6.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d658df35d6 | ||
|
|
91ae1036de | ||
|
|
844098a302 | ||
|
|
08454489b4 | ||
|
|
71110568fc | ||
|
|
9990f0ad1a | ||
|
|
735a966312 | ||
|
|
828d4e17c9 | ||
|
|
233c1f6d00 | ||
|
|
dbe1683ba9 | ||
|
|
a5b7f93971 | ||
|
|
d853ca6615 | ||
|
|
418d2bf012 | ||
|
|
030f1f4056 | ||
|
|
fc82dbb9aa | ||
|
|
6feb18c11c | ||
|
|
c07f3ea34d | ||
|
|
8948210e0a | ||
|
|
df0b5362c2 | ||
|
|
a9b60a87e0 | ||
|
|
694317a50f | ||
|
|
a856834693 | ||
|
|
35b71da140 | ||
|
|
3b2e39808e | ||
|
|
697a2ace20 | ||
|
|
c38c53d513 | ||
|
|
31745fe31e | ||
|
|
f8e3f4dd31 | ||
|
|
965ec4bc47 | ||
|
|
9cf28e7041 | ||
|
|
71f639d45e | ||
|
|
084476b8b0 | ||
|
|
f9abb8ca75 | ||
|
|
4b0c748eaf | ||
|
|
083674071b | ||
|
|
96ccf0afc6 | ||
|
|
cfb755ce20 | ||
|
|
cc8753b563 | ||
|
|
454c51f1b1 | ||
|
|
fa83326710 | ||
|
|
eb60690973 | ||
|
|
0983c72ea0 | ||
|
|
49436a00b3 | ||
|
|
8345bf890b | ||
|
|
5b2980562b | ||
|
|
b86249635f | ||
|
|
4286429402 | ||
|
|
6578fb79e6 | ||
|
|
f1169511ab | ||
|
|
6f5f04bb7e | ||
|
|
b33595d840 | ||
|
|
653b484674 | ||
|
|
5a0a63940d | ||
|
|
d734fdf761 | ||
|
|
3ab7fcf224 | ||
|
|
4f4fe04683 | ||
|
|
c83d03a2d6 | ||
|
|
8ffa95a26a | ||
|
|
da96b580a4 | ||
|
|
5a862ab742 | ||
|
|
7e6f077b0a | ||
|
|
b6676c9d58 | ||
|
|
37099156f3 | ||
|
|
018a2c080b | ||
|
|
890ef37b3f | ||
|
|
c7de3866c7 | ||
|
|
4cf11743f2 | ||
|
|
3ee3a28fd5 | ||
|
|
39ba048d07 | ||
|
|
40a9229681 | ||
|
|
8a7873d0c9 | ||
|
|
996ddb4360 | ||
|
|
eec11c6ec5 | ||
|
|
197cbb1808 | ||
|
|
32df7f3eae | ||
|
|
4b3839b143 | ||
|
|
79216eaa8d | ||
|
|
ba2998541d | ||
|
|
d5aaa4dc45 | ||
|
|
999e4f81ee | ||
|
|
c38d2f531c | ||
|
|
27d8af3551 | ||
|
|
b10ce1b884 | ||
|
|
18c207f39b | ||
|
|
898df454d5 | ||
|
|
577c5ca055 | ||
|
|
04558e93b3 | ||
|
|
6b0a88cd95 | ||
|
|
bd125df278 | ||
|
|
fc31881150 | ||
|
|
9a13b06452 | ||
|
|
01e3a726fe | ||
|
|
bcaa318941 | ||
|
|
c64e5c2d5e | ||
|
|
caafcf61aa | ||
|
|
e6280377ce | ||
|
|
a2e66550bf | ||
|
|
abad45f874 | ||
|
|
fa649c1cd0 | ||
|
|
96ad12a1b7 | ||
|
|
669979531c | ||
|
|
0c81f5c36c | ||
|
|
7dfc48744c | ||
|
|
943d95eed0 | ||
|
|
4b6e5e32d1 | ||
|
|
a1132fbe90 | ||
|
|
ea37370738 | ||
|
|
3b73f86e1b | ||
|
|
1633dedee3 | ||
|
|
9ae41c67a2 | ||
|
|
58819f48bc | ||
|
|
1f2cc92533 | ||
|
|
2e550b95ca | ||
|
|
6f27a0db4d | ||
|
|
ace7998c40 | ||
|
|
969d65bbb2 | ||
|
|
dd3c6abdd2 | ||
|
|
45c50c1f6e | ||
|
|
93d284dc82 | ||
|
|
9f6269c3e8 | ||
|
|
54ed9cfe22 | ||
|
|
efe0b7ace0 | ||
|
|
96eeac14ea | ||
|
|
85409f1616 | ||
|
|
f7f020cc26 | ||
|
|
889173a682 | ||
|
|
e2fab353fd | ||
|
|
41a0769cca | ||
|
|
f73fac0eb4 | ||
|
|
3047e40116 | ||
|
|
24c45c7cc2 | ||
|
|
88b2ce84d6 | ||
|
|
9c55543f1b | ||
|
|
76eac28192 | ||
|
|
b3d2c28f06 | ||
|
|
a4c0a56734 | ||
|
|
fc78637d2f | ||
|
|
7605706b89 | ||
|
|
862f3e4e9d | ||
|
|
64de8327a6 | ||
|
|
c8d5624600 | ||
|
|
abe2d7d10a | ||
|
|
27dbddac94 | ||
|
|
cfd18eef5c | ||
|
|
e2b30b8f71 | ||
|
|
0f86978d8a | ||
|
|
1e7c21bdcf | ||
|
|
9e8fa209d5 | ||
|
|
83caef1a97 | ||
|
|
056daed06b | ||
|
|
ab31c8fa9d | ||
|
|
f95fb7d08b | ||
|
|
b2939f511d | ||
|
|
e1a08629a4 | ||
|
|
9c5eba7372 | ||
|
|
97ecf45da3 | ||
|
|
cf4321492e | ||
|
|
d5f996d249 | ||
|
|
dc65f639c7 | ||
|
|
64aa9fe0ae | ||
|
|
73f65505b0 | ||
|
|
49dc938eb3 | ||
|
|
687b547b47 | ||
|
|
4b85ef2a99 | ||
|
|
be7ef159cd | ||
|
|
8b6f0fb8eb | ||
|
|
ef29c61fb0 | ||
|
|
77ba81d621 | ||
|
|
4d645f761c | ||
|
|
b0aeaaabb4 | ||
|
|
80661e1646 | ||
|
|
f53e4495de | ||
|
|
76ee1e776f | ||
|
|
81c880efb1 | ||
|
|
3b3c955e2c | ||
|
|
d786509a06 | ||
|
|
7e682810fb | ||
|
|
446fc7dc35 | ||
|
|
76ccc2f8b1 | ||
|
|
6f99e6b840 | ||
|
|
08d6b11add | ||
|
|
dccf161d54 | ||
|
|
c648a8ccbc | ||
|
|
83dc9b688b | ||
|
|
2d53623ab2 | ||
|
|
d0d14b9006 | ||
|
|
db85958008 | ||
|
|
58866b0595 | ||
|
|
f6f2d5e4de | ||
|
|
118aa4bda1 | ||
|
|
ba9b4617f7 | ||
|
|
a3646e83c8 | ||
|
|
b51b8e9c63 | ||
|
|
3638372602 | ||
|
|
647a0d8a2f | ||
|
|
0bcaf9cb41 | ||
|
|
52c6fb5d06 | ||
|
|
a1a192946c | ||
|
|
713b199f21 | ||
|
|
2364e8dd26 | ||
|
|
60f98c84d5 | ||
|
|
dfea87ed0e | ||
|
|
3010657620 | ||
|
|
35477a5edb | ||
|
|
793bb97909 | ||
|
|
ca4e9c2ea5 | ||
|
|
fa68e989c4 | ||
|
|
47291206ae | ||
|
|
77578174b7 | ||
|
|
f0f7b9e6e4 | ||
|
|
dbb87a6a79 | ||
|
|
8d8c1c505c | ||
|
|
970db2b6d1 | ||
|
|
330e0818fa | ||
|
|
7f4f48aa32 | ||
|
|
fb4300fe4d | ||
|
|
4e89b94916 | ||
|
|
0f76d42b8c | ||
|
|
6cc3060dc2 | ||
|
|
83d629c397 | ||
|
|
b7fe9ba794 | ||
|
|
c0594c5449 | ||
|
|
0eb562adf9 | ||
|
|
181bd88902 | ||
|
|
3d6cfd2457 | ||
|
|
bcb5191ecc | ||
|
|
5581283c52 | ||
|
|
c3cb530aa1 | ||
|
|
5cc0665a9d | ||
|
|
6c0dde8e79 | ||
|
|
a009e06fdf | ||
|
|
4282169fc9 | ||
|
|
7c76e72a7f | ||
|
|
e22e514d29 | ||
|
|
edfbc6ac95 | ||
|
|
a23a1fa928 | ||
|
|
8b2bb48dbc | ||
|
|
930183b0aa | ||
|
|
bfbf1c5e71 | ||
|
|
67684ee0e6 | ||
|
|
336ccd2ec4 | ||
|
|
21f4a79dd5 | ||
|
|
3e94b2854c | ||
|
|
9539060404 | ||
|
|
9f73524dd9 | ||
|
|
321360e180 | ||
|
|
8ac3d0a2a6 | ||
|
|
b80118c6d0 | ||
|
|
d795812fd4 | ||
|
|
aa945e491c | ||
|
|
1e566808cb | ||
|
|
6eaa1e9edd | ||
|
|
a2d53ce217 | ||
|
|
39707005bc | ||
|
|
dc8d24322f | ||
|
|
912ee35721 | ||
|
|
9321eea8f8 | ||
|
|
347503d96f | ||
|
|
fca3aade63 | ||
|
|
a2cd1fa8ef | ||
|
|
d9db3a6edf | ||
|
|
1d749eba48 | ||
|
|
22917b7e0a | ||
|
|
e4ca35cae8 | ||
|
|
0c74ee2602 | ||
|
|
40e533d9d8 | ||
|
|
01f4bb9494 | ||
|
|
e386c8e87c | ||
|
|
bf3b1b03e8 | ||
|
|
a69c1534f6 | ||
|
|
41671d7acf | ||
|
|
a909fc1fa8 | ||
|
|
919a49b22e | ||
|
|
9ae599cf05 | ||
|
|
5aad37c613 | ||
|
|
69e580e37d | ||
|
|
c735195d33 | ||
|
|
857f0d2dcc | ||
|
|
9b6e1f4c64 | ||
|
|
8a41b35317 | ||
|
|
f08cb0f0b5 | ||
|
|
bbf1e373ad | ||
|
|
069403ab6c | ||
|
|
73ac435b92 | ||
|
|
4b439dc7bb | ||
|
|
4c7879f423 | ||
|
|
fc7540ed1e | ||
|
|
08fd9b98c8 | ||
|
|
1af8cee719 | ||
|
|
ca6c9c454d | ||
|
|
8adcc14422 | ||
|
|
65777750ab | ||
|
|
96b437c0b4 | ||
|
|
d3ae5c38d9 | ||
|
|
c6452b2fea | ||
|
|
caf1900484 | ||
|
|
2e79d34f75 | ||
|
|
28566c4272 | ||
|
|
28fd2d18fa | ||
|
|
df7ce97d40 | ||
|
|
5b2504ba3f | ||
|
|
cbf4d17c7f | ||
|
|
642ba05dcf | ||
|
|
b2b5e87aed | ||
|
|
3562f3192f | ||
|
|
e58d8b35b8 | ||
|
|
2761a3778a | ||
|
|
1eaa8d9b67 | ||
|
|
169b292301 | ||
|
|
249cfdb78d | ||
|
|
3f34e17bd4 | ||
|
|
196b48cbd3 | ||
|
|
01bfa59c6a | ||
|
|
a921ef1ec7 | ||
|
|
d30f2b9d16 | ||
|
|
8f887e5242 | ||
|
|
f742a65af8 | ||
|
|
868ba6d232 | ||
|
|
47384bec0e | ||
|
|
2144de87d4 | ||
|
|
7a3c9c5b49 | ||
|
|
df35468771 | ||
|
|
5ac9d3fdff | ||
|
|
de73ed1986 | ||
|
|
da2a92b74a | ||
|
|
9aba2db8ce | ||
|
|
5141be475b | ||
|
|
9f8e7616d1 | ||
|
|
f7c7cdef8c | ||
|
|
98e529304f | ||
|
|
c04290493e | ||
|
|
12695faba2 | ||
|
|
c1a064e40a | ||
|
|
7798594071 | ||
|
|
34281c2850 | ||
|
|
17365a6e2c | ||
|
|
9b150e3263 | ||
|
|
a1648c70c6 | ||
|
|
1a0aa37992 | ||
|
|
ad4772ba01 | ||
|
|
78b6e16eb3 | ||
|
|
682c61d025 | ||
|
|
b026479f78 | ||
|
|
1d6fbba6d2 | ||
|
|
142a47346d | ||
|
|
406d1023e8 | ||
|
|
895db0526b | ||
|
|
d2ecf48af9 | ||
|
|
edca8bec50 | ||
|
|
b99571c28f | ||
|
|
70d9d9273b | ||
|
|
bcd0b0cd46 | ||
|
|
49238a1283 | ||
|
|
7a11129a5e | ||
|
|
395fc13142 | ||
|
|
3b594d239c | ||
|
|
e7701ee0f9 | ||
|
|
80e8f5db8a | ||
|
|
608eea8e7d | ||
|
|
7dae92b042 | ||
|
|
62bc67cbe1 | ||
|
|
fbcbf0afe5 | ||
|
|
6b06873568 | ||
|
|
2007a8d05c | ||
|
|
1007772ef3 | ||
|
|
23785ea8b6 | ||
|
|
10a6b43a97 | ||
|
|
72e1d8177c | ||
|
|
6ab9bfa0fa | ||
|
|
faeb73d8b7 | ||
|
|
beff3d4832 | ||
|
|
29a4fdb47a | ||
|
|
931411b6e4 | ||
|
|
8157172a9f | ||
|
|
5ac73d9f90 | ||
|
|
27085243c4 | ||
|
|
16b2fd93d8 | ||
|
|
edce6ed9e8 | ||
|
|
e9b6db6994 | ||
|
|
f5a1671e86 | ||
|
|
560d8ec5c0 | ||
|
|
4e7a3fe515 | ||
|
|
1ac28b5203 | ||
|
|
9b69d79a78 | ||
|
|
9e78a4ef99 | ||
|
|
8d2ef41d54 | ||
|
|
c507e960fd | ||
|
|
490f700e37 | ||
|
|
355f379f21 | ||
|
|
e69f8a2e7a | ||
|
|
450d5b6144 | ||
|
|
41fdc485b4 | ||
|
|
4d25aee6dc | ||
|
|
46574f8137 | ||
|
|
71cbd1221b | ||
|
|
7386a9b614 | ||
|
|
d5cfc39744 | ||
|
|
8a1770ffda | ||
|
|
01e51ecb73 | ||
|
|
43a62813e6 | ||
|
|
1b25438fca | ||
|
|
5858de4860 | ||
|
|
8128a3374b | ||
|
|
32cc860eda | ||
|
|
03d6687c64 | ||
|
|
51924d16db | ||
|
|
cd167313b7 | ||
|
|
365142c860 | ||
|
|
990f6f148a | ||
|
|
1eb6f33207 | ||
|
|
2d23a5b37d | ||
|
|
65f400cf46 | ||
|
|
84dfdfc11c | ||
|
|
ecb1cb8a0e | ||
|
|
b9a8da1231 | ||
|
|
27c9bee138 | ||
|
|
9902718082 | ||
|
|
270ab5e843 | ||
|
|
62961889d5 | ||
|
|
0217c4b4b2 | ||
|
|
1c950a8a57 | ||
|
|
f5563ccedb | ||
|
|
0672477817 | ||
|
|
c28a1f740e | ||
|
|
e0f3b65251 | ||
|
|
001acda866 | ||
|
|
eb9ceb4dc8 | ||
|
|
d622f9075a | ||
|
|
76699ad323 | ||
|
|
a5fc5c1e40 | ||
|
|
cb9302b2fe | ||
|
|
254ddf8dfb | ||
|
|
4995cc8876 | ||
|
|
1f120a7faf | ||
|
|
4411910930 | ||
|
|
931dc04ad2 | ||
|
|
e6b390addc | ||
|
|
a4027c0ad8 | ||
|
|
3451fbf42a | ||
|
|
65e3c24de6 | ||
|
|
90e74c1c3a | ||
|
|
4610729a82 | ||
|
|
ab8c2c8b10 | ||
|
|
bf0a5e13ae | ||
|
|
e4f8f7852f | ||
|
|
348365b8fd | ||
|
|
5c2f62defa | ||
|
|
bd5a98fe13 | ||
|
|
8a0da67705 | ||
|
|
479cbbf671 | ||
|
|
6708ca00b5 | ||
|
|
53e08bbf55 | ||
|
|
d389050a66 | ||
|
|
5d4f628b8b | ||
|
|
2ecbd26477 | ||
|
|
427e0d895f | ||
|
|
dfb86d40a5 | ||
|
|
b08c7cc9e6 | ||
|
|
2bbe9464b8 | ||
|
|
ef5937d5ee | ||
|
|
289846a53a | ||
|
|
6bc361bd0c | ||
|
|
47274b0085 | ||
|
|
d3ebd83c32 | ||
|
|
b076a57811 | ||
|
|
e9285ad46d | ||
|
|
5d91a1159e | ||
|
|
ee65c22a40 | ||
|
|
1cb5c9a711 | ||
|
|
f22a3e081d | ||
|
|
fff6a529e5 | ||
|
|
2260957b00 | ||
|
|
42c845bb72 | ||
|
|
8b7ae19508 | ||
|
|
c13274d0a7 | ||
|
|
2a424ca3a9 | ||
|
|
8effb2cb76 | ||
|
|
a03ce0e82c | ||
|
|
c0913a1b05 | ||
|
|
f504581468 | ||
|
|
f4528151ac | ||
|
|
2475ea9ec9 | ||
|
|
cc380a99f1 | ||
|
|
7c34720cf7 | ||
|
|
6debbe6853 | ||
|
|
d9cf784498 | ||
|
|
12b8ec740b | ||
|
|
ff6d492857 | ||
|
|
4ea29526b1 | ||
|
|
aae3772a3a | ||
|
|
3639ced11f | ||
|
|
a5dfcd4eb6 | ||
|
|
204191973e | ||
|
|
39b55f9045 | ||
|
|
f02bb9d4ba | ||
|
|
8b897c8a55 | ||
|
|
f25bc35ee3 | ||
|
|
81a41fb941 | ||
|
|
c714ac2175 | ||
|
|
f131bcb0d9 | ||
|
|
07627b9320 | ||
|
|
f32dab48a4 | ||
|
|
61abb8cf0d | ||
|
|
bbbee3421d | ||
|
|
972a79309b | ||
|
|
d318606102 | ||
|
|
8b057ea199 | ||
|
|
edaa9f885a | ||
|
|
616760cbbb | ||
|
|
6a120f5839 | ||
|
|
3fa7546ba6 | ||
|
|
b7fd4d094c | ||
|
|
fb92e06d62 | ||
|
|
744573817a | ||
|
|
b84dc7b721 | ||
|
|
e88191c54a | ||
|
|
a32bc8c378 | ||
|
|
cb5be7cec7 | ||
|
|
209eb9c397 | ||
|
|
58b6b1cd0c | ||
|
|
b8c27ec2a9 | ||
|
|
a17c42e07e | ||
|
|
cb9a2d7b8b | ||
|
|
a7b215fa19 | ||
|
|
367170d33b | ||
|
|
695cb1496d | ||
|
|
741ca2646e | ||
|
|
2ac4b923fe | ||
|
|
cac15b1436 | ||
|
|
e77281cfb6 | ||
|
|
81d0a71060 | ||
|
|
66cb1c28f7 | ||
|
|
051e3eb922 | ||
|
|
bd93dd543e | ||
|
|
1382ef1724 | ||
|
|
6004850792 | ||
|
|
79d43f76b3 | ||
|
|
d387287a04 | ||
|
|
413e209b0b | ||
|
|
1124069e52 | ||
|
|
3d0009f23e | ||
|
|
4df0cf1049 | ||
|
|
eb91e10661 | ||
|
|
88dbfba282 | ||
|
|
f6358f10f2 | ||
|
|
51b5c81aac | ||
|
|
20ff40896f | ||
|
|
df78d375fe | ||
|
|
206194353c | ||
|
|
dcd16704bd | ||
|
|
2b6b2a5e95 | ||
|
|
c767a3b3ad | ||
|
|
06a4eac1c9 | ||
|
|
ffba3ef18a | ||
|
|
1a6d6e1290 | ||
|
|
e75dedcffa | ||
|
|
a6e50452f0 | ||
|
|
008dd2f1d7 | ||
|
|
82c3f32a5e | ||
|
|
5c0a03e242 | ||
|
|
e25ce911ed | ||
|
|
b032002c33 | ||
|
|
667bd138c8 | ||
|
|
f0f017713e | ||
|
|
331ae6ff44 | ||
|
|
ee05f34963 | ||
|
|
2f6aa3dc0f | ||
|
|
c9c5ae0058 | ||
|
|
bff5a13c25 | ||
|
|
68781783c4 | ||
|
|
04ba6bdd7b | ||
|
|
f557420c7a | ||
|
|
8d2d633f6d | ||
|
|
39ed24d133 | ||
|
|
94ec700c78 | ||
|
|
92ac19fa2e | ||
|
|
c96aeb25e5 | ||
|
|
63ebd462b9 | ||
|
|
68fe024d5a | ||
|
|
12c0207f22 | ||
|
|
402eade4c2 | ||
|
|
c3f88773d2 | ||
|
|
5c689ebf9b | ||
|
|
efa99cb9ac | ||
|
|
19a96aac00 | ||
|
|
6c84d433f6 | ||
|
|
63c4ab822c | ||
|
|
f343397a68 | ||
|
|
3993f5a715 | ||
|
|
ac66b6ac1b | ||
|
|
de5c07d74b | ||
|
|
4f8a9b4d3f | ||
|
|
1e643b3a99 | ||
|
|
eddbe33f2e | ||
|
|
6b07e07bea | ||
|
|
8ff8502fed | ||
|
|
31ee93a2a0 | ||
|
|
ce17685154 | ||
|
|
4927e6135d | ||
|
|
6725873c8c | ||
|
|
90b1a244a5 | ||
|
|
c3a7191f31 | ||
|
|
e3a2a98c2f | ||
|
|
35aaafeba6 | ||
|
|
0c9d3c9152 | ||
|
|
6a93608427 | ||
|
|
c2b84ce240 | ||
|
|
e6b876314c | ||
|
|
9cb73fe9fc | ||
|
|
f4f0ae990f | ||
|
|
50c314af7d | ||
|
|
bd18a5bb41 | ||
|
|
263faa9895 | ||
|
|
36cc45b227 | ||
|
|
66ce27593b | ||
|
|
9a62690627 | ||
|
|
26dcee20dc | ||
|
|
8c73de6f94 | ||
|
|
8a3a3d05c4 | ||
|
|
11d78d0b44 | ||
|
|
ae3fb2d41e | ||
|
|
54d66c0119 | ||
|
|
beb37bad9a | ||
|
|
cb25353e60 | ||
|
|
7bd24f52c7 | ||
|
|
bfd646f409 | ||
|
|
ec5ade7b38 | ||
|
|
4bb6a64153 | ||
|
|
e7a29f6626 | ||
|
|
7d168d7f24 | ||
|
|
8b0e10442f | ||
|
|
10ed279650 | ||
|
|
18300fb1a2 | ||
|
|
82543ee67e | ||
|
|
58c8a23279 | ||
|
|
6b7db83a40 | ||
|
|
3d5a7e9bc7 | ||
|
|
d1e8f9db6b | ||
|
|
532e6bee5b | ||
|
|
a7af611dfb | ||
|
|
de1296f750 | ||
|
|
43e1c20046 | ||
|
|
b285205376 | ||
|
|
db4ec5eb07 | ||
|
|
51e6b6c908 | ||
|
|
e57ab4cdb0 | ||
|
|
c4d4a7f2c3 | ||
|
|
66045abf16 | ||
|
|
019af5d238 | ||
|
|
d71d10a97f | ||
|
|
f374466d0a | ||
|
|
97d9c57951 | ||
|
|
b651c1f9e9 | ||
|
|
d448b59dcc | ||
|
|
06ccc2d9c6 | ||
|
|
bf2ee271fd | ||
|
|
b9486e8853 | ||
|
|
7911f9bd20 | ||
|
|
6753039b92 | ||
|
|
802eff0796 | ||
|
|
ff8141705d | ||
|
|
b28f8f50e1 | ||
|
|
49a1f9527c | ||
|
|
240f5e6419 | ||
|
|
2f26cbe676 | ||
|
|
2a304bd413 | ||
|
|
189b0ea122 | ||
|
|
70b24f9b86 | ||
|
|
6c11701032 | ||
|
|
d4a28c0e7b | ||
|
|
0e50de91b4 | ||
|
|
ac78b96c2b | ||
|
|
aca091e3c3 | ||
|
|
572e7f5be8 | ||
|
|
ef4ab9570a | ||
|
|
6207299219 | ||
|
|
8eb8c666b6 | ||
|
|
8f40e8ae04 | ||
|
|
b93131b990 | ||
|
|
6bce621da4 | ||
|
|
9e9bc5193f | ||
|
|
178f937727 | ||
|
|
d94abfaef9 | ||
|
|
c03da7eb74 | ||
|
|
c39313d973 | ||
|
|
a0c126f39e | ||
|
|
354d184664 | ||
|
|
0e49d103b1 | ||
|
|
fbbae218c0 | ||
|
|
12387f0efb | ||
|
|
41be0bb069 | ||
|
|
6edcb95aab | ||
|
|
f022419ceb | ||
|
|
9232042b02 | ||
|
|
1c642a3cb2 | ||
|
|
b5e86473a4 | ||
|
|
8417f5cb7a | ||
|
|
f434be45b6 | ||
|
|
6d493aeb9c | ||
|
|
b4052039e3 | ||
|
|
727689a3bd | ||
|
|
b86f9792f3 | ||
|
|
83f4a5ea73 | ||
|
|
80ddea2c8c | ||
|
|
6618f71297 | ||
|
|
5c1c039188 | ||
|
|
af1afb5348 | ||
|
|
5bbd527837 | ||
|
|
2eb71fe1c9 | ||
|
|
e1157ad01a | ||
|
|
e5df3b9cb3 | ||
|
|
a52f7dc540 | ||
|
|
eff69eba7d | ||
|
|
f8a41b39ec | ||
|
|
cd31806100 | ||
|
|
e5d588c4eb | ||
|
|
3b0483752c | ||
|
|
5ea5ca0ef7 | ||
|
|
08bd2c4724 | ||
|
|
1c1a4d92f4 | ||
|
|
37e5dda38f | ||
|
|
be6232ee82 | ||
|
|
651fa42e4a | ||
|
|
c69d261e56 | ||
|
|
54446949a5 | ||
|
|
161d4ed314 | ||
|
|
a88c08bf3f | ||
|
|
eae4a0be35 | ||
|
|
0e28b40c25 | ||
|
|
f8adbcc252 | ||
|
|
6f9d557a64 | ||
|
|
ac939de42f | ||
|
|
82756e1073 | ||
|
|
1b14148b0f | ||
|
|
0cfb5eff0e | ||
|
|
ce3bab8a33 | ||
|
|
e56b8545c7 | ||
|
|
ac7fc21db5 | ||
|
|
48bc2174a4 | ||
|
|
d11d58889b | ||
|
|
8cf01c318a | ||
|
|
e18007e902 | ||
|
|
1f13b8d43d | ||
|
|
c6b82043d0 | ||
|
|
671937ff7f | ||
|
|
27478966bc | ||
|
|
442ea94cde | ||
|
|
13bd24ef83 | ||
|
|
32240b6004 | ||
|
|
abd0880c66 | ||
|
|
bb6c5f8e44 | ||
|
|
66d0141fae | ||
|
|
57cf7a582b | ||
|
|
706827fc0c | ||
|
|
54009dcb06 | ||
|
|
1049deb299 | ||
|
|
19b928782a | ||
|
|
4aafafb5bd | ||
|
|
65ff5a8c51 | ||
|
|
d2449c181a | ||
|
|
9527a33a58 | ||
|
|
419a513d8f | ||
|
|
a1e7ecbecc | ||
|
|
f996c90653 | ||
|
|
be2a7f5a51 | ||
|
|
a6ca193ae0 | ||
|
|
ff7f81c307 | ||
|
|
cad42948cd | ||
|
|
5ad88099e1 | ||
|
|
c849c2b867 | ||
|
|
1571263575 | ||
|
|
a335d60fcb | ||
|
|
c78c182e32 | ||
|
|
8ff5d05078 | ||
|
|
80a1c13a07 | ||
|
|
0b6ed46a0a | ||
|
|
cdaba3483a | ||
|
|
3681dba5fb | ||
|
|
ddbe5af21c | ||
|
|
da4263131e | ||
|
|
f3b771cb7d | ||
|
|
39c764e283 | ||
|
|
9d4e02c2d4 | ||
|
|
34ab157046 | ||
|
|
e76403f54f | ||
|
|
3f7b1ce204 | ||
|
|
0eff1225b8 | ||
|
|
4e2d024b2a | ||
|
|
1db289c35e | ||
|
|
c8a4a41eaf | ||
|
|
6221f87b8c | ||
|
|
ab3b682c47 | ||
|
|
38d6ab9895 | ||
|
|
4d6da7454c | ||
|
|
19ac30eed0 | ||
|
|
84a7b79e3c | ||
|
|
e2fc8015b8 | ||
|
|
5aac30f44a | ||
|
|
d9340349fa | ||
|
|
82019b5f40 | ||
|
|
06e0a82927 | ||
|
|
53409a6c70 | ||
|
|
59b68d80ca | ||
|
|
4f38f5a389 | ||
|
|
7a0bb07fc1 | ||
|
|
b1d3e712be | ||
|
|
bc0cd4a783 | ||
|
|
d4dc65c45f | ||
|
|
dd40a1de6b | ||
|
|
ea65a3423e | ||
|
|
04811da9cc | ||
|
|
46a4243093 | ||
|
|
03bee3e11e | ||
|
|
20491e1136 | ||
|
|
4ea01ea135 | ||
|
|
8440f72e9e | ||
|
|
65291259ee | ||
|
|
d42ee05740 | ||
|
|
c4bf644240 | ||
|
|
fe95ad2e11 | ||
|
|
644f80df57 | ||
|
|
46e5c3233a | ||
|
|
1da27edde6 | ||
|
|
75da8833dc | ||
|
|
c40a132811 | ||
|
|
288267ae11 | ||
|
|
8f1a493327 | ||
|
|
f97adc325f | ||
|
|
8aa72e9397 | ||
|
|
3ab7886eab | ||
|
|
551968a38c | ||
|
|
e954102f81 | ||
|
|
5a1d5c0a2d | ||
|
|
42b553266a | ||
|
|
d98d2915ca | ||
|
|
634dced5aa | ||
|
|
5ee94199bb | ||
|
|
23422220ce | ||
|
|
c141462432 | ||
|
|
41abf876a2 | ||
|
|
28793abc7a | ||
|
|
1ce213004a | ||
|
|
e223fa2002 | ||
|
|
6fc542ff7d | ||
|
|
a1dd4ab0e9 | ||
|
|
1232f1be28 | ||
|
|
738c7b968a | ||
|
|
d6e7d1ffee | ||
|
|
6fcdee14e4 | ||
|
|
8c139ad044 | ||
|
|
122baba999 | ||
|
|
27b12f5263 | ||
|
|
a801424b35 | ||
|
|
717a3eaede | ||
|
|
0061f35b46 | ||
|
|
ff20646a63 | ||
|
|
a5f4375ff2 | ||
|
|
99b2cbb606 | ||
|
|
15e8eee373 | ||
|
|
4cd2bc266a | ||
|
|
92472c815a | ||
|
|
a246257eff | ||
|
|
b6bab552be | ||
|
|
1afce00069 | ||
|
|
9ddfdf3638 | ||
|
|
06230ffa40 | ||
|
|
cac7ac2a8a | ||
|
|
fbbe4f5579 | ||
|
|
7d8373643f | ||
|
|
97bf88421e | ||
|
|
c067ab7cc9 | ||
|
|
b21e4f5296 | ||
|
|
ea0add13ad | ||
|
|
bfc3157186 | ||
|
|
a5aeb43305 | ||
|
|
2b77a15845 | ||
|
|
0d1b751600 | ||
|
|
754e2a7aa7 | ||
|
|
731f375332 | ||
|
|
faa8013208 | ||
|
|
90afe81856 | ||
|
|
c13e379908 | ||
|
|
689d3bb9b5 | ||
|
|
9ae8fcf3bc | ||
|
|
74f3a0a5ed | ||
|
|
d51e20bffe | ||
|
|
c5d6214ab2 | ||
|
|
898921ea4f | ||
|
|
8c6194da35 | ||
|
|
8b2a3ccb42 | ||
|
|
820c869e2c | ||
|
|
9a21a3e2a4 | ||
|
|
03e47cee56 | ||
|
|
1a28f15f75 | ||
|
|
72d5c74631 | ||
|
|
322ebdf8a6 | ||
|
|
3d3296d1b7 | ||
|
|
e3715c3f81 | ||
|
|
db9e019519 | ||
|
|
3a2ea56c92 | ||
|
|
df6b552ac1 | ||
|
|
9bb4d7ed93 | ||
|
|
562ef1b147 | ||
|
|
6d31a2f656 | ||
|
|
62c16d9f08 | ||
|
|
db17973ea6 | ||
|
|
8155376c45 | ||
|
|
ae26c7bc7a | ||
|
|
fa4e4cb2bb | ||
|
|
7842b7e0fd | ||
|
|
52d6245b4a | ||
|
|
4c03d4f0b4 | ||
|
|
de8aaf35a3 | ||
|
|
dda5d8206f | ||
|
|
f8ca2a50b7 | ||
|
|
329f4072be | ||
|
|
a88b392a43 | ||
|
|
28ec418c76 | ||
|
|
ce56b87b16 | ||
|
|
6d3155dff9 | ||
|
|
9fee1fda1e | ||
|
|
1e1c5fa139 | ||
|
|
70fb1090ca | ||
|
|
6f13aa4724 | ||
|
|
584f923c4c | ||
|
|
3d1c39da30 | ||
|
|
a3148c9825 | ||
|
|
40e33a43a9 | ||
|
|
a3fc74c44e | ||
|
|
3b28b5fa1c | ||
|
|
6070dc9db9 | ||
|
|
7608f02c21 | ||
|
|
cec5ec0a78 | ||
|
|
c72c683915 | ||
|
|
3384c773da | ||
|
|
8dfc16880e | ||
|
|
5d65861c49 | ||
|
|
179b1419ab | ||
|
|
4bebc8486c | ||
|
|
fd0f6e4113 | ||
|
|
8e4c40ba2a | ||
|
|
abf1a92977 | ||
|
|
e9ff6ece35 | ||
|
|
ba02ce9261 | ||
|
|
0ece4427b6 | ||
|
|
9afc192154 | ||
|
|
27691d8563 | ||
|
|
0ebc6dd2d8 | ||
|
|
5a2f57172f | ||
|
|
47d30c81b6 | ||
|
|
b80f78d12e | ||
|
|
835ffaafd4 | ||
|
|
66f0d580b9 | ||
|
|
524998811c | ||
|
|
1b3a8d0cf5 | ||
|
|
0134d343b2 | ||
|
|
202ddd8157 | ||
|
|
76159b2cc4 | ||
|
|
620e95cebf | ||
|
|
5f5357d28a | ||
|
|
faee08bb90 | ||
|
|
178f98c82f | ||
|
|
d0dc16aa6f | ||
|
|
77e56e8da9 | ||
|
|
045d7bb5ae | ||
|
|
135f043b60 | ||
|
|
682884355f | ||
|
|
702b473453 | ||
|
|
3f803a3f48 | ||
|
|
742be996fe | ||
|
|
228d929a5c | ||
|
|
703fddeb65 | ||
|
|
be89397e48 | ||
|
|
2a71716ba4 | ||
|
|
b8b5561850 | ||
|
|
88694f4256 | ||
|
|
4d7126d3c3 | ||
|
|
87ab6c1de2 | ||
|
|
e649c9128e | ||
|
|
9b774734c3 | ||
|
|
841164bafb | ||
|
|
bc61ec0495 | ||
|
|
c37bc213e5 | ||
|
|
080639d2d9 | ||
|
|
ab2d0771ab | ||
|
|
204d502c24 | ||
|
|
985b60a565 | ||
|
|
8202c4abe3 | ||
|
|
4de73cebe0 | ||
|
|
9bb6e833fb | ||
|
|
d227da0c10 | ||
|
|
962330d491 | ||
|
|
c5d9a98549 | ||
|
|
9b83c78346 | ||
|
|
8a7d0d4242 | ||
|
|
45440adc01 | ||
|
|
8feaf9ed3e | ||
|
|
636020d870 | ||
|
|
3150aa2e7c | ||
|
|
ce22449f41 | ||
|
|
1c938b5d29 | ||
|
|
0493104beb | ||
|
|
9786d9f927 | ||
|
|
73e5254e2b | ||
|
|
7c934d29ac | ||
|
|
bccbf3e55e | ||
|
|
8196ee531a | ||
|
|
39729cb3da | ||
|
|
cdc1f6014e | ||
|
|
aadfdac4c5 | ||
|
|
2e358116c2 | ||
|
|
71d88523b2 | ||
|
|
65e702aed7 | ||
|
|
99bb45d96a | ||
|
|
4f41be641a | ||
|
|
5ab030242b | ||
|
|
736d9bbd98 | ||
|
|
6295ebdb09 | ||
|
|
380a27b83d | ||
|
|
691af037a4 | ||
|
|
70cb78a67d | ||
|
|
4bd5fe0178 | ||
|
|
babd80edd9 | ||
|
|
74e4efba52 | ||
|
|
2d7e88a8c2 | ||
|
|
d82c8d466a | ||
|
|
9edbbf399f | ||
|
|
345316b9b0 | ||
|
|
0bb6636613 | ||
|
|
1929b75e34 | ||
|
|
245827825e | ||
|
|
fc9f81c5c9 | ||
|
|
987dce2643 | ||
|
|
4f2abad39d | ||
|
|
354000082b | ||
|
|
b8e87604c2 | ||
|
|
49f7ad1281 | ||
|
|
6731cdcc9d | ||
|
|
5306510ec1 | ||
|
|
e8c0655186 | ||
|
|
18d0bf1c7b | ||
|
|
0b9755405e | ||
|
|
8b7a183e63 | ||
|
|
7a094478f3 | ||
|
|
bd063c71f8 | ||
|
|
693ef6c4d3 | ||
|
|
60a02cd2a5 | ||
|
|
e25c7dac4a | ||
|
|
d918cade67 | ||
|
|
982400b74d | ||
|
|
c00a384591 | ||
|
|
b0e1630fc5 | ||
|
|
50ffd7b4a9 | ||
|
|
e4e75a5f73 | ||
|
|
7da4381d38 | ||
|
|
ef8076b626 | ||
|
|
ab5f5c1ce9 | ||
|
|
6d442b12a0 | ||
|
|
0019f83e33 | ||
|
|
f3011b1790 | ||
|
|
ea79f84b71 | ||
|
|
040d6929dc | ||
|
|
2fe9c38fff | ||
|
|
256b54b6b5 | ||
|
|
062ad5d92f | ||
|
|
5806ef248f | ||
|
|
257fcf1b3d | ||
|
|
284db016a0 | ||
|
|
5e2898f0ae | ||
|
|
207408ae20 | ||
|
|
2843ca9b49 | ||
|
|
efc09d8a3f | ||
|
|
e6714bf45e | ||
|
|
8049e2c322 | ||
|
|
fdb0db2d41 | ||
|
|
7465031d44 | ||
|
|
15eb3a421d | ||
|
|
ec52a07fe9 | ||
|
|
b7a475cc0a | ||
|
|
aed022d099 | ||
|
|
86b37e7df0 | ||
|
|
09e9b46efd | ||
|
|
90b685c15d | ||
|
|
daedffc0da | ||
|
|
c489f88f77 | ||
|
|
33779bb6cf | ||
|
|
660806fb7c | ||
|
|
40a52ed00b | ||
|
|
c216498198 | ||
|
|
547c887c0b | ||
|
|
dfe23da44d | ||
|
|
a5e8b88e32 | ||
|
|
539c94f36f | ||
|
|
fce5ae0498 | ||
|
|
8fbf96098e | ||
|
|
c8b43fa8ab | ||
|
|
9f305347a8 | ||
|
|
94d826d2a4 | ||
|
|
118cf70e40 | ||
|
|
895d8d594b | ||
|
|
60ce467859 | ||
|
|
6efa58006e | ||
|
|
f253019daf | ||
|
|
8600aceb64 | ||
|
|
a358af534a | ||
|
|
d080676997 | ||
|
|
b3412fcfb4 | ||
|
|
b5e3db279d | ||
|
|
a29ca46ca3 | ||
|
|
2fe54a28b1 | ||
|
|
6f9bc11078 | ||
|
|
cd5186cd0d | ||
|
|
34cdc299b7 | ||
|
|
274a3f78f2 | ||
|
|
0e31ad96a5 | ||
|
|
b191e4d83a | ||
|
|
16c554aa43 | ||
|
|
80550cd42e | ||
|
|
3ac28aa279 | ||
|
|
f16ea99cbc | ||
|
|
d4353bd237 | ||
|
|
8efa82abc2 | ||
|
|
a5f42f77ad | ||
|
|
384314d9ab | ||
|
|
56d63a46bf | ||
|
|
da00ccff58 | ||
|
|
313d5ab762 | ||
|
|
a569029c40 | ||
|
|
04748cf03f | ||
|
|
91871fb96d | ||
|
|
7292a12ff4 | ||
|
|
9b04e8ff38 | ||
|
|
b85a2ea6d2 | ||
|
|
da6c134540 | ||
|
|
e09da1a600 | ||
|
|
5aa1f9863d | ||
|
|
1eeb3637e8 | ||
|
|
79e229467e | ||
|
|
d483ea7f47 | ||
|
|
4e4b31a0d5 | ||
|
|
eb38c1ec15 | ||
|
|
b33406da4d | ||
|
|
10f4da71b8 | ||
|
|
6d27a8e489 | ||
|
|
c63477c34e | ||
|
|
761d1877fa | ||
|
|
cf4dbf6ed9 | ||
|
|
53e2df8aa2 | ||
|
|
07278de968 | ||
|
|
6710c47db0 | ||
|
|
a5cc820ea6 | ||
|
|
5eb95980bf | ||
|
|
57cc0e920a | ||
|
|
902b707b5e | ||
|
|
98600e9a19 | ||
|
|
fbf8ac4883 | ||
|
|
4c7a25407a | ||
|
|
af9359850d | ||
|
|
abe5638af5 | ||
|
|
c45e8a11d7 | ||
|
|
62ef84ea07 | ||
|
|
17b5098602 | ||
|
|
272a7afe9e | ||
|
|
9c7fa1a6a8 | ||
|
|
38a050dd13 | ||
|
|
377396d77c | ||
|
|
80fc8eddb9 | ||
|
|
d9c23ece15 | ||
|
|
84a913451f | ||
|
|
59794efc95 | ||
|
|
13977a07e5 | ||
|
|
b1eb8bd8ec | ||
|
|
69043cfd1c | ||
|
|
f34c713e7e | ||
|
|
e180bf21a7 | ||
|
|
fdaaa4be30 | ||
|
|
4db445dd3e | ||
|
|
530d4c0b08 | ||
|
|
100ce6ded9 | ||
|
|
03deb78aab | ||
|
|
bb220366be | ||
|
|
cd4e4a0892 | ||
|
|
aa42c302aa | ||
|
|
5128557e4e | ||
|
|
a181f5d1db | ||
|
|
5e3bc6c507 | ||
|
|
b11a9557d7 | ||
|
|
593e172832 | ||
|
|
6b62a7fde3 | ||
|
|
9a80028ec7 | ||
|
|
1252e22743 | ||
|
|
a3a25d9bef | ||
|
|
08fa3a5768 | ||
|
|
52cd521902 | ||
|
|
6dd5f1991f | ||
|
|
d3d29e03be | ||
|
|
2ddc88dd87 | ||
|
|
f768aa360f | ||
|
|
5ba12d009b | ||
|
|
6259891312 | ||
|
|
fad59e4a0f | ||
|
|
81852fe8c6 | ||
|
|
b33e262de6 | ||
|
|
7963919b72 | ||
|
|
e7f87478b4 | ||
|
|
c6b8d95747 | ||
|
|
8468c5ac52 | ||
|
|
27d7ae4b78 | ||
|
|
fffd2afe5e | ||
|
|
15ba670d5a | ||
|
|
7e7279a642 | ||
|
|
1b765e2408 | ||
|
|
db0ae9253e | ||
|
|
5573218794 | ||
|
|
6e38f16c83 | ||
|
|
4f0d2370ac | ||
|
|
f49f677107 | ||
|
|
cbb11ea723 | ||
|
|
566d011e2e | ||
|
|
1d2a62d06c | ||
|
|
743163fce7 | ||
|
|
afe5333c88 | ||
|
|
3bf15b5a35 | ||
|
|
1c9cfbc4bb | ||
|
|
65093b0388 | ||
|
|
e2320ea77c | ||
|
|
59f414f707 | ||
|
|
2a2c12cf77 | ||
|
|
5fd34dc967 | ||
|
|
65df6765da | ||
|
|
ff9729d27a | ||
|
|
9876741c50 | ||
|
|
a1f69f0732 | ||
|
|
8174b883a0 | ||
|
|
337065271e | ||
|
|
01dd0e7fc4 | ||
|
|
f3ee08ab9a | ||
|
|
ba08c09607 | ||
|
|
aecfd12911 | ||
|
|
c081e48467 | ||
|
|
237b0324c1 | ||
|
|
5aaf39f40b | ||
|
|
7a4ed29945 | ||
|
|
84014ce9b5 | ||
|
|
08c8e84d72 | ||
|
|
43171879a0 | ||
|
|
2f7a30f920 | ||
|
|
27a93b318d | ||
|
|
816bb2008a | ||
|
|
8eb430dffd | ||
|
|
641b5af8eb | ||
|
|
38ef1e4ed9 | ||
|
|
123c41b4e3 | ||
|
|
7001f17c88 | ||
|
|
1f10191bc7 | ||
|
|
ed5f5d3c6b | ||
|
|
a03bb7a353 | ||
|
|
44d4eae8a1 | ||
|
|
22ffcf5660 | ||
|
|
f40ea6cc8d | ||
|
|
adfcb2b4bf | ||
|
|
8d42be3bac | ||
|
|
e2036a5203 | ||
|
|
7107f05adb | ||
|
|
529fc42b72 | ||
|
|
7808605a8c | ||
|
|
0930013cc2 | ||
|
|
51054b70d6 | ||
|
|
471fb621e5 | ||
|
|
1e0c3e7621 | ||
|
|
a75d312770 | ||
|
|
0fd2ec58be | ||
|
|
be484a8077 | ||
|
|
3d8388ad92 | ||
|
|
7a5eac3621 | ||
|
|
ce34e937d6 | ||
|
|
c8646208f0 | ||
|
|
3ed83e4f59 | ||
|
|
ebe6a12555 | ||
|
|
1727456487 | ||
|
|
ef895777ca | ||
|
|
33fa10e9c1 | ||
|
|
1f9094caec | ||
|
|
9a5c58aae8 | ||
|
|
1b1e6c394d | ||
|
|
504872ac1e | ||
|
|
edc40f4a6c | ||
|
|
7b6e08f791 | ||
|
|
b184db2ee6 | ||
|
|
d10bf7fa35 | ||
|
|
ce20d21f4c | ||
|
|
48a35746a0 | ||
|
|
bf9afd4ba1 | ||
|
|
0eb2d59ecc | ||
|
|
b9bcfef817 | ||
|
|
27d01500fa | ||
|
|
d2ea3e4986 | ||
|
|
b0cecebe34 | ||
|
|
f4971589c6 | ||
|
|
506d64dc8b | ||
|
|
f56aa6cc29 | ||
|
|
a17ec8c2db | ||
|
|
a718986e2d | ||
|
|
242e0e7fdc | ||
|
|
75f7cb6df7 | ||
|
|
cf7d0792a2 | ||
|
|
29d260db9d | ||
|
|
d7dfad1fe8 | ||
|
|
82f404e627 | ||
|
|
c49b4c45c8 | ||
|
|
39f3feb210 | ||
|
|
4ed89e9515 | ||
|
|
0b6989c60c | ||
|
|
a9139c6023 | ||
|
|
2de580236e | ||
|
|
e5d4979f3e | ||
|
|
8f00de273a | ||
|
|
1a0f5a7460 | ||
|
|
b0229fb528 | ||
|
|
454f1aa00a | ||
|
|
12c25d786f | ||
|
|
7c65eca1fb | ||
|
|
6a87988d4c | ||
|
|
b65a17c867 | ||
|
|
f067fc9896 | ||
|
|
36cc53b4f8 | ||
|
|
497df9db96 | ||
|
|
26f66b0377 | ||
|
|
69c448f4e6 | ||
|
|
df08191857 | ||
|
|
bb88ff7649 | ||
|
|
7329bf8eef | ||
|
|
a58e409665 | ||
|
|
511a55e22c | ||
|
|
cfa2cfeded | ||
|
|
9d30414539 | ||
|
|
1b34de81eb | ||
|
|
827824500b | ||
|
|
35198ddbe8 | ||
|
|
546545a800 | ||
|
|
1023cc5cb2 | ||
|
|
094cf9e400 | ||
|
|
1bc834efe8 | ||
|
|
b4b039b2b0 | ||
|
|
d8e850673b | ||
|
|
35d7046d69 | ||
|
|
fad87abfea | ||
|
|
10d10ff18d | ||
|
|
161b99f252 | ||
|
|
87ff260c9f | ||
|
|
cc4dafa400 | ||
|
|
9d29904ec5 | ||
|
|
50be4f4ef8 | ||
|
|
5f9c05080f | ||
|
|
bbfcc87a68 | ||
|
|
d7e9510353 | ||
|
|
1a1057f56f | ||
|
|
93ff8cfe1b | ||
|
|
4d05e085ef | ||
|
|
d2d96d2ece | ||
|
|
78762cd479 | ||
|
|
cdaa37c5fd | ||
|
|
76140fb555 | ||
|
|
352c5ea3d3 | ||
|
|
f167d4fa3b | ||
|
|
7528cd3b58 | ||
|
|
cab8d0fb97 | ||
|
|
c7d9c174fd | ||
|
|
b7787c8cf6 | ||
|
|
4d218e3624 | ||
|
|
89fab45eb7 | ||
|
|
9bca338cc2 | ||
|
|
36c3c43f86 | ||
|
|
d9f762c485 | ||
|
|
3d206d1fb1 | ||
|
|
2a959f23d9 | ||
|
|
bb659d39db | ||
|
|
49cdd4d36d | ||
|
|
41d55cf780 | ||
|
|
5c5c948193 | ||
|
|
0e32dc982e | ||
|
|
9fffdb8cad | ||
|
|
76a1da2d0f | ||
|
|
ba012a4b0e | ||
|
|
09fd212e29 | ||
|
|
54fd4787fd | ||
|
|
023499a6c0 | ||
|
|
a8ad7f540b | ||
|
|
b7ee2c696b | ||
|
|
4528223dee | ||
|
|
cf729db0c0 | ||
|
|
a4deafa238 | ||
|
|
5e88eab617 | ||
|
|
950e51e0fe | ||
|
|
1c1d743638 | ||
|
|
ffa7e39121 | ||
|
|
84d6a24446 | ||
|
|
91c92d4b64 | ||
|
|
056229e8ee | ||
|
|
c4c5f7ab6b | ||
|
|
c7ab33f3ab | ||
|
|
3a3850ec4e | ||
|
|
84ed014909 | ||
|
|
39563682f5 | ||
|
|
53179f6e3f | ||
|
|
b1d5ba6787 | ||
|
|
361a3d0371 | ||
|
|
6b5b38535e | ||
|
|
8a4539b19a | ||
|
|
f6ee232d9e | ||
|
|
2562d28f4f | ||
|
|
f89cfa3ec8 | ||
|
|
d8b665f64f | ||
|
|
d473737ca8 | ||
|
|
52e7cc41e2 | ||
|
|
53978333fb | ||
|
|
183209baa3 | ||
|
|
0df4e1147c | ||
|
|
7c42b1f845 | ||
|
|
129e4762dc | ||
|
|
fc1c5ca6e9 | ||
|
|
8ae916ac28 | ||
|
|
0ccf9c961b | ||
|
|
b894e65618 | ||
|
|
801d31ed29 | ||
|
|
34fb9bb865 | ||
|
|
b5201e17ff | ||
|
|
204bbfc6c7 | ||
|
|
692fe0c885 | ||
|
|
3a372179d6 | ||
|
|
3de8bd49ad | ||
|
|
b9c02d5d99 | ||
|
|
185c4c09c9 | ||
|
|
ad709fd8f0 | ||
|
|
da7a1f34e1 | ||
|
|
47d2f3b6ad | ||
|
|
673759f17b | ||
|
|
0e2d6ac196 | ||
|
|
eb99915912 | ||
|
|
501df02260 | ||
|
|
d74f6dba0a | ||
|
|
b590c6b55a | ||
|
|
175f38f114 | ||
|
|
44f69b2b0b | ||
|
|
02a1b139bf | ||
|
|
326cf81a0f | ||
|
|
8952ddc9dc | ||
|
|
dfe2c6d064 | ||
|
|
fea2f30a1c | ||
|
|
5d8bd256f1 | ||
|
|
d7f41f4a56 | ||
|
|
9169ea6ebf | ||
|
|
20314c8952 | ||
|
|
f262075d2a | ||
|
|
37d9ea103b | ||
|
|
ef51f23308 | ||
|
|
11f75176b8 | ||
|
|
bd676c7c5c | ||
|
|
ccf51356a9 | ||
|
|
ece32cbe70 | ||
|
|
574e74350c | ||
|
|
4665f8c6c6 | ||
|
|
a1bdd1fc47 | ||
|
|
cdcb801b50 | ||
|
|
5f4bfed2b5 | ||
|
|
cffaa6fef8 | ||
|
|
bf2e5392f8 | ||
|
|
de6c93b849 | ||
|
|
3b6bbe4799 | ||
|
|
62250cd41f | ||
|
|
69a3963de0 | ||
|
|
acefebfcd7 | ||
|
|
b8f7d084c7 | ||
|
|
fd420b3d81 | ||
|
|
bf799869ef | ||
|
|
8fc3004349 | ||
|
|
13b22e2047 | ||
|
|
2a33ba9f93 | ||
|
|
ad09233d4c | ||
|
|
2be3a38bb0 | ||
|
|
26467221b3 | ||
|
|
dd19ad356a | ||
|
|
4fe91e9a6a | ||
|
|
63d660d152 | ||
|
|
730ff1758f | ||
|
|
174099dd1d | ||
|
|
f7f49b98ff | ||
|
|
18f0af1803 | ||
|
|
af2a4e1ce4 | ||
|
|
a4a58d9a06 | ||
|
|
861bb5f608 | ||
|
|
36c7f1244d | ||
|
|
8e4d07163d | ||
|
|
1af3cd40c0 | ||
|
|
8e0eb9d110 | ||
|
|
bf7dbe0098 | ||
|
|
f37d27f803 | ||
|
|
b98d2fbaa4 | ||
|
|
181539a424 | ||
|
|
6fe299d7c8 | ||
|
|
585eb23b3c | ||
|
|
04940fc4db | ||
|
|
75fa1b7fbe | ||
|
|
cc8e8b2dd1 | ||
|
|
0b3bbad98f | ||
|
|
9da11e05ee | ||
|
|
ecca6f8eda | ||
|
|
b5c6f32d1f | ||
|
|
f38c083b05 | ||
|
|
598a9e0918 | ||
|
|
c93c582ed5 | ||
|
|
010cfb6b35 | ||
|
|
b1d95a5fa0 | ||
|
|
0d4bf2ea2e | ||
|
|
d4eb451ba6 | ||
|
|
9435701d10 | ||
|
|
7603943c38 | ||
|
|
a959f79bcf | ||
|
|
9899d72dce | ||
|
|
4263ea6881 | ||
|
|
ab287858b3 | ||
|
|
eb6d168d50 | ||
|
|
52b8c4ae5e | ||
|
|
1c4640d69b | ||
|
|
3827459229 | ||
|
|
aaeb9f9e84 | ||
|
|
61acb3e42b | ||
|
|
4a8f299bb3 | ||
|
|
5530ca94f3 | ||
|
|
61a1b888bd | ||
|
|
a583701319 | ||
|
|
57623c710a | ||
|
|
93488528de | ||
|
|
e7a5ffdf5f | ||
|
|
dc56cb382b | ||
|
|
bb22ea3c8f | ||
|
|
b856ad0374 | ||
|
|
3aff3c51a8 | ||
|
|
3e096a25f7 | ||
|
|
adb21e4a7e | ||
|
|
3ea268aea7 | ||
|
|
567a8913e4 | ||
|
|
fd89b30192 | ||
|
|
ce3b327cff | ||
|
|
4e5ac4a921 | ||
|
|
b44c1478b8 | ||
|
|
b0890ad4bf | ||
|
|
83d632e24c | ||
|
|
1e5874aed5 | ||
|
|
e807d9a3e7 | ||
|
|
17bb74ff25 | ||
|
|
5c9e2839eb | ||
|
|
d670a761d9 | ||
|
|
b6a3f765a7 | ||
|
|
2ca2ceea5a | ||
|
|
01b4ab512c | ||
|
|
d18a6c3479 | ||
|
|
ab931cdcb4 | ||
|
|
7252b0e69a | ||
|
|
ab0ed23c83 | ||
|
|
18c002094f | ||
|
|
d5f73cb633 | ||
|
|
c738da065e | ||
|
|
a9296d67f7 | ||
|
|
78cb417cfc | ||
|
|
4bec5f6792 | ||
|
|
3da03f66e0 | ||
|
|
e379c676d0 | ||
|
|
6565494781 | ||
|
|
aafcd25403 | ||
|
|
72c4c0007f | ||
|
|
fd1ce7a750 | ||
|
|
fbbdec5e4e | ||
|
|
443b613513 | ||
|
|
2fa44fc0f4 | ||
|
|
5ed21c3ce3 | ||
|
|
f1d52fb530 | ||
|
|
d418ad8369 | ||
|
|
7b58d8e77e | ||
|
|
38971c2946 | ||
|
|
6cef7fd6b6 | ||
|
|
92ffb0b1e5 | ||
|
|
75f03f1fd1 | ||
|
|
0306817ac8 | ||
|
|
03b5dc046d | ||
|
|
bb5cb8a773 | ||
|
|
719bd257a6 | ||
|
|
e21257ab71 | ||
|
|
fc933ba9d5 | ||
|
|
9f9215cb3f | ||
|
|
8308c21d39 | ||
|
|
1f6ef5f045 | ||
|
|
7673918178 | ||
|
|
92b0aad937 | ||
|
|
233e126cd3 | ||
|
|
e9506787d5 | ||
|
|
e061289a26 | ||
|
|
7f87164235 | ||
|
|
0627d37f69 | ||
|
|
68dc49cb88 | ||
|
|
24cf97ca84 | ||
|
|
9859cbdca6 | ||
|
|
b819f303bb | ||
|
|
5ad4a25c33 | ||
|
|
265e5bb122 | ||
|
|
7964f862c6 | ||
|
|
622176d3de | ||
|
|
7e793a48e7 | ||
|
|
5bdc4615e8 | ||
|
|
ef04a4376a | ||
|
|
3c57007765 | ||
|
|
9965f2e79c | ||
|
|
f131997d6a | ||
|
|
08b9e3c46a | ||
|
|
8afbaefa6d | ||
|
|
88a8336ae7 | ||
|
|
0c6dface18 | ||
|
|
9bb2dd3887 | ||
|
|
1f3943fe9a | ||
|
|
9502034d58 | ||
|
|
c6b0b2021c | ||
|
|
0d889ec773 | ||
|
|
3d8f848936 | ||
|
|
6cbd108bca | ||
|
|
03c950f82e | ||
|
|
552b061357 | ||
|
|
ca98432e20 | ||
|
|
6ada1f04da | ||
|
|
edf1738e3c | ||
|
|
5b77205b77 | ||
|
|
67128c2fd5 | ||
|
|
490ec7ef3e | ||
|
|
0ab7ff919b | ||
|
|
489ccd9003 | ||
|
|
9971eef9b8 | ||
|
|
88e8bed395 | ||
|
|
9304942fd6 | ||
|
|
5f3b5cbe26 | ||
|
|
c5dbc8ddca | ||
|
|
eaea041e73 | ||
|
|
20b4ae230f | ||
|
|
037dce8be8 | ||
|
|
3ad6045377 | ||
|
|
3f683c3909 | ||
|
|
2f9974f6ef | ||
|
|
d2156a1179 | ||
|
|
d6a0b92f1e | ||
|
|
15aab58415 | ||
|
|
1e8679bf94 | ||
|
|
4eeb4c515f | ||
|
|
76a75942a9 | ||
|
|
4383c3486a | ||
|
|
e629877984 | ||
|
|
fb71ea83f0 | ||
|
|
d9cf090060 | ||
|
|
a0fd16513a | ||
|
|
c7c6d4e162 | ||
|
|
9a2eaa7a1d | ||
|
|
3b12843d5f | ||
|
|
6685649e39 | ||
|
|
7e63c902d3 | ||
|
|
c1e1530cd9 | ||
|
|
b1a190093c | ||
|
|
fcc7cf0566 | ||
|
|
ca3d51157f | ||
|
|
899a7b2500 | ||
|
|
d19be1731e | ||
|
|
90768b6933 | ||
|
|
89d6f050d1 | ||
|
|
5bfe7a2146 | ||
|
|
e54dc157ac | ||
|
|
5be7472dd9 | ||
|
|
1a2de2bbc2 | ||
|
|
0c9935b1b5 | ||
|
|
aba00ad17b | ||
|
|
5ca05394d6 | ||
|
|
5165415fb1 | ||
|
|
1f1060b97c | ||
|
|
3e9dabe2a5 | ||
|
|
86d72b15c5 | ||
|
|
3fd3a3e5aa | ||
|
|
3fbcf09326 | ||
|
|
d97cd9eabe | ||
|
|
de52bb1036 | ||
|
|
f9a073fd93 | ||
|
|
c77f99cf9b | ||
|
|
af8c1b2092 | ||
|
|
9095dcd9d8 | ||
|
|
08359103da | ||
|
|
9fe15fd8c2 | ||
|
|
728afbc887 | ||
|
|
1e10d49d0f | ||
|
|
4a81ea0ee4 | ||
|
|
acf89b1e9e | ||
|
|
1c99b5bbc7 | ||
|
|
d8d0c67462 | ||
|
|
2e9f050707 | ||
|
|
4b0a3741de | ||
|
|
c45c5f6e92 | ||
|
|
482515f538 | ||
|
|
42d75bd1ee | ||
|
|
296d7436ae | ||
|
|
5ba1466b96 | ||
|
|
7cdf36df87 | ||
|
|
02f25f8816 | ||
|
|
fa7e6bfa36 | ||
|
|
854890cc77 | ||
|
|
5445ec1b8d | ||
|
|
050f11bfab | ||
|
|
da89b95654 | ||
|
|
71631ccf0c | ||
|
|
f1c38dd742 | ||
|
|
0fc7126238 | ||
|
|
41cf366014 | ||
|
|
4288ae3582 | ||
|
|
e57d0a4d0e | ||
|
|
1b6a0188f3 | ||
|
|
e225b8a77c | ||
|
|
e4b1c542db | ||
|
|
b960460e02 | ||
|
|
c8f39802ae | ||
|
|
e3caad856d | ||
|
|
97bc512d1c | ||
|
|
5752fe698f | ||
|
|
727ef87a11 | ||
|
|
a92c85a0be | ||
|
|
225790cc4b | ||
|
|
0202d52e49 | ||
|
|
397ad4c597 | ||
|
|
ce00f31f0f | ||
|
|
b9dc9e6eb0 | ||
|
|
05884bba0d | ||
|
|
d8f37ac6a7 | ||
|
|
9c89e0b790 | ||
|
|
b37d695c8b | ||
|
|
263db54a12 | ||
|
|
dc8c25b8bc | ||
|
|
1d43cae1b1 | ||
|
|
7e02ae518d | ||
|
|
4afc439eb1 | ||
|
|
330d4f006f | ||
|
|
5929a7c6f9 | ||
|
|
1b95989b05 | ||
|
|
b284fc590e | ||
|
|
dd3e21e5d4 | ||
|
|
31c674c3dc | ||
|
|
b3632417a7 | ||
|
|
bc28919727 | ||
|
|
4333744248 | ||
|
|
d62599d62c | ||
|
|
bad745d2a9 | ||
|
|
0c6aa6afdb | ||
|
|
bb8e68d1b1 | ||
|
|
8c6b6e617c | ||
|
|
a9e4270ddb | ||
|
|
72262a77fd | ||
|
|
68c524a75d | ||
|
|
64c56288c9 | ||
|
|
2996bf7e6b | ||
|
|
5520c3778b | ||
|
|
4c6bdcb68a | ||
|
|
a925786899 | ||
|
|
e73a3cbf8c | ||
|
|
c86dbb4665 | ||
|
|
951c338edd | ||
|
|
5a22ad86b8 | ||
|
|
6db22d43a6 | ||
|
|
6569f59501 | ||
|
|
c30b04131b | ||
|
|
fb7e6d482a | ||
|
|
df252a0051 | ||
|
|
443d79f902 | ||
|
|
7ac9997950 | ||
|
|
aa59b5b4e3 | ||
|
|
2cd0b56f11 | ||
|
|
40c8485f02 | ||
|
|
f70e22fa4d | ||
|
|
cbaa7028d0 | ||
|
|
d6a4e33035 | ||
|
|
99bae93e07 | ||
|
|
9097fb6f60 | ||
|
|
8e9b91eeb6 | ||
|
|
419b46c7cc | ||
|
|
e927d76cb5 | ||
|
|
966d190b77 | ||
|
|
b7b06413d6 | ||
|
|
67ba5e21f7 | ||
|
|
27e401d3a1 | ||
|
|
45c1c40e9f | ||
|
|
29cfde5249 | ||
|
|
ffecb391b3 | ||
|
|
c87f8b0986 | ||
|
|
8cc4eef916 | ||
|
|
46f241f004 | ||
|
|
4dbe0bdbcb | ||
|
|
ee5e0beb7f | ||
|
|
ef89a35de6 | ||
|
|
26f94ccf07 | ||
|
|
c7a0b9d31c | ||
|
|
e2f91339b4 | ||
|
|
155b2723f1 | ||
|
|
2b5d5c5c12 | ||
|
|
660f854546 | ||
|
|
505ae78d14 | ||
|
|
0f19934021 | ||
|
|
0865e3262f | ||
|
|
8d266dbc4c | ||
|
|
8801365896 | ||
|
|
33249ea526 | ||
|
|
d1f3ec25fc | ||
|
|
8ea76ee1b8 | ||
|
|
660ed4c341 | ||
|
|
ef6f88a129 | ||
|
|
bf072e0802 | ||
|
|
d910521a0b | ||
|
|
3ceb40741c | ||
|
|
7d3a192435 | ||
|
|
50af4b83c5 | ||
|
|
2e4f79f6ef | ||
|
|
7c57be2181 | ||
|
|
2f238789ad | ||
|
|
29d4cfe669 | ||
|
|
df4eaa28b9 | ||
|
|
42dc55c664 | ||
|
|
a82ad37e78 | ||
|
|
5fdc7970cd | ||
|
|
8f45cc62d2 | ||
|
|
b8eaa369c2 | ||
|
|
fec2a4fcd5 | ||
|
|
f40ca2037b | ||
|
|
7227885e0e | ||
|
|
4fe66ddca8 | ||
|
|
5472f17c96 | ||
|
|
69b072f130 | ||
|
|
640106a78a | ||
|
|
2e31e1840a | ||
|
|
09334d2043 | ||
|
|
b52d22fd76 | ||
|
|
34233bf682 | ||
|
|
3ca111f6e0 | ||
|
|
09556cc3ac | ||
|
|
ff94d85e53 | ||
|
|
dd759d62e9 | ||
|
|
9739ed6b87 | ||
|
|
3d4c88ae7c | ||
|
|
eb5f842e7b | ||
|
|
6fb409dcd0 | ||
|
|
b2e36e8ca2 | ||
|
|
709524b7c9 | ||
|
|
4444438670 | ||
|
|
87ada19569 | ||
|
|
082fce33c3 | ||
|
|
ae93d2028a | ||
|
|
a36719686b | ||
|
|
981dc381cf | ||
|
|
261241c1ee | ||
|
|
7c8d432aa9 | ||
|
|
bd82d5d355 | ||
|
|
fed9270211 | ||
|
|
2fa0ef2572 | ||
|
|
02ffbb2b5c | ||
|
|
c00197d44a | ||
|
|
aa30390674 | ||
|
|
c54bf6f730 | ||
|
|
a5cc4cc01d | ||
|
|
ca02c342d7 | ||
|
|
bd58d81bcc | ||
|
|
15dfdb5bc1 | ||
|
|
36730dd6e2 | ||
|
|
0122589138 | ||
|
|
e82b1c4524 | ||
|
|
8a0ae86b28 | ||
|
|
d406277247 | ||
|
|
5e5015c552 | ||
|
|
22c46a50f0 | ||
|
|
7c9404864a | ||
|
|
82752f55d1 | ||
|
|
50a7643610 | ||
|
|
a85c84096f | ||
|
|
7edda12548 | ||
|
|
d91f1df9d8 | ||
|
|
7c2f9e529c | ||
|
|
d7ca06c599 | ||
|
|
f4ee0441ea | ||
|
|
7b28ea1fa3 | ||
|
|
e02da44288 | ||
|
|
e836880a39 | ||
|
|
2ef00808b7 | ||
|
|
1dca52e8cd | ||
|
|
a4f7bd4d2b | ||
|
|
5740917a11 | ||
|
|
0790220a44 | ||
|
|
462459c6c2 | ||
|
|
eee160c379 | ||
|
|
96c1ce5803 | ||
|
|
6dc705450b | ||
|
|
e9f5039817 | ||
|
|
4a7d0222a2 | ||
|
|
63e7803ea4 | ||
|
|
c522710f7f | ||
|
|
d9be2b2437 | ||
|
|
caeaefcc6a | ||
|
|
6048161924 | ||
|
|
4348bc20eb | ||
|
|
bff6d43701 | ||
|
|
944fc70ff4 | ||
|
|
a75a148278 | ||
|
|
a378eefb7b | ||
|
|
9782705e06 | ||
|
|
4cd47da3f4 | ||
|
|
65776d8401 | ||
|
|
1c5c5b3429 | ||
|
|
284d51918f | ||
|
|
06a1a49a73 | ||
|
|
1c2c72ca7c | ||
|
|
f2a28a34fe | ||
|
|
34e6eedf23 | ||
|
|
c1042d15b5 | ||
|
|
c40f258725 | ||
|
|
8619c180de | ||
|
|
1e9e5c5956 | ||
|
|
095769b7cd | ||
|
|
0740115724 | ||
|
|
862f2080ea | ||
|
|
2ed264b2b1 | ||
|
|
e9c2ea0e87 | ||
|
|
79cea28630 | ||
|
|
ec6ed8cea5 | ||
|
|
eab7a03c77 | ||
|
|
970d82e48e | ||
|
|
a9ad5169f2 | ||
|
|
27e0b3e62f | ||
|
|
3dfa2cd397 | ||
|
|
782eca7b95 | ||
|
|
17c6e46e8d | ||
|
|
684b3d2b2b | ||
|
|
e0c4ee102c | ||
|
|
0766a4b76d | ||
|
|
e16bd40fd3 | ||
|
|
479f72f160 | ||
|
|
56bfb32ad4 | ||
|
|
45dbb08993 | ||
|
|
5030f6e901 | ||
|
|
e64003a22f | ||
|
|
3abdeee850 | ||
|
|
dfec2dc308 | ||
|
|
abede2f979 | ||
|
|
695a0dac40 | ||
|
|
266e2e13cf | ||
|
|
79a81c86bb | ||
|
|
20f7bf5ebf | ||
|
|
bc3cd33a30 | ||
|
|
5eb3902854 | ||
|
|
eb75911d50 | ||
|
|
81b352c888 | ||
|
|
4b64dff6dd | ||
|
|
ea2eb39975 | ||
|
|
c3d64a7d2a | ||
|
|
4049a66c45 | ||
|
|
b06749aff9 | ||
|
|
6fa418b43b | ||
|
|
831786f55e | ||
|
|
952a9b69e1 | ||
|
|
8d367a1167 | ||
|
|
6cb366a978 | ||
|
|
aa99373d2d | ||
|
|
a6633365ec | ||
|
|
cac0f46d1c | ||
|
|
3e3b14779e | ||
|
|
225767acab | ||
|
|
7d1f31247e | ||
|
|
5fe1de5f7f | ||
|
|
0ac4f98ffa | ||
|
|
ebd3c72170 | ||
|
|
839a91d144 | ||
|
|
5c3df787bb | ||
|
|
daeec63b71 | ||
|
|
1aea0aa24c | ||
|
|
7b53957e55 | ||
|
|
09c7af4cc4 | ||
|
|
7ed9526828 | ||
|
|
27c61f1596 | ||
|
|
ca8e4d19bf | ||
|
|
938114f942 | ||
|
|
b7c61baafb | ||
|
|
5850e271c3 | ||
|
|
7ab15776df | ||
|
|
7f0b819993 | ||
|
|
c0af1fa1eb | ||
|
|
316eb681f6 | ||
|
|
2a841e4ae2 | ||
|
|
0033de0831 | ||
|
|
f70af84e16 | ||
|
|
d9571f3c4d | ||
|
|
fda2f1ff15 | ||
|
|
0d4824b7e0 | ||
|
|
0cd376e039 | ||
|
|
101c953020 | ||
|
|
46bf18cbfa | ||
|
|
5348cd3f22 | ||
|
|
9591ed744b | ||
|
|
efed8bbd4b | ||
|
|
7e0025d3fc | ||
|
|
b016b56a30 | ||
|
|
167d577f4d | ||
|
|
8888c97e07 | ||
|
|
b99e1f7b28 | ||
|
|
39b09c5b25 | ||
|
|
86842735bf | ||
|
|
05cf67d3e1 | ||
|
|
9f10d1b923 | ||
|
|
db66c6cde4 | ||
|
|
0eaa99a91a | ||
|
|
11de057064 | ||
|
|
d206ed5c0b | ||
|
|
6dd0275e5a | ||
|
|
cefaa78832 | ||
|
|
7253c56086 | ||
|
|
38dc4274ff | ||
|
|
833a5bd974 | ||
|
|
2443772e78 | ||
|
|
243a24c7f2 | ||
|
|
1166297dda | ||
|
|
58bfc4b36c | ||
|
|
1b07c8838f | ||
|
|
6592694778 | ||
|
|
08d5735af9 | ||
|
|
4cd69a52f6 | ||
|
|
542bab816f | ||
|
|
357be5296f | ||
|
|
57816a4f3a | ||
|
|
7486eb640f | ||
|
|
e946e23360 | ||
|
|
e233eed8eb | ||
|
|
80ac8906b3 | ||
|
|
ee9cb656fc | ||
|
|
cfe9f8e6e6 | ||
|
|
72f768cca4 | ||
|
|
9d0206c432 | ||
|
|
53bc91d399 | ||
|
|
ca63763d04 | ||
|
|
ac7589a3ff | ||
|
|
d752442aa3 | ||
|
|
930c27a890 | ||
|
|
2caec7be0e | ||
|
|
a7cfe98ca3 | ||
|
|
bad5f6f431 | ||
|
|
d0116c852c | ||
|
|
3f3ce39b30 | ||
|
|
8a5692e9df | ||
|
|
5cf96b1154 | ||
|
|
2d392de9ef | ||
|
|
f53aff1882 | ||
|
|
0cf6cbf3bc | ||
|
|
28b428a6f1 | ||
|
|
d311ced441 | ||
|
|
3ccce18f23 | ||
|
|
0ecd719799 | ||
|
|
5438e20b01 | ||
|
|
b5f72ecc5c | ||
|
|
dd20c8caf0 | ||
|
|
c3b96e4f68 | ||
|
|
b3132aa757 | ||
|
|
7685e3233f | ||
|
|
ee3e66580d | ||
|
|
26cd2acbcd | ||
|
|
0c3b099265 | ||
|
|
6971d320ae | ||
|
|
2dd6305e54 | ||
|
|
baf5479d0b | ||
|
|
c57be04143 | ||
|
|
f1030c2f10 | ||
|
|
1f1639e661 | ||
|
|
04d3445834 | ||
|
|
1559a14e39 | ||
|
|
fbbc6aa005 | ||
|
|
9c573b9bfd | ||
|
|
96bbe2c7dc | ||
|
|
ec9f6b82d9 | ||
|
|
2c120c3c45 | ||
|
|
b911f5c45b | ||
|
|
6366a22ff3 | ||
|
|
5fc819d8f5 | ||
|
|
df9d1ddcbf | ||
|
|
eed7b282d3 | ||
|
|
fc6237cd29 | ||
|
|
c473e634e0 | ||
|
|
28866fc1dc | ||
|
|
8028e8ca12 | ||
|
|
28d1cc2055 | ||
|
|
cffd99b68d | ||
|
|
ac92a92090 | ||
|
|
28dbb309cf | ||
|
|
7b585aaa05 | ||
|
|
82f0c85bd0 | ||
|
|
1c0a7d8475 | ||
|
|
5f63b5f907 | ||
|
|
7459cd65d9 | ||
|
|
0b61ddaff7 | ||
|
|
d554a7ee97 | ||
|
|
3d1e130b1e | ||
|
|
a63e008f84 | ||
|
|
d76ae27611 | ||
|
|
bf00d79e23 | ||
|
|
c08775f3e3 | ||
|
|
d94ceaf492 | ||
|
|
9802cb62d9 | ||
|
|
2c4f90b31c | ||
|
|
163d95800b | ||
|
|
0662d44a86 | ||
|
|
c3792191a2 | ||
|
|
97ff18c3e2 | ||
|
|
d069252f76 | ||
|
|
54fda151be | ||
|
|
04c4cf4426 | ||
|
|
7c81fe3339 | ||
|
|
330862e6c0 | ||
|
|
ce309a384e | ||
|
|
6bc01c3472 | ||
|
|
9ecf3e11bf | ||
|
|
7da306eb65 | ||
|
|
3d9d6d45fe | ||
|
|
7fc85a47be | ||
|
|
6accf7fd6c | ||
|
|
f619155667 | ||
|
|
89d50af521 | ||
|
|
fe3a111d6f | ||
|
|
75b6b6316b | ||
|
|
f64822a18b | ||
|
|
e566fb2d57 | ||
|
|
9f5931509c | ||
|
|
deb7ee8c3a | ||
|
|
6a56e29474 | ||
|
|
c79ccd7e23 | ||
|
|
36e395e3a3 | ||
|
|
c0b106c7ef | ||
|
|
23c8c5da19 | ||
|
|
883f1a7cf8 | ||
|
|
b2ff43bd9b | ||
|
|
471bb1cacd | ||
|
|
de30329a58 | ||
|
|
c990c8af56 | ||
|
|
2dbd099c68 | ||
|
|
25da363d56 | ||
|
|
a957566fc9 | ||
|
|
97db42427e | ||
|
|
34fabaab5f | ||
|
|
11893e244e | ||
|
|
24f1285a43 | ||
|
|
2d2e0fd6c0 | ||
|
|
5ea2aca8a8 | ||
|
|
088b4fa07e | ||
|
|
7dcfbc409c | ||
|
|
44bb2ff0f5 | ||
|
|
4effb02219 | ||
|
|
5d7ad6bb81 | ||
|
|
2bef5d7653 | ||
|
|
6b76bec480 | ||
|
|
76ab53078e | ||
|
|
ddf8df9c71 | ||
|
|
b312f28ab6 | ||
|
|
d9e536b789 | ||
|
|
2e768cb77b | ||
|
|
d611dbd386 | ||
|
|
8976e238a5 | ||
|
|
eb13bb8b6d | ||
|
|
d72eba1602 | ||
|
|
cd4685c093 | ||
|
|
bc3aa56ed9 | ||
|
|
903ead9e91 | ||
|
|
bacbd8baf3 | ||
|
|
407c742124 | ||
|
|
34a91b7e3d | ||
|
|
65f4724f87 | ||
|
|
afafe46221 | ||
|
|
ff220113af | ||
|
|
81c2c482cd | ||
|
|
afb06baa56 | ||
|
|
d25ff61f22 | ||
|
|
067f0b3a66 | ||
|
|
0f71efd1e9 | ||
|
|
351f9c1976 | ||
|
|
a11eff4d9a | ||
|
|
fe6a5a700a | ||
|
|
87a994b8ce | ||
|
|
3f06553604 | ||
|
|
e9f765a216 | ||
|
|
532e65f3a0 | ||
|
|
b038044347 | ||
|
|
0664d11f55 | ||
|
|
6d00a26530 | ||
|
|
a4d28b13ef | ||
|
|
fd01dc1bb1 | ||
|
|
fa72d3d0de | ||
|
|
f935f57d23 | ||
|
|
fbf1e855ad | ||
|
|
50a6037a6d | ||
|
|
d15d27defb | ||
|
|
7952bdcb48 | ||
|
|
dfdf3160d3 | ||
|
|
ce4f12179c | ||
|
|
491da787b4 | ||
|
|
6dbc7e2704 | ||
|
|
2a39c917ee | ||
|
|
3733ba45e2 | ||
|
|
2a410b9099 | ||
|
|
3ccc7c41e7 | ||
|
|
d03bc797fc | ||
|
|
5c4f8eb526 | ||
|
|
973464f1c4 | ||
|
|
e090ab2825 | ||
|
|
0413e5ded8 | ||
|
|
51c24f6fdf | ||
|
|
d1de248894 | ||
|
|
fdf3e618c2 | ||
|
|
5b0a228f4f | ||
|
|
9db11ce690 | ||
|
|
f9bdf6e181 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -32,6 +32,8 @@ bin
|
|||||||
gen
|
gen
|
||||||
*.log
|
*.log
|
||||||
|
|
||||||
|
# Ignore macOS Spotlight rubbish
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
# TODO: specify what these ignores are for (releasing?)
|
# TODO: specify what these ignores are for (releasing?)
|
||||||
|
|
||||||
|
|||||||
86
README.md
86
README.md
@@ -1,15 +1,15 @@
|
|||||||
# Forge
|
# 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)
|
Discord channel [here](https://discordapp.com/channels/267367946135928833/267742313390931968)
|
||||||
|
|
||||||
# Requirements / Tools
|
## Requirements / Tools
|
||||||
|
|
||||||
- Java IDE such as IntelliJ or Eclipse
|
- you favourite Java IDE (IntelliJ, Eclipse, VSCodium, Emacs, Vi...)
|
||||||
- Java JDK 8 or later
|
- Java JDK 8 or later (some IDEs such as Eclipse require JDK11+, whereas the Android build currently only works with JDK8)
|
||||||
- Git
|
- Git
|
||||||
- Git client (optional)
|
- Git client (optional)
|
||||||
- Maven
|
- Maven
|
||||||
@@ -18,7 +18,7 @@ Discord channel [here](https://discordapp.com/channels/267367946135928833/267742
|
|||||||
- Android SDK (optional: for Android releases)
|
- Android SDK (optional: for Android releases)
|
||||||
- RoboVM (optional: for iOS releases) (TBD: Current status of support by libgdx)
|
- 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.
|
- Log in to gitlab with your user account and fork the project.
|
||||||
|
|
||||||
@@ -26,11 +26,11 @@ 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`
|
- 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.
|
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.
|
||||||
|
|
||||||
@@ -55,9 +55,9 @@ Eclipse includes Maven integration so a separate install is not necessary. For
|
|||||||
|
|
||||||
- Once everything builds, all errors should disappear. You can now advance to Project launch.
|
- 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.
|
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!
|
- 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.
|
This is the configuration used for doing mobile development using the Windows / Linux / MacOS front-end. Knowledge of libgdx is helpful here.
|
||||||
|
|
||||||
@@ -73,24 +73,24 @@ This is the configuration used for doing mobile development using the Windows /
|
|||||||
|
|
||||||
- A view similar to a mobile phone should appear. Enjoy!
|
- 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.
|
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
|
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
|
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.
|
in the following instructions as your 'Android SDK Install' path.
|
||||||
|
|
||||||
#### Linux / Mac OSX
|
##### Linux / Mac OSX
|
||||||
|
|
||||||
TBD
|
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
|
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
|
from: https://github.com/khaledev/ADT/releases
|
||||||
@@ -98,25 +98,24 @@ 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
|
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.
|
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:
|
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 SDK Build-tools 26.0.1
|
||||||
- Android 7.1.1 (API 25) SDK Platform
|
- Android 8.0.0 (API 26) SDK Platform
|
||||||
- Google USB Driver 11
|
- 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.
|
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
|
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.
|
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.
|
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.
|
You'll need to have the Android SDK install path platform-tools/ path in your command search path to easily deploy builds.
|
||||||
|
|
||||||
@@ -149,14 +148,14 @@ 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`
|
- Install the new apk: `adb install forge-android-[version].apk`
|
||||||
|
|
||||||
### Android Debugging
|
#### Android Debugging
|
||||||
|
|
||||||
Assuming the apk is installed, launch it from the device.
|
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.
|
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.
|
SNAPSHOT builds can be built via the Maven integration in Eclipse.
|
||||||
|
|
||||||
@@ -167,19 +166,19 @@ SNAPSHOT builds can be built via the Maven integration in Eclipse.
|
|||||||
|
|
||||||
The resulting snapshot will be found at: forge-gui-desktop/target/forge-gui-desktop-[version]-SNAPSHOT
|
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.
|
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:
|
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
|
||||||
- forge-gui-mobile-dev
|
- 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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
Libgdx backend for desktop development for mobile backends. Utilizes LWJGL. Relies on forge-gui-mobile for GUI logic.
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>forge</artifactId>
|
<artifactId>forge</artifactId>
|
||||||
<groupId>forge</groupId>
|
<groupId>forge</groupId>
|
||||||
<version>1.6.40-SNAPSHOT</version>
|
<version>1.6.41</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<artifactId>forge-ai</artifactId>
|
<artifactId>forge-ai</artifactId>
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
package forge.ai;
|
package forge.ai;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import com.google.common.base.Predicate;
|
import com.google.common.base.Predicate;
|
||||||
@@ -43,6 +44,7 @@ import forge.game.combat.GlobalAttackRestrictions;
|
|||||||
import forge.game.keyword.Keyword;
|
import forge.game.keyword.Keyword;
|
||||||
import forge.game.keyword.KeywordInterface;
|
import forge.game.keyword.KeywordInterface;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
|
import forge.game.player.PlayerPredicates;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.trigger.Trigger;
|
import forge.game.trigger.Trigger;
|
||||||
import forge.game.trigger.TriggerType;
|
import forge.game.trigger.TriggerType;
|
||||||
@@ -54,7 +56,6 @@ import forge.util.TextUtil;
|
|||||||
import forge.util.collect.FCollectionView;
|
import forge.util.collect.FCollectionView;
|
||||||
|
|
||||||
|
|
||||||
//doesHumanAttackAndWin() uses the global variable AllZone.getComputerPlayer()
|
|
||||||
/**
|
/**
|
||||||
* <p>
|
* <p>
|
||||||
* ComputerUtil_Attack2 class.
|
* ComputerUtil_Attack2 class.
|
||||||
@@ -91,7 +92,7 @@ public class AiAttackController {
|
|||||||
|
|
||||||
public AiAttackController(final Player ai, boolean nextTurn) {
|
public AiAttackController(final Player ai, boolean nextTurn) {
|
||||||
this.ai = ai;
|
this.ai = ai;
|
||||||
this.defendingOpponent = choosePreferredDefenderPlayer();
|
this.defendingOpponent = choosePreferredDefenderPlayer(ai);
|
||||||
this.oppList = getOpponentCreatures(this.defendingOpponent);
|
this.oppList = getOpponentCreatures(this.defendingOpponent);
|
||||||
this.myList = ai.getCreaturesInPlay();
|
this.myList = ai.getCreaturesInPlay();
|
||||||
this.attackers = new ArrayList<>();
|
this.attackers = new ArrayList<>();
|
||||||
@@ -107,7 +108,7 @@ public class AiAttackController {
|
|||||||
|
|
||||||
public AiAttackController(final Player ai, Card attacker) {
|
public AiAttackController(final Player ai, Card attacker) {
|
||||||
this.ai = ai;
|
this.ai = ai;
|
||||||
this.defendingOpponent = choosePreferredDefenderPlayer();
|
this.defendingOpponent = choosePreferredDefenderPlayer(ai);
|
||||||
this.oppList = getOpponentCreatures(this.defendingOpponent);
|
this.oppList = getOpponentCreatures(this.defendingOpponent);
|
||||||
this.myList = ai.getCreaturesInPlay();
|
this.myList = ai.getCreaturesInPlay();
|
||||||
this.attackers = new ArrayList<>();
|
this.attackers = new ArrayList<>();
|
||||||
@@ -156,13 +157,12 @@ public class AiAttackController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Choose opponent for AI to attack here. Expand as necessary. */
|
/** Choose opponent for AI to attack here. Expand as necessary. */
|
||||||
private Player choosePreferredDefenderPlayer() {
|
public static Player choosePreferredDefenderPlayer(Player ai) {
|
||||||
Player defender = ai.getWeakestOpponent(); //Gets opponent with the least life
|
Player defender = ai.getWeakestOpponent(); //Concentrate on opponent within easy kill range
|
||||||
|
|
||||||
if (defender.getLife() < 8) { //Concentrate on opponent within easy kill range
|
if (defender.getLife() > 8) { //Otherwise choose a random opponent to ensure no ganging up on players
|
||||||
return defender;
|
// TODO should we cache the random for each turn? some functions like shouldPumpCard base their decisions on the assumption who will be attacked
|
||||||
} else { //Otherwise choose a random opponent to ensure no ganging up on players
|
return ai.getOpponents().get(MyRandom.getRandom().nextInt(ai.getOpponents().size()));
|
||||||
defender = ai.getOpponents().get(MyRandom.getRandom().nextInt(ai.getOpponents().size()));
|
|
||||||
}
|
}
|
||||||
return defender;
|
return defender;
|
||||||
}
|
}
|
||||||
@@ -393,7 +393,7 @@ public class AiAttackController {
|
|||||||
|
|
||||||
//Calculate the amount of creatures necessary
|
//Calculate the amount of creatures necessary
|
||||||
for (int i = 0; i < list.size(); i++) {
|
for (int i = 0; i < list.size(); i++) {
|
||||||
if (!this.doesHumanAttackAndWin(ai, i)) {
|
if (!doesHumanAttackAndWin(ai, i)) {
|
||||||
blockersNeeded = i;
|
blockersNeeded = i;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -413,12 +413,10 @@ public class AiAttackController {
|
|||||||
|
|
||||||
final Player opp = this.defendingOpponent;
|
final Player opp = this.defendingOpponent;
|
||||||
|
|
||||||
// Increase the total number of blockers needed by 1 if Finest Hour in
|
// Increase the total number of blockers needed by 1 if Finest Hour in play
|
||||||
// play
|
|
||||||
// (human will get an extra first attack with a creature that untaps)
|
// (human will get an extra first attack with a creature that untaps)
|
||||||
// In addition, if the computer guesses it needs no blockers, make sure
|
// In addition, if the computer guesses it needs no blockers, make sure
|
||||||
// that
|
// that it won't be surprised by Exalted
|
||||||
// it won't be surprised by Exalted
|
|
||||||
final int humanExaltedBonus = opp.countExaltedBonus();
|
final int humanExaltedBonus = opp.countExaltedBonus();
|
||||||
|
|
||||||
if (humanExaltedBonus > 0) {
|
if (humanExaltedBonus > 0) {
|
||||||
@@ -428,8 +426,7 @@ public class AiAttackController {
|
|||||||
// total attack = biggest creature + exalted, *2 if Rafiq is in play
|
// total attack = biggest creature + exalted, *2 if Rafiq is in play
|
||||||
int humanBasePower = getAttack(this.oppList.get(0)) + humanExaltedBonus;
|
int humanBasePower = getAttack(this.oppList.get(0)) + humanExaltedBonus;
|
||||||
if (finestHour) {
|
if (finestHour) {
|
||||||
// For Finest Hour, one creature could attack and get the
|
// For Finest Hour, one creature could attack and get the bonus TWICE
|
||||||
// bonus TWICE
|
|
||||||
humanBasePower = humanBasePower + humanExaltedBonus;
|
humanBasePower = humanBasePower + humanExaltedBonus;
|
||||||
}
|
}
|
||||||
final int totalExaltedAttack = opp.isCardInPlay("Rafiq of the Many") ? 2 * humanBasePower
|
final int totalExaltedAttack = opp.isCardInPlay("Rafiq of the Many") ? 2 * humanBasePower
|
||||||
@@ -450,7 +447,6 @@ public class AiAttackController {
|
|||||||
return notNeededAsBlockers;
|
return notNeededAsBlockers;
|
||||||
}
|
}
|
||||||
|
|
||||||
// this uses a global variable, which isn't perfect
|
|
||||||
public final boolean doesHumanAttackAndWin(final Player ai, final int nBlockingCreatures) {
|
public final boolean doesHumanAttackAndWin(final Player ai, final int nBlockingCreatures) {
|
||||||
int totalAttack = 0;
|
int totalAttack = 0;
|
||||||
int totalPoison = 0;
|
int totalPoison = 0;
|
||||||
@@ -624,7 +620,7 @@ public class AiAttackController {
|
|||||||
int totalCombatDamage = ComputerUtilCombat.sumDamageIfUnblocked(unblockedAttackers, opp) + trampleDamage;
|
int totalCombatDamage = ComputerUtilCombat.sumDamageIfUnblocked(unblockedAttackers, opp) + trampleDamage;
|
||||||
int totalPoisonDamage = ComputerUtilCombat.sumPoisonIfUnblocked(unblockedAttackers, opp);
|
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)) {
|
&& !((opp.cantLoseForZeroOrLessLife() || ai.cantWin()) && opp.getLife() < 1)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -641,7 +637,7 @@ public class AiAttackController {
|
|||||||
if (defs.size() == 1) {
|
if (defs.size() == 1) {
|
||||||
return defs.getFirst();
|
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...
|
// Attempt to see if there's a defined entity that must be attacked strictly this turn...
|
||||||
GameEntity entity = ai.getMustAttackEntityThisTurn();
|
GameEntity entity = ai.getMustAttackEntityThisTurn();
|
||||||
@@ -665,7 +661,7 @@ public class AiAttackController {
|
|||||||
// 2. attack planeswalkers
|
// 2. attack planeswalkers
|
||||||
List<Card> pwDefending = c.getDefendingPlaneswalkers();
|
List<Card> pwDefending = c.getDefendingPlaneswalkers();
|
||||||
if (!pwDefending.isEmpty()) {
|
if (!pwDefending.isEmpty()) {
|
||||||
return pwDefending.get(0);
|
return ComputerUtilCard.getBestPlaneswalkerToDamage(pwDefending);
|
||||||
} else {
|
} else {
|
||||||
return prefDefender;
|
return prefDefender;
|
||||||
}
|
}
|
||||||
@@ -682,7 +678,6 @@ public class AiAttackController {
|
|||||||
* @return a {@link forge.game.combat.Combat} object.
|
* @return a {@link forge.game.combat.Combat} object.
|
||||||
*/
|
*/
|
||||||
public final void declareAttackers(final Combat combat) {
|
public final void declareAttackers(final Combat combat) {
|
||||||
|
|
||||||
if (this.attackers.isEmpty()) {
|
if (this.attackers.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -702,14 +697,14 @@ public class AiAttackController {
|
|||||||
tradeIfLowerLifePressure = aic.getBooleanProperty(AiProps.RANDOMLY_ATKTRADE_ONLY_ON_LOWER_LIFE_PRESSURE);
|
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
|
// TODO: detect Lightmine Field by presence of a card with a specific trigger
|
||||||
final boolean lightmineField = ComputerUtilCard.isPresentOnBattlefield(ai.getGame(), "Lightmine Field");
|
final boolean lightmineField = ComputerUtilCard.isPresentOnBattlefield(ai.getGame(), "Lightmine Field");
|
||||||
// TODO: detect Season of the Witch by presence of a card with a specific trigger
|
// 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");
|
final boolean seasonOfTheWitch = ComputerUtilCard.isPresentOnBattlefield(ai.getGame(), "Season of the Witch");
|
||||||
|
|
||||||
// Determine who will be attacked
|
// Determine who will be attacked
|
||||||
GameEntity defender = this.chooseDefender(combat, bAssault);
|
GameEntity defender = chooseDefender(combat, bAssault);
|
||||||
List<Card> attackersLeft = new ArrayList<>(this.attackers);
|
List<Card> attackersLeft = new ArrayList<>(this.attackers);
|
||||||
|
|
||||||
// TODO probably use AttackConstraints instead of only GlobalAttackRestrictions?
|
// TODO probably use AttackConstraints instead of only GlobalAttackRestrictions?
|
||||||
@@ -717,7 +712,7 @@ public class AiAttackController {
|
|||||||
int attackMax = restrict.getMax();
|
int attackMax = restrict.getMax();
|
||||||
if (attackMax == -1) {
|
if (attackMax == -1) {
|
||||||
// check with the local limitations vs. the chosen defender
|
// 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) {
|
if (attackMax == 0) {
|
||||||
@@ -777,7 +772,6 @@ public class AiAttackController {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (bAssault) {
|
if (bAssault) {
|
||||||
if (LOG_AI_ATTACKS)
|
if (LOG_AI_ATTACKS)
|
||||||
System.out.println("Assault");
|
System.out.println("Assault");
|
||||||
@@ -852,7 +846,6 @@ public class AiAttackController {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// *******************
|
// *******************
|
||||||
// Evaluate the creature forces
|
// Evaluate the creature forces
|
||||||
// *******************
|
// *******************
|
||||||
@@ -919,7 +912,7 @@ public class AiAttackController {
|
|||||||
// find the potential damage ratio the AI can cause
|
// find the potential damage ratio the AI can cause
|
||||||
double humanLifeToDamageRatio = 1000000;
|
double humanLifeToDamageRatio = 1000000;
|
||||||
if (candidateUnblockedDamage > 0) {
|
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
|
// 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
|
// if outnumber and superior ratio work out whether attritional all out
|
||||||
// attacking will work
|
// attacking will work attritional attack will expect some creatures to die but to achieve
|
||||||
// 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
|
||||||
// victory by sheer weight
|
// carefully, the accuracy can probably be improved
|
||||||
// of numbers attacking turn after turn. It's not calculate very
|
|
||||||
// carefully, the accuracy
|
|
||||||
// can probably be improved
|
|
||||||
// *********************
|
// *********************
|
||||||
boolean doAttritionalAttack = false;
|
boolean doAttritionalAttack = false;
|
||||||
// get list of attackers ordered from low power to high
|
// get list of attackers ordered from low power to high
|
||||||
@@ -975,7 +965,6 @@ public class AiAttackController {
|
|||||||
doAttritionalAttack = true;
|
doAttritionalAttack = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// System.out.println(doAttritionalAttack + " = do attritional attack");
|
|
||||||
// *********************
|
// *********************
|
||||||
// end attritional attack calculation
|
// end attritional attack calculation
|
||||||
// *********************
|
// *********************
|
||||||
@@ -1025,11 +1014,9 @@ public class AiAttackController {
|
|||||||
// end see how long until unblockable attackers will be fatal
|
// end see how long until unblockable attackers will be fatal
|
||||||
// *****************
|
// *****************
|
||||||
|
|
||||||
|
|
||||||
// decide on attack aggression based on a comparison of forces, life
|
// decide on attack aggression based on a comparison of forces, life
|
||||||
// totals and other considerations
|
// totals and other considerations some bad "magic numbers" here
|
||||||
// some bad "magic numbers" here, TODO replace with nice descriptive
|
// TODO replace with nice descriptive variable names
|
||||||
// variable names
|
|
||||||
if (ratioDiff > 0 && doAttritionalAttack) {
|
if (ratioDiff > 0 && doAttritionalAttack) {
|
||||||
this.aiAggression = 5; // attack at all costs
|
this.aiAggression = 5; // attack at all costs
|
||||||
} else if ((ratioDiff >= 1 && this.attackers.size() > 1 && (humanLifeToDamageRatio < 2 || outNumber > 0))
|
} 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 enough damage: switch to next planeswalker or player
|
||||||
if (damage >= pw.getCounters(CounterEnumType.LOYALTY)) {
|
if (damage >= pw.getCounters(CounterEnumType.LOYALTY)) {
|
||||||
List<Card> pwDefending = combat.getDefendingPlaneswalkers();
|
List<Card> pwDefending = combat.getDefendingPlaneswalkers();
|
||||||
boolean found = false;
|
|
||||||
// look for next planeswalker
|
// look for next planeswalker
|
||||||
for (Card walker : pwDefending) {
|
for (Card walker : Lists.newArrayList(pwDefending)) {
|
||||||
if (combat.getAttackersOf(walker).isEmpty()) {
|
if (!combat.getAttackersOf(walker).isEmpty()) {
|
||||||
defender = walker;
|
pwDefending.remove(walker);
|
||||||
found = true;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!found) {
|
if (pwDefending.isEmpty()) {
|
||||||
defender = combat.getDefendingPlayers().get(0);
|
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
|
// look at the attacker in relation to the blockers to establish a
|
||||||
// number of factors about the attacking
|
// number of factors about the attacking context that will be relevant
|
||||||
// context that will be relevant to the attackers decision according to
|
// to the attackers decision according to the selected strategy
|
||||||
// the selected strategy
|
|
||||||
for (final Card defender : validBlockers) {
|
for (final Card defender : validBlockers) {
|
||||||
// if both isWorthLessThanAllKillers and canKillAllDangerous are false there's nothing more to check
|
// if both isWorthLessThanAllKillers and canKillAllDangerous are false there's nothing more to check
|
||||||
if (isWorthLessThanAllKillers || canKillAllDangerous || numberOfPossibleBlockers < 2) {
|
if (isWorthLessThanAllKillers || canKillAllDangerous || numberOfPossibleBlockers < 2) {
|
||||||
@@ -1300,14 +1286,11 @@ public class AiAttackController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (numberOfPossibleBlockers > 2
|
if (numberOfPossibleBlockers > 2
|
||||||
|| (numberOfPossibleBlockers >= 1 && CombatUtil.canAttackerBeBlockedWithAmount(attacker, 1, combat))
|
|| (numberOfPossibleBlockers >= 1 && CombatUtil.canAttackerBeBlockedWithAmount(attacker, 1, this.defendingOpponent))
|
||||||
|| (numberOfPossibleBlockers == 2 && CombatUtil.canAttackerBeBlockedWithAmount(attacker, 2, combat))) {
|
|| (numberOfPossibleBlockers == 2 && CombatUtil.canAttackerBeBlockedWithAmount(attacker, 2, this.defendingOpponent))) {
|
||||||
canBeBlocked = true;
|
canBeBlocked = true;
|
||||||
}
|
}
|
||||||
/*System.out.println(attacker + " canBeKilledByOne: " + canBeKilledByOne + " canKillAll: "
|
// decide if the creature should attack based on the prevailing strategy choice in aiAggression
|
||||||
+ canKillAll + " isWorthLessThanAllKillers: " + isWorthLessThanAllKillers + " canBeBlocked: " + canBeBlocked);*/
|
|
||||||
// decide if the creature should attack based on the prevailing strategy
|
|
||||||
// choice in aiAggression
|
|
||||||
switch (this.aiAggression) {
|
switch (this.aiAggression) {
|
||||||
case 6: // Exalted: expecting to at least kill a creature of equal value or not be blocked
|
case 6: // Exalted: expecting to at least kill a creature of equal value or not be blocked
|
||||||
if ((canKillAll && isWorthLessThanAllKillers) || !canBeBlocked) {
|
if ((canKillAll && isWorthLessThanAllKillers) || !canBeBlocked) {
|
||||||
@@ -1373,8 +1356,7 @@ public class AiAttackController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// if card has a Exert Trigger which would target,
|
// if card has a Exert Trigger which would target,
|
||||||
// but there are no creatures it can target, no need to exert with
|
// but there are no creatures it can target, no need to exert with it
|
||||||
// it
|
|
||||||
boolean missTarget = false;
|
boolean missTarget = false;
|
||||||
for (Trigger t : c.getTriggers()) {
|
for (Trigger t : c.getTriggers()) {
|
||||||
if (!TriggerType.Exerted.equals(t.getMode())) {
|
if (!TriggerType.Exerted.equals(t.getMode())) {
|
||||||
@@ -1563,4 +1545,4 @@ public class AiAttackController {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // end class ComputerUtil_Attack2
|
}
|
||||||
|
|||||||
@@ -180,11 +180,9 @@ public class AiBlockController {
|
|||||||
|
|
||||||
// Good Blocks means a good trade or no trade
|
// Good Blocks means a good trade or no trade
|
||||||
private void makeGoodBlocks(final Combat combat) {
|
private void makeGoodBlocks(final Combat combat) {
|
||||||
|
|
||||||
List<Card> currentAttackers = new ArrayList<>(attackersLeft);
|
List<Card> currentAttackers = new ArrayList<>(attackersLeft);
|
||||||
|
|
||||||
for (final Card attacker : attackersLeft) {
|
for (final Card attacker : attackersLeft) {
|
||||||
|
|
||||||
if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT")
|
if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT")
|
||||||
|| attacker.hasKeyword("CARDNAME can't be blocked unless all creatures defending player controls block it.")
|
|| attacker.hasKeyword("CARDNAME can't be blocked unless all creatures defending player controls block it.")
|
||||||
|| attacker.hasKeyword(Keyword.MENACE)) {
|
|| attacker.hasKeyword(Keyword.MENACE)) {
|
||||||
@@ -192,7 +190,6 @@ public class AiBlockController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Card blocker = null;
|
Card blocker = null;
|
||||||
|
|
||||||
final List<Card> blockers = getPossibleBlockers(combat, attacker, blockersLeft, true);
|
final List<Card> blockers = getPossibleBlockers(combat, attacker, blockersLeft, true);
|
||||||
|
|
||||||
final List<Card> safeBlockers = getSafeBlockers(combat, attacker, blockers);
|
final List<Card> safeBlockers = getSafeBlockers(combat, attacker, blockers);
|
||||||
@@ -305,7 +302,6 @@ public class AiBlockController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Card blocker = null;
|
Card blocker = null;
|
||||||
|
|
||||||
final List<Card> blockers = getPossibleBlockers(combat, attacker, blockersLeft, true);
|
final List<Card> blockers = getPossibleBlockers(combat, attacker, blockersLeft, true);
|
||||||
|
|
||||||
for (Card b : blockers) {
|
for (Card b : blockers) {
|
||||||
@@ -366,10 +362,9 @@ public class AiBlockController {
|
|||||||
final int damageNeeded = ComputerUtilCombat.getDamageToKill(attacker)
|
final int damageNeeded = ComputerUtilCombat.getDamageToKill(attacker)
|
||||||
+ ComputerUtilCombat.predictToughnessBonusOfAttacker(attacker, blocker, combat, false);
|
+ ComputerUtilCombat.predictToughnessBonusOfAttacker(attacker, blocker, combat, false);
|
||||||
// if the total damage of the blockgang was not enough
|
// if the total damage of the blockgang was not enough
|
||||||
// without but is enough with this blocker finish the
|
// without but is enough with this blocker finish the blockgang
|
||||||
// blockgang
|
|
||||||
if (ComputerUtilCombat.totalFirstStrikeDamageOfBlockers(attacker, blockGang) < damageNeeded
|
if (ComputerUtilCombat.totalFirstStrikeDamageOfBlockers(attacker, blockGang) < damageNeeded
|
||||||
|| CombatUtil.needsBlockers(attacker) > blockGang.size()) {
|
|| CombatUtil.getMinNumBlockersForAttacker(attacker, ai) > blockGang.size()) {
|
||||||
blockGang.add(blocker);
|
blockGang.add(blocker);
|
||||||
if (ComputerUtilCombat.totalFirstStrikeDamageOfBlockers(attacker, blockGang) >= damageNeeded) {
|
if (ComputerUtilCombat.totalFirstStrikeDamageOfBlockers(attacker, blockGang) >= damageNeeded) {
|
||||||
currentAttackers.remove(attacker);
|
currentAttackers.remove(attacker);
|
||||||
@@ -407,7 +402,7 @@ public class AiBlockController {
|
|||||||
boolean foundDoubleBlock = false; // if true, a good double block is found
|
boolean foundDoubleBlock = false; // if true, a good double block is found
|
||||||
|
|
||||||
// AI can't handle good blocks with more than three creatures yet
|
// 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;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -444,7 +439,7 @@ public class AiBlockController {
|
|||||||
final int addedValue = ComputerUtilCard.evaluateCreature(blocker);
|
final int addedValue = ComputerUtilCard.evaluateCreature(blocker);
|
||||||
final int damageNeeded = ComputerUtilCombat.getDamageToKill(attacker)
|
final int damageNeeded = ComputerUtilCombat.getDamageToKill(attacker)
|
||||||
+ ComputerUtilCombat.predictToughnessBonusOfAttacker(attacker, blocker, combat, false);
|
+ 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)
|
&& !(damageNeeded > currentDamage + additionalDamage)
|
||||||
// The attacker will be killed
|
// The attacker will be killed
|
||||||
&& (absorbedDamage2 + absorbedDamage > attacker.getNetCombatDamage()
|
&& (absorbedDamage2 + absorbedDamage > attacker.getNetCombatDamage()
|
||||||
@@ -454,8 +449,7 @@ public class AiBlockController {
|
|||||||
|| (lifeInDanger && ComputerUtilCombat.lifeInDanger(ai, combat)))
|
|| (lifeInDanger && ComputerUtilCombat.lifeInDanger(ai, combat)))
|
||||||
// or life is in danger
|
// or life is in danger
|
||||||
&& CombatUtil.canBlock(attacker, blocker, combat)) {
|
&& CombatUtil.canBlock(attacker, blocker, combat)) {
|
||||||
// this is needed for attackers that can't be blocked by
|
// this is needed for attackers that can't be blocked by more than 1
|
||||||
// more than 1
|
|
||||||
currentAttackers.remove(attacker);
|
currentAttackers.remove(attacker);
|
||||||
combat.addBlocker(attacker, blocker);
|
combat.addBlocker(attacker, blocker);
|
||||||
if (CombatUtil.canBlock(attacker, leader, combat)) {
|
if (CombatUtil.canBlock(attacker, leader, combat)) {
|
||||||
@@ -496,7 +490,7 @@ public class AiBlockController {
|
|||||||
final int addedValue3 = ComputerUtilCard.evaluateCreature(secondBlocker);
|
final int addedValue3 = ComputerUtilCard.evaluateCreature(secondBlocker);
|
||||||
final int netCombatDamage = attacker.getNetCombatDamage();
|
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)
|
&& !(damageNeeded > currentDamage + additionalDamage2 + additionalDamage3)
|
||||||
// The attacker will be killed
|
// The attacker will be killed
|
||||||
&& ((absorbedDamage2 + absorbedDamage > netCombatDamage && absorbedDamage3 + absorbedDamage > netCombatDamage
|
&& ((absorbedDamage2 + absorbedDamage > netCombatDamage && absorbedDamage3 + absorbedDamage > netCombatDamage
|
||||||
@@ -510,8 +504,7 @@ public class AiBlockController {
|
|||||||
// or life is in danger
|
// or life is in danger
|
||||||
&& CombatUtil.canBlock(attacker, secondBlocker, combat)
|
&& CombatUtil.canBlock(attacker, secondBlocker, combat)
|
||||||
&& CombatUtil.canBlock(attacker, thirdBlocker, combat)) {
|
&& CombatUtil.canBlock(attacker, thirdBlocker, combat)) {
|
||||||
// this is needed for attackers that can't be blocked by
|
// this is needed for attackers that can't be blocked by more than 1
|
||||||
// more than 1
|
|
||||||
currentAttackers.remove(attacker);
|
currentAttackers.remove(attacker);
|
||||||
combat.addBlocker(attacker, thirdBlocker);
|
combat.addBlocker(attacker, thirdBlocker);
|
||||||
if (CombatUtil.canBlock(attacker, secondBlocker, combat)) {
|
if (CombatUtil.canBlock(attacker, secondBlocker, combat)) {
|
||||||
@@ -587,12 +580,10 @@ public class AiBlockController {
|
|||||||
* @param combat a {@link forge.game.combat.Combat} object.
|
* @param combat a {@link forge.game.combat.Combat} object.
|
||||||
*/
|
*/
|
||||||
private void makeTradeBlocks(final Combat combat) {
|
private void makeTradeBlocks(final Combat combat) {
|
||||||
|
|
||||||
List<Card> currentAttackers = new ArrayList<>(attackersLeft);
|
List<Card> currentAttackers = new ArrayList<>(attackersLeft);
|
||||||
List<Card> killingBlockers;
|
List<Card> killingBlockers;
|
||||||
|
|
||||||
for (final Card attacker : attackersLeft) {
|
for (final Card attacker : attackersLeft) {
|
||||||
|
|
||||||
if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT")
|
if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT")
|
||||||
|| attacker.hasKeyword(Keyword.MENACE)
|
|| attacker.hasKeyword(Keyword.MENACE)
|
||||||
|| attacker.hasKeyword("CARDNAME can't be blocked unless all creatures defending player controls block it.")) {
|
|| 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)
|
// Chump Blocks (should only be made if life is in danger)
|
||||||
private void makeChumpBlocks(final Combat combat) {
|
private void makeChumpBlocks(final Combat combat) {
|
||||||
|
|
||||||
List<Card> currentAttackers = new ArrayList<>(attackersLeft);
|
List<Card> currentAttackers = new ArrayList<>(attackersLeft);
|
||||||
|
|
||||||
makeChumpBlocks(combat, currentAttackers);
|
makeChumpBlocks(combat, currentAttackers);
|
||||||
@@ -639,7 +629,6 @@ public class AiBlockController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void makeChumpBlocks(final Combat combat, List<Card> attackers) {
|
private void makeChumpBlocks(final Combat combat, List<Card> attackers) {
|
||||||
|
|
||||||
if (attackers.isEmpty() || !ComputerUtilCombat.lifeInDanger(ai, combat)) {
|
if (attackers.isEmpty() || !ComputerUtilCombat.lifeInDanger(ai, combat)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -694,11 +683,9 @@ public class AiBlockController {
|
|||||||
|
|
||||||
// Block creatures with "can't be blocked except by two or more creatures"
|
// Block creatures with "can't be blocked except by two or more creatures"
|
||||||
private void makeMultiChumpBlocks(final Combat combat) {
|
private void makeMultiChumpBlocks(final Combat combat) {
|
||||||
|
|
||||||
List<Card> currentAttackers = new ArrayList<>(attackersLeft);
|
List<Card> currentAttackers = new ArrayList<>(attackersLeft);
|
||||||
|
|
||||||
for (final Card attacker : currentAttackers) {
|
for (final Card attacker : currentAttackers) {
|
||||||
|
|
||||||
if (!attacker.hasStartOfKeyword("CantBeBlockedByAmount LT")
|
if (!attacker.hasStartOfKeyword("CantBeBlockedByAmount LT")
|
||||||
&& !attacker.hasKeyword(Keyword.MENACE)
|
&& !attacker.hasKeyword(Keyword.MENACE)
|
||||||
&& !attacker.hasKeyword("CARDNAME can't be blocked unless all creatures defending player controls block it.")) {
|
&& !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) */
|
/** Reinforce blockers blocking attackers with trample (should only be made if life is in danger) */
|
||||||
private void reinforceBlockersAgainstTrample(final Combat combat) {
|
private void reinforceBlockersAgainstTrample(final Combat combat) {
|
||||||
|
|
||||||
List<Card> chumpBlockers;
|
List<Card> chumpBlockers;
|
||||||
|
|
||||||
List<Card> tramplingAttackers = CardLists.getKeyword(attackers, Keyword.TRAMPLE);
|
List<Card> tramplingAttackers = CardLists.getKeyword(attackers, Keyword.TRAMPLE);
|
||||||
tramplingAttackers = CardLists.filter(tramplingAttackers, Predicates.not(rampagesOrNeedsManyToBlock));
|
tramplingAttackers = CardLists.filter(tramplingAttackers, Predicates.not(rampagesOrNeedsManyToBlock));
|
||||||
|
|
||||||
// TODO - should check here for a "rampage-like" trigger that replaced
|
// TODO - should check here for a "rampage-like" trigger that replaced the keyword:
|
||||||
// the keyword:
|
|
||||||
// "Whenever CARDNAME becomes blocked, it gets +1/+1 until end of turn for each creature blocking it."
|
// "Whenever CARDNAME becomes blocked, it gets +1/+1 until end of turn for each creature blocking it."
|
||||||
|
|
||||||
for (final Card attacker : tramplingAttackers) {
|
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 */
|
/** Support blockers not destroying the attacker with more blockers to try to kill the attacker */
|
||||||
private void reinforceBlockersToKill(final Combat combat) {
|
private void reinforceBlockersToKill(final Combat combat) {
|
||||||
|
|
||||||
List<Card> safeBlockers;
|
List<Card> safeBlockers;
|
||||||
List<Card> blockers;
|
List<Card> blockers;
|
||||||
List<Card> targetAttackers = CardLists.filter(blockedButUnkilled, Predicates.not(rampagesOrNeedsManyToBlock));
|
List<Card> targetAttackers = CardLists.filter(blockedButUnkilled, Predicates.not(rampagesOrNeedsManyToBlock));
|
||||||
@@ -1036,27 +1020,21 @@ public class AiBlockController {
|
|||||||
} else {
|
} else {
|
||||||
lifeInDanger = false;
|
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)) {
|
if (lifeInDanger && ComputerUtilCombat.lifeInDanger(ai, combat)) {
|
||||||
reinforceBlockersAgainstTrample(combat);
|
reinforceBlockersAgainstTrample(combat);
|
||||||
} else {
|
} else {
|
||||||
lifeInDanger = false;
|
lifeInDanger = false;
|
||||||
}
|
}
|
||||||
// Support blockers not destroying the attacker with more blockers
|
// Support blockers not destroying the attacker with more blockers
|
||||||
// to
|
// to try to kill the attacker
|
||||||
// try to kill the attacker
|
|
||||||
if (!lifeInDanger) {
|
if (!lifeInDanger) {
|
||||||
reinforceBlockersToKill(combat);
|
reinforceBlockersToKill(combat);
|
||||||
}
|
}
|
||||||
|
|
||||||
// == 2. If the AI life would still be in danger make a safer
|
// == 2. If the AI life would still be in danger make a safer approach ==
|
||||||
// approach ==
|
|
||||||
if (lifeInDanger && ComputerUtilCombat.lifeInDanger(ai, combat)) {
|
if (lifeInDanger && ComputerUtilCombat.lifeInDanger(ai, combat)) {
|
||||||
clearBlockers(combat, possibleBlockers); // reset every block
|
clearBlockers(combat, possibleBlockers); // reset every block assignment
|
||||||
// assignment
|
|
||||||
makeTradeBlocks(combat); // choose necessary trade blocks
|
makeTradeBlocks(combat); // choose necessary trade blocks
|
||||||
// if life is in danger
|
// if life is in danger
|
||||||
makeGoodBlocks(combat);
|
makeGoodBlocks(combat);
|
||||||
@@ -1066,8 +1044,7 @@ public class AiBlockController {
|
|||||||
} else {
|
} else {
|
||||||
lifeInDanger = false;
|
lifeInDanger = false;
|
||||||
}
|
}
|
||||||
// Reinforce blockers blocking attackers with trample if life is
|
// Reinforce blockers blocking attackers with trample if life is still in danger
|
||||||
// still in danger
|
|
||||||
if (lifeInDanger && ComputerUtilCombat.lifeInDanger(ai, combat)) {
|
if (lifeInDanger && ComputerUtilCombat.lifeInDanger(ai, combat)) {
|
||||||
reinforceBlockersAgainstTrample(combat);
|
reinforceBlockersAgainstTrample(combat);
|
||||||
} else {
|
} else {
|
||||||
@@ -1077,11 +1054,9 @@ public class AiBlockController {
|
|||||||
reinforceBlockersToKill(combat);
|
reinforceBlockersToKill(combat);
|
||||||
}
|
}
|
||||||
|
|
||||||
// == 3. If the AI life would be in serious danger make an even
|
// == 3. If the AI life would be in serious danger make an even safer approach ==
|
||||||
// safer approach ==
|
|
||||||
if (lifeInDanger && ComputerUtilCombat.lifeInSeriousDanger(ai, combat)) {
|
if (lifeInDanger && ComputerUtilCombat.lifeInSeriousDanger(ai, combat)) {
|
||||||
clearBlockers(combat, possibleBlockers); // reset every block
|
clearBlockers(combat, possibleBlockers); // reset every block assignment
|
||||||
// assignment
|
|
||||||
makeChumpBlocks(combat); // choose chump blocks
|
makeChumpBlocks(combat); // choose chump blocks
|
||||||
if (ComputerUtilCombat.lifeInDanger(ai, combat)) {
|
if (ComputerUtilCombat.lifeInDanger(ai, combat)) {
|
||||||
makeTradeBlocks(combat); // choose necessary trade
|
makeTradeBlocks(combat); // choose necessary trade
|
||||||
@@ -1090,15 +1065,13 @@ public class AiBlockController {
|
|||||||
if (!ComputerUtilCombat.lifeInDanger(ai, combat)) {
|
if (!ComputerUtilCombat.lifeInDanger(ai, combat)) {
|
||||||
makeGoodBlocks(combat);
|
makeGoodBlocks(combat);
|
||||||
}
|
}
|
||||||
// Reinforce blockers blocking attackers with trample if life is
|
// Reinforce blockers blocking attackers with trample if life is still in danger
|
||||||
// still in danger
|
|
||||||
else {
|
else {
|
||||||
reinforceBlockersAgainstTrample(combat);
|
reinforceBlockersAgainstTrample(combat);
|
||||||
}
|
}
|
||||||
makeGangBlocks(combat);
|
makeGangBlocks(combat);
|
||||||
// Support blockers not destroying the attacker with more
|
// Support blockers not destroying the attacker with more
|
||||||
// blockers
|
// blockers to try to kill the attacker
|
||||||
// to try to kill the attacker
|
|
||||||
reinforceBlockersToKill(combat);
|
reinforceBlockersToKill(combat);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1108,7 +1081,7 @@ public class AiBlockController {
|
|||||||
chumpBlockers.addAll(CardLists.getKeyword(blockersLeft, "CARDNAME blocks each combat if able."));
|
chumpBlockers.addAll(CardLists.getKeyword(blockersLeft, "CARDNAME blocks each combat if able."));
|
||||||
// if an attacker with lure attacks - all that can block
|
// if an attacker with lure attacks - all that can block
|
||||||
for (final Card blocker : blockersLeft) {
|
for (final Card blocker : blockersLeft) {
|
||||||
if (CombatUtil.mustBlockAnAttacker(blocker, combat)) {
|
if (CombatUtil.mustBlockAnAttacker(blocker, combat, null)) {
|
||||||
chumpBlockers.add(blocker);
|
chumpBlockers.add(blocker);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1118,7 +1091,7 @@ public class AiBlockController {
|
|||||||
blockers = getPossibleBlockers(combat, attacker, chumpBlockers, false);
|
blockers = getPossibleBlockers(combat, attacker, chumpBlockers, false);
|
||||||
for (final Card blocker : blockers) {
|
for (final Card blocker : blockers) {
|
||||||
if (CombatUtil.canBlock(attacker, blocker, combat) && blockersLeft.contains(blocker)
|
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 turn if able.")
|
||||||
|| blocker.hasKeyword("CARDNAME blocks each combat if able."))) {
|
|| blocker.hasKeyword("CARDNAME blocks each combat if able."))) {
|
||||||
combat.addBlocker(attacker, blocker);
|
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,
|
// 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
|
// unless life is low enough to be more worried about saving preserving the life total
|
||||||
if (ai.getController().isAI() && !ComputerUtilCombat.lifeInDanger(ai, combat)) {
|
if (ai.getController().isAI() && !ComputerUtilCombat.lifeInDanger(ai, combat)) {
|
||||||
|
|||||||
@@ -32,7 +32,9 @@ import com.google.common.collect.Lists;
|
|||||||
|
|
||||||
import forge.ai.ability.ChangeZoneAi;
|
import forge.ai.ability.ChangeZoneAi;
|
||||||
import forge.ai.ability.ExploreAi;
|
import forge.ai.ability.ExploreAi;
|
||||||
|
import forge.ai.ability.LearnAi;
|
||||||
import forge.ai.simulation.SpellAbilityPicker;
|
import forge.ai.simulation.SpellAbilityPicker;
|
||||||
|
import forge.card.CardStateName;
|
||||||
import forge.card.MagicColor;
|
import forge.card.MagicColor;
|
||||||
import forge.card.mana.ManaCost;
|
import forge.card.mana.ManaCost;
|
||||||
import forge.deck.CardPool;
|
import forge.deck.CardPool;
|
||||||
@@ -45,6 +47,7 @@ import forge.game.Game;
|
|||||||
import forge.game.GameActionUtil;
|
import forge.game.GameActionUtil;
|
||||||
import forge.game.GameEntity;
|
import forge.game.GameEntity;
|
||||||
import forge.game.GlobalRuleChange;
|
import forge.game.GlobalRuleChange;
|
||||||
|
import forge.game.ability.AbilityKey;
|
||||||
import forge.game.ability.AbilityUtils;
|
import forge.game.ability.AbilityUtils;
|
||||||
import forge.game.ability.ApiType;
|
import forge.game.ability.ApiType;
|
||||||
import forge.game.ability.SpellApiBased;
|
import forge.game.ability.SpellApiBased;
|
||||||
@@ -77,6 +80,7 @@ import forge.game.player.Player;
|
|||||||
import forge.game.player.PlayerActionConfirmMode;
|
import forge.game.player.PlayerActionConfirmMode;
|
||||||
import forge.game.replacement.ReplaceMoved;
|
import forge.game.replacement.ReplaceMoved;
|
||||||
import forge.game.replacement.ReplacementEffect;
|
import forge.game.replacement.ReplacementEffect;
|
||||||
|
import forge.game.replacement.ReplacementLayer;
|
||||||
import forge.game.replacement.ReplacementType;
|
import forge.game.replacement.ReplacementType;
|
||||||
import forge.game.spellability.AbilitySub;
|
import forge.game.spellability.AbilitySub;
|
||||||
import forge.game.spellability.LandAbility;
|
import forge.game.spellability.LandAbility;
|
||||||
@@ -88,6 +92,7 @@ import forge.game.spellability.SpellAbilityCondition;
|
|||||||
import forge.game.spellability.SpellAbilityPredicates;
|
import forge.game.spellability.SpellAbilityPredicates;
|
||||||
import forge.game.spellability.SpellPermanent;
|
import forge.game.spellability.SpellPermanent;
|
||||||
import forge.game.staticability.StaticAbility;
|
import forge.game.staticability.StaticAbility;
|
||||||
|
import forge.game.staticability.StaticAbilityMustTarget;
|
||||||
import forge.game.trigger.Trigger;
|
import forge.game.trigger.Trigger;
|
||||||
import forge.game.trigger.TriggerType;
|
import forge.game.trigger.TriggerType;
|
||||||
import forge.game.trigger.WrappedAbility;
|
import forge.game.trigger.WrappedAbility;
|
||||||
@@ -305,6 +310,8 @@ public class AiController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
exSA.setTrigger(tr);
|
exSA.setTrigger(tr);
|
||||||
|
// need to set TriggeredObject
|
||||||
|
exSA.setTriggeringObject(AbilityKey.Card, card);
|
||||||
|
|
||||||
// for trigger test, need to ignore the conditions
|
// for trigger test, need to ignore the conditions
|
||||||
SpellAbilityCondition cons = exSA.getConditions();
|
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);
|
final CardCollectionView hand = player.getCardsIn(ZoneType.Hand);
|
||||||
CardCollection lands = new CardCollection(battlefield);
|
CardCollection lands = new CardCollection(battlefield);
|
||||||
lands.addAll(hand);
|
lands.addAll(hand);
|
||||||
lands = CardLists.filter(lands, CardPredicates.Presets.LANDS);
|
lands = CardLists.filter(lands, CardPredicates.Presets.LANDS);
|
||||||
int maxCmcInHand = Aggregates.max(hand, CardPredicates.Accessors.fnGetCmc);
|
int maxCmcInHand = Aggregates.max(hand, CardPredicates.Accessors.fnGetCmc);
|
||||||
|
|
||||||
|
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) {
|
for (final SpellAbility sa : spellAbilities) {
|
||||||
if (sa.isCycling()) {
|
if (sa.isCycling()) {
|
||||||
if (lands.size() >= Math.max(maxCmcInHand, 6)) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -501,20 +513,35 @@ public class AiController {
|
|||||||
|
|
||||||
//try to skip lands that enter the battlefield tapped
|
//try to skip lands that enter the battlefield tapped
|
||||||
if (!nonLandsInHand.isEmpty()) {
|
if (!nonLandsInHand.isEmpty()) {
|
||||||
CardCollection nonTappeddLands = new CardCollection();
|
CardCollection nonTappedLands = new CardCollection();
|
||||||
for (Card land : landList) {
|
for (Card land : landList) {
|
||||||
// Is this the best way to check if a land ETB Tapped?
|
// check replacement effects if land would enter tapped or not
|
||||||
if (land.hasSVar("ETBTappedSVar")) {
|
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;
|
continue;
|
||||||
}
|
}
|
||||||
// Glacial Fortress and friends
|
reSA.setActivatingPlayer(reSA.getHostCard().getController());
|
||||||
if (land.hasSVar("ETBCheckSVar") && CardFactoryUtil.xCount(land, land.getSVar("ETBCheckSVar")) == 0) {
|
if (reSA.metConditions()) {
|
||||||
|
foundTapped = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (foundTapped) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
nonTappeddLands.add(land);
|
|
||||||
|
nonTappedLands.add(land);
|
||||||
}
|
}
|
||||||
if (!nonTappeddLands.isEmpty()) {
|
if (!nonTappedLands.isEmpty()) {
|
||||||
landList = nonTappeddLands;
|
landList = nonTappedLands;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -586,7 +613,6 @@ public class AiController {
|
|||||||
sa.setActivatingPlayer(player);
|
sa.setActivatingPlayer(player);
|
||||||
// check everything necessary
|
// check everything necessary
|
||||||
|
|
||||||
|
|
||||||
AiPlayDecision opinion = canPlayAndPayFor(currentSA);
|
AiPlayDecision opinion = canPlayAndPayFor(currentSA);
|
||||||
//PhaseHandler ph = game.getPhaseHandler();
|
//PhaseHandler ph = game.getPhaseHandler();
|
||||||
// System.out.printf("Ai thinks '%s' of %s @ %s %s >>> \n", opinion, sa, Lang.getPossesive(ph.getPlayerTurn().getName()), ph.getPhase());
|
// 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);
|
return canPlayFromEffectAI((SpellPermanent)sa, false, true);
|
||||||
}
|
}
|
||||||
if (sa.usesTargeting() && !sa.isTargetNumberValid()) {
|
if (sa.usesTargeting()) {
|
||||||
if (!sa.getTargetRestrictions().hasCandidates(sa, true)) {
|
if (!sa.isTargetNumberValid() && !sa.getTargetRestrictions().hasCandidates(sa, true)) {
|
||||||
|
return AiPlayDecision.TargetingFailed;
|
||||||
|
}
|
||||||
|
if (!StaticAbilityMustTarget.meetsMustTargetRestriction(sa)) {
|
||||||
return AiPlayDecision.TargetingFailed;
|
return AiPlayDecision.TargetingFailed;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1110,12 +1139,18 @@ public class AiController {
|
|||||||
final CardCollection discardList = new CardCollection();
|
final CardCollection discardList = new CardCollection();
|
||||||
int count = 0;
|
int count = 0;
|
||||||
if (sa != null) {
|
if (sa != null) {
|
||||||
|
String logic = sa.getParamOrDefault("AILogic", "");
|
||||||
sourceCard = sa.getHostCard();
|
sourceCard = sa.getHostCard();
|
||||||
if ("Always".equals(sa.getParam("AILogic")) && !validCards.isEmpty()) {
|
if ("Always".equals(logic) && !validCards.isEmpty()) {
|
||||||
min = 1;
|
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);
|
return SpecialCardAi.VolrathsShapeshifter.targetBestCreature(player, sa);
|
||||||
} else if ("DiscardCMCX".equals(sa.getParam("AILogic"))) {
|
} else if ("DiscardCMCX".equals(logic)) {
|
||||||
final int cmc = sa.getXManaCostPaid();
|
final int cmc = sa.getXManaCostPaid();
|
||||||
CardCollection discards = CardLists.filter(player.getCardsIn(ZoneType.Hand), CardPredicates.hasCMC(cmc));
|
CardCollection discards = CardLists.filter(player.getCardsIn(ZoneType.Hand), CardPredicates.hasCMC(cmc));
|
||||||
if (discards.isEmpty()) {
|
if (discards.isEmpty()) {
|
||||||
@@ -1290,15 +1325,6 @@ public class AiController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean confirmStaticApplication(Card hostCard, GameEntity affected, String logic, String message) {
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1328,7 +1354,7 @@ public class AiController {
|
|||||||
|
|
||||||
public AiPlayDecision canPlayFromEffectAI(Spell spell, boolean mandatory, boolean withoutPayingManaCost) {
|
public AiPlayDecision canPlayFromEffectAI(Spell spell, boolean mandatory, boolean withoutPayingManaCost) {
|
||||||
int damage = ComputerUtil.getDamageForPlaying(player, spell);
|
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;
|
return AiPlayDecision.CurseEffects;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1397,8 +1423,6 @@ public class AiController {
|
|||||||
private List<SpellAbility> singleSpellAbilityList(SpellAbility sa) {
|
private List<SpellAbility> singleSpellAbilityList(SpellAbility sa) {
|
||||||
if (sa == null) { return null; }
|
if (sa == null) { return null; }
|
||||||
|
|
||||||
// System.out.println("Chosen to play: " + sa);
|
|
||||||
|
|
||||||
final List<SpellAbility> abilities = Lists.newArrayList();
|
final List<SpellAbility> abilities = Lists.newArrayList();
|
||||||
abilities.add(sa);
|
abilities.add(sa);
|
||||||
return abilities;
|
return abilities;
|
||||||
@@ -1443,6 +1467,13 @@ public class AiController {
|
|||||||
if (!game.getPhaseHandler().is(PhaseType.MAIN1) || !isSafeToHoldLandDropForMain2(land)) {
|
if (!game.getPhaseHandler().is(PhaseType.MAIN1) || !isSafeToHoldLandDropForMain2(land)) {
|
||||||
final List<SpellAbility> abilities = Lists.newArrayList();
|
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);
|
LandAbility la = new LandAbility(land, player, null);
|
||||||
la.setCardState(land.getCurrentState());
|
la.setCardState(land.getCurrentState());
|
||||||
if (la.canPlay()) {
|
if (la.canPlay()) {
|
||||||
@@ -1693,10 +1724,8 @@ public class AiController {
|
|||||||
for (int i = 0; i < numToExile; i++) {
|
for (int i = 0; i < numToExile; i++) {
|
||||||
Card chosen = null;
|
Card chosen = null;
|
||||||
for (final Card c : grave) { // Exile noncreatures first in
|
for (final Card c : grave) { // Exile noncreatures first in
|
||||||
// case we can revive. Might
|
// case we can revive. Might wanna do some additional
|
||||||
// wanna do some additional
|
// checking here for Flashback and the like.
|
||||||
// checking here for Flashback
|
|
||||||
// and the like.
|
|
||||||
if (!c.isCreature()) {
|
if (!c.isCreature()) {
|
||||||
chosen = c;
|
chosen = c;
|
||||||
break;
|
break;
|
||||||
@@ -1735,12 +1764,21 @@ public class AiController {
|
|||||||
* @param sa the sa
|
* @param sa the sa
|
||||||
* @return true, if successful
|
* @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();
|
Card hostCard = effect.getHostCard();
|
||||||
if (hostCard.hasAlternateState()) {
|
if (hostCard.hasAlternateState()) {
|
||||||
hostCard = game.getCardState(hostCard);
|
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")) {
|
if (effect.hasParam("AICheckSVar")) {
|
||||||
System.out.println("aiShouldRun?" + sa);
|
System.out.println("aiShouldRun?" + sa);
|
||||||
final String svarToCheck = effect.getParam("AICheckSVar");
|
final String svarToCheck = effect.getParam("AICheckSVar");
|
||||||
@@ -1755,7 +1793,7 @@ public class AiController {
|
|||||||
compareTo = Integer.parseInt(strCmpTo);
|
compareTo = Integer.parseInt(strCmpTo);
|
||||||
} catch (final Exception ignored) {
|
} catch (final Exception ignored) {
|
||||||
if (sa == null) {
|
if (sa == null) {
|
||||||
compareTo = CardFactoryUtil.xCount(hostCard, hostCard.getSVar(strCmpTo));
|
compareTo = AbilityUtils.calculateAmount(hostCard, hostCard.getSVar(strCmpTo), effect);
|
||||||
} else {
|
} else {
|
||||||
compareTo = AbilityUtils.calculateAmount(hostCard, hostCard.getSVar(strCmpTo), sa);
|
compareTo = AbilityUtils.calculateAmount(hostCard, hostCard.getSVar(strCmpTo), sa);
|
||||||
}
|
}
|
||||||
@@ -1765,7 +1803,7 @@ public class AiController {
|
|||||||
int left = 0;
|
int left = 0;
|
||||||
|
|
||||||
if (sa == null) {
|
if (sa == null) {
|
||||||
left = CardFactoryUtil.xCount(hostCard, hostCard.getSVar(svarToCheck));
|
left = AbilityUtils.calculateAmount(hostCard, svarToCheck, effect);
|
||||||
} else {
|
} else {
|
||||||
left = AbilityUtils.calculateAmount(hostCard, svarToCheck, sa);
|
left = AbilityUtils.calculateAmount(hostCard, svarToCheck, sa);
|
||||||
}
|
}
|
||||||
@@ -1855,11 +1893,13 @@ public class AiController {
|
|||||||
} else if ("LowestLoseLife".equals(logic)) {
|
} else if ("LowestLoseLife".equals(logic)) {
|
||||||
return MyRandom.getRandom().nextInt(Math.min(player.getLife() / 3, player.getWeakestOpponent().getLife())) + 1;
|
return MyRandom.getRandom().nextInt(Math.min(player.getLife() / 3, player.getWeakestOpponent().getLife())) + 1;
|
||||||
} else if ("HighestLoseLife".equals(logic)) {
|
} 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)) {
|
} else if ("HighestGetCounter".equals(logic)) {
|
||||||
return MyRandom.getRandom().nextInt(3);
|
return MyRandom.getRandom().nextInt(3);
|
||||||
} else if (source.hasSVar("EnergyToPay")) {
|
} else if (source.hasSVar("EnergyToPay")) {
|
||||||
return AbilityUtils.calculateAmount(source, source.getSVar("EnergyToPay"), sa);
|
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;
|
return max;
|
||||||
}
|
}
|
||||||
@@ -1954,7 +1994,6 @@ public class AiController {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// this is where the computer cheats
|
// this is where the computer cheats
|
||||||
// changes AllZone.getComputerPlayer().getZone(Zone.Library)
|
// changes AllZone.getComputerPlayer().getZone(Zone.Library)
|
||||||
|
|
||||||
@@ -2089,8 +2128,11 @@ public class AiController {
|
|||||||
if (useSimulation) {
|
if (useSimulation) {
|
||||||
return simPicker.chooseCardToHiddenOriginChangeZone(destination, origin, sa, fetchList, player2, decider);
|
return simPicker.chooseCardToHiddenOriginChangeZone(destination, origin, sa, fetchList, player2, decider);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sa.getApi() == ApiType.Explore) {
|
if (sa.getApi() == ApiType.Explore) {
|
||||||
return ExploreAi.shouldPutInGraveyard(fetchList, decider);
|
return ExploreAi.shouldPutInGraveyard(fetchList, decider);
|
||||||
|
} else if (sa.getApi() == ApiType.Learn) {
|
||||||
|
return LearnAi.chooseCardToLearn(fetchList, decider, sa);
|
||||||
} else {
|
} else {
|
||||||
return ChangeZoneAi.chooseCardToHiddenOriginChangeZone(destination, origin, sa, fetchList, player2, decider);
|
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
|
// AI logic for choosing which replacement effect to apply happens here.
|
||||||
// happens here.
|
|
||||||
return Iterables.getFirst(list, null);
|
return Iterables.getFirst(list, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,9 +6,11 @@ import java.util.Collections;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
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.Predicate;
|
||||||
import com.google.common.base.Predicates;
|
import com.google.common.base.Predicates;
|
||||||
import com.google.common.collect.Iterables;
|
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
|
|
||||||
import forge.card.CardType;
|
import forge.card.CardType;
|
||||||
@@ -22,43 +24,12 @@ import forge.game.card.CardLists;
|
|||||||
import forge.game.card.CardPredicates;
|
import forge.game.card.CardPredicates;
|
||||||
import forge.game.card.CounterEnumType;
|
import forge.game.card.CounterEnumType;
|
||||||
import forge.game.card.CounterType;
|
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.keyword.Keyword;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.spellability.SpellAbilityStackInstance;
|
import forge.game.spellability.SpellAbilityStackInstance;
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
import forge.util.Aggregates;
|
import forge.util.Aggregates;
|
||||||
import forge.util.Localizer;
|
|
||||||
import forge.util.TextUtil;
|
import forge.util.TextUtil;
|
||||||
import forge.util.collect.FCollectionView;
|
import forge.util.collect.FCollectionView;
|
||||||
|
|
||||||
@@ -92,15 +63,9 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PaymentDecision visit(CostChooseCreatureType cost) {
|
public PaymentDecision visit(CostChooseCreatureType cost) {
|
||||||
Integer amount = cost.convertAmount();
|
String choice = player.getController().chooseSomeType("Creature", ability, CardType.getAllCreatureTypes(),
|
||||||
Iterable<String> choices = player.getController().chooseSomeType(
|
Lists.newArrayList());
|
||||||
Localizer.getInstance().getMessage("lblCreature"), ability, amount, amount, Lists.newArrayList(CardType.getAllCreatureTypes()));
|
return PaymentDecision.type(choice);
|
||||||
|
|
||||||
if (choices == null || Iterables.isEmpty(choices)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return PaymentDecision.types(choices);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -583,6 +548,11 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
|||||||
return PaymentDecision.card(aic.getCardsToDiscard(c, type.split(";"), ability));
|
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) {
|
protected int removeCounter(GameEntityCounterTable table, List<Card> prefs, CounterEnumType cType, int stillToRemove) {
|
||||||
int removed = 0;
|
int removed = 0;
|
||||||
if (!prefs.isEmpty() && stillToRemove > 0) {
|
if (!prefs.isEmpty() && stillToRemove > 0) {
|
||||||
@@ -596,7 +566,7 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
|||||||
int thisRemove = Math.min(prefCard.getCounters(cType), stillToRemove);
|
int thisRemove = Math.min(prefCard.getCounters(cType), stillToRemove);
|
||||||
if (thisRemove > 0) {
|
if (thisRemove > 0) {
|
||||||
removed += thisRemove;
|
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) {
|
public PaymentDecision visit(CostRemoveAnyCounter cost) {
|
||||||
final String amount = cost.getAmount();
|
final String amount = cost.getAmount();
|
||||||
final int c = AbilityUtils.calculateAmount(source, amount, ability);
|
final int c = AbilityUtils.calculateAmount(source, amount, ability);
|
||||||
final Card originalHost = ability.getOriginalOrHost();
|
final Card originalHost = ObjectUtils.defaultIfNull(ability.getOriginalHost(), source);
|
||||||
|
|
||||||
if (c <= 0) {
|
if (c <= 0) {
|
||||||
return null;
|
return null;
|
||||||
@@ -662,7 +632,7 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
|||||||
int thisRemove = Math.min(card.getCounters(ctype), c - toRemove);
|
int thisRemove = Math.min(card.getCounters(ctype), c - toRemove);
|
||||||
if (thisRemove > 0) {
|
if (thisRemove > 0) {
|
||||||
toRemove += thisRemove;
|
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);
|
int over = Math.min(e.getValue(), c - toRemove);
|
||||||
if (over > 0) {
|
if (over > 0) {
|
||||||
toRemove += over;
|
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);
|
int over = Math.min(e.getValue(), c - toRemove);
|
||||||
if (over > 0) {
|
if (over > 0) {
|
||||||
toRemove += over;
|
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);
|
int over = Math.min(crd.getCounters(CounterEnumType.QUEST) - e, c - toRemove);
|
||||||
if (over > 0) {
|
if (over > 0) {
|
||||||
toRemove += over;
|
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);
|
int thisRemove = Math.min(card.getCounters(cost.counter), c - toRemove);
|
||||||
if (thisRemove > 0) {
|
if (thisRemove > 0) {
|
||||||
toRemove += thisRemove;
|
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);
|
int thisRemove = Math.min(e.getValue(), c - toRemove);
|
||||||
if (thisRemove > 0) {
|
if (thisRemove > 0) {
|
||||||
toRemove += thisRemove;
|
toRemove += thisRemove;
|
||||||
table.put(card, e.getKey(), thisRemove);
|
table.put(null, card, e.getKey(), thisRemove);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -188,7 +188,6 @@ public class ComputerUtil {
|
|||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
|
|
||||||
|
|
||||||
// Play higher costing spells first?
|
// Play higher costing spells first?
|
||||||
final Cost cost = sa.getPayCosts();
|
final Cost cost = sa.getPayCosts();
|
||||||
|
|
||||||
@@ -213,7 +212,8 @@ public class ComputerUtil {
|
|||||||
if (unless != null && !unless.endsWith(">")) {
|
if (unless != null && !unless.endsWith(">")) {
|
||||||
final int amount = AbilityUtils.calculateAmount(source, unless, sa);
|
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 the Unless isn't enough, this should be less likely to be used
|
||||||
if (amount > usableManaSources) {
|
if (amount > usableManaSources) {
|
||||||
@@ -280,7 +280,7 @@ public class ComputerUtil {
|
|||||||
SpellAbility newSA = sa.copyWithNoManaCost();
|
SpellAbility newSA = sa.copyWithNoManaCost();
|
||||||
newSA.setActivatingPlayer(ai);
|
newSA.setActivatingPlayer(ai);
|
||||||
|
|
||||||
if (!CostPayment.canPayAdditionalCosts(newSA.getPayCosts(), newSA)) {
|
if (!CostPayment.canPayAdditionalCosts(newSA.getPayCosts(), newSA) || !ComputerUtilMana.canPayManaCost(newSA, ai, 0)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1068,9 +1068,6 @@ public class ComputerUtil {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (card.isEquipment() && buffedcard.isCreature() && CombatUtil.canAttack(buffedcard, ComputerUtil.getOpponentFor(ai))) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (card.isCreature()) {
|
if (card.isCreature()) {
|
||||||
if (buffedcard.hasKeyword(Keyword.SOULBOND) && !buffedcard.isPaired()) {
|
if (buffedcard.hasKeyword(Keyword.SOULBOND) && !buffedcard.isPaired()) {
|
||||||
return true;
|
return true;
|
||||||
@@ -1093,8 +1090,8 @@ public class ComputerUtil {
|
|||||||
|
|
||||||
} // BuffedBy
|
} // BuffedBy
|
||||||
|
|
||||||
// get all cards the human controls with AntiBuffedBy
|
// there's a good chance AI will attack weak target
|
||||||
final CardCollectionView antibuffed = ComputerUtil.getOpponentFor(ai).getCardsIn(ZoneType.Battlefield);
|
final CardCollectionView antibuffed = ai.getWeakestOpponent().getCardsIn(ZoneType.Battlefield);
|
||||||
for (Card buffedcard : antibuffed) {
|
for (Card buffedcard : antibuffed) {
|
||||||
if (buffedcard.hasSVar("AntiBuffedBy")) {
|
if (buffedcard.hasSVar("AntiBuffedBy")) {
|
||||||
final String buffedby = buffedcard.getSVar("AntiBuffedBy");
|
final String buffedby = buffedcard.getSVar("AntiBuffedBy");
|
||||||
@@ -1128,7 +1125,7 @@ public class ComputerUtil {
|
|||||||
creatures2.add(creatures.get(i));
|
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) {
|
&& card.isCreature() && card.getManaCost().getCMC() <= 3) {
|
||||||
return true;
|
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
|
* @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) {
|
public static boolean shouldCastLessThanMax(final Player ai, final Card source) {
|
||||||
boolean ret = true;
|
if (source.getXManaCostPaid() > 0) {
|
||||||
if (source.getManaCost().countX() > 0) {
|
// If TargetMax is MaxTgts (i.e., an "X" cost), this is fine because AI is limited by payment resources available.
|
||||||
// If TargetMax is MaxTgts (i.e., an "X" cost), this is fine because AI is limited by mana available.
|
return true;
|
||||||
return ret;
|
}
|
||||||
} else {
|
if (aiLifeInDanger(ai, false, 0)) {
|
||||||
// Otherwise, if life is possibly in danger, then this is fine.
|
// Otherwise, if life is possibly in danger, then this is fine.
|
||||||
Combat combat = new Combat(ComputerUtil.getOpponentFor(ai));
|
return true;
|
||||||
CardCollectionView attackers = ComputerUtil.getOpponentFor(ai).getCreaturesInPlay();
|
|
||||||
for (Card att : attackers) {
|
|
||||||
if (ComputerUtilCombat.canAttackNextTurn(att, ai)) {
|
|
||||||
combat.addAttacker(att, ComputerUtil.getOpponentFor(att.getController()));
|
|
||||||
}
|
}
|
||||||
}
|
// do not play now.
|
||||||
AiBlockController aiBlock = new AiBlockController(ai);
|
return false;
|
||||||
aiBlock.assignBlockersForCombat(combat);
|
|
||||||
if (!ComputerUtilCombat.lifeInDanger(ai, combat)) {
|
|
||||||
// Otherwise, return false. Do not play now.
|
|
||||||
ret = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1266,8 +1252,8 @@ public class ComputerUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// get all cards the human controls with AntiBuffedBy
|
// there's a good chance AI will attack weak target
|
||||||
final CardCollectionView antibuffed = ComputerUtil.getOpponentFor(ai).getCardsIn(ZoneType.Battlefield);
|
final CardCollectionView antibuffed = ai.getWeakestOpponent().getCardsIn(ZoneType.Battlefield);
|
||||||
for (Card buffedcard : antibuffed) {
|
for (Card buffedcard : antibuffed) {
|
||||||
if (buffedcard.hasSVar("AntiBuffedBy")) {
|
if (buffedcard.hasSVar("AntiBuffedBy")) {
|
||||||
final String buffedby = buffedcard.getSVar("AntiBuffedBy");
|
final String buffedby = buffedcard.getSVar("AntiBuffedBy");
|
||||||
@@ -1463,7 +1449,7 @@ public class ComputerUtil {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int possibleNonCombatDamage(Player ai) {
|
public static int possibleNonCombatDamage(Player ai, Player enemy) {
|
||||||
int damage = 0;
|
int damage = 0;
|
||||||
final CardCollection all = new CardCollection(ai.getCardsIn(ZoneType.Battlefield));
|
final CardCollection all = new CardCollection(ai.getCardsIn(ZoneType.Battlefield));
|
||||||
all.addAll(ai.getCardsActivableInExternalZones(true));
|
all.addAll(ai.getCardsActivableInExternalZones(true));
|
||||||
@@ -1483,7 +1469,6 @@ public class ComputerUtil {
|
|||||||
if (tgt == null) {
|
if (tgt == null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
final Player enemy = ComputerUtil.getOpponentFor(ai);
|
|
||||||
if (!sa.canTarget(enemy)) {
|
if (!sa.canTarget(enemy)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -2256,24 +2241,24 @@ public class ComputerUtil {
|
|||||||
return getCardsToDiscardFromOpponent(aiChooser, p, sa, validCards, min, max);
|
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) {
|
if (validTypes == null) {
|
||||||
validTypes = ImmutableList.of();
|
validTypes = ImmutableList.of();
|
||||||
}
|
}
|
||||||
|
|
||||||
final Game game = ai.getGame();
|
final Game game = ai.getGame();
|
||||||
List<String> chosenList = Lists.newArrayList();
|
|
||||||
if (kindOfType.equals("Card")) {
|
|
||||||
String chosen = "";
|
String chosen = "";
|
||||||
|
if (kindOfType.equals("Card")) {
|
||||||
// TODO
|
// TODO
|
||||||
// computer will need to choose a type
|
// computer will need to choose a type based on whether it needs a creature or land,
|
||||||
// based on whether it needs a creature or land,
|
// otherwise, lib search for most common type left then, reveal chosenType to Human
|
||||||
// otherwise, lib search for most common type left
|
|
||||||
// then, reveal chosenType to Human
|
|
||||||
if (game.getPhaseHandler().is(PhaseType.UNTAP) && logic == null) { // Storage Matrix
|
if (game.getPhaseHandler().is(PhaseType.UNTAP) && logic == null) { // Storage Matrix
|
||||||
double amount = 0;
|
double amount = 0;
|
||||||
for (String type : validTypes) {
|
for (String type : CardType.getAllCardTypes()) {
|
||||||
|
if (!invalidTypes.contains(type)) {
|
||||||
CardCollection list = CardLists.filter(ai.getCardsIn(ZoneType.Battlefield), CardPredicates.isType(type), Presets.TAPPED);
|
CardCollection list = CardLists.filter(ai.getCardsIn(ZoneType.Battlefield), CardPredicates.isType(type), Presets.TAPPED);
|
||||||
double i = type.equals("Creature") ? list.size() * 1.5 : list.size();
|
double i = type.equals("Creature") ? list.size() * 1.5 : list.size();
|
||||||
if (i > amount) {
|
if (i > amount) {
|
||||||
@@ -2281,17 +2266,15 @@ public class ComputerUtil {
|
|||||||
chosen = type;
|
chosen = type;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (logic == "MostProminentInComputerDeck") {
|
}
|
||||||
chosen = ComputerUtilCard.getMostProminentType(ai.getAllCards(), validTypes);
|
|
||||||
}
|
}
|
||||||
if (StringUtils.isEmpty(chosen)) {
|
if (StringUtils.isEmpty(chosen)) {
|
||||||
chosen = validTypes.isEmpty() ? "Creature" : Aggregates.random(validTypes);
|
chosen = validTypes.isEmpty() ? "Creature" : Aggregates.random(validTypes);
|
||||||
}
|
}
|
||||||
chosenList.add(chosen);
|
|
||||||
} else if (kindOfType.equals("Creature")) {
|
} else if (kindOfType.equals("Creature")) {
|
||||||
String chosen = "";
|
|
||||||
if (logic != null) {
|
if (logic != null) {
|
||||||
List <String> valid = Lists.newArrayList(validTypes);
|
List <String> valid = Lists.newArrayList(CardType.getAllCreatureTypes());
|
||||||
|
valid.removeAll(invalidTypes);
|
||||||
|
|
||||||
if (logic.equals("MostProminentOnBattlefield")) {
|
if (logic.equals("MostProminentOnBattlefield")) {
|
||||||
chosen = ComputerUtilCard.getMostProminentType(game.getCardsIn(ZoneType.Battlefield), valid);
|
chosen = ComputerUtilCard.getMostProminentType(game.getCardsIn(ZoneType.Battlefield), valid);
|
||||||
@@ -2302,7 +2285,7 @@ public class ComputerUtil {
|
|||||||
else if (logic.equals("MostProminentOppControls")) {
|
else if (logic.equals("MostProminentOppControls")) {
|
||||||
CardCollection list = CardLists.filterControlledBy(game.getCardsIn(ZoneType.Battlefield), ai.getOpponents());
|
CardCollection list = CardLists.filterControlledBy(game.getCardsIn(ZoneType.Battlefield), ai.getOpponents());
|
||||||
chosen = ComputerUtilCard.getMostProminentType(list, valid);
|
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());
|
list = CardLists.filterControlledBy(game.getCardsInGame(), ai.getOpponents());
|
||||||
chosen = ComputerUtilCard.getMostProminentType(list, valid);
|
chosen = ComputerUtilCard.getMostProminentType(list, valid);
|
||||||
}
|
}
|
||||||
@@ -2314,17 +2297,16 @@ public class ComputerUtil {
|
|||||||
chosen = ComputerUtilCard.getMostProminentType(ai.getCardsIn(ZoneType.Graveyard), valid);
|
chosen = ComputerUtilCard.getMostProminentType(ai.getCardsIn(ZoneType.Graveyard), valid);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!CardType.isACreatureType(chosen) || !validTypes.contains(chosen)) {
|
if (!CardType.isACreatureType(chosen) || invalidTypes.contains(chosen)) {
|
||||||
chosen = Iterables.getFirst(validTypes, null);
|
chosen = "Sliver";
|
||||||
}
|
}
|
||||||
|
|
||||||
chosenList.add(chosen);
|
|
||||||
} else if (kindOfType.equals("Basic Land")) {
|
} else if (kindOfType.equals("Basic Land")) {
|
||||||
String chosen = "";
|
|
||||||
if (logic != null) {
|
if (logic != null) {
|
||||||
if (logic.equals("MostProminentOppControls")) {
|
if (logic.equals("MostProminentOppControls")) {
|
||||||
CardCollection list = CardLists.filterControlledBy(game.getCardsIn(ZoneType.Battlefield), ai.getOpponents());
|
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);
|
chosen = ComputerUtilCard.getMostProminentType(list, valid);
|
||||||
} else if (logic.equals("MostNeededType")) {
|
} else if (logic.equals("MostNeededType")) {
|
||||||
@@ -2347,9 +2329,9 @@ public class ComputerUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (logic.equals("ChosenLandwalk")) {
|
else if (logic.equals("ChosenLandwalk")) {
|
||||||
for (Card c : ai.getWeakestOpponent().getLandsInPlay()) {
|
for (Card c : AiAttackController.choosePreferredDefenderPlayer(ai).getLandsInPlay()) {
|
||||||
for (String t : c.getType()) {
|
for (String t : c.getType()) {
|
||||||
if (validTypes.contains(t) && CardType.isABasicLandType(t)) {
|
if (!invalidTypes.contains(t) && CardType.isABasicLandType(t)) {
|
||||||
chosen = t;
|
chosen = t;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -2358,28 +2340,16 @@ public class ComputerUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!CardType.isABasicLandType(chosen) || !validTypes.contains(chosen)) {
|
if (!CardType.isABasicLandType(chosen) || invalidTypes.contains(chosen)) {
|
||||||
chosen = Iterables.getFirst(validTypes, null);
|
chosen = "Island";
|
||||||
}
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (kindOfType.equals("Land")) {
|
else if (kindOfType.equals("Land")) {
|
||||||
String chosen = "";
|
|
||||||
if (logic != null) {
|
if (logic != null) {
|
||||||
if (logic.equals("ChosenLandwalk")) {
|
if (logic.equals("ChosenLandwalk")) {
|
||||||
for (Card c : ai.getWeakestOpponent().getLandsInPlay()) {
|
for (Card c : AiAttackController.choosePreferredDefenderPlayer(ai).getLandsInPlay()) {
|
||||||
for (String t : c.getType().getLandTypes()) {
|
for (String t : c.getType().getLandTypes()) {
|
||||||
if (validTypes.contains(t)) {
|
if (!invalidTypes.contains(t)) {
|
||||||
chosen = t;
|
chosen = t;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -2388,11 +2358,10 @@ public class ComputerUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (StringUtils.isEmpty(chosen)) {
|
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) {
|
public static Object vote(Player ai, List<Object> options, SpellAbility sa, Multimap<Object, Player> votes, Player forPlayer) {
|
||||||
@@ -2413,15 +2382,18 @@ public class ComputerUtil {
|
|||||||
case "Torture":
|
case "Torture":
|
||||||
return "Torture";
|
return "Torture";
|
||||||
case "GraceOrCondemnation":
|
case "GraceOrCondemnation":
|
||||||
return ai.getCreaturesInPlay().size() > ComputerUtil.getOpponentFor(ai).getCreaturesInPlay().size() ? "Grace"
|
List<ZoneType> graceZones = new ArrayList<ZoneType>();
|
||||||
: "Condemnation";
|
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":
|
case "CarnageOrHomage":
|
||||||
CardCollection cardsInPlay = CardLists
|
CardCollection cardsInPlay = CardLists.getNotType(sa.getHostCard().getGame().getCardsIn(ZoneType.Battlefield), "Land");
|
||||||
.getNotType(sa.getHostCard().getGame().getCardsIn(ZoneType.Battlefield), "Land");
|
|
||||||
CardCollection humanlist = CardLists.filterControlledBy(cardsInPlay, ai.getOpponents());
|
CardCollection humanlist = CardLists.filterControlledBy(cardsInPlay, ai.getOpponents());
|
||||||
CardCollection computerlist = CardLists.filterControlledBy(cardsInPlay, ai);
|
CardCollection computerlist = ai.getCreaturesInPlay();
|
||||||
return (ComputerUtilCard.evaluatePermanentList(computerlist) + 3) < ComputerUtilCard
|
return (ComputerUtilCard.evaluatePermanentList(computerlist) + 3) < ComputerUtilCard.evaluatePermanentList(humanlist) ? "Carnage" : "Homage";
|
||||||
.evaluatePermanentList(humanlist) ? "Carnage" : "Homage";
|
|
||||||
case "Judgment":
|
case "Judgment":
|
||||||
if (votes.isEmpty()) {
|
if (votes.isEmpty()) {
|
||||||
CardCollection list = new CardCollection();
|
CardCollection list = new CardCollection();
|
||||||
@@ -2460,8 +2432,7 @@ public class ComputerUtil {
|
|||||||
if (!source.canReceiveCounters(p1p1Type)) {
|
if (!source.canReceiveCounters(p1p1Type)) {
|
||||||
return opponent ? "Feather" : "Quill";
|
return opponent ? "Feather" : "Quill";
|
||||||
}
|
}
|
||||||
// if source is not on the battlefield anymore, choose +1/+1
|
// if source is not on the battlefield anymore, choose +1/+1 ones
|
||||||
// ones
|
|
||||||
if (!game.getCardState(source).isInZone(ZoneType.Battlefield)) {
|
if (!game.getCardState(source).isInZone(ZoneType.Battlefield)) {
|
||||||
return opponent ? "Feather" : "Quill";
|
return opponent ? "Feather" : "Quill";
|
||||||
}
|
}
|
||||||
@@ -2503,8 +2474,7 @@ public class ComputerUtil {
|
|||||||
return opponent ? "Numbers" : "Strength";
|
return opponent ? "Numbers" : "Strength";
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO check for ETB to +1/+1 counters
|
// TODO check for ETB to +1/+1 counters or over another trigger like lifegain
|
||||||
// or over another trigger like lifegain
|
|
||||||
|
|
||||||
int tokenScore = ComputerUtilCard.evaluateCreature(token);
|
int tokenScore = ComputerUtilCard.evaluateCreature(token);
|
||||||
|
|
||||||
@@ -2582,8 +2552,7 @@ public class ComputerUtil {
|
|||||||
return "Taxes";
|
return "Taxes";
|
||||||
} else {
|
} else {
|
||||||
// ai is first voter or ally of controller
|
// ai is first voter or ally of controller
|
||||||
// both are not affected, but if opponents controll creatures,
|
// both are not affected, but if opponents control creatures, sacrifice is worse
|
||||||
// sacrifice is worse
|
|
||||||
return controller.getOpponents().getCreaturesInPlay().isEmpty() ? "Taxes" : "Death";
|
return controller.getOpponents().getCreaturesInPlay().isEmpty() ? "Taxes" : "Death";
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
@@ -2667,7 +2636,7 @@ public class ComputerUtil {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (trigger.hasParam("ValidCard")) {
|
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;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2880,7 +2849,6 @@ public class ComputerUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static boolean lifegainNegative(final Player player, final Card source, final int n) {
|
public static boolean lifegainNegative(final Player player, final Card source, final int n) {
|
||||||
|
|
||||||
if (!player.canGainLife()) {
|
if (!player.canGainLife()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -2948,23 +2916,6 @@ public class ComputerUtil {
|
|||||||
return true;
|
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) {
|
public static int countUsefulCreatures(Player p) {
|
||||||
CardCollection creats = p.getCreaturesInPlay();
|
CardCollection creats = p.getCreaturesInPlay();
|
||||||
int count = 0;
|
int count = 0;
|
||||||
@@ -3047,7 +2998,7 @@ public class ComputerUtil {
|
|||||||
// call this to determine if it's safe to use a life payment spell
|
// 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.
|
// or trigger "emergency" strategies such as holding mana for Spike Weaver of Counterspell.
|
||||||
public static boolean aiLifeInDanger(Player ai, boolean serious, int payment) {
|
public static boolean aiLifeInDanger(Player ai, boolean serious, int payment) {
|
||||||
Player opponent = ComputerUtil.getOpponentFor(ai);
|
for (Player opponent: ai.getOpponents()) {
|
||||||
// test whether the human can kill the ai next turn
|
// test whether the human can kill the ai next turn
|
||||||
Combat combat = new Combat(opponent);
|
Combat combat = new Combat(opponent);
|
||||||
boolean containsAttacker = false;
|
boolean containsAttacker = false;
|
||||||
@@ -3073,6 +3024,7 @@ public class ComputerUtil {
|
|||||||
if ((!serious) && (ComputerUtilCombat.lifeInDanger(ai, combat, payment))) {
|
if ((!serious) && (ComputerUtilCombat.lifeInDanger(ai, combat, payment))) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -171,8 +171,12 @@ public class ComputerUtilAbility {
|
|||||||
return sa;
|
return sa;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Card getAbilitySource(SpellAbility sa) {
|
||||||
|
return sa.getOriginalHost() != null ? sa.getOriginalHost() : sa.getHostCard();
|
||||||
|
}
|
||||||
|
|
||||||
public static String getAbilitySourceName(SpellAbility sa) {
|
public static String getAbilitySourceName(SpellAbility sa) {
|
||||||
final Card c = sa.getOriginalOrHost();
|
final Card c = getAbilitySource(sa);
|
||||||
return c != null ? c.getName() : "";
|
return c != null ? c.getName() : "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ import forge.game.combat.Combat;
|
|||||||
import forge.game.combat.CombatUtil;
|
import forge.game.combat.CombatUtil;
|
||||||
import forge.game.cost.Cost;
|
import forge.game.cost.Cost;
|
||||||
import forge.game.cost.CostPayEnergy;
|
import forge.game.cost.CostPayEnergy;
|
||||||
|
import forge.game.cost.CostRemoveCounter;
|
||||||
import forge.game.keyword.Keyword;
|
import forge.game.keyword.Keyword;
|
||||||
import forge.game.keyword.KeywordCollection;
|
import forge.game.keyword.KeywordCollection;
|
||||||
import forge.game.keyword.KeywordInterface;
|
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);
|
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.
|
// The AI doesn't really pick the best enchantment, just the most expensive.
|
||||||
/**
|
/**
|
||||||
* <p>
|
* <p>
|
||||||
@@ -265,16 +316,14 @@ public class ComputerUtilCard {
|
|||||||
* @return a {@link forge.game.card.Card} object.
|
* @return a {@link forge.game.card.Card} object.
|
||||||
*/
|
*/
|
||||||
public static Card getBestAI(final Iterable<Card> list) {
|
public static Card getBestAI(final Iterable<Card> list) {
|
||||||
// Get Best will filter by appropriate getBest list if ALL of the list
|
// Get Best will filter by appropriate getBest list if ALL of the list is of that type
|
||||||
// is of that type
|
|
||||||
if (Iterables.all(list, CardPredicates.Presets.CREATURES)) {
|
if (Iterables.all(list, CardPredicates.Presets.CREATURES)) {
|
||||||
return ComputerUtilCard.getBestCreatureAI(list);
|
return ComputerUtilCard.getBestCreatureAI(list);
|
||||||
}
|
}
|
||||||
if (Iterables.all(list, CardPredicates.Presets.LANDS)) {
|
if (Iterables.all(list, CardPredicates.Presets.LANDS)) {
|
||||||
return getBestLandAI(list);
|
return getBestLandAI(list);
|
||||||
}
|
}
|
||||||
// TODO - Once we get an EvaluatePermanent this should call
|
// TODO - Once we get an EvaluatePermanent this should call getBestPermanent()
|
||||||
// getBestPermanent()
|
|
||||||
return ComputerUtilCard.getMostExpensivePermanentAI(list);
|
return ComputerUtilCard.getMostExpensivePermanentAI(list);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -383,7 +432,7 @@ public class ComputerUtilCard {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (biasLand && Iterables.any(list, CardPredicates.Presets.LANDS)) {
|
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);
|
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);
|
List<Card> lands = CardLists.filter(list, CardPredicates.Presets.LANDS);
|
||||||
if (lands.size() > 6) {
|
if (lands.size() > 6) {
|
||||||
return ComputerUtilCard.getWorstLand(lands);
|
return getWorstLand(lands);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasEnchantmants || hasArtifacts) {
|
if (hasEnchantmants || hasArtifacts) {
|
||||||
@@ -410,8 +459,7 @@ public class ComputerUtilCard {
|
|||||||
return getWorstCreatureAI(CardLists.filter(list, CardPredicates.Presets.CREATURES));
|
return getWorstCreatureAI(CardLists.filter(list, CardPredicates.Presets.CREATURES));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Planeswalkers fall through to here, lands will fall through if there
|
// Planeswalkers fall through to here, lands will fall through if there aren't very many
|
||||||
// aren't very many
|
|
||||||
return getCheapestPermanentAI(list, null, false);
|
return getCheapestPermanentAI(list, null, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -444,7 +492,7 @@ public class ComputerUtilCard {
|
|||||||
public static final Comparator<Card> EvaluateCreatureComparator = new Comparator<Card>() {
|
public static final Comparator<Card> EvaluateCreatureComparator = new Comparator<Card>() {
|
||||||
@Override
|
@Override
|
||||||
public int compare(final Card a, final Card b) {
|
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) {
|
public static CardCollectionView getLikelyBlockers(final Player ai, final CardCollectionView blockers) {
|
||||||
AiBlockController aiBlk = new AiBlockController(ai);
|
AiBlockController aiBlk = new AiBlockController(ai);
|
||||||
final Player opp = ai.getWeakestOpponent();
|
final Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
|
||||||
Combat combat = new Combat(opp);
|
Combat combat = new Combat(opp);
|
||||||
//Use actual attackers if available, else consider all possible attackers
|
//Use actual attackers if available, else consider all possible attackers
|
||||||
Combat currentCombat = ai.getGame().getCombat();
|
Combat currentCombat = ai.getGame().getCombat();
|
||||||
@@ -1633,7 +1681,7 @@ public class ComputerUtilCard {
|
|||||||
pumped.addChangedCardKeywords(kws, null, false, false, timestamp);
|
pumped.addChangedCardKeywords(kws, null, false, false, timestamp);
|
||||||
Set<CounterType> types = c.getCounters().keySet();
|
Set<CounterType> types = c.getCounters().keySet();
|
||||||
for(CounterType ct : types) {
|
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.)
|
//Copies tap-state and extra keywords (auras, equipment, etc.)
|
||||||
if (c.isTapped()) {
|
if (c.isTapped()) {
|
||||||
@@ -1644,11 +1692,12 @@ public class ComputerUtilCard {
|
|||||||
copiedKeywords.insertAll(pumped.getKeywords());
|
copiedKeywords.insertAll(pumped.getKeywords());
|
||||||
List<KeywordInterface> toCopy = Lists.newArrayList();
|
List<KeywordInterface> toCopy = Lists.newArrayList();
|
||||||
for (KeywordInterface k : c.getKeywords()) {
|
for (KeywordInterface k : c.getKeywords()) {
|
||||||
if (!copiedKeywords.contains(k.getOriginal())) {
|
KeywordInterface copiedKI = k.copy(c, true);
|
||||||
if (k.getHidden()) {
|
if (!copiedKeywords.contains(copiedKI.getOriginal())) {
|
||||||
pumped.addHiddenExtrinsicKeyword(k);
|
if (copiedKI.getHidden()) {
|
||||||
|
pumped.addHiddenExtrinsicKeyword(copiedKI);
|
||||||
} else {
|
} else {
|
||||||
toCopy.add(k);
|
toCopy.add(copiedKI);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1678,37 +1727,27 @@ public class ComputerUtilCard {
|
|||||||
// remove old boost that might be copied
|
// remove old boost that might be copied
|
||||||
for (final StaticAbility stAb : c.getStaticAbilities()) {
|
for (final StaticAbility stAb : c.getStaticAbilities()) {
|
||||||
vCard.removePTBoost(c.getTimestamp(), stAb.getId());
|
vCard.removePTBoost(c.getTimestamp(), stAb.getId());
|
||||||
final Map<String, String> params = stAb.getMapParams();
|
if (!stAb.getParam("Mode").equals("Continuous")) {
|
||||||
if (!params.get("Mode").equals("Continuous")) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (!params.containsKey("Affected")) {
|
if (!stAb.hasParam("Affected")) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (!params.containsKey("AddPower") && !params.containsKey("AddToughness")) {
|
if (!stAb.hasParam("AddPower") && !stAb.hasParam("AddToughness")) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
final String valid = params.get("Affected");
|
if (!vCard.isValid(stAb.getParam("Affected").split(","), c.getController(), c, stAb)) {
|
||||||
if (!vCard.isValid(valid, c.getController(), c, null)) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
int att = 0;
|
int att = 0;
|
||||||
if (params.containsKey("AddPower")) {
|
if (stAb.hasParam("AddPower")) {
|
||||||
String addP = params.get("AddPower");
|
String addP = stAb.getParam("AddPower");
|
||||||
if (addP.equals("AffectedX")) {
|
att = AbilityUtils.calculateAmount(addP.startsWith("Affected") ? vCard : c, addP, stAb, true);
|
||||||
att = CardFactoryUtil.xCount(vCard, AbilityUtils.getSVar(stAb, addP));
|
|
||||||
} else {
|
|
||||||
att = AbilityUtils.calculateAmount(c, addP, stAb);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
int def = 0;
|
int def = 0;
|
||||||
if (params.containsKey("AddToughness")) {
|
if (stAb.hasParam("AddToughness")) {
|
||||||
String addT = params.get("AddToughness");
|
String addT = stAb.getParam("AddToughness");
|
||||||
if (addT.equals("AffectedY")) {
|
def = AbilityUtils.calculateAmount(addT.startsWith("Affected") ? vCard : c, addT, stAb, true);
|
||||||
def = CardFactoryUtil.xCount(vCard, AbilityUtils.getSVar(stAb, addT));
|
|
||||||
} else {
|
|
||||||
def = AbilityUtils.calculateAmount(c, addT, stAb);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
vCard.addPTBoost(att, def, c.getTimestamp(), stAb.getId());
|
vCard.addPTBoost(att, def, c.getTimestamp(), stAb.getId());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,6 @@ import forge.game.ability.ApiType;
|
|||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
import forge.game.card.CardCollection;
|
import forge.game.card.CardCollection;
|
||||||
import forge.game.card.CardCollectionView;
|
import forge.game.card.CardCollectionView;
|
||||||
import forge.game.card.CardFactoryUtil;
|
|
||||||
import forge.game.card.CardLists;
|
import forge.game.card.CardLists;
|
||||||
import forge.game.card.CardPredicates;
|
import forge.game.card.CardPredicates;
|
||||||
import forge.game.card.CardUtil;
|
import forge.game.card.CardUtil;
|
||||||
@@ -97,34 +96,39 @@ public class ComputerUtilCombat {
|
|||||||
* canAttackNextTurn.
|
* canAttackNextTurn.
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param atacker
|
* @param attacker
|
||||||
* a {@link forge.game.card.Card} object.
|
* a {@link forge.game.card.Card} object.
|
||||||
* @param defender
|
* @param defender
|
||||||
* the defending {@link GameEntity}.
|
* the defending {@link GameEntity}.
|
||||||
* @return a boolean.
|
* @return a boolean.
|
||||||
*/
|
*/
|
||||||
public static boolean canAttackNextTurn(final Card atacker, final GameEntity defender) {
|
public static boolean canAttackNextTurn(final Card attacker, final GameEntity defender) {
|
||||||
if (!atacker.isCreature()) {
|
if (!attacker.isCreature()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!CombatUtil.canAttackNextTurn(atacker, defender)) {
|
if (!CombatUtil.canAttackNextTurn(attacker, defender)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (final KeywordInterface inst : atacker.getKeywords()) {
|
for (final KeywordInterface inst : attacker.getKeywords()) {
|
||||||
final String keyword = inst.getOriginal();
|
final String keyword = inst.getOriginal();
|
||||||
if (keyword.startsWith("CARDNAME attacks specific player each combat if able")) {
|
if (keyword.startsWith("CARDNAME attacks specific player each combat if able")) {
|
||||||
final String defined = keyword.split(":")[1];
|
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)) {
|
if (!defender.equals(player)) {
|
||||||
return false;
|
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
|
// The creature won't untap next turn
|
||||||
return !atacker.isTapped() || Untap.canUntap(atacker);
|
return !attacker.isTapped() || Untap.canUntap(attacker);
|
||||||
} // canAttackNextTurn(Card, GameEntity)
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>
|
* <p>
|
||||||
@@ -330,7 +334,7 @@ public class ComputerUtilCombat {
|
|||||||
*/
|
*/
|
||||||
public static int resultingPoison(final Player ai, final Combat combat) {
|
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)) {
|
if (!ai.canReceiveCounters(CounterEnumType.POISON)) {
|
||||||
return ai.getPoisonCounters();
|
return ai.getPoisonCounters();
|
||||||
}
|
}
|
||||||
@@ -408,7 +412,6 @@ public class ComputerUtilCombat {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// check for creatures that must be blocked
|
// check for creatures that must be blocked
|
||||||
final List<Card> attackers = combat.getAttackersOf(ai);
|
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) {
|
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
|
// life in danger only cares about the player's life. Not about a Planeswalkers life
|
||||||
// Planeswalkers life
|
|
||||||
if (ai.cantLose() || combat == null) {
|
if (ai.cantLose() || combat == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -564,8 +566,7 @@ public class ComputerUtilCombat {
|
|||||||
return damage;
|
return damage;
|
||||||
}
|
}
|
||||||
|
|
||||||
// This calculates the amount of damage a blocker in a blockgang can deal to
|
// This calculates the amount of damage a blocker in a blockgang can deal to the attacker
|
||||||
// the attacker
|
|
||||||
/**
|
/**
|
||||||
* <p>
|
* <p>
|
||||||
* dealsDamageAsBlocker.
|
* dealsDamageAsBlocker.
|
||||||
@@ -578,7 +579,6 @@ public class ComputerUtilCombat {
|
|||||||
* @return a int.
|
* @return a int.
|
||||||
*/
|
*/
|
||||||
public static int dealsDamageAsBlocker(final Card attacker, final Card defender) {
|
public static int dealsDamageAsBlocker(final Card attacker, final Card defender) {
|
||||||
|
|
||||||
int defenderDamage = predictDamageByBlockerWithoutDoubleStrike(attacker, defender);
|
int defenderDamage = predictDamageByBlockerWithoutDoubleStrike(attacker, defender);
|
||||||
|
|
||||||
if (defender.hasKeyword(Keyword.DOUBLE_STRIKE)) {
|
if (defender.hasKeyword(Keyword.DOUBLE_STRIKE)) {
|
||||||
@@ -643,7 +643,6 @@ public class ComputerUtilCombat {
|
|||||||
* @return a int.
|
* @return a int.
|
||||||
*/
|
*/
|
||||||
public static int totalShieldDamage(final Card attacker, final List<Card> defenders) {
|
public static int totalShieldDamage(final Card attacker, final List<Card> defenders) {
|
||||||
|
|
||||||
int defenderDefense = 0;
|
int defenderDefense = 0;
|
||||||
|
|
||||||
for (final Card defender : defenders) {
|
for (final Card defender : defenders) {
|
||||||
@@ -667,7 +666,6 @@ public class ComputerUtilCombat {
|
|||||||
* @return a int.
|
* @return a int.
|
||||||
*/
|
*/
|
||||||
public static int shieldDamage(final Card attacker, final Card blocker) {
|
public static int shieldDamage(final Card attacker, final Card blocker) {
|
||||||
|
|
||||||
if (ComputerUtilCombat.canDestroyBlockerBeforeFirstStrike(blocker, attacker, false)) {
|
if (ComputerUtilCombat.canDestroyBlockerBeforeFirstStrike(blocker, attacker, false)) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -772,7 +770,6 @@ public class ComputerUtilCombat {
|
|||||||
public static boolean combatTriggerWillTrigger(final Card attacker, final Card defender, final Trigger trigger,
|
public static boolean combatTriggerWillTrigger(final Card attacker, final Card defender, final Trigger trigger,
|
||||||
Combat combat, final List<Card> plannedAttackers) {
|
Combat combat, final List<Card> plannedAttackers) {
|
||||||
final Game game = attacker.getGame();
|
final Game game = attacker.getGame();
|
||||||
final Map<String, String> trigParams = trigger.getMapParams();
|
|
||||||
boolean willTrigger = false;
|
boolean willTrigger = false;
|
||||||
final Card source = trigger.getHostCard();
|
final Card source = trigger.getHostCard();
|
||||||
if (combat == null) {
|
if (combat == null) {
|
||||||
@@ -795,29 +792,27 @@ public class ComputerUtilCombat {
|
|||||||
if (combat.isAttacking(attacker)) {
|
if (combat.isAttacking(attacker)) {
|
||||||
return false; // The trigger should have triggered already
|
return false; // The trigger should have triggered already
|
||||||
}
|
}
|
||||||
if (trigParams.containsKey("ValidCard")) {
|
if (trigger.hasParam("ValidCard")) {
|
||||||
if (!trigger.matchesValid(attacker, trigParams.get("ValidCard").split(","))
|
if (!trigger.matchesValidParam("ValidCard", attacker)
|
||||||
&& !(combat.isAttacking(source) && trigger.matchesValid(source,
|
&& !(combat.isAttacking(source) && trigger.matchesValidParam("ValidCard", source)
|
||||||
trigParams.get("ValidCard").split(","))
|
&& !trigger.hasParam("Alone"))) {
|
||||||
&& !trigParams.containsKey("Alone"))) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (trigParams.containsKey("Attacked")) {
|
if (trigger.hasParam("Attacked")) {
|
||||||
if (combat.isAttacking(attacker)) {
|
if (combat.isAttacking(attacker)) {
|
||||||
GameEntity attacked = combat.getDefenderByAttacker(attacker);
|
if (!trigger.matchesValidParam("Attacked", combat.getDefenderByAttacker(attacker))) {
|
||||||
if (!trigger.matchesValid(attacked, trigParams.get("Attacked").split(","))) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if ("You,Planeswalker.YouCtrl".equals(trigParams.get("Attacked"))) {
|
if ("You,Planeswalker.YouCtrl".equals(trigger.getParam("Attacked"))) {
|
||||||
if (source.getController() == attacker.getController()) {
|
if (source.getController() == attacker.getController()) {
|
||||||
return false;
|
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
|
return false; // won't trigger since the AI is planning to attack with more than one creature
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -825,12 +820,10 @@ public class ComputerUtilCombat {
|
|||||||
// defender == null means unblocked
|
// defender == null means unblocked
|
||||||
if ((defender == null) && mode == TriggerType.AttackerUnblocked) {
|
if ((defender == null) && mode == TriggerType.AttackerUnblocked) {
|
||||||
willTrigger = true;
|
willTrigger = true;
|
||||||
if (trigParams.containsKey("ValidCard")) {
|
if (!trigger.matchesValidParam("ValidCard", attacker)) {
|
||||||
if (!trigger.matchesValid(attacker, trigParams.get("ValidCard").split(","))) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (defender == null) {
|
if (defender == null) {
|
||||||
return willTrigger;
|
return willTrigger;
|
||||||
@@ -838,8 +831,8 @@ public class ComputerUtilCombat {
|
|||||||
|
|
||||||
if (mode == TriggerType.Blocks) {
|
if (mode == TriggerType.Blocks) {
|
||||||
willTrigger = true;
|
willTrigger = true;
|
||||||
if (trigParams.containsKey("ValidBlocked")) {
|
if (trigger.hasParam("ValidBlocked")) {
|
||||||
String validBlocked = trigParams.get("ValidBlocked");
|
String validBlocked = trigger.getParam("ValidBlocked");
|
||||||
if (validBlocked.contains(".withLesserPower")) {
|
if (validBlocked.contains(".withLesserPower")) {
|
||||||
// Have to check this restriction here as triggering objects aren't set yet, so
|
// Have to check this restriction here as triggering objects aren't set yet, so
|
||||||
// ValidBlocked$Creature.powerLTX where X:TriggeredBlocker$CardPower crashes with NPE
|
// ValidBlocked$Creature.powerLTX where X:TriggeredBlocker$CardPower crashes with NPE
|
||||||
@@ -852,8 +845,8 @@ public class ComputerUtilCombat {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (trigParams.containsKey("ValidCard")) {
|
if (trigger.hasParam("ValidCard")) {
|
||||||
String validBlocker = trigParams.get("ValidCard");
|
String validBlocker = trigger.getParam("ValidCard");
|
||||||
if (validBlocker.contains(".withLesserPower")) {
|
if (validBlocker.contains(".withLesserPower")) {
|
||||||
// Have to check this restriction here as triggering objects aren't set yet, so
|
// Have to check this restriction here as triggering objects aren't set yet, so
|
||||||
// ValidCard$Creature.powerLTX where X:TriggeredAttacker$CardPower crashes with NPE
|
// 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) {
|
} else if (mode == TriggerType.AttackerBlocked || mode == TriggerType.AttackerBlockedByCreature) {
|
||||||
willTrigger = true;
|
willTrigger = true;
|
||||||
if (trigParams.containsKey("ValidBlocker")) {
|
if (!trigger.matchesValidParam("ValidBlocker", defender)) {
|
||||||
if (!trigger.matchesValid(defender, trigParams.get("ValidBlocker").split(","))) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
if (!trigger.matchesValidParam("ValidCard", attacker)) {
|
||||||
if (trigParams.containsKey("ValidCard")) {
|
|
||||||
if (!trigger.matchesValid(attacker, trigParams.get("ValidCard").split(","))) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
} else if (mode == TriggerType.DamageDone) {
|
} else if (mode == TriggerType.DamageDone) {
|
||||||
willTrigger = true;
|
willTrigger = true;
|
||||||
if (trigParams.containsKey("ValidSource")) {
|
if (trigger.hasParam("ValidSource")) {
|
||||||
if (!(trigger.matchesValid(defender, trigParams.get("ValidSource").split(","))
|
if (!(trigger.matchesValidParam("ValidSource", defender)
|
||||||
&& defender.getNetCombatDamage() > 0
|
&& defender.getNetCombatDamage() > 0
|
||||||
&& (!trigParams.containsKey("ValidTarget")
|
&& trigger.matchesValidParam("ValidTarget", attacker))) {
|
||||||
|| trigger.matchesValid(attacker, trigParams.get("ValidTarget").split(","))))) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!(trigger.matchesValid(attacker, trigParams.get("ValidSource").split(","))
|
if (!(trigger.matchesValidParam("ValidSource", attacker)
|
||||||
&& attacker.getNetCombatDamage() > 0
|
&& attacker.getNetCombatDamage() > 0
|
||||||
&& (!trigParams.containsKey("ValidTarget")
|
&& trigger.matchesValidParam("ValidTarget", defender))) {
|
||||||
|| trigger.matchesValid(defender, trigParams.get("ValidTarget").split(","))))) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -939,30 +926,22 @@ public class ComputerUtilCombat {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final Game game = attacker.getGame();
|
final Game game = attacker.getGame();
|
||||||
// look out for continuous static abilities that only care for blocking
|
// look out for continuous static abilities that only care for blocking creatures
|
||||||
// creatures
|
|
||||||
final CardCollectionView cardList = CardCollection.combine(game.getCardsIn(ZoneType.Battlefield), game.getCardsIn(ZoneType.Command));
|
final CardCollectionView cardList = CardCollection.combine(game.getCardsIn(ZoneType.Battlefield), game.getCardsIn(ZoneType.Command));
|
||||||
for (final Card card : cardList) {
|
for (final Card card : cardList) {
|
||||||
for (final StaticAbility stAb : card.getStaticAbilities()) {
|
for (final StaticAbility stAb : card.getStaticAbilities()) {
|
||||||
final Map<String, String> params = stAb.getMapParams();
|
if (!stAb.getParam("Mode").equals("Continuous")) {
|
||||||
if (!params.get("Mode").equals("Continuous")) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (!params.containsKey("Affected") || !params.get("Affected").contains("blocking")) {
|
if (!stAb.hasParam("Affected") || !stAb.getParam("Affected").contains("blocking")) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
final String valid = TextUtil.fastReplace(params.get("Affected"), "blocking", "Creature");
|
final String valid = TextUtil.fastReplace(stAb.getParam("Affected"), "blocking", "Creature");
|
||||||
if (!blocker.isValid(valid, card.getController(), card, null)) {
|
if (!blocker.isValid(valid, card.getController(), card, stAb)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (params.containsKey("AddPower")) {
|
if (stAb.hasParam("AddPower")) {
|
||||||
if (params.get("AddPower").equals("X")) {
|
power += AbilityUtils.calculateAmount(card, stAb.getParam("AddPower"), stAb);
|
||||||
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"));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1247,31 +1226,23 @@ public class ComputerUtilCombat {
|
|||||||
theTriggers.addAll(blocker.getTriggers());
|
theTriggers.addAll(blocker.getTriggers());
|
||||||
}
|
}
|
||||||
|
|
||||||
// look out for continuous static abilities that only care for attacking
|
// look out for continuous static abilities that only care for attacking creatures
|
||||||
// creatures
|
|
||||||
if (!withoutCombatStaticAbilities) {
|
if (!withoutCombatStaticAbilities) {
|
||||||
final CardCollectionView cardList = CardCollection.combine(game.getCardsIn(ZoneType.Battlefield), game.getCardsIn(ZoneType.Command));
|
final CardCollectionView cardList = CardCollection.combine(game.getCardsIn(ZoneType.Battlefield), game.getCardsIn(ZoneType.Command));
|
||||||
for (final Card card : cardList) {
|
for (final Card card : cardList) {
|
||||||
for (final StaticAbility stAb : card.getStaticAbilities()) {
|
for (final StaticAbility stAb : card.getStaticAbilities()) {
|
||||||
final Map<String, String> params = stAb.getMapParams();
|
if (!stAb.getParam("Mode").equals("Continuous")) {
|
||||||
if (!params.get("Mode").equals("Continuous")) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (!params.containsKey("Affected") || !params.get("Affected").contains("attacking")) {
|
if (!stAb.hasParam("Affected") || !stAb.getParam("Affected").contains("attacking")) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
final String valid = TextUtil.fastReplace(params.get("Affected"), "attacking", "Creature");
|
final String valid = TextUtil.fastReplace(stAb.getParam("Affected"), "attacking", "Creature");
|
||||||
if (!attacker.isValid(valid, card.getController(), card, null)) {
|
if (!attacker.isValid(valid, card.getController(), card, stAb)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (params.containsKey("AddPower")) {
|
if (stAb.hasParam("AddPower")) {
|
||||||
if (params.get("AddPower").equals("X")) {
|
power += AbilityUtils.calculateAmount(card, stAb.getParam("AddPower"), stAb);
|
||||||
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"));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1345,7 +1316,7 @@ public class ComputerUtilCombat {
|
|||||||
} else if (bonus.contains("TriggeredAttacker$CardToughness")) {
|
} else if (bonus.contains("TriggeredAttacker$CardToughness")) {
|
||||||
bonus = TextUtil.fastReplace(bonus, "TriggeredAttacker$CardToughness", TextUtil.concatNoSpace("Number$", String.valueOf(attacker.getNetToughness())));
|
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());
|
theTriggers.addAll(blocker.getTriggers());
|
||||||
}
|
}
|
||||||
|
|
||||||
// look out for continuous static abilities that only care for attacking
|
// look out for continuous static abilities that only care for attacking creatures
|
||||||
// creatures
|
|
||||||
if (!withoutCombatStaticAbilities) {
|
if (!withoutCombatStaticAbilities) {
|
||||||
final CardCollectionView cardList = game.getCardsIn(ZoneType.Battlefield);
|
final CardCollectionView cardList = game.getCardsIn(ZoneType.Battlefield);
|
||||||
for (final Card card : cardList) {
|
for (final Card card : cardList) {
|
||||||
@@ -1535,7 +1505,7 @@ public class ComputerUtilCombat {
|
|||||||
} else if (bonus.contains("TriggeredPlayersDefenders$Amount")) { // for Melee
|
} else if (bonus.contains("TriggeredPlayersDefenders$Amount")) { // for Melee
|
||||||
bonus = TextUtil.fastReplace(bonus, "TriggeredPlayersDefenders$Amount", "Number$1");
|
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())) {
|
} else if (ApiType.PumpAll.equals(sa.getApi())) {
|
||||||
|
|
||||||
@@ -1568,7 +1538,7 @@ public class ComputerUtilCombat {
|
|||||||
} else if (bonus.contains("TriggeredPlayersDefenders$Amount")) { // for Melee
|
} else if (bonus.contains("TriggeredPlayersDefenders$Amount")) { // for Melee
|
||||||
bonus = TextUtil.fastReplace(bonus, "TriggeredPlayersDefenders$Amount", "Number$1");
|
bonus = TextUtil.fastReplace(bonus, "TriggeredPlayersDefenders$Amount", "Number$1");
|
||||||
}
|
}
|
||||||
toughness += CardFactoryUtil.xCount(source, bonus);
|
toughness += AbilityUtils.calculateAmount(source, bonus, sa);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2251,11 +2221,12 @@ public class ComputerUtilCombat {
|
|||||||
* @return a int.
|
* @return a int.
|
||||||
*/
|
*/
|
||||||
public final static int getDamageToKill(final Card c) {
|
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")) {
|
&& c.hasSVar("DestroyWhenDamaged")) {
|
||||||
killDamage = 1 + c.getPreventNextDamageTotalShields();
|
killDamage = 1 + damageShield;
|
||||||
}
|
}
|
||||||
|
|
||||||
return killDamage;
|
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) {
|
public final static int predictDamageTo(final Player target, final int damage, final Card source, final boolean isCombat) {
|
||||||
|
|
||||||
final Game game = target.getGame();
|
final Game game = target.getGame();
|
||||||
int restDamage = damage;
|
int restDamage = damage;
|
||||||
|
|
||||||
@@ -2286,39 +2256,38 @@ public class ComputerUtilCombat {
|
|||||||
// Predict replacement effects
|
// Predict replacement effects
|
||||||
for (final Card ca : game.getCardsIn(ZoneType.STATIC_ABILITIES_SOURCE_ZONES)) {
|
for (final Card ca : game.getCardsIn(ZoneType.STATIC_ABILITIES_SOURCE_ZONES)) {
|
||||||
for (final ReplacementEffect re : ca.getReplacementEffects()) {
|
for (final ReplacementEffect re : ca.getReplacementEffects()) {
|
||||||
Map<String, String> params = re.getMapParams();
|
if (!re.getMode().equals(ReplacementType.DamageDone) ||
|
||||||
if (!re.getMode().equals(ReplacementType.DamageDone) || !params.containsKey("PreventionEffect")) {
|
(!re.hasParam("PreventionEffect") && !re.hasParam("Prevent"))) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// Immortal Coil prevents the damage but has a similar negative effect
|
// Immortal Coil prevents the damage but has a similar negative effect
|
||||||
if ("Immortal Coil".equals(ca.getName())) {
|
if ("Immortal Coil".equals(ca.getName())) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (params.containsKey("ValidSource")
|
if (!re.matchesValidParam("ValidSource", source)) {
|
||||||
&& !source.isValid(params.get("ValidSource"), ca.getController(), ca, null)) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (params.containsKey("ValidTarget")
|
if (!re.matchesValidParam("ValidTarget", source)) {
|
||||||
&& !target.isValid(params.get("ValidTarget"), ca.getController(), ca, null)) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (params.containsKey("IsCombat")) {
|
if (re.hasParam("IsCombat")) {
|
||||||
if (params.get("IsCombat").equals("True")) {
|
if (re.getParam("IsCombat").equals("True") != isCombat) {
|
||||||
if (!isCombat) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (isCombat) {
|
|
||||||
continue;
|
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;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
restDamage = target.staticDamagePrevention(restDamage, source, isCombat, true);
|
restDamage = target.staticDamagePrevention(restDamage, 0, source, isCombat);
|
||||||
|
|
||||||
return restDamage;
|
return restDamage;
|
||||||
}
|
}
|
||||||
@@ -2339,13 +2308,7 @@ public class ComputerUtilCombat {
|
|||||||
// This function helps the AI calculate the actual amount of damage an
|
// This function helps the AI calculate the actual amount of damage an
|
||||||
// effect would deal
|
// effect would deal
|
||||||
public final static int predictDamageTo(final Card target, final int damage, final Card source, final boolean isCombat) {
|
public final static int predictDamageTo(final Card target, final int damage, final Card source, final boolean isCombat) {
|
||||||
|
return predictDamageTo(target, damage, 0, source, isCombat);
|
||||||
int restDamage = damage;
|
|
||||||
|
|
||||||
restDamage = target.staticReplaceDamage(restDamage, source, isCombat);
|
|
||||||
restDamage = target.staticDamagePrevention(restDamage, source, isCombat, true);
|
|
||||||
|
|
||||||
return restDamage;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -2367,7 +2330,6 @@ public class ComputerUtilCombat {
|
|||||||
* @return a int.
|
* @return a int.
|
||||||
*/
|
*/
|
||||||
public final static int predictDamageTo(final Card target, final int damage, final int possiblePrevention, final Card source, final boolean isCombat) {
|
public final static int predictDamageTo(final Card target, final int damage, final int possiblePrevention, final Card source, final boolean isCombat) {
|
||||||
|
|
||||||
int restDamage = damage;
|
int restDamage = damage;
|
||||||
|
|
||||||
restDamage = target.staticReplaceDamage(restDamage, source, isCombat);
|
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) {
|
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)) {
|
if (combatant.hasKeyword(Keyword.DOUBLE_STRIKE) || combatant.hasKeyword(Keyword.FIRST_STRIKE)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -2485,7 +2446,14 @@ public class ComputerUtilCombat {
|
|||||||
List<ReplacementEffect> list = game.getReplacementHandler().getReplacementList(
|
List<ReplacementEffect> list = game.getReplacementHandler().getReplacementList(
|
||||||
ReplacementType.DamageDone, repParams, ReplacementLayer.Other);
|
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) {
|
public static boolean attackerHasThreateningAfflict(Card attacker, Player aiDefender) {
|
||||||
@@ -2494,20 +2462,6 @@ public class ComputerUtilCombat {
|
|||||||
return afflictDmg > attacker.getNetPower() || afflictDmg >= aiDefender.getLife();
|
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) {
|
public static List<Card> categorizeAttackersByEvasion(List<Card> attackers) {
|
||||||
List<Card> categorizedAttackers = Lists.newArrayList();
|
List<Card> categorizedAttackers = Lists.newArrayList();
|
||||||
|
|
||||||
@@ -2597,5 +2551,3 @@ public class ComputerUtilCombat {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package forge.ai;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
import forge.game.ability.ApiType;
|
||||||
import org.apache.commons.lang3.ObjectUtils;
|
import org.apache.commons.lang3.ObjectUtils;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
@@ -137,9 +138,8 @@ public class ComputerUtilCost {
|
|||||||
* the source
|
* the source
|
||||||
* @return true, if successful
|
* @return true, if successful
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public static boolean checkDiscardCost(final Player ai, final Cost cost, final Card source, SpellAbility sa) {
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -157,7 +157,7 @@ public class ComputerUtilCost {
|
|||||||
if (typeList.size() > ai.getMaxHandSize()) {
|
if (typeList.size() > ai.getMaxHandSize()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
int num = AbilityUtils.calculateAmount(source, disc.getAmount(), null);
|
int num = AbilityUtils.calculateAmount(source, disc.getAmount(), sa);
|
||||||
|
|
||||||
for (int i = 0; i < num; i++) {
|
for (int i = 0; i < num; i++) {
|
||||||
Card pref = ComputerUtil.getCardPreference(ai, source, "DiscardCost", typeList);
|
Card pref = ComputerUtil.getCardPreference(ai, source, "DiscardCost", typeList);
|
||||||
@@ -558,7 +558,6 @@ public class ComputerUtilCost {
|
|||||||
boolean payForOwnOnly = "OnlyOwn".equals(aiLogic);
|
boolean payForOwnOnly = "OnlyOwn".equals(aiLogic);
|
||||||
boolean payOwner = sa.hasParam("UnlessAI") && aiLogic.startsWith("Defined");
|
boolean payOwner = sa.hasParam("UnlessAI") && aiLogic.startsWith("Defined");
|
||||||
boolean payNever = "Never".equals(aiLogic);
|
boolean payNever = "Never".equals(aiLogic);
|
||||||
boolean shockland = "Shockland".equals(aiLogic);
|
|
||||||
boolean isMine = sa.getActivatingPlayer().equals(payer);
|
boolean isMine = sa.getActivatingPlayer().equals(payer);
|
||||||
|
|
||||||
if (payNever) { return false; }
|
if (payNever) { return false; }
|
||||||
@@ -573,26 +572,6 @@ public class ComputerUtilCost {
|
|||||||
if (sa.getHostCard() == null || payer.equals(sa.getHostCard().getController())) {
|
if (sa.getHostCard() == null || payer.equals(sa.getHostCard().getController())) {
|
||||||
return false;
|
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)) {
|
} else if ("Paralyze".equals(aiLogic)) {
|
||||||
final Card c = source.getEnchantingCard();
|
final Card c = source.getEnchantingCard();
|
||||||
if (c == null || c.isUntapped()) {
|
if (c == null || c.isUntapped()) {
|
||||||
@@ -613,7 +592,7 @@ public class ComputerUtilCost {
|
|||||||
// if payer can't lose life its no need to pay unless
|
// if payer can't lose life its no need to pay unless
|
||||||
if (!payer.canLoseLife())
|
if (!payer.canLoseLife())
|
||||||
return false;
|
return false;
|
||||||
else if (payer.getLife() <= Integer.valueOf(aiLogic.substring(6))) {
|
else if (payer.getLife() <= AbilityUtils.calculateAmount(source, aiLogic.substring(6), sa)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
} else if ("WillAttack".equals(aiLogic)) {
|
} else if ("WillAttack".equals(aiLogic)) {
|
||||||
@@ -630,6 +609,29 @@ public class ComputerUtilCost {
|
|||||||
return false;
|
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
|
// AI will only pay when it's not already payed and only opponents abilities
|
||||||
if (alreadyPaid || (payers.size() > 1 && (isMine && !payForOwnOnly))) {
|
if (alreadyPaid || (payers.size() > 1 && (isMine && !payForOwnOnly))) {
|
||||||
return false;
|
return false;
|
||||||
@@ -685,14 +687,14 @@ public class ComputerUtilCost {
|
|||||||
public static int getMaxXValue(SpellAbility sa, Player ai) {
|
public static int getMaxXValue(SpellAbility sa, Player ai) {
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
final SpellAbility root = sa.getRootAbility();
|
final SpellAbility root = sa.getRootAbility();
|
||||||
final Cost abCost = sa.getPayCosts();
|
final Cost abCost = root.getPayCosts();
|
||||||
if (abCost == null || !abCost.hasXInAnyCostPart()) {
|
if (abCost == null || !abCost.hasXInAnyCostPart()) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
Integer val = null;
|
Integer val = null;
|
||||||
|
|
||||||
if (sa.costHasManaX()) {
|
if (root.costHasManaX()) {
|
||||||
val = ComputerUtilMana.determineLeftoverMana(root, ai);
|
val = ComputerUtilMana.determineLeftoverMana(root, ai);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -319,8 +319,8 @@ public class ComputerUtilMana {
|
|||||||
|
|
||||||
// Exception: when paying generic mana with Cavern of Souls, prefer the colored mana producing ability
|
// Exception: when paying generic mana with Cavern of Souls, prefer the colored mana producing ability
|
||||||
// to attempt to make the spell uncounterable when possible.
|
// to attempt to make the spell uncounterable when possible.
|
||||||
if (ComputerUtilAbility.getAbilitySourceName(ma).equals("Cavern of Souls") && ma.hasChosenType()
|
if (ComputerUtilAbility.getAbilitySourceName(ma).equals("Cavern of Souls")
|
||||||
&& sa.getHostCard().getType().getCreatureTypes().contains(ma.getChosenType(0))) {
|
&& sa.getHostCard().getType().getCreatureTypes().contains(ma.getHostCard().getChosenType())) {
|
||||||
if (toPay == ManaCostShard.COLORLESS && cost.getUnpaidShards().contains(ManaCostShard.GENERIC)) {
|
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
|
// Deprioritize Cavern of Souls, try to pay generic mana with it instead to use the NoCounter ability
|
||||||
continue;
|
continue;
|
||||||
@@ -341,7 +341,7 @@ public class ComputerUtilMana {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (canPayShardWithSpellAbility(toPay, ai, paymentChoice, sa, checkCosts)) {
|
if (canPayShardWithSpellAbility(toPay, ai, paymentChoice, sa, checkCosts, cost.getXManaCostPaidByColor())) {
|
||||||
return paymentChoice;
|
return paymentChoice;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -414,6 +414,7 @@ public class ComputerUtilMana {
|
|||||||
// then apply this one
|
// then apply this one
|
||||||
if (!replaceType.isEmpty()) {
|
if (!replaceType.isEmpty()) {
|
||||||
for (SpellAbility saMana : replaceAmount) {
|
for (SpellAbility saMana : replaceAmount) {
|
||||||
|
Card card = saMana.getHostCard();
|
||||||
if (saMana.hasParam("ReplaceType")) {
|
if (saMana.hasParam("ReplaceType")) {
|
||||||
// replace color and colorless
|
// replace color and colorless
|
||||||
String color = saMana.getParam("ReplaceType");
|
String color = saMana.getParam("ReplaceType");
|
||||||
@@ -435,8 +436,8 @@ public class ComputerUtilMana {
|
|||||||
// replace color
|
// replace color
|
||||||
String color = saMana.getParam("ReplaceColor");
|
String color = saMana.getParam("ReplaceColor");
|
||||||
if ("Chosen".equals(color)) {
|
if ("Chosen".equals(color)) {
|
||||||
if (saMana.hasChosenColor()) {
|
if (card.hasChosenColor()) {
|
||||||
color = MagicColor.toShortString(saMana.getChosenColor());
|
color = MagicColor.toShortString(card.getChosenColor());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (saMana.hasParam("ReplaceOnly")) {
|
if (saMana.hasParam("ReplaceOnly")) {
|
||||||
@@ -488,7 +489,7 @@ public class ComputerUtilMana {
|
|||||||
int pAmount = AbilityUtils.calculateAmount(trSA.getHostCard(), trSA.getParamOrDefault("Amount", "1"), trSA);
|
int pAmount = AbilityUtils.calculateAmount(trSA.getHostCard(), trSA.getParamOrDefault("Amount", "1"), trSA);
|
||||||
String produced = trSA.getParam("Produced");
|
String produced = trSA.getParam("Produced");
|
||||||
if (produced.equals("Chosen")) {
|
if (produced.equals("Chosen")) {
|
||||||
produced = MagicColor.toShortString(trSA.getHostCard().getChosenColor(trSA));
|
produced = MagicColor.toShortString(trSA.getHostCard().getChosenColor());
|
||||||
}
|
}
|
||||||
manaProduced += " " + StringUtils.repeat(produced, pAmount);
|
manaProduced += " " + StringUtils.repeat(produced, pAmount);
|
||||||
} else if (ApiType.ManaReflected.equals(trSA.getApi())) {
|
} 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
|
// 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 (mana != null) {
|
||||||
if (ai.getManaPool().tryPayCostWithMana(sa, cost, mana, false)) {
|
if (ai.getManaPool().tryPayCostWithMana(sa, cost, mana, false)) {
|
||||||
manaSpentToPay.add(0, mana);
|
manaSpentToPay.add(0, mana);
|
||||||
@@ -608,7 +609,6 @@ public class ComputerUtilMana {
|
|||||||
|
|
||||||
String manaProduced = predictManafromSpellAbility(saPayment, ai, toPay);
|
String manaProduced = predictManafromSpellAbility(saPayment, ai, toPay);
|
||||||
|
|
||||||
//System.out.println(manaProduced);
|
|
||||||
payMultipleMana(cost, manaProduced, ai);
|
payMultipleMana(cost, manaProduced, ai);
|
||||||
|
|
||||||
// remove from available lists
|
// remove from available lists
|
||||||
@@ -935,7 +935,7 @@ public class ComputerUtilMana {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// get a mana of this type from floating, bail if none available
|
// 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 (mana != null) {
|
||||||
if (ai.getManaPool().tryPayCostWithMana(sa, cost, mana, test)) {
|
if (ai.getManaPool().tryPayCostWithMana(sa, cost, mana, test)) {
|
||||||
manaSpentToPay.add(0, mana);
|
manaSpentToPay.add(0, mana);
|
||||||
@@ -964,8 +964,10 @@ public class ComputerUtilMana {
|
|||||||
* a {@link forge.game.spellability.SpellAbility} object.
|
* a {@link forge.game.spellability.SpellAbility} object.
|
||||||
* @return a {@link forge.game.mana.Mana} 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) {
|
private static Mana getMana(final Player ai, final ManaCostShard shard, final SpellAbility saBeingPaidFor,
|
||||||
final List<Pair<Mana, Integer>> weightedOptions = selectManaToPayFor(ai.getManaPool(), shard, saBeingPaidFor, restriction, colorsPaid);
|
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
|
// Exclude border case
|
||||||
if (weightedOptions.isEmpty()) {
|
if (weightedOptions.isEmpty()) {
|
||||||
@@ -1014,9 +1016,13 @@ public class ComputerUtilMana {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static List<Pair<Mana, Integer>> selectManaToPayFor(final ManaPool manapool, final ManaCostShard shard,
|
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<>();
|
final List<Pair<Mana, Integer>> weightedOptions = new ArrayList<>();
|
||||||
for (final Mana thisMana : manapool) {
|
for (final Mana thisMana : manapool) {
|
||||||
|
if (shard == ManaCostShard.COLORED_X && !ManaCostBeingPaid.canColoredXShardBePaidByColor(MagicColor.toShortString(thisMana.getColor()), xManaCostPaidByColor)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (!manapool.canPayForShardWithColor(shard, thisMana.getColor())) {
|
if (!manapool.canPayForShardWithColor(shard, thisMana.getColor())) {
|
||||||
continue;
|
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();
|
final Card sourceCard = ma.getHostCard();
|
||||||
|
|
||||||
if (isManaSourceReserved(ai, sourceCard, sa)) {
|
if (isManaSourceReserved(ai, sourceCard, sa)) {
|
||||||
@@ -1130,6 +1136,10 @@ public class ComputerUtilMana {
|
|||||||
|
|
||||||
if (m.isComboMana()) {
|
if (m.isComboMana()) {
|
||||||
for (String s : m.getComboColors().split(" ")) {
|
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)))
|
if ("Any".equals(s) || ai.getManaPool().canPayForShardWithColor(toPay, ManaAtom.fromName(s)))
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -1140,6 +1150,9 @@ public class ComputerUtilMana {
|
|||||||
Set<String> reflected = CardUtil.getReflectableManaColors(ma);
|
Set<String> reflected = CardUtil.getReflectableManaColors(ma);
|
||||||
|
|
||||||
for (byte c : MagicColor.WUBRG) {
|
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))) {
|
if (ai.getManaPool().canPayForShardWithColor(toPay, c) && reflected.contains(MagicColor.toLongString(c))) {
|
||||||
m.setExpressChoice(MagicColor.toShortString(c));
|
m.setExpressChoice(MagicColor.toShortString(c));
|
||||||
return true;
|
return true;
|
||||||
@@ -1147,6 +1160,16 @@ public class ComputerUtilMana {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (toPay == ManaCostShard.COLORED_X) {
|
||||||
|
for (String s : m.mana().split(" ")) {
|
||||||
|
if (ManaCostBeingPaid.canColoredXShardBePaidByColor(s, xManaCostPaidByColor)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1433,17 +1456,26 @@ public class ComputerUtilMana {
|
|||||||
// Tack xMana Payments into mana here if X is a set value
|
// Tack xMana Payments into mana here if X is a set value
|
||||||
if (cost.getXcounter() > 0 || extraMana > 0) {
|
if (cost.getXcounter() > 0 || extraMana > 0) {
|
||||||
int manaToAdd = 0;
|
int manaToAdd = 0;
|
||||||
|
int xCounter = cost.getXcounter();
|
||||||
if (test && extraMana > 0) {
|
if (test && extraMana > 0) {
|
||||||
final int multiplicator = Math.max(cost.getXcounter(), 1);
|
final int multiplicator = Math.max(xCounter, 1);
|
||||||
manaToAdd = extraMana * multiplicator;
|
manaToAdd = extraMana * multiplicator;
|
||||||
} else {
|
} 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) {
|
if (!test) {
|
||||||
sa.setXManaCostPaid(manaToAdd / cost.getXcounter());
|
sa.setXManaCostPaid(manaToAdd / xCounter);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1530,7 +1562,7 @@ public class ComputerUtilMana {
|
|||||||
public boolean apply(final Card c) {
|
public boolean apply(final Card c) {
|
||||||
for (final SpellAbility am : getAIPlayableMana(c)) {
|
for (final SpellAbility am : getAIPlayableMana(c)) {
|
||||||
am.setActivatingPlayer(ai);
|
am.setActivatingPlayer(ai);
|
||||||
if (!checkPlayable || am.canPlay()) {
|
if (!checkPlayable || (am.canPlay() && am.checkRestrictions(ai))) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -162,9 +162,9 @@ public class CreatureEvaluator implements Function<Card, Integer> {
|
|||||||
value -= subValue(10, "must-attack");
|
value -= subValue(10, "must-attack");
|
||||||
} else if (c.hasStartOfKeyword("CARDNAME attacks specific player each combat if able")) {
|
} else if (c.hasStartOfKeyword("CARDNAME attacks specific player each combat if able")) {
|
||||||
value -= subValue(10, "must-attack-player");
|
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");
|
value -= subValue(toughness * 5, "reverse-reach");
|
||||||
}
|
}//*/
|
||||||
|
|
||||||
if (c.hasSVar("DestroyWhenDamaged")) {
|
if (c.hasSVar("DestroyWhenDamaged")) {
|
||||||
value -= subValue((toughness - 1) * 9, "dies-to-dmg");
|
value -= subValue((toughness - 1) * 9, "dies-to-dmg");
|
||||||
|
|||||||
@@ -3,23 +3,12 @@ package forge.ai;
|
|||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
import java.util.ArrayList;
|
import java.util.*;
|
||||||
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.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
|
import com.google.common.collect.*;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
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.StaticData;
|
||||||
import forge.card.CardStateName;
|
import forge.card.CardStateName;
|
||||||
import forge.card.MagicColor;
|
import forge.card.MagicColor;
|
||||||
@@ -27,7 +16,6 @@ import forge.game.Game;
|
|||||||
import forge.game.GameEntity;
|
import forge.game.GameEntity;
|
||||||
import forge.game.GameObject;
|
import forge.game.GameObject;
|
||||||
import forge.game.ability.AbilityFactory;
|
import forge.game.ability.AbilityFactory;
|
||||||
import forge.game.ability.AbilityKey;
|
|
||||||
import forge.game.ability.effects.DetachedCardEffect;
|
import forge.game.ability.effects.DetachedCardEffect;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
import forge.game.card.CardCloneStates;
|
import forge.game.card.CardCloneStates;
|
||||||
@@ -44,9 +32,7 @@ import forge.game.mana.ManaPool;
|
|||||||
import forge.game.phase.PhaseType;
|
import forge.game.phase.PhaseType;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.spellability.AbilityManaPart;
|
import forge.game.spellability.AbilityManaPart;
|
||||||
import forge.game.spellability.AbilitySub;
|
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.trigger.TriggerType;
|
|
||||||
import forge.game.zone.PlayerZone;
|
import forge.game.zone.PlayerZone;
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
import forge.item.IPaperCard;
|
import forge.item.IPaperCard;
|
||||||
@@ -89,7 +75,8 @@ public abstract class GameState {
|
|||||||
private final Map<Card, Integer> markedDamage = new HashMap<>();
|
private final Map<Card, Integer> markedDamage = new HashMap<>();
|
||||||
private final Map<Card, List<String>> cardToChosenClrs = new HashMap<>();
|
private final Map<Card, List<String>> cardToChosenClrs = new HashMap<>();
|
||||||
private final Map<Card, CardCollection> cardToChosenCards = 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>> cardToRememberedId = new HashMap<>();
|
||||||
private final Map<Card, List<String>> cardToImprintedId = new HashMap<>();
|
private final Map<Card, List<String>> cardToImprintedId = new HashMap<>();
|
||||||
private final Map<Card, List<String>> cardToMergedCards = 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());
|
newText.append("|Damage:").append(c.getDamage());
|
||||||
}
|
}
|
||||||
|
|
||||||
SpellAbility first = c.getFirstSpellAbility();
|
if (!c.getChosenColor().isEmpty()) {
|
||||||
if (first != null) {
|
newText.append("|ChosenColor:").append(TextUtil.join(c.getChosenColors(), ","));
|
||||||
if (first.hasChosenColor()) {
|
|
||||||
newText.append("|ChosenColor:").append(TextUtil.join(first.getChosenColors(), ","));
|
|
||||||
}
|
}
|
||||||
if (first.hasChosenType()) {
|
if (!c.getChosenType().isEmpty()) {
|
||||||
newText.append("|ChosenType:").append(TextUtil.join(first.getChosenType(), ","));
|
newText.append("|ChosenType:").append(c.getChosenType());
|
||||||
}
|
}
|
||||||
|
if (!c.getChosenType2().isEmpty()) {
|
||||||
|
newText.append("|ChosenType2:").append(c.getChosenType2());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!c.getNamedCard().isEmpty()) {
|
if (!c.getNamedCard().isEmpty()) {
|
||||||
newText.append("|NamedCard:").append(c.getNamedCard());
|
newText.append("|NamedCard:").append(c.getNamedCard());
|
||||||
}
|
}
|
||||||
@@ -638,7 +624,8 @@ public abstract class GameState {
|
|||||||
markedDamage.clear();
|
markedDamage.clear();
|
||||||
cardToChosenClrs.clear();
|
cardToChosenClrs.clear();
|
||||||
cardToChosenCards.clear();
|
cardToChosenCards.clear();
|
||||||
cardToChosenTypes.clear();
|
cardToChosenType.clear();
|
||||||
|
cardToChosenType2.clear();
|
||||||
cardToMergedCards.clear();
|
cardToMergedCards.clear();
|
||||||
cardToScript.clear();
|
cardToScript.clear();
|
||||||
cardAttackMap.clear();
|
cardAttackMap.clear();
|
||||||
@@ -733,7 +720,7 @@ public abstract class GameState {
|
|||||||
if (persistent) {
|
if (persistent) {
|
||||||
produced.put("PersistentMana", "True");
|
produced.put("PersistentMana", "True");
|
||||||
}
|
}
|
||||||
final AbilityManaPart abMana = new AbilityManaPart(dummy, null, produced);
|
final AbilityManaPart abMana = new AbilityManaPart(dummy, produced);
|
||||||
game.getAction().invoke(new Runnable() {
|
game.getAction().invoke(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
@@ -769,23 +756,10 @@ public abstract class GameState {
|
|||||||
}
|
}
|
||||||
game.fireEvent(new GameEventAttackersDeclared(attackingPlayer, attackersMap));
|
game.fireEvent(new GameEventAttackersDeclared(attackingPlayer, attackersMap));
|
||||||
|
|
||||||
if (!combat.getAttackers().isEmpty()) {
|
|
||||||
List<GameEntity> attackedTarget = Lists.newArrayList();
|
|
||||||
for (final Card c : combat.getAttackers()) {
|
for (final Card c : combat.getAttackers()) {
|
||||||
attackedTarget.add(combat.getDefenderByAttacker(c));
|
CombatUtil.checkDeclaredAttacker(game, c, combat, false);
|
||||||
}
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
game.getTriggerHandler().resetActiveTriggers();
|
|
||||||
game.updateCombatForView();
|
game.updateCombatForView();
|
||||||
game.fireEvent(new GameEventCombatChanged());
|
game.fireEvent(new GameEventCombatChanged());
|
||||||
|
|
||||||
@@ -828,7 +802,6 @@ public abstract class GameState {
|
|||||||
Card exiledWith = idToCard.get(Integer.parseInt(id));
|
Card exiledWith = idToCard.get(Integer.parseInt(id));
|
||||||
c.setExiledWith(exiledWith);
|
c.setExiledWith(exiledWith);
|
||||||
c.setExiledBy(exiledWith.getController());
|
c.setExiledBy(exiledWith.getController());
|
||||||
exiledWith.addExiledWith(c);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1087,13 +1060,19 @@ public abstract class GameState {
|
|||||||
Card c = entry.getKey();
|
Card c = entry.getKey();
|
||||||
List<String> colors = entry.getValue();
|
List<String> colors = entry.getValue();
|
||||||
|
|
||||||
c.setChosenColors(colors, c.getFirstSpellAbility());
|
c.setChosenColors(colors);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Chosen type
|
// Chosen type
|
||||||
for (Entry<Card, List<String>> entry : cardToChosenTypes.entrySet()) {
|
for (Entry<Card, String> entry : cardToChosenType.entrySet()) {
|
||||||
Card c = entry.getKey();
|
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
|
// Named card
|
||||||
@@ -1196,7 +1175,7 @@ public abstract class GameState {
|
|||||||
String[] allCounterStrings = counterString.split(",");
|
String[] allCounterStrings = counterString.split(",");
|
||||||
for (final String counterPair : allCounterStrings) {
|
for (final String counterPair : allCounterStrings) {
|
||||||
String[] pair = counterPair.split("=", 2);
|
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 tapped = c.isTapped();
|
||||||
boolean sickness = c.hasSickness();
|
boolean sickness = c.hasSickness();
|
||||||
Map<CounterType, Integer> counters = c.getCounters();
|
Map<CounterType, Integer> counters = c.getCounters();
|
||||||
// Note: Not clearCounters() since we want to keep the counters
|
// Note: Not clearCounters() since we want to keep the counters var as-is.
|
||||||
// var as-is.
|
|
||||||
c.setCounters(Maps.newHashMap());
|
c.setCounters(Maps.newHashMap());
|
||||||
if (c.isAura()) {
|
if (c.isAura()) {
|
||||||
// dummy "enchanting" to indicate that the card will be force-attached elsewhere
|
// 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")) {
|
else if (info.startsWith("OnAdventure")) {
|
||||||
String abAdventure = "DB$ Effect | RememberObjects$ Self | StaticAbilities$ Play | ExileOnMoved$ Exile | Duration$ Permanent | ConditionDefined$ Self | ConditionPresent$ Card.nonCopiedSpell";
|
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();
|
StringBuilder sbPlay = new StringBuilder();
|
||||||
sbPlay.append("Mode$ Continuous | MayPlay$ True | EffectZone$ Command | Affected$ Card.IsRemembered+nonAdventure");
|
sbPlay.append("Mode$ Continuous | MayPlay$ True | EffectZone$ Command | Affected$ Card.IsRemembered+nonAdventure");
|
||||||
sbPlay.append(" | AffectedZone$ Exile | Description$ You may cast the card.");
|
sbPlay.append(" | AffectedZone$ Exile | Description$ You may cast the card.");
|
||||||
@@ -1379,7 +1357,9 @@ public abstract class GameState {
|
|||||||
} else if (info.startsWith("ChosenColor:")) {
|
} else if (info.startsWith("ChosenColor:")) {
|
||||||
cardToChosenClrs.put(c, Arrays.asList(info.substring(info.indexOf(':') + 1).split(",")));
|
cardToChosenClrs.put(c, Arrays.asList(info.substring(info.indexOf(':') + 1).split(",")));
|
||||||
} else if (info.startsWith("ChosenType:")) {
|
} 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:")) {
|
} else if (info.startsWith("ChosenCards:")) {
|
||||||
CardCollection chosen = new CardCollection();
|
CardCollection chosen = new CardCollection();
|
||||||
String[] idlist = info.substring(info.indexOf(':') + 1).split(",");
|
String[] idlist = info.substring(info.indexOf(':') + 1).split(",");
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import java.util.HashMap;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.apache.commons.lang3.tuple.ImmutablePair;
|
import org.apache.commons.lang3.tuple.ImmutablePair;
|
||||||
import org.apache.commons.lang3.tuple.Pair;
|
import org.apache.commons.lang3.tuple.Pair;
|
||||||
|
|
||||||
@@ -79,6 +80,7 @@ import forge.util.MyRandom;
|
|||||||
import forge.util.collect.FCollection;
|
import forge.util.collect.FCollection;
|
||||||
import forge.util.collect.FCollectionView;
|
import forge.util.collect.FCollectionView;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A prototype for player controller class
|
* A prototype for player controller class
|
||||||
*
|
*
|
||||||
@@ -129,6 +131,12 @@ public class PlayerControllerAi extends PlayerController {
|
|||||||
return ComputerUtilCombat.distributeAIDamage(attacker, blockers, damageDealt, defender, overrideOrder);
|
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
|
@Override
|
||||||
public Integer announceRequirements(SpellAbility ability, String announce) {
|
public Integer announceRequirements(SpellAbility ability, String announce) {
|
||||||
// For now, these "announcements" are made within the AI classes of the appropriate SA effects
|
// For now, these "announcements" are made within the AI classes of the appropriate SA effects
|
||||||
@@ -528,7 +536,14 @@ public class PlayerControllerAi extends PlayerController {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CardCollectionView chooseCardsToDiscardUnlessType(int num, CardCollectionView hand, String uType, SpellAbility sa) {
|
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()) {
|
if (!cardsOfType.isEmpty()) {
|
||||||
Card toDiscard = Aggregates.itemWithMin(cardsOfType, CardPredicates.Accessors.fnGetCmc);
|
Card toDiscard = Aggregates.itemWithMin(cardsOfType, CardPredicates.Accessors.fnGetCmc);
|
||||||
return new CardCollection(toDiscard);
|
return new CardCollection(toDiscard);
|
||||||
@@ -543,12 +558,13 @@ public class PlayerControllerAi extends PlayerController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<String> chooseSomeType(String kindOfType, SpellAbility sa, int min, int max, List<String> validTypes) {
|
public String chooseSomeType(String kindOfType, SpellAbility sa, Collection<String> validTypes, List<String> invalidTypes, boolean isOptional) {
|
||||||
List<String> chosen = ComputerUtil.chooseSomeType(player, kindOfType, sa.getParam("AILogic"), min, max, validTypes);
|
String chosen = ComputerUtil.chooseSomeType(player, kindOfType, sa.getParam("AILogic"), validTypes, invalidTypes);
|
||||||
if (chosen.isEmpty()) {
|
if (StringUtils.isBlank(chosen) && !validTypes.isEmpty()) {
|
||||||
return validTypes.subList(0, min);
|
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;
|
return chosen;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -558,8 +574,8 @@ public class PlayerControllerAi extends PlayerController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean confirmReplacementEffect(ReplacementEffect replacementEffect, SpellAbility effectSA, String question) {
|
public boolean confirmReplacementEffect(ReplacementEffect replacementEffect, SpellAbility effectSA, GameEntity affected, String question) {
|
||||||
return brains.aiShouldRun(replacementEffect, effectSA);
|
return brains.aiShouldRun(replacementEffect, effectSA, affected);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -639,7 +655,6 @@ public class PlayerControllerAi extends PlayerController {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean playChosenSpellAbility(SpellAbility sa) {
|
public boolean playChosenSpellAbility(SpellAbility sa) {
|
||||||
// System.out.println("Playing sa: " + sa);
|
|
||||||
if (sa instanceof LandAbility) {
|
if (sa instanceof LandAbility) {
|
||||||
if (sa.canPlay()) {
|
if (sa.canPlay()) {
|
||||||
sa.resolve();
|
sa.resolve();
|
||||||
@@ -778,7 +793,8 @@ public class PlayerControllerAi extends PlayerController {
|
|||||||
case "BetterTgtThanRemembered":
|
case "BetterTgtThanRemembered":
|
||||||
if (source.getRememberedCount() > 0) {
|
if (source.getRememberedCount() > 0) {
|
||||||
Card rem = (Card) source.getFirstRemembered();
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
for (Card c : source.getController().getCreaturesInPlay()) {
|
for (Card c : source.getController().getCreaturesInPlay()) {
|
||||||
@@ -988,6 +1004,7 @@ public class PlayerControllerAi extends PlayerController {
|
|||||||
emptyAbility.setActivatingPlayer(player);
|
emptyAbility.setActivatingPlayer(player);
|
||||||
emptyAbility.setTriggeringObjects(sa.getTriggeringObjects());
|
emptyAbility.setTriggeringObjects(sa.getTriggeringObjects());
|
||||||
emptyAbility.setSVars(sa.getSVars());
|
emptyAbility.setSVars(sa.getSVars());
|
||||||
|
emptyAbility.setXManaCostPaid(sa.getRootAbility().getXManaCostPaid());
|
||||||
if (ComputerUtilCost.willPayUnlessCost(sa, player, cost, alreadyPaid, allPayers) && ComputerUtilCost.canPayCost(emptyAbility, player)) {
|
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
|
ComputerUtil.playNoStack(player, emptyAbility, getGame()); // AI needs something to resolve to pay that cost
|
||||||
return true;
|
return true;
|
||||||
@@ -1019,7 +1036,7 @@ public class PlayerControllerAi extends PlayerController {
|
|||||||
*/
|
*/
|
||||||
if (sa.isMayChooseNewTargets() && !sa.setupTargets()) {
|
if (sa.isMayChooseNewTargets() && !sa.setupTargets()) {
|
||||||
if (sa.isSpell()) {
|
if (sa.isSpell()) {
|
||||||
sa.getHostCard().ceaseToExist();
|
player.getGame().getAction().ceaseToExist(sa.getHostCard(), false);
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -1053,13 +1070,12 @@ public class PlayerControllerAi extends PlayerController {
|
|||||||
boolean noManaCost = tgtSA.hasParam("WithoutManaCost");
|
boolean noManaCost = tgtSA.hasParam("WithoutManaCost");
|
||||||
if (tgtSA instanceof Spell) { // Isn't it ALWAYS a spell?
|
if (tgtSA instanceof Spell) { // Isn't it ALWAYS a spell?
|
||||||
Spell spell = (Spell) tgtSA;
|
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) {
|
if (noManaCost) {
|
||||||
return ComputerUtil.playSpellAbilityWithoutPayingManaCost(player, tgtSA, getGame());
|
return ComputerUtil.playSpellAbilityWithoutPayingManaCost(player, tgtSA, getGame());
|
||||||
} else {
|
}
|
||||||
return ComputerUtil.playStack(tgtSA, player, getGame());
|
return ComputerUtil.playStack(tgtSA, player, getGame());
|
||||||
}
|
}
|
||||||
} else
|
|
||||||
return false; // didn't play spell
|
return false; // didn't play spell
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@@ -1083,7 +1099,6 @@ public class PlayerControllerAi extends PlayerController {
|
|||||||
boolean allCreatures = Iterables.all(Iterables.concat(pile1, pile2), CardPredicates.Presets.CREATURES);
|
boolean allCreatures = Iterables.all(Iterables.concat(pile1, pile2), CardPredicates.Presets.CREATURES);
|
||||||
int cmc1 = allCreatures ? ComputerUtilCard.evaluateCreatureList(pile1) : ComputerUtilCard.evaluatePermanentList(pile1);
|
int cmc1 = allCreatures ? ComputerUtilCard.evaluateCreatureList(pile1) : ComputerUtilCard.evaluatePermanentList(pile1);
|
||||||
int cmc2 = allCreatures ? ComputerUtilCard.evaluateCreatureList(pile2) : ComputerUtilCard.evaluatePermanentList(pile2);
|
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
|
// for now, this assumes that the outcome will be bad
|
||||||
// TODO: This should really have a ChooseLogic param to
|
// TODO: This should really have a ChooseLogic param to
|
||||||
|
|||||||
@@ -39,7 +39,6 @@ import forge.game.ability.ApiType;
|
|||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
import forge.game.card.CardCollection;
|
import forge.game.card.CardCollection;
|
||||||
import forge.game.card.CardCollectionView;
|
import forge.game.card.CardCollectionView;
|
||||||
import forge.game.card.CardFactoryUtil;
|
|
||||||
import forge.game.card.CardLists;
|
import forge.game.card.CardLists;
|
||||||
import forge.game.card.CardPredicates;
|
import forge.game.card.CardPredicates;
|
||||||
import forge.game.card.CardUtil;
|
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
|
// Chain of Acid
|
||||||
public static class ChainOfAcid {
|
public static class ChainOfAcid {
|
||||||
public static boolean consider(final Player ai, final SpellAbility sa) {
|
public static boolean consider(final Player ai, final SpellAbility sa) {
|
||||||
@@ -159,10 +215,9 @@ public class SpecialCardAi {
|
|||||||
final PhaseHandler ph = ai.getGame().getPhaseHandler();
|
final PhaseHandler ph = ai.getGame().getPhaseHandler();
|
||||||
final Combat combat = ai.getGame().getCombat();
|
final Combat combat = ai.getGame().getCombat();
|
||||||
|
|
||||||
Card animated = AnimateAi.becomeAnimated(sa.getHostCard(), sa);
|
Card animated = AnimateAi.becomeAnimated(sa.getHostCard(), sa.getSubAbility());
|
||||||
animated.addType("Creature");
|
|
||||||
if (sa.getHostCard().canReceiveCounters(CounterEnumType.P1P1)) {
|
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 isOppEOT = ph.is(PhaseType.END_OF_TURN) && ph.getNextTurn() == ai;
|
||||||
boolean isValuableAttacker = ph.is(PhaseType.MAIN1, ai) && ComputerUtilCard.doesSpecifiedCreatureAttackAI(ai, animated);
|
boolean isValuableAttacker = ph.is(PhaseType.MAIN1, ai) && ComputerUtilCard.doesSpecifiedCreatureAttackAI(ai, animated);
|
||||||
@@ -170,10 +225,6 @@ public class SpecialCardAi {
|
|||||||
|
|
||||||
return isOppEOT || isValuableAttacker || isValuableBlocker;
|
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
|
// Cursed Scroll
|
||||||
@@ -327,7 +378,7 @@ public class SpecialCardAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Do not activate if damage will be prevented
|
// 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;
|
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
|
// Necropotence
|
||||||
public static class Necropotence {
|
public static class Necropotence {
|
||||||
public static boolean consider(final Player ai, final SpellAbility sa) {
|
public static boolean consider(final Player ai, final SpellAbility sa) {
|
||||||
|
|||||||
@@ -58,6 +58,11 @@ public abstract class SpellAbilityAi {
|
|||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
final Cost cost = sa.getPayCosts();
|
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())) {
|
if (!checkConditions(ai, sa, sa.getConditions())) {
|
||||||
SpellAbility sub = sa.getSubAbility();
|
SpellAbility sub = sa.getSubAbility();
|
||||||
if (sub != null && !checkConditions(ai, sub, sub.getConditions())) {
|
if (sub != null && !checkConditions(ai, sub, sub.getConditions())) {
|
||||||
@@ -188,7 +193,7 @@ public abstract class SpellAbilityAi {
|
|||||||
* Handles the AI decision to play a triggered SpellAbility
|
* Handles the AI decision to play a triggered SpellAbility
|
||||||
*/
|
*/
|
||||||
protected boolean doTriggerAINoCost(final Player aiPlayer, final SpellAbility sa, final boolean mandatory) {
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ public enum SpellApiToAi {
|
|||||||
.put(ApiType.BidLife, BidLifeAi.class)
|
.put(ApiType.BidLife, BidLifeAi.class)
|
||||||
.put(ApiType.Bond, BondAi.class)
|
.put(ApiType.Bond, BondAi.class)
|
||||||
.put(ApiType.Branch, AlwaysPlayAi.class)
|
.put(ApiType.Branch, AlwaysPlayAi.class)
|
||||||
|
.put(ApiType.Camouflage, ChooseCardAi.class)
|
||||||
.put(ApiType.ChangeCombatants, ChangeCombatantsAi.class)
|
.put(ApiType.ChangeCombatants, ChangeCombatantsAi.class)
|
||||||
.put(ApiType.ChangeTargets, ChangeTargetsAi.class)
|
.put(ApiType.ChangeTargets, ChangeTargetsAi.class)
|
||||||
.put(ApiType.ChangeX, AlwaysPlayAi.class)
|
.put(ApiType.ChangeX, AlwaysPlayAi.class)
|
||||||
@@ -94,8 +95,10 @@ public enum SpellApiToAi {
|
|||||||
.put(ApiType.Haunt, HauntAi.class)
|
.put(ApiType.Haunt, HauntAi.class)
|
||||||
.put(ApiType.ImmediateTrigger, ImmediateTriggerAi.class)
|
.put(ApiType.ImmediateTrigger, ImmediateTriggerAi.class)
|
||||||
.put(ApiType.Investigate, InvestigateAi.class)
|
.put(ApiType.Investigate, InvestigateAi.class)
|
||||||
|
.put(ApiType.Learn, LearnAi.class)
|
||||||
.put(ApiType.LoseLife, LifeLoseAi.class)
|
.put(ApiType.LoseLife, LifeLoseAi.class)
|
||||||
.put(ApiType.LosesGame, GameLossAi.class)
|
.put(ApiType.LosesGame, GameLossAi.class)
|
||||||
|
.put(ApiType.MakeCard, AlwaysPlayAi.class)
|
||||||
.put(ApiType.Mana, ManaEffectAi.class)
|
.put(ApiType.Mana, ManaEffectAi.class)
|
||||||
.put(ApiType.ManaReflected, CannotPlayAi.class)
|
.put(ApiType.ManaReflected, CannotPlayAi.class)
|
||||||
.put(ApiType.Manifest, ManifestAi.class)
|
.put(ApiType.Manifest, ManifestAi.class)
|
||||||
|
|||||||
@@ -21,8 +21,7 @@ public class ActivateAbilityAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
final Player opp = ai.getWeakestOpponent();
|
final Player opp = ai.getStrongestOpponent();
|
||||||
boolean randomReturn = MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
|
|
||||||
|
|
||||||
List<Card> list = CardLists.getType(opp.getCardsIn(ZoneType.Battlefield), sa.getParamOrDefault("Type", "Card"));
|
List<Card> list = CardLists.getType(opp.getCardsIn(ZoneType.Battlefield), sa.getParamOrDefault("Type", "Card"));
|
||||||
if (list.isEmpty()) {
|
if (list.isEmpty()) {
|
||||||
@@ -40,12 +39,13 @@ public class ActivateAbilityAi extends SpellAbilityAi {
|
|||||||
sa.getTargets().add(opp);
|
sa.getTargets().add(opp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
boolean randomReturn = MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
|
||||||
return randomReturn;
|
return randomReturn;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
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 TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ import forge.game.cost.CostPutCounter;
|
|||||||
import forge.game.phase.PhaseHandler;
|
import forge.game.phase.PhaseHandler;
|
||||||
import forge.game.phase.PhaseType;
|
import forge.game.phase.PhaseType;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
|
import forge.game.player.PlayerActionConfirmMode;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.staticability.StaticAbility;
|
import forge.game.staticability.StaticAbility;
|
||||||
import forge.game.staticability.StaticAbilityContinuous;
|
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
|
// Don't use instant speed animate abilities before AI's COMBAT_BEGIN
|
||||||
if (!ph.is(PhaseType.COMBAT_BEGIN) && ph.isPlayerTurn(ai) && !SpellAbilityAi.isSorcerySpeed(sa)
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
// Don't use instant speed animate abilities outside human's
|
// Don't use instant speed animate abilities outside human's
|
||||||
// COMBAT_DECLARE_ATTACKERS or if no attackers
|
// 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)
|
&& (!ph.is(PhaseType.COMBAT_DECLARE_ATTACKERS)
|
||||||
|| game.getCombat() != null && game.getCombat().getAttackersOf(ai).isEmpty())) {
|
|| game.getCombat() != null && game.getCombat().getAttackersOf(ai).isEmpty())) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// Don't activate during MAIN2 unless this effect is permanent
|
// 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;
|
return false;
|
||||||
}
|
}
|
||||||
// Don't animate if the AI won't attack anyway or use as a potential blocker
|
// Don't animate if the AI won't attack anyway or use as a potential blocker
|
||||||
Player opponent = ai.getWeakestOpponent();
|
Player opponent = ai.getWeakestOpponent();
|
||||||
// Activating as a potential blocker is only viable if it's an ability activated from a permanent, otherwise
|
// Activating as a potential blocker is only viable if it's an ability activated from a permanent, otherwise
|
||||||
// the AI will waste resources
|
// the AI will waste resources
|
||||||
boolean activateAsPotentialBlocker = sa.hasParam("UntilYourNextTurn")
|
boolean activateAsPotentialBlocker = "UntilYourNextTurn".equals(sa.getParam("Duration"))
|
||||||
&& ai.getGame().getPhaseHandler().getNextTurn() != ai
|
&& ai.getGame().getPhaseHandler().getNextTurn() != ai
|
||||||
&& source.isPermanent();
|
&& source.isPermanent();
|
||||||
if (ph.isPlayerTurn(ai) && ai.getLife() < 6 && opponent.getLife() > 6
|
if (ph.isPlayerTurn(ai) && ai.getLife() < 6 && opponent.getLife() > 6
|
||||||
&& Iterables.any(opponent.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.CREATURES)
|
&& 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 false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@@ -155,7 +156,7 @@ public class AnimateAi extends SpellAbilityAi {
|
|||||||
&& !c.isEquipping();
|
&& !c.isEquipping();
|
||||||
|
|
||||||
// for creatures that could be improved (like Figure of Destiny)
|
// 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;
|
int power = -5;
|
||||||
if (sa.hasParam("Power")) {
|
if (sa.hasParam("Power")) {
|
||||||
power = AbilityUtils.calculateAmount(c, sa.getParam("Power"), sa);
|
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()) {
|
if (sa.hasParam("Crew") && c.isCreature()) {
|
||||||
// Do not try to crew a vehicle which is already a creature
|
// Do not try to crew a vehicle which is already a creature
|
||||||
return false;
|
return false;
|
||||||
@@ -243,6 +244,11 @@ public class AnimateAi extends SpellAbilityAi {
|
|||||||
return true;
|
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) {
|
private boolean animateTgtAI(final SpellAbility sa) {
|
||||||
final Player ai = sa.getActivatingPlayer();
|
final Player ai = sa.getActivatingPlayer();
|
||||||
final PhaseHandler ph = ai.getGame().getPhaseHandler();
|
final PhaseHandler ph = ai.getGame().getPhaseHandler();
|
||||||
@@ -272,7 +278,7 @@ public class AnimateAi extends SpellAbilityAi {
|
|||||||
Map<Card, Integer> data = Maps.newHashMap();
|
Map<Card, Integer> data = Maps.newHashMap();
|
||||||
for (final Card c : list) {
|
for (final Card c : list) {
|
||||||
// don't use Permanent animate on something that would leave the field
|
// 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;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -306,9 +312,9 @@ public class AnimateAi extends SpellAbilityAi {
|
|||||||
// if its player turn,
|
// if its player turn,
|
||||||
// check if its Permanent or that creature would attack
|
// check if its Permanent or that creature would attack
|
||||||
if (ph.isPlayerTurn(ai)) {
|
if (ph.isPlayerTurn(ai)) {
|
||||||
if (!sa.hasParam("Permanent")
|
if (!"Permanent".equals(sa.getParam("Duration"))
|
||||||
&& !ComputerUtilCard.doesSpecifiedCreatureAttackAI(ai, animatedCopy)
|
&& !ComputerUtilCard.doesSpecifiedCreatureAttackAI(ai, animatedCopy)
|
||||||
&& !sa.hasParam("UntilHostLeavesPlay")) {
|
&& !"UntilHostLeavesPlay".equals(sa.getParam("Duration"))) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -354,8 +360,7 @@ public class AnimateAi extends SpellAbilityAi {
|
|||||||
// This is reasonable for now. Kamahl, Fist of Krosa and a sorcery or
|
// This is reasonable for now. Kamahl, Fist of Krosa and a sorcery or
|
||||||
// two are the only things
|
// two are the only things
|
||||||
// that animate a target. Those can just use AI:RemoveDeck:All until
|
// that animate a target. Those can just use AI:RemoveDeck:All until
|
||||||
// this can do a reasonably
|
// this can do a reasonably good job of picking a good target
|
||||||
// good job of picking a good target
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -401,7 +406,7 @@ public class AnimateAi extends SpellAbilityAi {
|
|||||||
// allow ChosenType - overrides anything else specified
|
// allow ChosenType - overrides anything else specified
|
||||||
if (types.hasSubtype("ChosenType")) {
|
if (types.hasSubtype("ChosenType")) {
|
||||||
types.clear();
|
types.clear();
|
||||||
types.addAll(sa.getChosenType());
|
types.add(source.getChosenType());
|
||||||
}
|
}
|
||||||
|
|
||||||
final List<String> keywords = Lists.newArrayList();
|
final List<String> keywords = Lists.newArrayList();
|
||||||
@@ -432,7 +437,7 @@ public class AnimateAi extends SpellAbilityAi {
|
|||||||
if (sa.hasParam("Colors")) {
|
if (sa.hasParam("Colors")) {
|
||||||
final String colors = sa.getParam("Colors");
|
final String colors = sa.getParam("Colors");
|
||||||
if (colors.equals("ChosenColor")) {
|
if (colors.equals("ChosenColor")) {
|
||||||
tmpDesc = CardUtil.getShortColorsString(sa.getChosenColors());
|
tmpDesc = CardUtil.getShortColorsString(source.getChosenColors());
|
||||||
} else {
|
} else {
|
||||||
tmpDesc = CardUtil.getShortColorsString(Lists.newArrayList(Arrays.asList(colors.split(","))));
|
tmpDesc = CardUtil.getShortColorsString(Lists.newArrayList(Arrays.asList(colors.split(","))));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import java.util.Iterator;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
|
||||||
import com.google.common.base.Predicate;
|
import com.google.common.base.Predicate;
|
||||||
import com.google.common.base.Predicates;
|
import com.google.common.base.Predicates;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
@@ -554,12 +555,7 @@ public class AttachAi extends SpellAbilityAi {
|
|||||||
if (!evenBetterList.isEmpty()) {
|
if (!evenBetterList.isEmpty()) {
|
||||||
betterList = evenBetterList;
|
betterList = evenBetterList;
|
||||||
}
|
}
|
||||||
evenBetterList = CardLists.filter(betterList, new Predicate<Card>() {
|
evenBetterList = CardLists.filter(betterList, CardPredicates.Presets.UNTAPPED);
|
||||||
@Override
|
|
||||||
public boolean apply(final Card c) {
|
|
||||||
return c.isUntapped();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (!evenBetterList.isEmpty()) {
|
if (!evenBetterList.isEmpty()) {
|
||||||
betterList = evenBetterList;
|
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,
|
private static Card attachAISpecificCardPreference(final SpellAbility sa, final List<Card> list, final boolean mandatory,
|
||||||
final Card attachSource) {
|
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 Player ai = sa.getActivatingPlayer();
|
||||||
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
|
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
|
||||||
Card chosen = null;
|
Card chosen = null;
|
||||||
|
|
||||||
if ("Guilty Conscience".equals(sourceName)) {
|
if ("Guilty Conscience".equals(sourceName)) {
|
||||||
chosen = SpecialCardAi.GuiltyConscience.getBestAttachTarget(ai, sa, list);
|
chosen = SpecialCardAi.GuiltyConscience.getBestAttachTarget(ai, sa, list);
|
||||||
} else if ("Bonds of Faith".equals(sourceName)) {
|
} else if (sa.hasParam("AIValid")) {
|
||||||
chosen = doPumpOrCurseAILogic(ai, sa, list, "Human");
|
// TODO: Make the AI recognize which cards to pump based on the card's abilities alone
|
||||||
} else if ("Clutch of Undeath".equals(sourceName)) {
|
chosen = doPumpOrCurseAILogic(ai, sa, list, sa.getParam("AIValid"));
|
||||||
chosen = doPumpOrCurseAILogic(ai, sa, list, "Zombie");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If Mandatory (brought directly into play without casting) gotta
|
// If Mandatory (brought directly into play without casting) gotta
|
||||||
@@ -767,7 +761,7 @@ public class AttachAi extends SpellAbilityAi {
|
|||||||
int powerBuff = 0;
|
int powerBuff = 0;
|
||||||
for (StaticAbility stAb : sa.getHostCard().getStaticAbilities()) {
|
for (StaticAbility stAb : sa.getHostCard().getStaticAbilities()) {
|
||||||
if ("Card.EquippedBy".equals(stAb.getParam("Affected")) && stAb.hasParam("AddPower")) {
|
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())) {
|
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 (Card target : list) {
|
||||||
for (Trigger t : target.getTriggers()) {
|
for (Trigger t : target.getTriggers()) {
|
||||||
if (t.getMode() == TriggerType.SpellCast) {
|
if (t.getMode() == TriggerType.SpellCast) {
|
||||||
final Map<String, String> params = t.getMapParams();
|
if ("Card.Self".equals(t.getParam("TargetsValid")) && "You".equals(t.getParam("ValidActivatingPlayer"))) {
|
||||||
if ("Card.Self".equals(params.get("TargetsValid")) && "You".equals(params.get("ValidActivatingPlayer"))) {
|
|
||||||
magnetList.add(target);
|
magnetList.add(target);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -1646,7 +1639,7 @@ public class AttachAi extends SpellAbilityAi {
|
|||||||
return ComputerUtilCombat.canAttackNextTurn(card) && card.getNetCombatDamage() >= 1;
|
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.")) {
|
} 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();
|
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);
|
return CombatUtil.canBlock(card, true);
|
||||||
} else if (keyword.endsWith("CARDNAME's activated abilities can't be activated.")) {
|
} else if (keyword.endsWith("CARDNAME's activated abilities can't be activated.")) {
|
||||||
for (SpellAbility ability : card.getSpellAbilities()) {
|
for (SpellAbility ability : card.getSpellAbilities()) {
|
||||||
@@ -1699,7 +1692,7 @@ public class AttachAi extends SpellAbilityAi {
|
|||||||
if (!c.getController().equals(ai)) {
|
if (!c.getController().equals(ai)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return c.getType().hasCreatureType(type);
|
return c.isValid(type, ai, sa.getHostCard(), sa);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
List<Card> oppNonType = CardLists.filter(list, new Predicate<Card>() {
|
List<Card> oppNonType = CardLists.filter(list, new Predicate<Card>() {
|
||||||
@@ -1709,7 +1702,7 @@ public class AttachAi extends SpellAbilityAi {
|
|||||||
if (c.getController().equals(ai)) {
|
if (c.getController().equals(ai)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return !c.getType().hasCreatureType(type) && !ComputerUtilCard.isUselessCreature(ai, c);
|
return !c.isValid(type, ai, sa.getHostCard(), sa) && !ComputerUtilCard.isUselessCreature(ai, c);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -13,15 +13,18 @@ public class BalanceAi extends SpellAbilityAi {
|
|||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
||||||
String logic = sa.getParam("AILogic");
|
String logic = sa.getParam("AILogic");
|
||||||
|
|
||||||
int diff = 0;
|
int diff = 0;
|
||||||
// TODO Add support for multiplayer logic
|
Player opp = aiPlayer.getWeakestOpponent();
|
||||||
final Player opp = aiPlayer.getWeakestOpponent();
|
|
||||||
final CardCollectionView humPerms = opp.getCardsIn(ZoneType.Battlefield);
|
|
||||||
final CardCollectionView compPerms = aiPlayer.getCardsIn(ZoneType.Battlefield);
|
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)) {
|
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() -
|
diff += CardLists.filter(humPerms, CardPredicates.Presets.LANDS).size() -
|
||||||
CardLists.filter(compPerms, 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() -
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package forge.ai.ability;
|
|||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import forge.ai.AiAttackController;
|
||||||
import forge.ai.ComputerUtilCard;
|
import forge.ai.ComputerUtilCard;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.Game;
|
import forge.game.Game;
|
||||||
@@ -24,7 +25,7 @@ public class BidLifeAi extends SpellAbilityAi {
|
|||||||
if (tgt != null) {
|
if (tgt != null) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
if (tgt.canTgtCreature()) {
|
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);
|
list = CardLists.getValidCards(list, tgt.getValidTgts(), source.getController(), source, sa);
|
||||||
if (list.isEmpty()) {
|
if (list.isEmpty()) {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import forge.game.card.Card;
|
|||||||
import forge.game.mana.ManaCostBeingPaid;
|
import forge.game.mana.ManaCostBeingPaid;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
|
import forge.game.spellability.TargetChoices;
|
||||||
|
|
||||||
public class ChangeTargetsAi extends SpellAbilityAi {
|
public class ChangeTargetsAi extends SpellAbilityAi {
|
||||||
|
|
||||||
@@ -40,18 +41,20 @@ public class ChangeTargetsAi extends SpellAbilityAi {
|
|||||||
// nothing on stack, so nothing to target
|
// nothing on stack, so nothing to target
|
||||||
return false;
|
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
|
// something was already chosen before (e.g. in response to a trigger - Mizzium Meddler), so just proceed
|
||||||
return true;
|
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
|
// if this does not target at all or already targets host, no need to redirect it again
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (Card tgt : topSa.getTargets().getTargetCards()) {
|
for (Card tgt : topTargets.getTargetCards()) {
|
||||||
if (ComputerUtilAbility.getAbilitySourceName(sa).equals(tgt.getName()) && tgt.getController().equals(aiPlayer)) {
|
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),
|
// 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
|
// 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
|
// make sure not to redirect our own abilities
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -80,12 +83,22 @@ public class ChangeTargetsAi extends SpellAbilityAi {
|
|||||||
ManaCost normalizedMana = manaCost.getNormalizedMana();
|
ManaCost normalizedMana = manaCost.getNormalizedMana();
|
||||||
boolean canPay = ComputerUtilMana.canPayManaCost(new ManaCostBeingPaid(normalizedMana), sa, aiPlayer);
|
boolean canPay = ComputerUtilMana.canPayManaCost(new ManaCostBeingPaid(normalizedMana), sa, aiPlayer);
|
||||||
if (potentialDmg != -1 && potentialDmg <= payDamage && !canPay
|
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
|
// 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;
|
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.resetTargets();
|
||||||
sa.getTargets().add(topSa);
|
sa.getTargets().add(topSa);
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import com.google.common.collect.Iterables;
|
|||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
import com.google.common.collect.Maps;
|
import com.google.common.collect.Maps;
|
||||||
|
|
||||||
|
import forge.ai.AiAttackController;
|
||||||
import forge.ai.AiBlockController;
|
import forge.ai.AiBlockController;
|
||||||
import forge.ai.AiCardMemory;
|
import forge.ai.AiCardMemory;
|
||||||
import forge.ai.AiController;
|
import forge.ai.AiController;
|
||||||
@@ -57,6 +58,7 @@ import forge.game.player.PlayerActionConfirmMode;
|
|||||||
import forge.game.spellability.AbilitySub;
|
import forge.game.spellability.AbilitySub;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.spellability.TargetRestrictions;
|
import forge.game.spellability.TargetRestrictions;
|
||||||
|
import forge.game.staticability.StaticAbilityMustTarget;
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
import forge.util.MyRandom;
|
import forge.util.MyRandom;
|
||||||
|
|
||||||
@@ -74,11 +76,9 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean checkAiLogic(final Player ai, final SpellAbility sa, final String aiLogic) {
|
protected boolean checkAiLogic(final Player ai, final SpellAbility sa, final String aiLogic) {
|
||||||
Card host = sa.getHostCard();
|
if (sa.getHostCard() != null && sa.getHostCard().hasSVar("AIPreferenceOverride")) {
|
||||||
|
|
||||||
if (host != null && host.hasSVar("AIPreferenceOverride")) {
|
|
||||||
// currently used by SacAndUpgrade logic, might need simplification
|
// currently used by SacAndUpgrade logic, might need simplification
|
||||||
host.removeSVar("AIPreferenceOverride");
|
sa.getHostCard().removeSVar("AIPreferenceOverride");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (aiLogic.equals("BeforeCombat")) {
|
if (aiLogic.equals("BeforeCombat")) {
|
||||||
@@ -92,7 +92,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
} else if (aiLogic.equals("PriorityOptionalCost")) {
|
} else if (aiLogic.equals("PriorityOptionalCost")) {
|
||||||
boolean highPriority = false;
|
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
|
// 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
|
// 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)
|
highPriority |= ai.getGame().getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)
|
||||||
&& ai.getGame().getCombat() != null && ComputerUtilCombat.lifeInDanger(ai, ai.getGame().getCombat());
|
&& ai.getGame().getCombat() != null && ComputerUtilCombat.lifeInDanger(ai, ai.getGame().getCombat());
|
||||||
@@ -127,40 +127,6 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
return true;
|
return true;
|
||||||
} else if (aiLogic.equals("Pongify")) {
|
} else if (aiLogic.equals("Pongify")) {
|
||||||
return SpecialAiLogic.doPongifyLogic(ai, sa);
|
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);
|
return super.checkAiLogic(ai, sa, aiLogic);
|
||||||
@@ -197,16 +163,9 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
return SpecialCardAi.MazesEnd.consider(aiPlayer, sa);
|
return SpecialCardAi.MazesEnd.consider(aiPlayer, sa);
|
||||||
} else if (aiLogic.equals("Pongify")) {
|
} else if (aiLogic.equals("Pongify")) {
|
||||||
return sa.isTargetNumberValid(); // Pre-targeted in checkAiLogic
|
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 (sa.isHidden()) {
|
||||||
if (isHidden(sa)) {
|
|
||||||
return hiddenOriginCanPlayAI(aiPlayer, sa);
|
return hiddenOriginCanPlayAI(aiPlayer, sa);
|
||||||
}
|
}
|
||||||
return knownOriginCanPlayAI(aiPlayer, sa);
|
return knownOriginCanPlayAI(aiPlayer, sa);
|
||||||
@@ -223,21 +182,12 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
|
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
|
||||||
if (isHidden(sa)) {
|
if (sa.isHidden()) {
|
||||||
return hiddenOriginPlayDrawbackAI(aiPlayer, sa);
|
return hiddenOriginPlayDrawbackAI(aiPlayer, sa);
|
||||||
}
|
}
|
||||||
return knownOriginPlayDrawbackAI(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>
|
* <p>
|
||||||
* changeZoneTriggerAINoCost.
|
* changeZoneTriggerAINoCost.
|
||||||
@@ -273,7 +223,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
return delta <= 0;
|
return delta <= 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isHidden(sa)) {
|
if (sa.isHidden()) {
|
||||||
return hiddenTriggerAI(aiPlayer, sa, mandatory);
|
return hiddenTriggerAI(aiPlayer, sa, mandatory);
|
||||||
}
|
}
|
||||||
return knownOriginTriggerAI(aiPlayer, sa, mandatory);
|
return knownOriginTriggerAI(aiPlayer, sa, mandatory);
|
||||||
@@ -306,7 +256,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
|
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
|
||||||
ZoneType origin = null;
|
ZoneType origin = null;
|
||||||
final Player opponent = ai.getWeakestOpponent();
|
final Player opponent = AiAttackController.choosePreferredDefenderPlayer(ai);
|
||||||
boolean activateForCost = ComputerUtil.activateForCost(sa, ai);
|
boolean activateForCost = ComputerUtil.activateForCost(sa, ai);
|
||||||
|
|
||||||
if (sa.hasParam("Origin")) {
|
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
|
// if putting cards from hand to library and parent is drawing cards
|
||||||
// make sure this will actually do something:
|
// make sure this will actually do something:
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
final Player opp = aiPlayer.getWeakestOpponent();
|
final Player opp = AiAttackController.choosePreferredDefenderPlayer(aiPlayer);
|
||||||
if (tgt != null && tgt.canTgtPlayer()) {
|
if (tgt != null && tgt.canTgtPlayer()) {
|
||||||
boolean isCurse = sa.isCurse();
|
boolean isCurse = sa.isCurse();
|
||||||
if (isCurse && sa.canTarget(opp)) {
|
if (isCurse && sa.canTarget(opp)) {
|
||||||
@@ -573,7 +523,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
Iterable<Player> pDefined;
|
Iterable<Player> pDefined;
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
if ((tgt != null) && tgt.canTgtPlayer()) {
|
if ((tgt != null) && tgt.canTgtPlayer()) {
|
||||||
final Player opp = ai.getWeakestOpponent();
|
final Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
|
||||||
if (sa.isCurse()) {
|
if (sa.isCurse()) {
|
||||||
if (sa.canTarget(opp)) {
|
if (sa.canTarget(opp)) {
|
||||||
sa.getTargets().add(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);
|
return ph.getNextTurn().equals(ai) && ph.is(PhaseType.END_OF_TURN);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isHidden(sa)) {
|
if (sa.isHidden()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -935,7 +885,6 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
// TODO need to set XManaCostPaid for targets, maybe doesn't need PayX anymore?
|
// TODO need to set XManaCostPaid for targets, maybe doesn't need PayX anymore?
|
||||||
sa.setXManaCostPaid(xPay);
|
sa.setXManaCostPaid(xPay);
|
||||||
// TODO since change of PayX. the shouldCastLessThanMax logic might be faulty
|
|
||||||
}
|
}
|
||||||
CardCollection list = CardLists.getTargetableCards(game.getCardsIn(origin), sa);
|
CardCollection list = CardLists.getTargetableCards(game.getCardsIn(origin), sa);
|
||||||
|
|
||||||
@@ -956,9 +905,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
if (sa.isSpell()) {
|
if (sa.isSpell()) {
|
||||||
list.remove(source); // spells can't target their own source, because it's actually in the stack zone
|
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")) {
|
if (sa.hasParam("AttachedTo")) {
|
||||||
//System.out.println("isPreferredTarget att " + list);
|
|
||||||
list = CardLists.filter(list, new Predicate<Card>() {
|
list = CardLists.filter(list, new Predicate<Card>() {
|
||||||
@Override
|
@Override
|
||||||
public boolean apply(final Card c) {
|
public boolean apply(final Card c) {
|
||||||
@@ -970,7 +917,6 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
//System.out.println("isPreferredTarget ok " + list);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (list.size() < sa.getMinTargets()) {
|
if (list.size() < sa.getMinTargets()) {
|
||||||
@@ -1206,6 +1152,10 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
if (!list.isEmpty()) {
|
if (!list.isEmpty()) {
|
||||||
if (destination.equals(ZoneType.Battlefield) || origin.contains(ZoneType.Battlefield)) {
|
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);
|
final Card mostExpensive = ComputerUtilCard.getMostExpensivePermanentAI(list, sa, false);
|
||||||
if (mostExpensive.isCreature()) {
|
if (mostExpensive.isCreature()) {
|
||||||
// if a creature is most expensive take the best one
|
// if a creature is most expensive take the best one
|
||||||
@@ -1234,6 +1184,11 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
return false;
|
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)) {
|
} else if (destination.equals(ZoneType.Hand) || destination.equals(ZoneType.Library)) {
|
||||||
List<Card> nonLands = CardLists.getNotType(list, "Land");
|
List<Card> nonLands = CardLists.getNotType(list, "Land");
|
||||||
// Prefer to pull a creature, generally more useful for AI.
|
// 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) {
|
private static Card canBouncePermanent(final Player ai, SpellAbility sa, CardCollectionView list) {
|
||||||
Game game = ai.getGame();
|
Game game = ai.getGame();
|
||||||
// filter out untargetables
|
// filter out untargetables
|
||||||
CardCollectionView aiPermanents = CardLists
|
CardCollectionView aiPermanents = CardLists.filterControlledBy(list, ai);
|
||||||
.filterControlledBy(list, ai);
|
|
||||||
CardCollection aiPlaneswalkers = CardLists.filter(aiPermanents, Presets.PLANESWALKERS);
|
CardCollection aiPlaneswalkers = CardLists.filter(aiPermanents, Presets.PLANESWALKERS);
|
||||||
|
|
||||||
// Felidar Guardian + Saheeli Rai combo support
|
// Felidar Guardian + Saheeli Rai combo support
|
||||||
@@ -1525,9 +1479,11 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
if ("DeathgorgeScavenger".equals(logic)) {
|
if ("DeathgorgeScavenger".equals(logic)) {
|
||||||
return SpecialCardAi.DeathgorgeScavenger.consider(ai, sa);
|
return SpecialCardAi.DeathgorgeScavenger.consider(ai, sa);
|
||||||
} else if ("ExtraplanarLens".equals(logic)) {
|
}
|
||||||
|
if ("ExtraplanarLens".equals(logic)) {
|
||||||
return SpecialCardAi.ExtraplanarLens.consider(ai, sa);
|
return SpecialCardAi.ExtraplanarLens.consider(ai, sa);
|
||||||
} else if ("ExileCombatThreat".equals(logic)) {
|
}
|
||||||
|
if ("ExileCombatThreat".equals(logic)) {
|
||||||
return doExileCombatThreatLogic(ai, sa);
|
return doExileCombatThreatLogic(ai, sa);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1904,6 +1860,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!data.isEmpty()) {
|
||||||
// JAVA 1.8 use Map.Entry.comparingByValue() somehow
|
// 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>>>() {
|
Map.Entry<Player, Map.Entry<String, Integer>> max = Collections.max(data.entrySet(), new Comparator<Map.Entry<Player, Map.Entry<String, Integer>>>() {
|
||||||
@Override
|
@Override
|
||||||
@@ -1922,6 +1879,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
sa.getTargets().add(c);
|
sa.getTargets().add(c);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -2021,17 +1979,12 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
boolean setPayX = false;
|
boolean setPayX = false;
|
||||||
if (unlessCost.equals("X") && sa.getSVar(unlessCost).equals("Count$xPaid")) {
|
if (unlessCost.equals("X") && sa.getSVar(unlessCost).equals("Count$xPaid")) {
|
||||||
setPayX = true;
|
setPayX = true;
|
||||||
// TODO use ComputerUtilCost.getMaxXValue if able
|
toPay = ComputerUtilCost.getMaxXValue(sa, ai);
|
||||||
toPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
|
||||||
} else {
|
} else {
|
||||||
toPay = AbilityUtils.calculateAmount(source, unlessCost, sa);
|
toPay = AbilityUtils.calculateAmount(source, unlessCost, sa);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (toPay == 0) {
|
if (toPay == 0 || toPay <= usableManaSources) {
|
||||||
canBeSaved.add(potentialTgt);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (toPay <= usableManaSources) {
|
|
||||||
canBeSaved.add(potentialTgt);
|
canBeSaved.add(potentialTgt);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -245,8 +245,25 @@ public class ChangeZoneAllAi extends SpellAbilityAi {
|
|||||||
&& !ComputerUtil.isPlayingReanimator(ai);
|
&& !ComputerUtil.isPlayingReanimator(ai);
|
||||||
}
|
}
|
||||||
} else if (origin.equals(ZoneType.Exile)) {
|
} else if (origin.equals(ZoneType.Exile)) {
|
||||||
// TODO: nothing to do here at the moment
|
String logic = sa.getParam("AILogic");
|
||||||
return false;
|
|
||||||
|
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)) {
|
} else if (origin.equals(ZoneType.Stack)) {
|
||||||
// time stop can do something like this:
|
// time stop can do something like this:
|
||||||
// Origin$ Stack | Destination$ Exile | SubAbility$ DBSkip
|
// Origin$ Stack | Destination$ Exile | SubAbility$ DBSkip
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import com.google.common.base.Predicates;
|
|||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
|
|
||||||
|
import forge.ai.AiAttackController;
|
||||||
import forge.ai.ComputerUtilAbility;
|
import forge.ai.ComputerUtilAbility;
|
||||||
import forge.ai.ComputerUtilCard;
|
import forge.ai.ComputerUtilCard;
|
||||||
import forge.ai.ComputerUtilCombat;
|
import forge.ai.ComputerUtilCombat;
|
||||||
@@ -20,6 +21,7 @@ import forge.game.card.CardCollectionView;
|
|||||||
import forge.game.card.CardLists;
|
import forge.game.card.CardLists;
|
||||||
import forge.game.card.CardPredicates;
|
import forge.game.card.CardPredicates;
|
||||||
import forge.game.card.CardPredicates.Presets;
|
import forge.game.card.CardPredicates.Presets;
|
||||||
|
import forge.game.card.CounterEnumType;
|
||||||
import forge.game.combat.Combat;
|
import forge.game.combat.Combat;
|
||||||
import forge.game.keyword.Keyword;
|
import forge.game.keyword.Keyword;
|
||||||
import forge.game.phase.PhaseType;
|
import forge.game.phase.PhaseType;
|
||||||
@@ -73,10 +75,8 @@ public class ChooseCardAi extends SpellAbilityAi {
|
|||||||
return !choices.isEmpty();
|
return !choices.isEmpty();
|
||||||
} else if (aiLogic.equals("AtLeast2") || aiLogic.equals("BestBlocker")) {
|
} else if (aiLogic.equals("AtLeast2") || aiLogic.equals("BestBlocker")) {
|
||||||
return choices.size() >= 2;
|
return choices.size() >= 2;
|
||||||
} else if (aiLogic.equals("Clone") || aiLogic.equals("Vesuva")) {
|
} else if (aiLogic.equals("Clone")) {
|
||||||
final String filter = aiLogic.equals("Clone") ? "Permanent.YouDontCtrl,Permanent.nonLegendary"
|
final String filter = "Permanent.YouDontCtrl,Permanent.nonLegendary";
|
||||||
: "Permanent.YouDontCtrl+notnamedVesuva,Permanent.nonLegendary+notnamedVesuva";
|
|
||||||
|
|
||||||
choices = CardLists.getValidCards(choices, filter, host.getController(), host, sa);
|
choices = CardLists.getValidCards(choices, filter, host.getController(), host, sa);
|
||||||
return !choices.isEmpty();
|
return !choices.isEmpty();
|
||||||
} else if (aiLogic.equals("Never")) {
|
} else if (aiLogic.equals("Never")) {
|
||||||
@@ -96,12 +96,24 @@ public class ChooseCardAi extends SpellAbilityAi {
|
|||||||
return ComputerUtilCombat.damageIfUnblocked(c, ai, combat, true) > ref;
|
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();
|
return !choices.isEmpty();
|
||||||
} else if (aiLogic.equals("RandomNonLand")) {
|
} else if (aiLogic.equals("RandomNonLand")) {
|
||||||
return !CardLists.getValidCards(choices, "Card.nonLand", host.getController(), host, sa).isEmpty();
|
return !CardLists.getValidCards(choices, "Card.nonLand", host.getController(), host, sa).isEmpty();
|
||||||
} else if (aiLogic.equals("Duneblast")) {
|
} else if (aiLogic.equals("Duneblast")) {
|
||||||
CardCollection aiCreatures = ai.getCreaturesInPlay();
|
CardCollection aiCreatures = ai.getCreaturesInPlay();
|
||||||
CardCollection oppCreatures = ai.getWeakestOpponent().getCreaturesInPlay();
|
CardCollection oppCreatures = AiAttackController.choosePreferredDefenderPlayer(ai).getCreaturesInPlay();
|
||||||
aiCreatures = CardLists.getNotKeyword(aiCreatures, Keyword.INDESTRUCTIBLE);
|
aiCreatures = CardLists.getNotKeyword(aiCreatures, Keyword.INDESTRUCTIBLE);
|
||||||
oppCreatures = CardLists.getNotKeyword(oppCreatures, Keyword.INDESTRUCTIBLE);
|
oppCreatures = CardLists.getNotKeyword(oppCreatures, Keyword.INDESTRUCTIBLE);
|
||||||
|
|
||||||
@@ -159,18 +171,13 @@ public class ChooseCardAi extends SpellAbilityAi {
|
|||||||
options = CardLists.filter(options, Presets.UNTAPPED);
|
options = CardLists.filter(options, Presets.UNTAPPED);
|
||||||
}
|
}
|
||||||
choice = ComputerUtilCard.getBestCreatureAI(options);
|
choice = ComputerUtilCard.getBestCreatureAI(options);
|
||||||
} else if (logic.equals("Clone") || logic.equals("Vesuva")) {
|
} else if (logic.equals("Clone")) {
|
||||||
final String filter = logic.equals("Clone") ? "Permanent.YouDontCtrl,Permanent.nonLegendary" :
|
final String filter = "Permanent.YouDontCtrl,Permanent.nonLegendary";
|
||||||
"Permanent.YouDontCtrl+notnamedVesuva,Permanent.nonLegendary+notnamedVesuva";
|
|
||||||
|
|
||||||
CardCollection newOptions = CardLists.getValidCards(options, filter.split(","), ctrl, host, sa);
|
CardCollection newOptions = CardLists.getValidCards(options, filter.split(","), ctrl, host, sa);
|
||||||
if (!newOptions.isEmpty()) {
|
if (!newOptions.isEmpty()) {
|
||||||
options = newOptions;
|
options = newOptions;
|
||||||
}
|
}
|
||||||
choice = ComputerUtilCard.getBestAI(options);
|
choice = ComputerUtilCard.getBestAI(options);
|
||||||
if (logic.equals("Vesuva") && "Vesuva".equals(choice.getName())) {
|
|
||||||
choice = null;
|
|
||||||
}
|
|
||||||
} else if ("RandomNonLand".equals(logic)) {
|
} else if ("RandomNonLand".equals(logic)) {
|
||||||
options = CardLists.getValidCards(options, "Card.nonLand", host.getController(), host, sa);
|
options = CardLists.getValidCards(options, "Card.nonLand", host.getController(), host, sa);
|
||||||
choice = Aggregates.random(options);
|
choice = Aggregates.random(options);
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import com.google.common.collect.Iterables;
|
|||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
|
|
||||||
import forge.StaticData;
|
import forge.StaticData;
|
||||||
|
import forge.ai.AiAttackController;
|
||||||
import forge.ai.ComputerUtil;
|
import forge.ai.ComputerUtil;
|
||||||
import forge.ai.ComputerUtilCard;
|
import forge.ai.ComputerUtilCard;
|
||||||
import forge.ai.SpecialCardAi;
|
import forge.ai.SpecialCardAi;
|
||||||
@@ -34,9 +35,7 @@ public class ChooseCardNameAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
String logic = sa.getParam("AILogic");
|
String logic = sa.getParam("AILogic");
|
||||||
if (logic.equals("MomirAvatar")) {
|
if (logic.equals("CursedScroll")) {
|
||||||
return SpecialCardAi.MomirVigAvatar.consider(ai, sa);
|
|
||||||
} else if (logic.equals("CursedScroll")) {
|
|
||||||
return SpecialCardAi.CursedScroll.consider(ai, sa);
|
return SpecialCardAi.CursedScroll.consider(ai, sa);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,7 +43,7 @@ public class ChooseCardNameAi extends SpellAbilityAi {
|
|||||||
if (tgt != null) {
|
if (tgt != null) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
if (tgt.canOnlyTgtOpponent()) {
|
if (tgt.canOnlyTgtOpponent()) {
|
||||||
sa.getTargets().add(ai.getWeakestOpponent());
|
sa.getTargets().add(AiAttackController.choosePreferredDefenderPlayer(ai));
|
||||||
} else {
|
} else {
|
||||||
sa.getTargets().add(ai);
|
sa.getTargets().add(ai);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
import forge.ai.AiAttackController;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
@@ -16,7 +17,7 @@ public class ChooseEvenOddAi extends SpellAbilityAi {
|
|||||||
TargetRestrictions tgt = sa.getTargetRestrictions();
|
TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
if (tgt != null) {
|
if (tgt != null) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
Player opp = aiPlayer.getWeakestOpponent();
|
Player opp = AiAttackController.choosePreferredDefenderPlayer(aiPlayer);
|
||||||
if (sa.canTarget(opp)) {
|
if (sa.canTarget(opp)) {
|
||||||
sa.getTargets().add(opp);
|
sa.getTargets().add(opp);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import com.google.common.collect.Iterables;
|
|||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
import com.google.common.collect.Sets;
|
import com.google.common.collect.Sets;
|
||||||
|
|
||||||
|
|
||||||
import forge.ai.ComputerUtilAbility;
|
import forge.ai.ComputerUtilAbility;
|
||||||
import forge.ai.ComputerUtilCard;
|
import forge.ai.ComputerUtilCard;
|
||||||
import forge.ai.ComputerUtilCost;
|
import forge.ai.ComputerUtilCost;
|
||||||
@@ -32,7 +31,6 @@ import forge.game.phase.PhaseType;
|
|||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.spellability.AbilitySub;
|
import forge.game.spellability.AbilitySub;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.staticability.StaticAbilityCantBeCast;
|
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
import forge.util.Aggregates;
|
import forge.util.Aggregates;
|
||||||
import forge.util.collect.FCollection;
|
import forge.util.collect.FCollection;
|
||||||
@@ -54,8 +52,6 @@ public class ChooseGenericEffectAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
} else if ("GideonBlackblade".equals(aiLogic)) {
|
} else if ("GideonBlackblade".equals(aiLogic)) {
|
||||||
return SpecialCardAi.GideonBlackblade.consider(ai, sa);
|
return SpecialCardAi.GideonBlackblade.consider(ai, sa);
|
||||||
} else if ("SoulEcho".equals(aiLogic)) {
|
|
||||||
return doTriggerAINoCost(ai, sa, true);
|
|
||||||
} else if ("Always".equals(aiLogic)) {
|
} else if ("Always".equals(aiLogic)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -72,7 +68,7 @@ public class ChooseGenericEffectAi extends SpellAbilityAi {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
|
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
|
||||||
return checkApiLogic(aiPlayer, sa);
|
return sa.isTrigger() ? doTriggerAINoCost(aiPlayer, sa, sa.isMandatory()) : checkApiLogic(aiPlayer, sa);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -160,6 +156,22 @@ public class ChooseGenericEffectAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return others;
|
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)) {
|
} else if ("Fatespinner".equals(logic)) {
|
||||||
SpellAbility skipDraw = null, /*skipMain = null,*/ skipCombat = null;
|
SpellAbility skipDraw = null, /*skipMain = null,*/ skipCombat = null;
|
||||||
for (final SpellAbility sp : spells) {
|
for (final SpellAbility sp : spells) {
|
||||||
@@ -230,11 +242,12 @@ public class ChooseGenericEffectAi extends SpellAbilityAi {
|
|||||||
return allow;
|
return allow;
|
||||||
}
|
}
|
||||||
|
|
||||||
SpellAbility firstSpell = imprinted.getFirstSpellAbility();
|
//if Iona does prevent from casting, allow it to draw
|
||||||
// check if something would prevent it from casting
|
for (final Card io : player.getCardsIn(ZoneType.Battlefield, "Iona, Shield of Emeria")) {
|
||||||
if (firstSpell == null || StaticAbilityCantBeCast.cantBeCastAbility(firstSpell, imprinted, owner)) {
|
if (CardUtil.getColors(imprinted).hasAnyColor(MagicColor.fromName(io.getChosenColor()))) {
|
||||||
return allow;
|
return allow;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (dmg == 0) {
|
if (dmg == 0) {
|
||||||
// If CMC = 0, mill it!
|
// If CMC = 0, mill it!
|
||||||
@@ -364,8 +377,6 @@ public class ChooseGenericEffectAi extends SpellAbilityAi {
|
|||||||
} else if ("Riot".equals(logic)) {
|
} else if ("Riot".equals(logic)) {
|
||||||
SpellAbility counterSA = spells.get(0), hasteSA = spells.get(1);
|
SpellAbility counterSA = spells.get(0), hasteSA = spells.get(1);
|
||||||
return preferHasteForRiot(sa, player) ? hasteSA : counterSA;
|
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
|
return spells.get(0); // return first choice if no logic found
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
import forge.ai.AiAttackController;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
@@ -16,7 +17,7 @@ public class ChooseNumberAi extends SpellAbilityAi {
|
|||||||
TargetRestrictions tgt = sa.getTargetRestrictions();
|
TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
if (tgt != null) {
|
if (tgt != null) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
Player opp = aiPlayer.getWeakestOpponent();
|
Player opp = AiAttackController.choosePreferredDefenderPlayer(aiPlayer);
|
||||||
if (sa.canTarget(opp)) {
|
if (sa.canTarget(opp)) {
|
||||||
sa.getTargets().add(opp);
|
sa.getTargets().add(opp);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import com.google.common.base.Predicates;
|
|||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
|
|
||||||
|
import forge.ai.AiAttackController;
|
||||||
import forge.ai.ComputerUtilCard;
|
import forge.ai.ComputerUtilCard;
|
||||||
import forge.ai.ComputerUtilCombat;
|
import forge.ai.ComputerUtilCombat;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
@@ -54,7 +55,7 @@ public class ChooseSourceAi extends SpellAbilityAi {
|
|||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
if (tgt != null) {
|
if (tgt != null) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
Player opp = ai.getWeakestOpponent();
|
Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
|
||||||
if (sa.canTarget(opp)) {
|
if (sa.canTarget(opp)) {
|
||||||
sa.getTargets().add(opp);
|
sa.getTargets().add(opp);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -3,13 +3,17 @@ package forge.ai.ability;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import com.google.common.base.Predicates;
|
||||||
|
|
||||||
import forge.ai.ComputerUtilCard;
|
import forge.ai.ComputerUtilCard;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.Game;
|
import forge.game.Game;
|
||||||
import forge.game.ability.AbilityUtils;
|
import forge.game.ability.AbilityUtils;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
import forge.game.card.CardCollection;
|
import forge.game.card.CardCollection;
|
||||||
|
import forge.game.card.CardCollectionView;
|
||||||
import forge.game.card.CardLists;
|
import forge.game.card.CardLists;
|
||||||
|
import forge.game.card.CardPredicates;
|
||||||
import forge.game.phase.PhaseHandler;
|
import forge.game.phase.PhaseHandler;
|
||||||
import forge.game.phase.PhaseType;
|
import forge.game.phase.PhaseType;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
@@ -52,7 +56,7 @@ public class CloneAi extends SpellAbilityAi {
|
|||||||
bFlag |= (!c.isCreature() && !c.isTapped() && !(c.getTurnInZone() == phase.getTurn()));
|
bFlag |= (!c.isCreature() && !c.isTapped() && !(c.getTurnInZone() == phase.getTurn()));
|
||||||
|
|
||||||
// for creatures that could be improved (like Figure of Destiny)
|
// 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;
|
int power = -5;
|
||||||
if (sa.hasParam("Power")) {
|
if (sa.hasParam("Power")) {
|
||||||
power = AbilityUtils.calculateAmount(source, sa.getParam("Power"), sa);
|
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
|
if (!bFlag) { // All of the defined stuff is cloned, not very useful
|
||||||
// useful
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -95,11 +98,18 @@ public class CloneAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
||||||
|
Card host = sa.getHostCard();
|
||||||
boolean chance = true;
|
boolean chance = true;
|
||||||
|
|
||||||
if (sa.usesTargeting()) {
|
if (sa.usesTargeting()) {
|
||||||
chance = cloneTgtAI(sa);
|
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:
|
// Improve AI for triggers. If source is a creature with:
|
||||||
@@ -171,18 +181,18 @@ public class CloneAi extends SpellAbilityAi {
|
|||||||
@Override
|
@Override
|
||||||
protected Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional,
|
protected Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional,
|
||||||
Player targetedPlayer, Map<String, Object> params) {
|
Player targetedPlayer, Map<String, Object> params) {
|
||||||
|
|
||||||
final Card host = sa.getHostCard();
|
final Card host = sa.getHostCard();
|
||||||
|
final String name = host.getName();
|
||||||
final Player ctrl = host.getController();
|
final Player ctrl = host.getController();
|
||||||
|
|
||||||
final Card cloneTarget = getCloneTarget(sa);
|
final Card cloneTarget = getCloneTarget(sa);
|
||||||
final boolean isOpp = cloneTarget.getController().isOpponentOf(sa.getActivatingPlayer());
|
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"));
|
final boolean canCloneLegendary = "True".equalsIgnoreCase(sa.getParam("NonLegendary"));
|
||||||
|
|
||||||
String filter = !isVesuva ? "Permanent.YouDontCtrl,Permanent.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
|
// TODO: rewrite this block so that this is done somehow more elegantly
|
||||||
if (canCloneLegendary) {
|
if (canCloneLegendary) {
|
||||||
@@ -201,12 +211,13 @@ public class CloneAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Card choice = isOpp ? ComputerUtilCard.getWorstAI(options) : ComputerUtilCard.getBestAI(options);
|
// prevent loop of choosing copy of same card
|
||||||
|
if (isVesuva) {
|
||||||
if (isVesuva && "Vesuva".equals(choice.getName())) {
|
options = CardLists.filter(options, Predicates.not(CardPredicates.sharesNameWith(host)));
|
||||||
choice = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Card choice = isOpp ? ComputerUtilCard.getWorstAI(options) : ComputerUtilCard.getBestAI(options);
|
||||||
|
|
||||||
return choice;
|
return choice;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -234,7 +245,7 @@ public class CloneAi extends SpellAbilityAi {
|
|||||||
// Combat_Begin step
|
// Combat_Begin step
|
||||||
if (!ph.is(PhaseType.COMBAT_BEGIN)
|
if (!ph.is(PhaseType.COMBAT_BEGIN)
|
||||||
&& ph.isPlayerTurn(ai) && !SpellAbilityAi.isSorcerySpeed(sa)
|
&& ph.isPlayerTurn(ai) && !SpellAbilityAi.isSorcerySpeed(sa)
|
||||||
&& !sa.hasParam("ActivationPhases") && !sa.hasParam("Permanent")) {
|
&& !sa.hasParam("ActivationPhases") && sa.hasParam("Duration")) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -245,6 +256,6 @@ public class CloneAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// don't activate during main2 unless this effect is permanent
|
// 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");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package forge.ai.ability;
|
|||||||
import com.google.common.base.Predicate;
|
import com.google.common.base.Predicate;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
|
|
||||||
|
import forge.ai.AiAttackController;
|
||||||
import forge.ai.ComputerUtilCard;
|
import forge.ai.ComputerUtilCard;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.ability.AbilityUtils;
|
import forge.game.ability.AbilityUtils;
|
||||||
@@ -29,9 +30,8 @@ public class ControlExchangeAi extends SpellAbilityAi {
|
|||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
|
|
||||||
CardCollection list =
|
CardCollection list =
|
||||||
CardLists.getValidCards(ai.getWeakestOpponent().getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), ai, sa.getHostCard(), sa);
|
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
|
// AI won't try to grab cards that are filtered out of AI decks on purpose
|
||||||
// purpose
|
|
||||||
list = CardLists.filter(list, new Predicate<Card>() {
|
list = CardLists.filter(list, new Predicate<Card>() {
|
||||||
@Override
|
@Override
|
||||||
public boolean apply(final Card c) {
|
public boolean apply(final Card c) {
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ import forge.game.player.PlayerCollection;
|
|||||||
import forge.game.player.PlayerPredicates;
|
import forge.game.player.PlayerPredicates;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.spellability.TargetRestrictions;
|
import forge.game.spellability.TargetRestrictions;
|
||||||
|
import forge.game.staticability.StaticAbilityMustTarget;
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
import forge.util.Aggregates;
|
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
|
// Don't steal something if I can't Attack without, or prevent it from blocking at least
|
||||||
// blocking at least
|
|
||||||
if (lose.contains("EOT")
|
if (lose.contains("EOT")
|
||||||
&& ai.getGame().getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)
|
&& ai.getGame().getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)
|
||||||
&& !sa.isTrigger()) {
|
&& !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) {
|
while (t == null) {
|
||||||
|
// filter by MustTarget requirement
|
||||||
|
CardCollection originalList = new CardCollection(list);
|
||||||
|
boolean mustTargetFiltered = StaticAbilityMustTarget.filterMustTargetCards(ai, list, sa);
|
||||||
|
|
||||||
if (planeswalkers > 0) {
|
if (planeswalkers > 0) {
|
||||||
t = ComputerUtilCard.getBestPlaneswalkerAI(list);
|
t = ComputerUtilCard.getBestPlaneswalkerAI(list);
|
||||||
} else if (creatures > 0) {
|
} else if (creatures > 0) {
|
||||||
@@ -238,6 +243,11 @@ public class ControlGainAi extends SpellAbilityAi {
|
|||||||
enchantments--;
|
enchantments--;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Restore original list for next loop if filtered by MustTarget requirement
|
||||||
|
if (mustTargetFiltered) {
|
||||||
|
list = originalList;
|
||||||
|
}
|
||||||
|
|
||||||
if (!sa.canTarget(t)) {
|
if (!sa.canTarget(t)) {
|
||||||
list.remove(t);
|
list.remove(t);
|
||||||
t = null;
|
t = null;
|
||||||
@@ -254,7 +264,6 @@ public class ControlGainAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -44,7 +44,9 @@ public class CopyPermanentAi extends SpellAbilityAi {
|
|||||||
return false;
|
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);
|
return SpecialCardAi.MimicVat.considerCopy(aiPlayer, sa);
|
||||||
} else if ("AtEOT".equals(aiLogic)) {
|
} else if ("AtEOT".equals(aiLogic)) {
|
||||||
return ph.is(PhaseType.END_OF_TURN);
|
return ph.is(PhaseType.END_OF_TURN);
|
||||||
|
|||||||
@@ -113,8 +113,7 @@ public class CounterAi extends SpellAbilityAi {
|
|||||||
boolean setPayX = false;
|
boolean setPayX = false;
|
||||||
if (unlessCost.equals("X") && sa.getSVar(unlessCost).equals("Count$xPaid")) {
|
if (unlessCost.equals("X") && sa.getSVar(unlessCost).equals("Count$xPaid")) {
|
||||||
setPayX = true;
|
setPayX = true;
|
||||||
// TODO use ComputerUtilCost.getMaxXValue
|
toPay = Math.min(ComputerUtilCost.getMaxXValue(sa, ai), usableManaSources + 1);
|
||||||
toPay = Math.min(ComputerUtilMana.determineLeftoverMana(sa, ai), usableManaSources + 1);
|
|
||||||
} else {
|
} else {
|
||||||
toPay = AbilityUtils.calculateAmount(source, unlessCost, sa);
|
toPay = AbilityUtils.calculateAmount(source, unlessCost, sa);
|
||||||
}
|
}
|
||||||
@@ -124,8 +123,7 @@ public class CounterAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (toPay <= usableManaSources) {
|
if (toPay <= usableManaSources) {
|
||||||
// If this is a reusable Resource, feel free to play it most of
|
// If this is a reusable Resource, feel free to play it most of the time
|
||||||
// the time
|
|
||||||
if (!SpellAbilityAi.playReusable(ai,sa)) {
|
if (!SpellAbilityAi.playReusable(ai,sa)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -372,6 +372,10 @@ public class CountersMoveAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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);
|
List<Card> aiList = CardLists.filterControlledBy(tgtCards, ai);
|
||||||
if (!aiList.isEmpty()) {
|
if (!aiList.isEmpty()) {
|
||||||
List<Card> best = CardLists.filter(aiList, new Predicate<Card>() {
|
List<Card> best = CardLists.filter(aiList, new Predicate<Card>() {
|
||||||
@@ -392,7 +396,7 @@ public class CountersMoveAi extends SpellAbilityAi {
|
|||||||
if (cType.is(CounterEnumType.P1P1) && card.hasKeyword(Keyword.UNDYING)) {
|
if (cType.is(CounterEnumType.P1P1) && card.hasKeyword(Keyword.UNDYING)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (cType.is(CounterEnumType.M1M1) && card.hasKeyword(Keyword.PERSIST)) {
|
if (cType.is(CounterEnumType.M1M1)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -415,11 +419,10 @@ public class CountersMoveAi extends SpellAbilityAi {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// move counter to opponents creature but only if you can not steal
|
// move counter to opponents creature but only if you can not steal them
|
||||||
// them
|
// try to move to something useless or something that would leave play
|
||||||
// try to move to something useless or something that would leave
|
|
||||||
// play
|
|
||||||
List<Card> oppList = CardLists.filterControlledBy(tgtCards, ai.getOpponents());
|
List<Card> oppList = CardLists.filterControlledBy(tgtCards, ai.getOpponents());
|
||||||
if (!oppList.isEmpty()) {
|
if (!oppList.isEmpty()) {
|
||||||
List<Card> best = CardLists.filter(oppList, new Predicate<Card>() {
|
List<Card> best = CardLists.filter(oppList, new Predicate<Card>() {
|
||||||
@@ -441,7 +444,7 @@ public class CountersMoveAi extends SpellAbilityAi {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (best.isEmpty()) {
|
if (best.isEmpty()) {
|
||||||
best = aiList;
|
best = oppList;
|
||||||
}
|
}
|
||||||
|
|
||||||
Card card = ComputerUtilCard.getBestCreatureAI(best);
|
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
|
// or for source -> multiple defined
|
||||||
@Override
|
@Override
|
||||||
protected Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional,
|
protected Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional,
|
||||||
|
|||||||
@@ -120,8 +120,7 @@ public class CountersMultiplyAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
CardCollection list = CardLists.getTargetableCards(game.getCardsIn(ZoneType.Battlefield), sa);
|
CardCollection list = CardLists.getTargetableCards(game.getCardsIn(ZoneType.Battlefield), sa);
|
||||||
|
|
||||||
// pre filter targetable cards with counters and can receive one of
|
// pre filter targetable cards with counters and can receive one of them
|
||||||
// them
|
|
||||||
list = CardLists.filter(list, new Predicate<Card>() {
|
list = CardLists.filter(list, new Predicate<Card>() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -8,8 +8,10 @@ import com.google.common.base.Predicate;
|
|||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
|
|
||||||
|
import forge.ai.AiProps;
|
||||||
import forge.ai.ComputerUtil;
|
import forge.ai.ComputerUtil;
|
||||||
import forge.ai.ComputerUtilCard;
|
import forge.ai.ComputerUtilCard;
|
||||||
|
import forge.ai.PlayerControllerAi;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.GameEntity;
|
import forge.game.GameEntity;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
@@ -121,6 +123,7 @@ public class CountersProliferateAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
final CounterType poison = CounterType.get(CounterEnumType.POISON);
|
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
|
// because countertype can't be chosen anymore, only look for posion counters
|
||||||
for (final Player p : Iterables.filter(options, Player.class)) {
|
for (final Player p : Iterables.filter(options, Player.class)) {
|
||||||
if (p.isOpponentOf(ai)) {
|
if (p.isOpponentOf(ai)) {
|
||||||
@@ -128,7 +131,8 @@ public class CountersProliferateAi extends SpellAbilityAi {
|
|||||||
return (T)p;
|
return (T)p;
|
||||||
}
|
}
|
||||||
} else {
|
} 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;
|
return (T)p;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,6 +47,8 @@ import forge.game.player.PlayerCollection;
|
|||||||
import forge.game.player.PlayerPredicates;
|
import forge.game.player.PlayerPredicates;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.spellability.TargetRestrictions;
|
import forge.game.spellability.TargetRestrictions;
|
||||||
|
import forge.game.trigger.Trigger;
|
||||||
|
import forge.game.trigger.TriggerType;
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
import forge.util.Aggregates;
|
import forge.util.Aggregates;
|
||||||
import forge.util.MyRandom;
|
import forge.util.MyRandom;
|
||||||
@@ -313,7 +315,12 @@ public class CountersPutAi extends SpellAbilityAi {
|
|||||||
} else if (logic.startsWith("MoveCounter")) {
|
} else if (logic.startsWith("MoveCounter")) {
|
||||||
return doMoveCounterLogic(ai, sa, ph);
|
return doMoveCounterLogic(ai, sa, ph);
|
||||||
} else if (logic.equals("CrawlingBarrens")) {
|
} 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) {
|
if (!sa.metConditions() && sa.getSubAbility() == null) {
|
||||||
@@ -401,19 +408,37 @@ public class CountersPutAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ("Polukranos".equals(logic)) {
|
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()){
|
if (!targets.isEmpty()){
|
||||||
boolean canSurvive = false;
|
boolean canSurvive = false;
|
||||||
for (Card humanCreature : targets) {
|
for (Card humanCreature : targets) {
|
||||||
if (!FightAi.canKill(humanCreature, source, 0)){
|
if (!FightAi.canKill(humanCreature, source, 0)){
|
||||||
canSurvive = true;
|
canSurvive = true;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!canSurvive){
|
if (!canSurvive){
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (!found) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -841,6 +866,7 @@ public class CountersPutAi extends SpellAbilityAi {
|
|||||||
choice = Aggregates.random(list);
|
choice = Aggregates.random(list);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if (choice != null && divided) {
|
if (choice != null && divided) {
|
||||||
int alloc = Math.max(amount / totalTargets, 1);
|
int alloc = Math.max(amount / totalTargets, 1);
|
||||||
if (sa.getTargets().size() == Math.min(totalTargets, sa.getMaxTargets()) - 1) {
|
if (sa.getTargets().size() == Math.min(totalTargets, sa.getMaxTargets()) - 1) {
|
||||||
@@ -850,8 +876,6 @@ public class CountersPutAi extends SpellAbilityAi {
|
|||||||
left -= alloc;
|
left -= alloc;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (choice != null) {
|
if (choice != null) {
|
||||||
sa.getTargets().add(choice);
|
sa.getTargets().add(choice);
|
||||||
list.remove(choice);
|
list.remove(choice);
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import forge.game.phase.PhaseType;
|
|||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.trigger.Trigger;
|
import forge.game.trigger.Trigger;
|
||||||
import forge.game.trigger.TriggerDamageDone;
|
import forge.game.trigger.TriggerType;
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
import forge.util.MyRandom;
|
import forge.util.MyRandom;
|
||||||
|
|
||||||
@@ -56,7 +56,7 @@ public abstract class DamageAiBase extends SpellAbilityAi {
|
|||||||
boolean dmgByCardsInHand = false;
|
boolean dmgByCardsInHand = false;
|
||||||
|
|
||||||
if ("X".equals(sa.getParam("NumDmg")) && sa.getHostCard() != null && sa.hasSVar(sa.getParam("NumDmg")) &&
|
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;
|
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
|
// 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!
|
// If has triggered ability on dealing damage to an opponent, go for it!
|
||||||
Card hostcard = sa.getHostCard();
|
Card hostcard = sa.getHostCard();
|
||||||
for (Trigger trig : hostcard.getTriggers()) {
|
for (Trigger trig : hostcard.getTriggers()) {
|
||||||
if (trig instanceof TriggerDamageDone) {
|
if (trig.getMode() == TriggerType.DamageDone) {
|
||||||
if (("Opponent".equals(trig.getParam("ValidTarget")))
|
if (("Opponent".equals(trig.getParam("ValidTarget")))
|
||||||
&& (!"True".equals(trig.getParam("CombatDamage")))) {
|
&& (!"True".equals(trig.getParam("CombatDamage")))) {
|
||||||
return true;
|
return true;
|
||||||
@@ -111,8 +111,7 @@ public abstract class DamageAiBase extends SpellAbilityAi {
|
|||||||
final CardCollectionView hand = comp.getCardsIn(ZoneType.Hand);
|
final CardCollectionView hand = comp.getCardsIn(ZoneType.Hand);
|
||||||
|
|
||||||
if ((enemy.getLife() - restDamage) < 5) {
|
if ((enemy.getLife() - restDamage) < 5) {
|
||||||
// drop the human to less than 5
|
// drop the human to less than 5 life
|
||||||
// life
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -30,22 +30,20 @@ import forge.game.ability.AbilityUtils;
|
|||||||
import forge.game.ability.ApiType;
|
import forge.game.ability.ApiType;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
import forge.game.card.CardCollection;
|
import forge.game.card.CardCollection;
|
||||||
import forge.game.card.CardFactoryUtil;
|
|
||||||
import forge.game.card.CardLists;
|
import forge.game.card.CardLists;
|
||||||
import forge.game.card.CardPredicates;
|
import forge.game.card.CardPredicates;
|
||||||
import forge.game.card.CounterEnumType;
|
import forge.game.card.CounterEnumType;
|
||||||
import forge.game.cost.Cost;
|
import forge.game.cost.Cost;
|
||||||
import forge.game.cost.CostPartMana;
|
import forge.game.cost.CostPartMana;
|
||||||
import forge.game.cost.CostRemoveCounter;
|
|
||||||
import forge.game.keyword.Keyword;
|
import forge.game.keyword.Keyword;
|
||||||
import forge.game.phase.PhaseHandler;
|
import forge.game.phase.PhaseHandler;
|
||||||
import forge.game.phase.PhaseType;
|
import forge.game.phase.PhaseType;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.player.PlayerPredicates;
|
|
||||||
import forge.game.spellability.AbilitySub;
|
import forge.game.spellability.AbilitySub;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.spellability.TargetChoices;
|
import forge.game.spellability.TargetChoices;
|
||||||
import forge.game.spellability.TargetRestrictions;
|
import forge.game.spellability.TargetRestrictions;
|
||||||
|
import forge.game.staticability.StaticAbilityMustTarget;
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
import forge.util.Aggregates;
|
import forge.util.Aggregates;
|
||||||
import forge.util.MyRandom;
|
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.
|
// Set PayX here to maximum value. It will be adjusted later depending on the target.
|
||||||
sa.setXManaCostPaid(dmg);
|
sa.setXManaCostPaid(dmg);
|
||||||
} else if (sa.getSVar(damage).contains("InYourHand") && source.isInZone(ZoneType.Hand)) {
|
} 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")) {
|
} 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
|
// cards that deal damage by the number of cards in target player's hand, e.g. Sudden Impact
|
||||||
if (sa.getTargetRestrictions().canTgtPlayer()) {
|
if (sa.getTargetRestrictions().canTgtPlayer()) {
|
||||||
@@ -343,6 +341,9 @@ public class DamageDealAi extends DamageAiBase {
|
|||||||
final Game game = source.getGame();
|
final Game game = source.getGame();
|
||||||
List<Card> hPlay = getTargetableCards(ai, sa, pl, tgt, activator, source, game);
|
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>() {
|
CardCollection killables = CardLists.filter(hPlay, new Predicate<Card>() {
|
||||||
@Override
|
@Override
|
||||||
public boolean apply(final Card c) {
|
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
|
// We can hurt a planeswalker, so rank the one which is the best target
|
||||||
if (!hPlay.isEmpty() && pl.isOpponentOf(ai) && activator.equals(ai)) {
|
if (!hPlay.isEmpty() && pl.isOpponentOf(ai) && activator.equals(ai)) {
|
||||||
return getBestPlaneswalkerToDamage(hPlay);
|
return ComputerUtilCard.getBestPlaneswalkerToDamage(hPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
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) {
|
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);
|
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
|
// When giving priority to targeting Creatures for mandatory
|
||||||
// triggers
|
// triggers feel free to add the Human after we run out of good targets
|
||||||
// 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
|
// TODO: add check here if card is about to die from something
|
||||||
// on the stack
|
// on the stack or from taking combat damage
|
||||||
// or from taking combat damage
|
|
||||||
|
|
||||||
final Cost abCost = sa.getPayCosts();
|
final Cost abCost = sa.getPayCosts();
|
||||||
boolean freePing = immediately || abCost == null
|
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
|
// TODO: Improve Damage, we shouldn't just target the player just because we can
|
||||||
// because we can
|
|
||||||
if (sa.canTarget(enemy) && tcs.size() < tgt.getMaxTargets(source, sa)) {
|
if (sa.canTarget(enemy) && tcs.size() < tgt.getMaxTargets(source, sa)) {
|
||||||
if (((phase.is(PhaseType.END_OF_TURN) && phase.getNextTurn().equals(ai))
|
if (((phase.is(PhaseType.END_OF_TURN) && phase.getNextTurn().equals(ai))
|
||||||
|| (SpellAbilityAi.isSorcerySpeed(sa) && phase.is(PhaseType.MAIN2))
|
|| (SpellAbilityAi.isSorcerySpeed(sa) && phase.is(PhaseType.MAIN2))
|
||||||
@@ -951,7 +898,7 @@ public class DamageDealAi extends DamageAiBase {
|
|||||||
}
|
}
|
||||||
else if (tgt.canTgtPlaneswalker()) {
|
else if (tgt.canTgtPlaneswalker()) {
|
||||||
// Second pass for planeswalkers: choose AI's worst planeswalker
|
// 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) {
|
if (c != null) {
|
||||||
sa.getTargets().add(c);
|
sa.getTargets().add(c);
|
||||||
if (divided) {
|
if (divided) {
|
||||||
@@ -981,7 +928,6 @@ public class DamageDealAi extends DamageAiBase {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
|
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
final String damage = sa.getParam("NumDmg");
|
final String damage = sa.getParam("NumDmg");
|
||||||
int dmg = AbilityUtils.calculateAmount(source, damage, sa);
|
int dmg = AbilityUtils.calculateAmount(source, damage, sa);
|
||||||
@@ -1038,7 +984,7 @@ public class DamageDealAi extends DamageAiBase {
|
|||||||
saTgt = saTgt.getParent();
|
saTgt = saTgt.getParent();
|
||||||
}
|
}
|
||||||
|
|
||||||
Player opponent = ai.getOpponents().min(PlayerPredicates.compareByLife());
|
Player opponent = ai.getWeakestOpponent();
|
||||||
|
|
||||||
// TODO: somehow account for the possible cost reduction?
|
// TODO: somehow account for the possible cost reduction?
|
||||||
int dmg = ComputerUtilMana.determineLeftoverMana(sa, ai, saTgt.getParam("XColor"));
|
int dmg = ComputerUtilMana.determineLeftoverMana(sa, ai, saTgt.getParam("XColor"));
|
||||||
@@ -1081,7 +1027,7 @@ public class DamageDealAi extends DamageAiBase {
|
|||||||
saTgt.resetTargets();
|
saTgt.resetTargets();
|
||||||
saTgt.getTargets().add(tgtCreature != null && dmg < opponent.getLife() ? tgtCreature : opponent);
|
saTgt.getTargets().add(tgtCreature != null && dmg < opponent.getLife() ? tgtCreature : opponent);
|
||||||
|
|
||||||
sa.setXManaCostPaid(dmg);
|
saTgt.setXManaCostPaid(dmg);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import com.google.common.base.Predicate;
|
|||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
|
|
||||||
|
import forge.ai.AiAttackController;
|
||||||
import forge.ai.ComputerUtilCard;
|
import forge.ai.ComputerUtilCard;
|
||||||
import forge.ai.ComputerUtilCost;
|
import forge.ai.ComputerUtilCost;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
@@ -26,9 +27,6 @@ import forge.game.spellability.TargetRestrictions;
|
|||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
|
|
||||||
public class DebuffAi extends SpellAbilityAi {
|
public class DebuffAi extends SpellAbilityAi {
|
||||||
// *************************************************************************
|
|
||||||
// ***************************** Debuff ************************************
|
|
||||||
// *************************************************************************
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(final Player ai, final SpellAbility sa) {
|
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)) {
|
while (sa.getTargets().size() < tgt.getMaxTargets(sa.getHostCard(), sa)) {
|
||||||
Card t = null;
|
Card t = null;
|
||||||
// boolean goodt = false;
|
|
||||||
|
|
||||||
if (list.isEmpty()) {
|
if (list.isEmpty()) {
|
||||||
if ((sa.getTargets().size() < tgt.getMinTargets(sa.getHostCard(), sa)) || (sa.getTargets().size() == 0)) {
|
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.
|
* @return a CardCollection.
|
||||||
*/
|
*/
|
||||||
private CardCollection getCurseCreatures(final Player ai, final SpellAbility sa, final List<String> kws) {
|
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);
|
CardCollection list = CardLists.getTargetableCards(opp.getCreaturesInPlay(), sa);
|
||||||
if (!list.isEmpty()) {
|
if (!list.isEmpty()) {
|
||||||
list = CardLists.filter(list, new Predicate<Card>() {
|
list = CardLists.filter(list, new Predicate<Card>() {
|
||||||
@Override
|
@Override
|
||||||
public boolean apply(final Card c) {
|
public boolean apply(final Card c) {
|
||||||
return c.hasAnyKeyword(kws); // don't add duplicate negative
|
return c.hasAnyKeyword(kws); // don't add duplicate negative keywords
|
||||||
// keywords
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return list;
|
return list;
|
||||||
} // getCurseCreatures()
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>
|
* <p>
|
||||||
@@ -216,7 +212,7 @@ public class DebuffAi extends SpellAbilityAi {
|
|||||||
list.remove(c);
|
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 CardCollection forced = CardLists.filterControlledBy(list, ai);
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
|
|
||||||
@@ -242,8 +238,7 @@ public class DebuffAi extends SpellAbilityAi {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO - if forced targeting, just pick something without the given
|
// TODO - if forced targeting, just pick something without the given keyword
|
||||||
// keyword
|
|
||||||
Card c;
|
Card c;
|
||||||
if (CardLists.getNotType(forced, "Creature").size() == 0) {
|
if (CardLists.getNotType(forced, "Creature").size() == 0) {
|
||||||
c = ComputerUtilCard.getWorstCreatureAI(forced);
|
c = ComputerUtilCard.getWorstCreatureAI(forced);
|
||||||
|
|||||||
@@ -2,15 +2,7 @@ package forge.ai.ability;
|
|||||||
|
|
||||||
import com.google.common.base.Predicate;
|
import com.google.common.base.Predicate;
|
||||||
|
|
||||||
import forge.ai.AiController;
|
import forge.ai.*;
|
||||||
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.game.ability.AbilityUtils;
|
import forge.game.ability.AbilityUtils;
|
||||||
import forge.game.ability.ApiType;
|
import forge.game.ability.ApiType;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
@@ -26,6 +18,7 @@ import forge.game.phase.PhaseHandler;
|
|||||||
import forge.game.phase.PhaseType;
|
import forge.game.phase.PhaseType;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
|
import forge.game.staticability.StaticAbilityMustTarget;
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
|
|
||||||
public class DestroyAi extends SpellAbilityAi {
|
public class DestroyAi extends SpellAbilityAi {
|
||||||
@@ -122,17 +115,21 @@ public class DestroyAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
CardCollection list;
|
CardCollection list;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Targeting
|
// Targeting
|
||||||
if (sa.usesTargeting()) {
|
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
|
// 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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -140,11 +137,11 @@ public class DestroyAi extends SpellAbilityAi {
|
|||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
int maxTargets;
|
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.
|
// TODO: currently the AI will maximize mana spent on X, trying to maximize damage. This may need improvement.
|
||||||
maxTargets = ComputerUtilCost.getMaxXValue(sa, ai);
|
maxTargets = ComputerUtilCost.getMaxXValue(sa, ai);
|
||||||
// need to set XPaid to get the right number for
|
// need to set XPaid to get the right number for
|
||||||
sa.setXManaCostPaid(maxTargets);
|
sa.getRootAbility().setXManaCostPaid(maxTargets);
|
||||||
// need to check for maxTargets
|
// need to check for maxTargets
|
||||||
maxTargets = Math.min(maxTargets, sa.getMaxTargets());
|
maxTargets = Math.min(maxTargets, sa.getMaxTargets());
|
||||||
} else {
|
} else {
|
||||||
@@ -226,6 +223,10 @@ public class DestroyAi extends SpellAbilityAi {
|
|||||||
// target loop
|
// target loop
|
||||||
// TODO use can add more Targets
|
// TODO use can add more Targets
|
||||||
while (sa.getTargets().size() < maxTargets) {
|
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 (list.isEmpty()) {
|
||||||
if (!sa.isMinTargetChosen() || sa.isZeroTargets()) {
|
if (!sa.isMinTargetChosen() || sa.isZeroTargets()) {
|
||||||
sa.resetTargets();
|
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);
|
list.remove(choice);
|
||||||
sa.getTargets().add(choice);
|
sa.getTargets().add(choice);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,12 +70,12 @@ public class DestroyAllAi extends SpellAbilityAi {
|
|||||||
return doMassRemovalLogic(ai, sa);
|
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 Card source = sa.getHostCard();
|
||||||
final String logic = sa.getParamOrDefault("AILogic", "");
|
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")) {
|
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
|
return true; // e.g. Tetzimoc, Primal Death, where we want to cast the permanent even if the removal trigger does nothing
|
||||||
@@ -93,10 +93,10 @@ public class DestroyAllAi extends SpellAbilityAi {
|
|||||||
valid = valid.replace("X", Integer.toString(xPay));
|
valid = valid.replace("X", Integer.toString(xPay));
|
||||||
}
|
}
|
||||||
|
|
||||||
CardCollection opplist = CardLists.getValidCards(opponent.getCardsIn(ZoneType.Battlefield),
|
// TODO should probably sort results when targeted to use on biggest threat instead of first match
|
||||||
valid.split(","), source.getController(), source, sa);
|
for (Player opponent: ai.getOpponents()) {
|
||||||
CardCollection ailist = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), valid.split(","),
|
CardCollection opplist = CardLists.getValidCards(opponent.getCardsIn(ZoneType.Battlefield), valid.split(","), source.getController(), source, sa);
|
||||||
source.getController(), source, sa);
|
CardCollection ailist = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), valid.split(","), source.getController(), source, sa);
|
||||||
|
|
||||||
opplist = CardLists.filter(opplist, predicate);
|
opplist = CardLists.filter(opplist, predicate);
|
||||||
ailist = CardLists.filter(ailist, predicate);
|
ailist = CardLists.filter(ailist, predicate);
|
||||||
@@ -135,8 +135,7 @@ public class DestroyAllAi extends SpellAbilityAi {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// if only creatures are affected evaluate both lists and pass only if
|
// if only creatures are affected evaluate both lists and pass only if human creatures are more valuable
|
||||||
// human creatures are more valuable
|
|
||||||
if (CardLists.getNotType(opplist, "Creature").isEmpty() && CardLists.getNotType(ailist, "Creature").isEmpty()) {
|
if (CardLists.getNotType(opplist, "Creature").isEmpty() && CardLists.getNotType(ailist, "Creature").isEmpty()) {
|
||||||
if (ComputerUtilCard.evaluateCreatureList(ailist) + CREATURE_EVAL_THRESHOLD < ComputerUtilCard.evaluateCreatureList(opplist)) {
|
if (ComputerUtilCard.evaluateCreatureList(ailist) + CREATURE_EVAL_THRESHOLD < ComputerUtilCard.evaluateCreatureList(opplist)) {
|
||||||
return true;
|
return true;
|
||||||
@@ -167,7 +166,7 @@ public class DestroyAllAi extends SpellAbilityAi {
|
|||||||
return false;
|
return false;
|
||||||
} // only lands involved
|
} // only lands involved
|
||||||
else if (CardLists.getNotType(opplist, "Land").isEmpty() && CardLists.getNotType(ailist, "Land").isEmpty()) {
|
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()) {
|
if (ai.isCardInPlay("Crucible of Worlds") && !opponent.isCardInPlay("Crucible of Worlds")) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
// evaluate the situation with creatures on the battlefield separately, as that's where the AI typically makes mistakes
|
// evaluate the situation with creatures on the battlefield separately, as that's where the AI typically makes mistakes
|
||||||
@@ -188,4 +187,7 @@ public class DestroyAllAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -5,6 +5,7 @@ import java.util.Map;
|
|||||||
import com.google.common.base.Predicate;
|
import com.google.common.base.Predicate;
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
|
|
||||||
|
import forge.ai.AiAttackController;
|
||||||
import forge.ai.ComputerUtil;
|
import forge.ai.ComputerUtil;
|
||||||
import forge.ai.ComputerUtilAbility;
|
import forge.ai.ComputerUtilAbility;
|
||||||
import forge.ai.ComputerUtilCard;
|
import forge.ai.ComputerUtilCard;
|
||||||
@@ -32,7 +33,7 @@ public class DigAi extends SpellAbilityAi {
|
|||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||||
final Game game = ai.getGame();
|
final Game game = ai.getGame();
|
||||||
Player opp = ai.getWeakestOpponent();
|
Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
|
||||||
final Card host = sa.getHostCard();
|
final Card host = sa.getHostCard();
|
||||||
Player libraryOwner = ai;
|
Player libraryOwner = ai;
|
||||||
|
|
||||||
@@ -44,9 +45,8 @@ public class DigAi extends SpellAbilityAi {
|
|||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
if (!opp.canBeTargetedBy(sa)) {
|
if (!opp.canBeTargetedBy(sa)) {
|
||||||
return false;
|
return false;
|
||||||
} else {
|
|
||||||
sa.getTargets().add(opp);
|
|
||||||
}
|
}
|
||||||
|
sa.getTargets().add(opp);
|
||||||
libraryOwner = opp;
|
libraryOwner = opp;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,7 +120,7 @@ public class DigAi extends SpellAbilityAi {
|
|||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
final SpellAbility root = sa.getRootAbility();
|
final SpellAbility root = sa.getRootAbility();
|
||||||
final Player opp = ai.getWeakestOpponent();
|
final Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
|
||||||
if (sa.usesTargeting()) {
|
if (sa.usesTargeting()) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
if (mandatory && sa.canTarget(opp)) {
|
if (mandatory && sa.canTarget(opp)) {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
import forge.ai.AiAttackController;
|
||||||
import forge.ai.ComputerUtil;
|
import forge.ai.ComputerUtil;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.Game;
|
import forge.game.Game;
|
||||||
@@ -19,7 +20,7 @@ public class DigMultipleAi extends SpellAbilityAi {
|
|||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||||
final Game game = ai.getGame();
|
final Game game = ai.getGame();
|
||||||
Player opp = ai.getWeakestOpponent();
|
Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
|
||||||
final Card host = sa.getHostCard();
|
final Card host = sa.getHostCard();
|
||||||
Player libraryOwner = ai;
|
Player libraryOwner = ai;
|
||||||
|
|
||||||
@@ -27,9 +28,8 @@ public class DigMultipleAi extends SpellAbilityAi {
|
|||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
if (!opp.canBeTargetedBy(sa)) {
|
if (!opp.canBeTargetedBy(sa)) {
|
||||||
return false;
|
return false;
|
||||||
} else {
|
|
||||||
sa.getTargets().add(opp);
|
|
||||||
}
|
}
|
||||||
|
sa.getTargets().add(opp);
|
||||||
libraryOwner = opp;
|
libraryOwner = opp;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,7 +77,7 @@ public class DigMultipleAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
final Player opp = ai.getWeakestOpponent();
|
final Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
|
||||||
if (sa.usesTargeting()) {
|
if (sa.usesTargeting()) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
if (mandatory && sa.canTarget(opp)) {
|
if (mandatory && sa.canTarget(opp)) {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package forge.ai.ability;
|
|||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import forge.ai.AiAttackController;
|
||||||
import forge.ai.ComputerUtilCost;
|
import forge.ai.ComputerUtilCost;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
@@ -31,10 +32,8 @@ public class DigUntilAi extends SpellAbilityAi {
|
|||||||
chance = 1;
|
chance = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
final boolean randomReturn = MyRandom.getRandom().nextFloat() <= Math.pow(chance, sa.getActivationsThisTurn() + 1);
|
|
||||||
|
|
||||||
Player libraryOwner = ai;
|
Player libraryOwner = ai;
|
||||||
Player opp = ai.getWeakestOpponent();
|
Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
|
||||||
|
|
||||||
if ("DontMillSelf".equals(logic)) {
|
if ("DontMillSelf".equals(logic)) {
|
||||||
// A card that digs for specific things and puts everything revealed before it into graveyard
|
// 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();
|
sa.resetTargets();
|
||||||
if (!sa.canTarget(opp)) {
|
if (!sa.canTarget(opp)) {
|
||||||
return false;
|
return false;
|
||||||
} else {
|
|
||||||
sa.getTargets().add(opp);
|
|
||||||
}
|
}
|
||||||
|
sa.getTargets().add(opp);
|
||||||
libraryOwner = opp;
|
libraryOwner = opp;
|
||||||
} else {
|
} else {
|
||||||
if (sa.hasParam("Valid")) {
|
if (sa.hasParam("Valid")) {
|
||||||
@@ -92,12 +90,12 @@ public class DigUntilAi extends SpellAbilityAi {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final boolean randomReturn = MyRandom.getRandom().nextFloat() <= Math.pow(chance, sa.getActivationsThisTurn() + 1);
|
||||||
return randomReturn;
|
return randomReturn;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
|
|
||||||
if (sa.usesTargeting()) {
|
if (sa.usesTargeting()) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
if (sa.isCurse()) {
|
if (sa.isCurse()) {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package forge.ai.ability;
|
|||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import forge.ai.AiAttackController;
|
||||||
import forge.ai.ComputerUtil;
|
import forge.ai.ComputerUtil;
|
||||||
import forge.ai.ComputerUtilAbility;
|
import forge.ai.ComputerUtilAbility;
|
||||||
import forge.ai.ComputerUtilCost;
|
import forge.ai.ComputerUtilCost;
|
||||||
@@ -60,11 +61,9 @@ public class DiscardAi extends SpellAbilityAi {
|
|||||||
if (players.get(0) == ai) {
|
if (players.get(0) == ai) {
|
||||||
// the ai should only be using something like this if he has
|
// the ai should only be using something like this if he has
|
||||||
// few cards in hand,
|
// few cards in hand,
|
||||||
// cards like this better have a good drawback to be in the
|
// cards like this better have a good drawback to be in the AIs deck
|
||||||
// AIs deck
|
|
||||||
} else {
|
} else {
|
||||||
// defined to the human, so that's fine as long the human
|
// defined to the human, so that's fine as long the human has cards
|
||||||
// has cards
|
|
||||||
if (!humanHasHand) {
|
if (!humanHasHand) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -170,7 +169,7 @@ public class DiscardAi extends SpellAbilityAi {
|
|||||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
if (tgt != null) {
|
if (tgt != null) {
|
||||||
Player opp = ai.getWeakestOpponent();
|
Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
|
||||||
if (!discardTargetAI(ai, sa)) {
|
if (!discardTargetAI(ai, sa)) {
|
||||||
if (mandatory && sa.canTarget(opp)) {
|
if (mandatory && sa.canTarget(opp)) {
|
||||||
sa.getTargets().add(opp);
|
sa.getTargets().add(opp);
|
||||||
|
|||||||
@@ -23,8 +23,7 @@ public class DrainManaAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
if (tgt == null) {
|
if (tgt == null) {
|
||||||
// assume we are looking to tap human's stuff
|
// assume we are looking to tap human's stuff
|
||||||
// TODO - check for things with untap abilities, and don't tap
|
// TODO - check for things with untap abilities, and don't tap those.
|
||||||
// those.
|
|
||||||
final List<Player> defined = AbilityUtils.getDefinedPlayers(source, sa.getParam("Defined"), sa);
|
final List<Player> defined = AbilityUtils.getDefinedPlayers(source, sa.getParam("Defined"), sa);
|
||||||
|
|
||||||
if (!defined.contains(opp)) {
|
if (!defined.contains(opp)) {
|
||||||
|
|||||||
@@ -257,7 +257,7 @@ public class DrawAi extends SpellAbilityAi {
|
|||||||
} else {
|
} else {
|
||||||
numCards = ComputerUtilCost.getMaxXValue(sa, ai);
|
numCards = ComputerUtilCost.getMaxXValue(sa, ai);
|
||||||
// try not to overdraw
|
// 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
|
if (sa.getHostCard().isInstant() || sa.getHostCard().isSorcery()) { safeDraw++; } // card will be spent
|
||||||
numCards = Math.min(numCards, safeDraw);
|
numCards = Math.min(numCards, safeDraw);
|
||||||
|
|
||||||
@@ -377,7 +377,7 @@ public class DrawAi extends SpellAbilityAi {
|
|||||||
// checks what the ai prevent from casting it on itself
|
// checks what the ai prevent from casting it on itself
|
||||||
// if spell is not mandatory
|
// if spell is not mandatory
|
||||||
if (aiTarget && !ai.cantLose()) {
|
if (aiTarget && !ai.cantLose()) {
|
||||||
if (numCards >= computerLibrarySize) {
|
if (numCards >= computerLibrarySize - 3) {
|
||||||
if (xPaid) {
|
if (xPaid) {
|
||||||
numCards = computerLibrarySize - 1;
|
numCards = computerLibrarySize - 1;
|
||||||
if (numCards <= 0 && !mandatory) {
|
if (numCards <= 0 && !mandatory) {
|
||||||
@@ -422,8 +422,7 @@ public class DrawAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
root.setXManaCostPaid(numCards);
|
root.setXManaCostPaid(numCards);
|
||||||
} else {
|
} else {
|
||||||
// Don't draw too many cards and then risk discarding
|
// Don't draw too many cards and then risk discarding cards at EOT
|
||||||
// cards at EOT
|
|
||||||
if (!drawback && !mandatory) {
|
if (!drawback && !mandatory) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -441,7 +440,7 @@ public class DrawAi extends SpellAbilityAi {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// use xPaid abilties only for itself
|
// use xPaid abilities only for itself
|
||||||
if (xPaid) {
|
if (xPaid) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -493,7 +492,7 @@ public class DrawAi extends SpellAbilityAi {
|
|||||||
// TODO: consider if human is the defined player
|
// TODO: consider if human is the defined player
|
||||||
|
|
||||||
// ability is not targeted
|
// ability is not targeted
|
||||||
if (numCards >= computerLibrarySize) {
|
if (numCards >= computerLibrarySize - 3) {
|
||||||
if (ai.isCardInPlay("Laboratory Maniac")) {
|
if (ai.isCardInPlay("Laboratory Maniac")) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -509,8 +508,7 @@ public class DrawAi extends SpellAbilityAi {
|
|||||||
&& game.getPhaseHandler().isPlayerTurn(ai)
|
&& game.getPhaseHandler().isPlayerTurn(ai)
|
||||||
&& !sa.isTrigger()
|
&& !sa.isTrigger()
|
||||||
&& !assumeSafeX) {
|
&& !assumeSafeX) {
|
||||||
// Don't draw too many cards and then risk discarding cards at
|
// Don't draw too many cards and then risk discarding cards at EOT
|
||||||
// EOT
|
|
||||||
if (!drawback) {
|
if (!drawback) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ public class EffectAi extends SpellAbilityAi {
|
|||||||
if (!game.getStack().isEmpty()) {
|
if (!game.getStack().isEmpty()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (game.getPhaseHandler().isPreventCombatDamageThisTurn()) {
|
if (game.getReplacementHandler().isPreventCombatDamageThisTurn()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!ComputerUtilCombat.lifeInDanger(ai, game.getCombat())) {
|
if (!ComputerUtilCombat.lifeInDanger(ai, game.getCombat())) {
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import forge.game.keyword.Keyword;
|
|||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.spellability.AbilitySub;
|
import forge.game.spellability.AbilitySub;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
|
import forge.game.staticability.StaticAbilityMustTarget;
|
||||||
import forge.game.trigger.Trigger;
|
import forge.game.trigger.Trigger;
|
||||||
import forge.game.trigger.TriggerType;
|
import forge.game.trigger.TriggerType;
|
||||||
import forge.util.MyRandom;
|
import forge.util.MyRandom;
|
||||||
@@ -50,6 +51,9 @@ public class FightAi extends SpellAbilityAi {
|
|||||||
aiCreatures = ComputerUtil.getSafeTargets(ai, sa, aiCreatures);
|
aiCreatures = ComputerUtil.getSafeTargets(ai, sa, aiCreatures);
|
||||||
List<Card> humCreatures = ai.getOpponents().getCreaturesInPlay();
|
List<Card> humCreatures = ai.getOpponents().getCreaturesInPlay();
|
||||||
humCreatures = CardLists.getTargetableCards(humCreatures, sa);
|
humCreatures = CardLists.getTargetableCards(humCreatures, sa);
|
||||||
|
// Filter MustTarget requirements
|
||||||
|
StaticAbilityMustTarget.filterMustTargetCards(ai, humCreatures, sa);
|
||||||
|
|
||||||
if (humCreatures.isEmpty())
|
if (humCreatures.isEmpty())
|
||||||
return false; //prevent IndexOutOfBoundsException on MOJHOSTO variant
|
return false; //prevent IndexOutOfBoundsException on MOJHOSTO variant
|
||||||
|
|
||||||
|
|||||||
@@ -16,14 +16,32 @@ public class FlipACoinAi extends SpellAbilityAi {
|
|||||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||||
|
|
||||||
if (sa.hasParam("AILogic")) {
|
if (sa.hasParam("AILogic")) {
|
||||||
String AILogic = sa.getParam("AILogic");
|
String ailogic = sa.getParam("AILogic");
|
||||||
if (AILogic.equals("Never")) {
|
if (ailogic.equals("Never")) {
|
||||||
return false;
|
return false;
|
||||||
} else if (AILogic.equals("PhaseOut")) {
|
} else if (ailogic.equals("PhaseOut")) {
|
||||||
if (!ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), sa).contains(sa.getHostCard())) {
|
if (!ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), sa).contains(sa.getHostCard())) {
|
||||||
return false;
|
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) ) {
|
if (ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.END_OF_TURN) ) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ public class FogAi extends SpellAbilityAi {
|
|||||||
final Card hostCard = sa.getHostCard();
|
final Card hostCard = sa.getHostCard();
|
||||||
|
|
||||||
// Don't cast it, if the effect is already in place
|
// Don't cast it, if the effect is already in place
|
||||||
if (game.getPhaseHandler().isPreventCombatDamageThisTurn()) {
|
if (game.getReplacementHandler().isPreventCombatDamageThisTurn()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,8 +13,7 @@ public class GameLossAi extends SpellAbilityAi {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only one SA Lose the Game card right now, which is Door to
|
// Only one SA Lose the Game card right now, which is Door to Nothingness
|
||||||
// Nothingness
|
|
||||||
|
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
if (tgt != null) {
|
if (tgt != null) {
|
||||||
@@ -29,20 +28,23 @@ public class GameLossAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
|
Player loser = ai;
|
||||||
|
|
||||||
// Phage the Untouchable
|
// Phage the Untouchable
|
||||||
// (Final Fortune would need to attach it's delayed trigger to a
|
// (Final Fortune would need to attach it's delayed trigger to a
|
||||||
// specific turn, which can't be done yet)
|
// 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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
if (tgt != null) {
|
if (tgt != null) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
sa.getTargets().add(opp);
|
sa.getTargets().add(loser);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
56
forge-ai/src/main/java/forge/ai/ability/LearnAi.java
Normal file
56
forge-ai/src/main/java/forge/ai/ability/LearnAi.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
import forge.ai.AiAttackController;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
@@ -19,7 +20,7 @@ public class LifeExchangeAi extends SpellAbilityAi {
|
|||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
||||||
final int myLife = aiPlayer.getLife();
|
final int myLife = aiPlayer.getLife();
|
||||||
Player opponent = aiPlayer.getWeakestOpponent();
|
Player opponent = AiAttackController.choosePreferredDefenderPlayer(aiPlayer);
|
||||||
final int hLife = opponent.getLife();
|
final int hLife = opponent.getLife();
|
||||||
|
|
||||||
if (!aiPlayer.canGainLife()) {
|
if (!aiPlayer.canGainLife()) {
|
||||||
@@ -75,7 +76,7 @@ public class LifeExchangeAi extends SpellAbilityAi {
|
|||||||
final boolean mandatory) {
|
final boolean mandatory) {
|
||||||
|
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
Player opp = ai.getWeakestOpponent();
|
Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
|
||||||
if (tgt != null) {
|
if (tgt != null) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
if (sa.canTarget(opp) && (mandatory || ai.getLife() < opp.getLife())) {
|
if (sa.canTarget(opp) && (mandatory || ai.getLife() < opp.getLife())) {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
import forge.ai.AiAttackController;
|
||||||
import forge.ai.AiProps;
|
import forge.ai.AiProps;
|
||||||
import forge.ai.ComputerUtil;
|
import forge.ai.ComputerUtil;
|
||||||
import forge.ai.ComputerUtilAbility;
|
import forge.ai.ComputerUtilAbility;
|
||||||
@@ -154,7 +155,7 @@ public class LifeExchangeVariantAi extends SpellAbilityAi {
|
|||||||
final boolean mandatory) {
|
final boolean mandatory) {
|
||||||
|
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
Player opp = ai.getWeakestOpponent();
|
Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
|
||||||
if (tgt != null) {
|
if (tgt != null) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
if (sa.canTarget(opp) && (mandatory || ai.getLife() < opp.getLife())) {
|
if (sa.canTarget(opp) && (mandatory || ai.getLife() < opp.getLife())) {
|
||||||
|
|||||||
@@ -266,8 +266,7 @@ public class LifeGainAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
if (!hasTgt && mandatory) {
|
if (!hasTgt && mandatory) {
|
||||||
// need to target something but its neither negative against
|
// need to target something but its neither negative against
|
||||||
// opponents,
|
// opponents, nor positive against allies
|
||||||
// nor posive against allies
|
|
||||||
|
|
||||||
// hurting ally is probably better than healing opponent
|
// hurting ally is probably better than healing opponent
|
||||||
// look for Lifegain not Negative (case of lifegain negated)
|
// look for Lifegain not Negative (case of lifegain negated)
|
||||||
@@ -295,8 +294,7 @@ public class LifeGainAi extends SpellAbilityAi {
|
|||||||
sa.getTargets().add(ally);
|
sa.getTargets().add(ally);
|
||||||
hasTgt = true;
|
hasTgt = true;
|
||||||
}
|
}
|
||||||
// better heal opponent which most life then the one with the
|
// better heal opponent which most life then the one with the lowest
|
||||||
// lowest
|
|
||||||
if (!hasTgt) {
|
if (!hasTgt) {
|
||||||
Player opp = opps.max(PlayerPredicates.compareByLife());
|
Player opp = opps.max(PlayerPredicates.compareByLife());
|
||||||
sa.getTargets().add(opp);
|
sa.getTargets().add(opp);
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ public class LifeLoseAi extends SpellAbilityAi {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
||||||
|
|
||||||
final PlayerCollection tgtPlayers = getPlayers(ai, sa);
|
final PlayerCollection tgtPlayers = getPlayers(ai, sa);
|
||||||
|
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
@@ -60,7 +59,6 @@ public class LifeLoseAi extends SpellAbilityAi {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* (non-Javadoc)
|
* (non-Javadoc)
|
||||||
*
|
*
|
||||||
@@ -123,12 +121,12 @@ public class LifeLoseAi extends SpellAbilityAi {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
final PlayerCollection tgtPlayers = getPlayers(ai, sa);
|
|
||||||
|
|
||||||
if (ComputerUtil.playImmediately(ai, sa)) {
|
if (ComputerUtil.playImmediately(ai, sa)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final PlayerCollection tgtPlayers = getPlayers(ai, sa);
|
||||||
|
// TODO: check against the amount we could obtain when multiple activations are possible
|
||||||
PlayerCollection filteredPlayer = tgtPlayers
|
PlayerCollection filteredPlayer = tgtPlayers
|
||||||
.filter(Predicates.and(PlayerPredicates.isOpponentOf(ai), PlayerPredicates.lifeLessOrEqualTo(amount)));
|
.filter(Predicates.and(PlayerPredicates.isOpponentOf(ai), PlayerPredicates.lifeLessOrEqualTo(amount)));
|
||||||
// killing opponents asap
|
// killing opponents asap
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ public class LifeSetAi extends SpellAbilityAi {
|
|||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||||
final int myLife = ai.getLife();
|
final int myLife = ai.getLife();
|
||||||
final Player opponent = ai.getWeakestOpponent();
|
final Player opponent = ai.getStrongestOpponent();
|
||||||
final int hlife = opponent.getLife();
|
final int hlife = opponent.getLife();
|
||||||
final String amountStr = sa.getParam("LifeAmount");
|
final String amountStr = sa.getParam("LifeAmount");
|
||||||
|
|
||||||
@@ -36,8 +36,7 @@ public class LifeSetAi extends SpellAbilityAi {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO handle proper calculation of X values based on Cost and what
|
// TODO handle proper calculation of X values based on Cost and what would be paid
|
||||||
// would be paid
|
|
||||||
int amount;
|
int amount;
|
||||||
// we shouldn't have to worry too much about PayX for SetLife
|
// we shouldn't have to worry too much about PayX for SetLife
|
||||||
if (amountStr.equals("X") && sa.getSVar(amountStr).equals("Count$xPaid")) {
|
if (amountStr.equals("X") && sa.getSVar(amountStr).equals("Count$xPaid")) {
|
||||||
@@ -58,11 +57,9 @@ public class LifeSetAi extends SpellAbilityAi {
|
|||||||
if (tgt.canOnlyTgtOpponent()) {
|
if (tgt.canOnlyTgtOpponent()) {
|
||||||
sa.getTargets().add(opponent);
|
sa.getTargets().add(opponent);
|
||||||
// if we can only target the human, and the Human's life
|
// if we can only target the human, and the Human's life
|
||||||
// would
|
// would go up, don't play it.
|
||||||
// go up, don't play it.
|
|
||||||
// possibly add a combo here for Magister Sphinx and
|
// possibly add a combo here for Magister Sphinx and
|
||||||
// Higedetsu's
|
// Higedetsu's (sp?) Second Rite
|
||||||
// (sp?) Second Rite
|
|
||||||
if ((amount > hlife) || !opponent.canLoseLife()) {
|
if ((amount > hlife) || !opponent.canLoseLife()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -81,8 +78,7 @@ public class LifeSetAi extends SpellAbilityAi {
|
|||||||
if (sa.getParam("Defined").equals("Player")) {
|
if (sa.getParam("Defined").equals("Player")) {
|
||||||
if (amount == 0) {
|
if (amount == 0) {
|
||||||
return false;
|
return false;
|
||||||
} else if (myLife > amount) { // will decrease computer's
|
} else if (myLife > amount) { // will decrease computer's life
|
||||||
// life
|
|
||||||
if ((myLife < 5) || ((myLife - amount) > (hlife - amount))) {
|
if ((myLife < 5) || ((myLife - amount) > (hlife - amount))) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -104,7 +100,7 @@ public class LifeSetAi extends SpellAbilityAi {
|
|||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
final int myLife = ai.getLife();
|
final int myLife = ai.getLife();
|
||||||
final Player opponent = ai.getWeakestOpponent();
|
final Player opponent = ai.getStrongestOpponent();
|
||||||
final int hlife = opponent.getLife();
|
final int hlife = opponent.getLife();
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
|
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 gaining life, target self.
|
||||||
// if the Target is modifying how much life is gained, this needs to
|
// if the Target is modifying how much life is gained, this needs to be handled better
|
||||||
// be handled better
|
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
if (tgt != null) {
|
if (tgt != null) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import forge.game.phase.PhaseHandler;
|
|||||||
import forge.game.phase.PhaseType;
|
import forge.game.phase.PhaseType;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.player.PlayerActionConfirmMode;
|
import forge.game.player.PlayerActionConfirmMode;
|
||||||
|
import forge.game.player.PlayerPredicates;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.spellability.TargetRestrictions;
|
import forge.game.spellability.TargetRestrictions;
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
@@ -214,13 +215,7 @@ public class MillAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// get targeted or defined Player with largest library
|
// get targeted or defined Player with largest library
|
||||||
// TODO in Java 8 find better way
|
final Player m = Collections.max(list, PlayerPredicates.compareByZoneSize(ZoneType.Library));
|
||||||
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();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
int cardsToDiscard = m.getCardsIn(ZoneType.Library).size();
|
int cardsToDiscard = m.getCardsIn(ZoneType.Library).size();
|
||||||
|
|
||||||
|
|||||||
@@ -297,7 +297,7 @@ public class PermanentAi extends SpellAbilityAi {
|
|||||||
if (!checkPhaseRestrictions(ai, sa, ai.getGame().getPhaseHandler())) {
|
if (!checkPhaseRestrictions(ai, sa, ai.getGame().getPhaseHandler())) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return checkApiLogic(ai, sa);
|
return checkApiLogic(ai, sa) || mandatory;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import forge.game.card.CardCollection;
|
|||||||
import forge.game.card.CardLists;
|
import forge.game.card.CardLists;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.spellability.TargetRestrictions;
|
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -40,10 +39,8 @@ public class PermanentNoncreatureAi extends PermanentAi {
|
|||||||
if (host.hasSVar("OblivionRing")) {
|
if (host.hasSVar("OblivionRing")) {
|
||||||
SpellAbility effectExile = AbilityFactory.getAbility(host.getSVar("TrigExile"), host);
|
SpellAbility effectExile = AbilityFactory.getAbility(host.getSVar("TrigExile"), host);
|
||||||
final ZoneType origin = ZoneType.listValueOf(effectExile.getParam("Origin")).get(0);
|
final ZoneType origin = ZoneType.listValueOf(effectExile.getParam("Origin")).get(0);
|
||||||
final TargetRestrictions tgt = effectExile.getTargetRestrictions();
|
effectExile.setActivatingPlayer(ai);
|
||||||
final CardCollection list = CardLists.getValidCards(game.getCardsIn(origin), tgt.getValidTgts(), ai, host,
|
CardCollection targets = CardLists.getTargetableCards(game.getCardsIn(origin), effectExile);
|
||||||
effectExile);
|
|
||||||
CardCollection targets = CardLists.getTargetableCards(list, sa);
|
|
||||||
if (sourceName.equals("Suspension Field")
|
if (sourceName.equals("Suspension Field")
|
||||||
|| sourceName.equals("Detention Sphere")) {
|
|| sourceName.equals("Detention Sphere")) {
|
||||||
// existing "exile until leaves" enchantments only target
|
// existing "exile until leaves" enchantments only target
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ import forge.game.card.CardPredicates;
|
|||||||
import forge.game.cost.Cost;
|
import forge.game.cost.Cost;
|
||||||
import forge.game.keyword.Keyword;
|
import forge.game.keyword.Keyword;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.player.PlayerActionConfirmMode;
|
|
||||||
import forge.game.spellability.Spell;
|
import forge.game.spellability.Spell;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.spellability.SpellAbilityPredicates;
|
import forge.game.spellability.SpellAbilityPredicates;
|
||||||
@@ -159,15 +158,6 @@ public class PlayAi extends SpellAbilityAi {
|
|||||||
return true;
|
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)
|
/* (non-Javadoc)
|
||||||
* @see forge.card.ability.SpellAbilityAi#chooseSingleCard(forge.game.player.Player, forge.card.spellability.SpellAbility, java.util.List, boolean)
|
* @see forge.card.ability.SpellAbilityAi#chooseSingleCard(forge.game.player.Player, forge.card.spellability.SpellAbility, java.util.List, boolean)
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -30,13 +30,12 @@ public class PowerExchangeAi extends SpellAbilityAi {
|
|||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
|
|
||||||
List<Card> list =
|
List<Card> list =
|
||||||
CardLists.getValidCards(ai.getWeakestOpponent().getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), ai, sa.getHostCard(), sa);
|
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
|
// AI won't try to grab cards that are filtered out of AI decks on purpose
|
||||||
// purpose
|
|
||||||
list = CardLists.filter(list, new Predicate<Card>() {
|
list = CardLists.filter(list, new Predicate<Card>() {
|
||||||
@Override
|
@Override
|
||||||
public boolean apply(final Card c) {
|
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);
|
CardLists.sortByPowerAsc(list);
|
||||||
|
|||||||
@@ -400,7 +400,6 @@ public class PumpAi extends PumpAiBase {
|
|||||||
if (ComputerUtilCard.shouldPumpCard(ai, sa, card, defense, attack, keywords, false)) {
|
if (ComputerUtilCard.shouldPumpCard(ai, sa, card, defense, attack, keywords, false)) {
|
||||||
return true;
|
return true;
|
||||||
} else if (containsUsefulKeyword(ai, keywords, card, sa, attack)) {
|
} else if (containsUsefulKeyword(ai, keywords, card, sa, attack)) {
|
||||||
|
|
||||||
Card pumped = ComputerUtilCard.getPumpedCreature(ai, sa, card, 0, 0, keywords);
|
Card pumped = ComputerUtilCard.getPumpedCreature(ai, sa, card, 0, 0, keywords);
|
||||||
if (game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_ATTACKERS, ai)
|
if (game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_ATTACKERS, ai)
|
||||||
|| game.getPhaseHandler().is(PhaseType.COMBAT_BEGIN, 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)
|
&& game.getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS)
|
||||||
&& !(sa.isCurse() && defense < 0)
|
&& !(sa.isCurse() && defense < 0)
|
||||||
&& !containsNonCombatKeyword(keywords)
|
&& !containsNonCombatKeyword(keywords)
|
||||||
&& !sa.hasParam("UntilYourNextTurn")
|
&& !"UntilYourNextTurn".equals(sa.getParam("Duration"))
|
||||||
&& !"Snapcaster".equals(sa.getParam("AILogic"))
|
&& !"Snapcaster".equals(sa.getParam("AILogic"))
|
||||||
&& !isFight) {
|
&& !isFight) {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import com.google.common.base.Predicate;
|
|||||||
import com.google.common.base.Predicates;
|
import com.google.common.base.Predicates;
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
|
|
||||||
|
import forge.ai.AiAttackController;
|
||||||
import forge.ai.ComputerUtil;
|
import forge.ai.ComputerUtil;
|
||||||
import forge.ai.ComputerUtilCard;
|
import forge.ai.ComputerUtilCard;
|
||||||
import forge.ai.ComputerUtilCombat;
|
import forge.ai.ComputerUtilCombat;
|
||||||
@@ -43,7 +44,6 @@ public abstract class PumpAiBase extends SpellAbilityAi {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public boolean grantsUsefulExtraBlockOpts(final Player ai, final SpellAbility sa, final Card card, List<String> keywords) {
|
public boolean grantsUsefulExtraBlockOpts(final Player ai, final SpellAbility sa, final Card card, List<String> keywords) {
|
||||||
PhaseHandler ph = ai.getGame().getPhaseHandler();
|
PhaseHandler ph = ai.getGame().getPhaseHandler();
|
||||||
Card pumped = ComputerUtilCard.getPumpedCreature(ai, sa, card, 0, 0, keywords);
|
Card pumped = ComputerUtilCard.getPumpedCreature(ai, sa, card, 0, 0, keywords);
|
||||||
@@ -109,7 +109,7 @@ public abstract class PumpAiBase extends SpellAbilityAi {
|
|||||||
&& (card.getNetCombatDamage() > 0)
|
&& (card.getNetCombatDamage() > 0)
|
||||||
&& !ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS);
|
&& !ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS);
|
||||||
} else if (keyword.endsWith("CARDNAME can't attack or block.")) {
|
} 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);
|
return CombatUtil.canAttack(card, ai) || CombatUtil.canBlock(card, true);
|
||||||
}
|
}
|
||||||
if (!ph.isPlayerTurn(ai)) {
|
if (!ph.isPlayerTurn(ai)) {
|
||||||
@@ -153,14 +153,6 @@ public abstract class PumpAiBase extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
return CombatUtil.canBlockAtLeastOne(card, attackers);
|
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.")) {
|
} 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)
|
return !ph.getPhase().isBefore(PhaseType.MAIN2) && !card.isUntapped() && ph.isPlayerTurn(ai)
|
||||||
&& Untap.canUntap(card);
|
&& Untap.canUntap(card);
|
||||||
@@ -201,7 +193,7 @@ public abstract class PumpAiBase extends SpellAbilityAi {
|
|||||||
final Game game = ai.getGame();
|
final Game game = ai.getGame();
|
||||||
final Combat combat = game.getCombat();
|
final Combat combat = game.getCombat();
|
||||||
final PhaseHandler ph = game.getPhaseHandler();
|
final PhaseHandler ph = game.getPhaseHandler();
|
||||||
final Player opp = ai.getWeakestOpponent();
|
final Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
|
||||||
final int newPower = card.getNetCombatDamage() + attack;
|
final int newPower = card.getNetCombatDamage() + attack;
|
||||||
//int defense = getNumDefense(sa);
|
//int defense = getNumDefense(sa);
|
||||||
if (!CardUtil.isStackingKeyword(keyword) && card.hasKeyword(keyword)) {
|
if (!CardUtil.isStackingKeyword(keyword) && card.hasKeyword(keyword)) {
|
||||||
@@ -479,7 +471,7 @@ public abstract class PumpAiBase extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
}); // leaves all creatures that will be destroyed
|
}); // leaves all creatures that will be destroyed
|
||||||
} // -X/-X end
|
} // -X/-X end
|
||||||
else if (attack < 0 && !game.getPhaseHandler().isPreventCombatDamageThisTurn()) {
|
else if (attack < 0 && !game.getReplacementHandler().isPreventCombatDamageThisTurn()) {
|
||||||
// spells that give -X/0
|
// spells that give -X/0
|
||||||
boolean isMyTurn = game.getPhaseHandler().isPlayerTurn(ai);
|
boolean isMyTurn = game.getPhaseHandler().isPlayerTurn(ai);
|
||||||
if (isMyTurn) {
|
if (isMyTurn) {
|
||||||
@@ -513,7 +505,6 @@ public abstract class PumpAiBase extends SpellAbilityAi {
|
|||||||
else {
|
else {
|
||||||
final boolean addsKeywords = !keywords.isEmpty();
|
final boolean addsKeywords = !keywords.isEmpty();
|
||||||
if (addsKeywords) {
|
if (addsKeywords) {
|
||||||
|
|
||||||
// If the keyword can prevent a creature from attacking, see if there's some kind of viable prioritization
|
// 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.")
|
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.")) {
|
|| 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) {
|
protected boolean containsNonCombatKeyword(final List<String> keywords) {
|
||||||
for (final String keyword : keywords) {
|
for (final String keyword : keywords) {
|
||||||
// since most keywords are combat relevant check for those that are
|
// since most keywords are combat relevant check for those that are not
|
||||||
// not
|
|
||||||
if (keyword.endsWith("This card doesn't untap during your next untap step.")
|
if (keyword.endsWith("This card doesn't untap during your next untap step.")
|
||||||
|| keyword.endsWith("Shroud") || keyword.endsWith("Hexproof")) {
|
|| keyword.endsWith("Shroud") || keyword.endsWith("Hexproof")) {
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -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)) {
|
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -64,15 +58,9 @@ public class PumpAllAi extends PumpAiBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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 TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
|
final Player opp = ai.getStrongestOpponent();
|
||||||
|
|
||||||
if (tgt != null && sa.canTarget(opp) && sa.hasParam("IsCurse")) {
|
if (tgt != null && sa.canTarget(opp) && sa.hasParam("IsCurse")) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
sa.getTargets().add(opp);
|
sa.getTargets().add(opp);
|
||||||
@@ -85,6 +73,18 @@ public class PumpAllAi extends PumpAiBase {
|
|||||||
return true;
|
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()) {
|
if (!game.getStack().isEmpty() && !sa.isCurse()) {
|
||||||
return pumpAgainstRemoval(ai, sa, comp);
|
return pumpAgainstRemoval(ai, sa, comp);
|
||||||
}
|
}
|
||||||
@@ -113,7 +113,7 @@ public class PumpAllAi extends PumpAiBase {
|
|||||||
if (phase.isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS)
|
if (phase.isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS)
|
||||||
|| phase.isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)
|
|| phase.isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)
|
||||||
|| game.getPhaseHandler().isPlayerTurn(sa.getActivatingPlayer())
|
|| game.getPhaseHandler().isPlayerTurn(sa.getActivatingPlayer())
|
||||||
|| game.getPhaseHandler().isPreventCombatDamageThisTurn()) {
|
|| game.getReplacementHandler().isPreventCombatDamageThisTurn()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
int totalPower = 0;
|
int totalPower = 0;
|
||||||
@@ -139,8 +139,7 @@ public class PumpAllAi extends PumpAiBase {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// evaluate both lists and pass only if human creatures are more
|
// evaluate both lists and pass only if human creatures are more valuable
|
||||||
// valuable
|
|
||||||
return (ComputerUtilCard.evaluateCreatureList(comp) + 200) < ComputerUtilCard.evaluateCreatureList(human);
|
return (ComputerUtilCard.evaluateCreatureList(comp) + 200) < ComputerUtilCard.evaluateCreatureList(human);
|
||||||
} // end Curse
|
} // end Curse
|
||||||
|
|
||||||
@@ -152,6 +151,12 @@ public class PumpAllAi extends PumpAiBase {
|
|||||||
return true;
|
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) {
|
boolean pumpAgainstRemoval(Player ai, SpellAbility sa, List<Card> comp) {
|
||||||
final List<GameObject> objects = ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), sa, true);
|
final List<GameObject> objects = ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), sa, true);
|
||||||
for (final Card c : comp) {
|
for (final Card c : comp) {
|
||||||
|
|||||||
@@ -49,14 +49,6 @@ import forge.game.zone.ZoneType;
|
|||||||
*/
|
*/
|
||||||
public class RegenerateAi extends SpellAbilityAi {
|
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
|
@Override
|
||||||
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
|
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
|
||||||
final Game game = ai.getGame();
|
final Game game = ai.getGame();
|
||||||
@@ -65,8 +57,7 @@ public class RegenerateAi extends SpellAbilityAi {
|
|||||||
boolean chance = false;
|
boolean chance = false;
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
if (tgt == null) {
|
if (tgt == null) {
|
||||||
// As far as I can tell these Defined Cards will only have one of
|
// As far as I can tell these Defined Cards will only have one of them
|
||||||
// them
|
|
||||||
final List<Card> list = AbilityUtils.getDefinedCards(hostCard, sa.getParam("Defined"), sa);
|
final List<Card> list = AbilityUtils.getDefinedCards(hostCard, sa.getParam("Defined"), sa);
|
||||||
|
|
||||||
if (!game.getStack().isEmpty()) {
|
if (!game.getStack().isEmpty()) {
|
||||||
@@ -105,8 +96,7 @@ public class RegenerateAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!game.getStack().isEmpty()) {
|
if (!game.getStack().isEmpty()) {
|
||||||
// check stack for something on the stack will kill anything i
|
// check stack for something on the stack will kill anything i control
|
||||||
// control
|
|
||||||
final List<GameObject> objects = ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), sa, true);
|
final List<GameObject> objects = ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), sa, true);
|
||||||
|
|
||||||
final List<Card> threatenedTargets = new ArrayList<>();
|
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
|
// TODO see if something on the stack is about to kill something i can target
|
||||||
// can target
|
|
||||||
|
|
||||||
// choose my best X without regen
|
// choose my best X without regen
|
||||||
if (CardLists.getNotType(compTargetables, "Creature").isEmpty()) {
|
if (CardLists.getNotType(compTargetables, "Creature").isEmpty()) {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
|
||||||
|
import forge.ai.AiAttackController;
|
||||||
import forge.ai.AiController;
|
import forge.ai.AiController;
|
||||||
import forge.ai.ComputerUtilCost;
|
import forge.ai.ComputerUtilCost;
|
||||||
import forge.ai.PlayerControllerAi;
|
import forge.ai.PlayerControllerAi;
|
||||||
@@ -14,10 +15,10 @@ public class RepeatAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||||
final Player opp = ai.getWeakestOpponent();
|
final Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
|
||||||
|
|
||||||
if (sa.usesTargeting()) {
|
if (sa.usesTargeting()) {
|
||||||
if (!opp.canBeTargetedBy(sa)) {
|
if (!sa.canTarget(opp)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
@@ -44,9 +45,8 @@ public class RepeatAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
|
|
||||||
if (sa.usesTargeting()) {
|
if (sa.usesTargeting()) {
|
||||||
final Player opp = ai.getWeakestOpponent();
|
final Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
|
||||||
if (sa.canTarget(opp)) {
|
if (sa.canTarget(opp)) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
sa.getTargets().add(opp);
|
sa.getTargets().add(opp);
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ import forge.game.card.CardPredicates.Presets;
|
|||||||
import forge.game.card.CardUtil;
|
import forge.game.card.CardUtil;
|
||||||
import forge.game.phase.PhaseType;
|
import forge.game.phase.PhaseType;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.spellability.AbilitySub;
|
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
import forge.util.TextUtil;
|
import forge.util.TextUtil;
|
||||||
@@ -67,7 +66,7 @@ public class RepeatEachAi extends SpellAbilityAi {
|
|||||||
return false;
|
return false;
|
||||||
} else if ("AllPlayerLoseLife".equals(logic)) {
|
} else if ("AllPlayerLoseLife".equals(logic)) {
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
AbilitySub repeat = sa.getAdditionalAbility("RepeatSubAbility");
|
SpellAbility repeat = sa.getAdditionalAbility("RepeatSubAbility");
|
||||||
|
|
||||||
String svar = repeat.getSVar(repeat.getParam("LifeAmount"));
|
String svar = repeat.getSVar(repeat.getParam("LifeAmount"));
|
||||||
// replace RememberedPlayerCtrl with YouCtrl
|
// replace RememberedPlayerCtrl with YouCtrl
|
||||||
|
|||||||
@@ -20,9 +20,6 @@ import forge.game.spellability.TargetRestrictions;
|
|||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
|
|
||||||
public class SacrificeAi extends SpellAbilityAi {
|
public class SacrificeAi extends SpellAbilityAi {
|
||||||
// **************************************************************
|
|
||||||
// *************************** Sacrifice ***********************
|
|
||||||
// **************************************************************
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
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:
|
// Improve AI for triggers. If source is a creature with:
|
||||||
// When ETB, sacrifice a creature. Check to see if the AI has something
|
// When ETB, sacrifice a creature. Check to see if the AI has something to sacrifice
|
||||||
// to sacrifice
|
|
||||||
|
|
||||||
// Eventually, we can call the trigger of ETB abilities with not
|
// Eventually, we can call the trigger of ETB abilities with not
|
||||||
// mandatory as part of the checks to cast something
|
// 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) {
|
private boolean sacrificeTgtAI(final Player ai, final SpellAbility sa) {
|
||||||
|
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
final boolean destroy = sa.hasParam("Destroy");
|
final boolean destroy = sa.hasParam("Destroy");
|
||||||
|
|
||||||
Player opp = ai.getWeakestOpponent();
|
Player opp = ai.getStrongestOpponent();
|
||||||
|
|
||||||
if (tgt != null) {
|
if (tgt != null) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
@@ -109,8 +104,7 @@ public class SacrificeAi extends SpellAbilityAi {
|
|||||||
sa.setXManaCostPaid(Math.min(ComputerUtilCost.getMaxXValue(sa, ai), amount));
|
sa.setXManaCostPaid(Math.min(ComputerUtilCost.getMaxXValue(sa, ai), amount));
|
||||||
}
|
}
|
||||||
|
|
||||||
final int half = (amount / 2) + (amount % 2); // Half of amount
|
final int half = (amount / 2) + (amount % 2); // Half of amount rounded up
|
||||||
// rounded up
|
|
||||||
|
|
||||||
// If the Human has at least half rounded up of the amount to be
|
// If the Human has at least half rounded up of the amount to be
|
||||||
// sacrificed, cast the spell
|
// sacrificed, cast the spell
|
||||||
@@ -130,8 +124,7 @@ public class SacrificeAi extends SpellAbilityAi {
|
|||||||
// If Sacrifice hits both players:
|
// If Sacrifice hits both players:
|
||||||
// Only cast it if Human has the full amount of valid
|
// Only cast it if Human has the full amount of valid
|
||||||
// Only cast it if AI doesn't have 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
|
// TODO: Cast if the type is favorable: my "worst" valid is worse than his "worst" valid
|
||||||
// worse than his "worst" valid
|
|
||||||
final String num = sa.hasParam("Amount") ? sa.getParam("Amount") : "1";
|
final String num = sa.hasParam("Amount") ? sa.getParam("Amount") : "1";
|
||||||
int amount = AbilityUtils.calculateAmount(source, num, sa);
|
int amount = AbilityUtils.calculateAmount(source, num, sa);
|
||||||
|
|
||||||
|
|||||||
@@ -2,17 +2,12 @@ package forge.ai.ability;
|
|||||||
|
|
||||||
import forge.ai.ComputerUtilCard;
|
import forge.ai.ComputerUtilCard;
|
||||||
import forge.ai.ComputerUtilCost;
|
import forge.ai.ComputerUtilCost;
|
||||||
import forge.ai.ComputerUtilMana;
|
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
import forge.game.card.CardCollection;
|
|
||||||
import forge.game.card.CardLists;
|
|
||||||
import forge.game.cost.Cost;
|
import forge.game.cost.Cost;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.zone.ZoneType;
|
|
||||||
import forge.util.MyRandom;
|
import forge.util.MyRandom;
|
||||||
import forge.util.TextUtil;
|
|
||||||
|
|
||||||
public class SacrificeAllAi extends SpellAbilityAi {
|
public class SacrificeAllAi extends SpellAbilityAi {
|
||||||
|
|
||||||
@@ -22,22 +17,7 @@ public class SacrificeAllAi extends SpellAbilityAi {
|
|||||||
// based on what the expected targets could be
|
// based on what the expected targets could be
|
||||||
final Cost abCost = sa.getPayCosts();
|
final Cost abCost = sa.getPayCosts();
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
String valid = "";
|
final String logic = sa.getParamOrDefault("AILogic", "");
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
if (abCost != null) {
|
if (abCost != null) {
|
||||||
// AI currently disabled for some costs
|
// AI currently disabled for some costs
|
||||||
@@ -46,29 +26,19 @@ public class SacrificeAllAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
// prevent run-away activations - first time will always return true
|
||||||
boolean chance = MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
|
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);
|
return ((MyRandom.getRandom().nextFloat() < .9667) && chance);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,14 +3,12 @@ package forge.ai.ability;
|
|||||||
import com.google.common.base.Predicates;
|
import com.google.common.base.Predicates;
|
||||||
|
|
||||||
import forge.ai.ComputerUtilMana;
|
import forge.ai.ComputerUtilMana;
|
||||||
|
import forge.ai.SpecialCardAi;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.ability.ApiType;
|
import forge.game.ability.ApiType;
|
||||||
import forge.game.card.Card;
|
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.CardLists;
|
||||||
import forge.game.card.CardPredicates;
|
import forge.game.card.CardPredicates;
|
||||||
import forge.game.card.CounterEnumType;
|
|
||||||
import forge.game.phase.PhaseHandler;
|
import forge.game.phase.PhaseHandler;
|
||||||
import forge.game.phase.PhaseType;
|
import forge.game.phase.PhaseType;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
@@ -47,6 +45,11 @@ public class ScryAi extends SpellAbilityAi {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, final PhaseHandler ph) {
|
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
|
// 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
|
// 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,
|
// 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)) {
|
if ("Never".equals(aiLogic)) {
|
||||||
return false;
|
return false;
|
||||||
} else if ("BrainJar".equals(aiLogic)) {
|
} else if ("BrainJar".equals(aiLogic)) {
|
||||||
final Card source = sa.getHostCard();
|
return SpecialCardAi.BrainInAJar.consider(ai, sa);
|
||||||
|
} else if ("MultipleChoice".equals(aiLogic)) {
|
||||||
int counterNum = source.getCounters(CounterEnumType.CHARGE);
|
return SpecialCardAi.MultipleChoice.consider(ai, sa);
|
||||||
// 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 true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -163,11 +119,9 @@ public class ScryAi extends SpellAbilityAi {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
double chance = .4; // 40 percent chance of milling with instant speed
|
double chance = .4; // 40 percent chance of milling with instant speed stuff
|
||||||
// stuff
|
|
||||||
if (SpellAbilityAi.isSorcerySpeed(sa)) {
|
if (SpellAbilityAi.isSorcerySpeed(sa)) {
|
||||||
chance = .667; // 66.7% chance for sorcery speed (since it will
|
chance = .667; // 66.7% chance for sorcery speed (since it will never activate EOT)
|
||||||
// never activate EOT)
|
|
||||||
}
|
}
|
||||||
boolean randomReturn = MyRandom.getRandom().nextFloat() <= Math.pow(chance, sa.getActivationsThisTurn() + 1);
|
boolean randomReturn = MyRandom.getRandom().nextFloat() <= Math.pow(chance, sa.getActivationsThisTurn() + 1);
|
||||||
|
|
||||||
|
|||||||
@@ -52,8 +52,7 @@ public class SetStateAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
|
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
|
||||||
// Gross generalization, but this always considers alternate
|
// Gross generalization, but this always considers alternate states more powerful
|
||||||
// states more powerful
|
|
||||||
return !sa.getHostCard().isInAlternateState();
|
return !sa.getHostCard().isInAlternateState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,8 +17,7 @@ public class ShuffleAi extends SpellAbilityAi {
|
|||||||
return aiPlayer.getGame().getPhaseHandler().is(PhaseType.MAIN2, aiPlayer);
|
return aiPlayer.getGame().getPhaseHandler().is(PhaseType.MAIN2, aiPlayer);
|
||||||
}
|
}
|
||||||
|
|
||||||
// not really sure when the compy would use this; maybe only after a
|
// not really sure when the compy would use this; maybe only after a human
|
||||||
// human
|
|
||||||
// deliberately put a card on top of their library
|
// deliberately put a card on top of their library
|
||||||
return false;
|
return false;
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -66,7 +66,6 @@ public class TapAi extends TapAiBase {
|
|||||||
// Set PayX here to maximum value.
|
// Set PayX here to maximum value.
|
||||||
// TODO need to set XManaCostPaid for targets, maybe doesn't need PayX anymore?
|
// TODO need to set XManaCostPaid for targets, maybe doesn't need PayX anymore?
|
||||||
sa.setXManaCostPaid(ComputerUtilCost.getMaxXValue(sa, ai));
|
sa.setXManaCostPaid(ComputerUtilCost.getMaxXValue(sa, ai));
|
||||||
// TODO since change of PayX. the shouldCastLessThanMax logic might be faulty
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
|
|||||||
@@ -5,11 +5,13 @@ import java.util.List;
|
|||||||
import com.google.common.base.Predicate;
|
import com.google.common.base.Predicate;
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
|
|
||||||
|
import forge.ai.AiAttackController;
|
||||||
import forge.ai.ComputerUtil;
|
import forge.ai.ComputerUtil;
|
||||||
import forge.ai.ComputerUtilAbility;
|
import forge.ai.ComputerUtilAbility;
|
||||||
import forge.ai.ComputerUtilCard;
|
import forge.ai.ComputerUtilCard;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.Game;
|
import forge.game.Game;
|
||||||
|
import forge.game.ability.AbilityUtils;
|
||||||
import forge.game.ability.ApiType;
|
import forge.game.ability.ApiType;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
import forge.game.card.CardCollection;
|
import forge.game.card.CardCollection;
|
||||||
@@ -109,7 +111,7 @@ public abstract class TapAiBase extends SpellAbilityAi {
|
|||||||
* @return a boolean.
|
* @return a boolean.
|
||||||
*/
|
*/
|
||||||
protected boolean tapPrefTargeting(final Player ai, final Card source, final SpellAbility sa, final boolean mandatory) {
|
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();
|
final Game game = ai.getGame();
|
||||||
CardCollection tapList = CardLists.filterControlledBy(game.getCardsIn(ZoneType.Battlefield), ai.getOpponents());
|
CardCollection tapList = CardLists.filterControlledBy(game.getCardsIn(ZoneType.Battlefield), ai.getOpponents());
|
||||||
tapList = CardLists.getTargetableCards(tapList, sa);
|
tapList = CardLists.getTargetableCards(tapList, sa);
|
||||||
@@ -308,9 +310,8 @@ public abstract class TapAiBase extends SpellAbilityAi {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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.isEmpty() || (pDefined.get(0).isUntapped() && pDefined.get(0).getController() != ai);
|
||||||
return true;
|
|
||||||
} else {
|
} else {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
if (tapPrefTargeting(ai, source, sa, mandatory)) {
|
if (tapPrefTargeting(ai, source, sa, mandatory)) {
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import forge.game.ability.AbilityUtils;
|
|||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
import forge.game.card.CardCollectionView;
|
import forge.game.card.CardCollectionView;
|
||||||
import forge.game.card.CardLists;
|
import forge.game.card.CardLists;
|
||||||
import forge.game.card.CardPredicates.Presets;
|
import forge.game.card.CardPredicates;
|
||||||
import forge.game.combat.CombatUtil;
|
import forge.game.combat.CombatUtil;
|
||||||
import forge.game.phase.PhaseType;
|
import forge.game.phase.PhaseType;
|
||||||
import forge.game.player.Player;
|
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.getValidCards(validTappables, valid, source.getController(), source, sa);
|
||||||
validTappables = CardLists.filter(validTappables, Presets.UNTAPPED);
|
validTappables = CardLists.filter(validTappables, CardPredicates.Presets.UNTAPPED);
|
||||||
|
|
||||||
if (sa.hasParam("AILogic")) {
|
if (sa.hasParam("AILogic")) {
|
||||||
String logic = sa.getParam("AILogic");
|
String logic = sa.getParam("AILogic");
|
||||||
@@ -69,18 +69,8 @@ public class TapAllAi extends SpellAbilityAi {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
final List<Card> human = CardLists.filter(validTappables, new Predicate<Card>() {
|
final List<Card> human = CardLists.filterControlledBy(validTappables, opp);
|
||||||
@Override
|
final List<Card> compy = CardLists.filterControlledBy(validTappables, ai);
|
||||||
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);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (human.size() <= compy.size()) {
|
if (human.size() <= compy.size()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -102,7 +92,7 @@ public class TapAllAi extends SpellAbilityAi {
|
|||||||
final Game game = source.getGame();
|
final Game game = source.getGame();
|
||||||
CardCollectionView tmpList = game.getCardsIn(ZoneType.Battlefield);
|
CardCollectionView tmpList = game.getCardsIn(ZoneType.Battlefield);
|
||||||
tmpList = CardLists.getValidCards(tmpList, valid, source.getController(), source, sa);
|
tmpList = CardLists.getValidCards(tmpList, valid, source.getController(), source, sa);
|
||||||
tmpList = CardLists.filter(tmpList, Presets.UNTAPPED);
|
tmpList = CardLists.filter(tmpList, CardPredicates.Presets.UNTAPPED);
|
||||||
return tmpList;
|
return tmpList;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,8 +19,7 @@ public class TapOrUntapAi extends TapAiBase {
|
|||||||
|
|
||||||
if (!sa.usesTargeting()) {
|
if (!sa.usesTargeting()) {
|
||||||
// assume we are looking to tap human's stuff
|
// assume we are looking to tap human's stuff
|
||||||
// TODO - check for things with untap abilities, and don't tap
|
// TODO - check for things with untap abilities, and don't tap those.
|
||||||
// those.
|
|
||||||
|
|
||||||
boolean bFlag = false;
|
boolean bFlag = false;
|
||||||
for (final Card c : AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa)) {
|
for (final Card c : AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa)) {
|
||||||
@@ -40,6 +39,4 @@ public class TapOrUntapAi extends TapAiBase {
|
|||||||
return randomReturn;
|
return randomReturn;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package forge.ai.ability;
|
|||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import forge.ai.AiAttackController;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.ability.AbilityUtils;
|
import forge.game.ability.AbilityUtils;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
@@ -28,7 +29,7 @@ public class TwoPilesAi extends SpellAbilityAi {
|
|||||||
valid = sa.getParam("ValidCards");
|
valid = sa.getParam("ValidCards");
|
||||||
}
|
}
|
||||||
|
|
||||||
final Player opp = ai.getWeakestOpponent();
|
final Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
|
||||||
|
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
if (tgt != null) {
|
if (tgt != null) {
|
||||||
|
|||||||
@@ -56,7 +56,6 @@ public class UnattachAllAi extends SpellAbilityAi {
|
|||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
final Card card = sa.getHostCard();
|
final Card card = sa.getHostCard();
|
||||||
final Player opp = ai.getWeakestOpponent();
|
|
||||||
// Check if there are any valid targets
|
// Check if there are any valid targets
|
||||||
List<GameObject> targets = new ArrayList<>();
|
List<GameObject> targets = new ArrayList<>();
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
@@ -66,8 +65,8 @@ public class UnattachAllAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
if (!mandatory && card.isEquipment() && !targets.isEmpty()) {
|
if (!mandatory && card.isEquipment() && !targets.isEmpty()) {
|
||||||
Card newTarget = (Card) targets.get(0);
|
Card newTarget = (Card) targets.get(0);
|
||||||
//don't equip human creatures
|
//don't equip opponent creatures
|
||||||
if (newTarget.getController().equals(opp)) {
|
if (!newTarget.getController().equals(ai)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import java.util.Map;
|
|||||||
import com.google.common.base.Predicate;
|
import com.google.common.base.Predicate;
|
||||||
import com.google.common.base.Predicates;
|
import com.google.common.base.Predicates;
|
||||||
|
|
||||||
|
import forge.ai.AiAttackController;
|
||||||
import forge.ai.ComputerUtil;
|
import forge.ai.ComputerUtil;
|
||||||
import forge.ai.ComputerUtilAbility;
|
import forge.ai.ComputerUtilAbility;
|
||||||
import forge.ai.ComputerUtilCard;
|
import forge.ai.ComputerUtilCard;
|
||||||
@@ -65,7 +66,7 @@ public class UntapAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
if (!sa.usesTargeting()) {
|
if (!sa.usesTargeting()) {
|
||||||
final List<Card> pDefined = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa);
|
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 {
|
} else {
|
||||||
return untapPrefTargeting(ai, sa, false);
|
return untapPrefTargeting(ai, sa, false);
|
||||||
}
|
}
|
||||||
@@ -81,9 +82,8 @@ public class UntapAi extends SpellAbilityAi {
|
|||||||
return false;
|
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);
|
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 {
|
} else {
|
||||||
if (untapPrefTargeting(ai, sa, mandatory)) {
|
if (untapPrefTargeting(ai, sa, mandatory)) {
|
||||||
return true;
|
return true;
|
||||||
@@ -130,7 +130,8 @@ public class UntapAi extends SpellAbilityAi {
|
|||||||
Player targetController = ai;
|
Player targetController = ai;
|
||||||
|
|
||||||
if (sa.isCurse()) {
|
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);
|
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);
|
CardCollection untapList = targetUntapped ? list : CardLists.filter(list, Presets.TAPPED);
|
||||||
// filter out enchantments and planeswalkers, their tapped state doesn't
|
// filter out enchantments and planeswalkers, their tapped state doesn't matter.
|
||||||
// matter.
|
|
||||||
final String[] tappablePermanents = {"Creature", "Land", "Artifact"};
|
final String[] tappablePermanents = {"Creature", "Land", "Artifact"};
|
||||||
untapList = CardLists.getValidCards(untapList, tappablePermanents, source.getController(), source, sa);
|
untapList = CardLists.getValidCards(untapList, tappablePermanents, source.getController(), source, sa);
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import forge.game.card.CardLists;
|
|||||||
import forge.game.card.CardPredicates;
|
import forge.game.card.CardPredicates;
|
||||||
import forge.game.phase.PhaseType;
|
import forge.game.phase.PhaseType;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
|
import forge.game.player.PlayerCollection;
|
||||||
import forge.game.spellability.AbilitySub;
|
import forge.game.spellability.AbilitySub;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
@@ -29,6 +30,10 @@ public class UntapAllAi extends SpellAbilityAi {
|
|||||||
valid = sa.getParam("ValidCards");
|
valid = sa.getParam("ValidCards");
|
||||||
}
|
}
|
||||||
list = CardLists.getValidCards(list, valid.split(","), source.getController(), source, sa);
|
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 !list.isEmpty();
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -88,7 +88,6 @@ public class GameCopier {
|
|||||||
newPlayer.setCounters(Maps.newHashMap(origPlayer.getCounters()));
|
newPlayer.setCounters(Maps.newHashMap(origPlayer.getCounters()));
|
||||||
newPlayer.setLifeLostLastTurn(origPlayer.getLifeLostLastTurn());
|
newPlayer.setLifeLostLastTurn(origPlayer.getLifeLostLastTurn());
|
||||||
newPlayer.setLifeLostThisTurn(origPlayer.getLifeLostThisTurn());
|
newPlayer.setLifeLostThisTurn(origPlayer.getLifeLostThisTurn());
|
||||||
newPlayer.setPreventNextDamage(origPlayer.getPreventNextDamage());
|
|
||||||
newPlayer.getManaPool().add(origPlayer.getManaPool());
|
newPlayer.getManaPool().add(origPlayer.getManaPool());
|
||||||
newPlayer.setCommanders(origPlayer.getCommanders()); // will be fixed up below
|
newPlayer.setCommanders(origPlayer.getCommanders()); // will be fixed up below
|
||||||
playerMap.put(origPlayer, newPlayer);
|
playerMap.put(origPlayer, newPlayer);
|
||||||
@@ -124,6 +123,7 @@ public class GameCopier {
|
|||||||
System.err.println(c + " Remembered: " + o + "/" + o.getClass());
|
System.err.println(c + " Remembered: " + o + "/" + o.getClass());
|
||||||
c.addRemembered(o);
|
c.addRemembered(o);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (SpellAbility sa : c.getSpellAbilities()) {
|
for (SpellAbility sa : c.getSpellAbilities()) {
|
||||||
@@ -334,16 +334,15 @@ public class GameCopier {
|
|||||||
if (c.getChosenPlayer() != null) {
|
if (c.getChosenPlayer() != null) {
|
||||||
newCard.setChosenPlayer(playerMap.get(c.getChosenPlayer()));
|
newCard.setChosenPlayer(playerMap.get(c.getChosenPlayer()));
|
||||||
}
|
}
|
||||||
|
if (!c.getChosenType().isEmpty()) {
|
||||||
SpellAbility first = c.getFirstSpellAbility();
|
newCard.setChosenType(c.getChosenType());
|
||||||
|
|
||||||
if (first != null && first.hasChosenColor()) {
|
|
||||||
newCard.setChosenColors(Lists.newArrayList(first.getChosenColors()), newCard.getFirstSpellAbility());
|
|
||||||
}
|
}
|
||||||
if (first != null && first.hasChosenType()) {
|
if (!c.getChosenType2().isEmpty()) {
|
||||||
newCard.setChosenColors(Lists.newArrayList(first.getChosenType()), newCard.getFirstSpellAbility());
|
newCard.setChosenType2(c.getChosenType2());
|
||||||
|
}
|
||||||
|
if (c.getChosenColors() != null) {
|
||||||
|
newCard.setChosenColors(Lists.newArrayList(c.getChosenColors()));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!c.getNamedCard().isEmpty()) {
|
if (!c.getNamedCard().isEmpty()) {
|
||||||
newCard.setNamedCard(c.getNamedCard());
|
newCard.setNamedCard(c.getNamedCard());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import forge.ai.ComputerUtilAbility;
|
|||||||
import forge.ai.ComputerUtilCost;
|
import forge.ai.ComputerUtilCost;
|
||||||
import forge.ai.ability.ChangeZoneAi;
|
import forge.ai.ability.ChangeZoneAi;
|
||||||
import forge.ai.ability.ExploreAi;
|
import forge.ai.ability.ExploreAi;
|
||||||
|
import forge.ai.ability.LearnAi;
|
||||||
import forge.ai.simulation.GameStateEvaluator.Score;
|
import forge.ai.simulation.GameStateEvaluator.Score;
|
||||||
import forge.game.Game;
|
import forge.game.Game;
|
||||||
import forge.game.ability.ApiType;
|
import forge.game.ability.ApiType;
|
||||||
@@ -423,6 +424,8 @@ public class SpellAbilityPicker {
|
|||||||
}
|
}
|
||||||
if (sa.getApi() == ApiType.Explore) {
|
if (sa.getApi() == ApiType.Explore) {
|
||||||
return ExploreAi.shouldPutInGraveyard(fetchList, decider);
|
return ExploreAi.shouldPutInGraveyard(fetchList, decider);
|
||||||
|
} else if (sa.getApi() == ApiType.Learn) {
|
||||||
|
return LearnAi.chooseCardToLearn(fetchList, decider, sa);
|
||||||
} else {
|
} else {
|
||||||
return ChangeZoneAi.chooseCardToHiddenOriginChangeZone(destination, origin, sa, fetchList, player2, decider);
|
return ChangeZoneAi.chooseCardToHiddenOriginChangeZone(destination, origin, sa, fetchList, player2, decider);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>forge</artifactId>
|
<artifactId>forge</artifactId>
|
||||||
<groupId>forge</groupId>
|
<groupId>forge</groupId>
|
||||||
<version>1.6.40-SNAPSHOT</version>
|
<version>1.6.41</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<artifactId>forge-core</artifactId>
|
<artifactId>forge-core</artifactId>
|
||||||
|
|||||||
@@ -121,6 +121,9 @@ public final class ImageKeys {
|
|||||||
// if there's a 1st art variant try without it for .fullborder images
|
// if there's a 1st art variant try without it for .fullborder images
|
||||||
file = findFile(dir, TextUtil.fastReplace(fullborderFile, "1.fullborder", ".fullborder"));
|
file = findFile(dir, TextUtil.fastReplace(fullborderFile, "1.fullborder", ".fullborder"));
|
||||||
if (file != null) { return file; }
|
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
|
// if there's an art variant try without it for .full images
|
||||||
file = findFile(dir, filename.replaceAll("[0-9].full",".full"));
|
file = findFile(dir, filename.replaceAll("[0-9].full",".full"));
|
||||||
if (file != null) { return file; }
|
if (file != null) { return file; }
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package forge;
|
|||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -79,16 +78,30 @@ public class StaticData {
|
|||||||
this.customEditions = new CardEdition.Collection(new CardEdition.Reader(new File(customEditionsFolder)));
|
this.customEditions = new CardEdition.Collection(new CardEdition.Reader(new File(customEditionsFolder)));
|
||||||
this.prefferedArt = prefferedArt;
|
this.prefferedArt = prefferedArt;
|
||||||
lastInstance = this;
|
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> regularCards = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
|
||||||
final Map<String, CardRules> variantsCards = 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);
|
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()) {
|
for (CardRules card : cardReader.loadCards()) {
|
||||||
if (null == card) continue;
|
if (null == card) continue;
|
||||||
|
|
||||||
final String cardName = card.getName();
|
final String cardName = card.getName();
|
||||||
|
|
||||||
|
if (!loadNonLegalCards && !card.getType().isBasicLand() && funnyCards.contains(cardName))
|
||||||
|
filtered.add(cardName);
|
||||||
|
|
||||||
if (card.isVariant()) {
|
if (card.isVariant()) {
|
||||||
variantsCards.put(cardName, card);
|
variantsCards.put(cardName, card);
|
||||||
} else {
|
} else {
|
||||||
@@ -104,15 +117,18 @@ public class StaticData {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!filtered.isEmpty()) {
|
||||||
|
Collections.sort(filtered);
|
||||||
|
}
|
||||||
|
|
||||||
commonCards = new CardDb(regularCards, editions);
|
commonCards = new CardDb(regularCards, editions, filtered);
|
||||||
variantCards = new CardDb(variantsCards, editions);
|
variantCards = new CardDb(variantsCards, editions, filtered);
|
||||||
customCards = new CardDb(customizedCards, customEditions);
|
customCards = new CardDb(customizedCards, customEditions, filtered);
|
||||||
|
|
||||||
//must initialize after establish field values for the sake of card image logic
|
//must initialize after establish field values for the sake of card image logic
|
||||||
commonCards.initialize(false, false, enableUnknownCards, loadNonLegalCards);
|
commonCards.initialize(false, false, enableUnknownCards);
|
||||||
variantCards.initialize(false, false, enableUnknownCards, loadNonLegalCards);
|
variantCards.initialize(false, false, enableUnknownCards);
|
||||||
customCards.initialize(false, false, enableUnknownCards, loadNonLegalCards);
|
customCards.initialize(false, false, enableUnknownCards);
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -125,14 +141,6 @@ public class StaticData {
|
|||||||
}
|
}
|
||||||
allTokens = new TokenDb(tokens, editions);
|
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() {
|
public static StaticData instance() {
|
||||||
@@ -143,6 +151,11 @@ public class StaticData {
|
|||||||
return this.editions;
|
return this.editions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public final CardEdition.Collection getCustomEditions(){
|
||||||
|
return this.customEditions;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private List<CardEdition> sortedEditions;
|
private List<CardEdition> sortedEditions;
|
||||||
public final List<CardEdition> getSortedEditions() {
|
public final List<CardEdition> getSortedEditions() {
|
||||||
if (sortedEditions == null) {
|
if (sortedEditions == null) {
|
||||||
@@ -150,12 +163,24 @@ public class StaticData {
|
|||||||
for (CardEdition set : editions) {
|
for (CardEdition set : editions) {
|
||||||
sortedEditions.add(set);
|
sortedEditions.add(set);
|
||||||
}
|
}
|
||||||
|
if (customEditions.size() > 0){
|
||||||
|
for (CardEdition set : customEditions) {
|
||||||
|
sortedEditions.add(set);
|
||||||
|
}
|
||||||
|
}
|
||||||
Collections.sort(sortedEditions);
|
Collections.sort(sortedEditions);
|
||||||
Collections.reverse(sortedEditions); //put newer sets at the top
|
Collections.reverse(sortedEditions); //put newer sets at the top
|
||||||
}
|
}
|
||||||
return sortedEditions;
|
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) {
|
public PaperCard getOrLoadCommonCard(String cardName, String setCode, int artIndex, boolean foil) {
|
||||||
PaperCard card = commonCards.getCard(cardName, setCode, artIndex);
|
PaperCard card = commonCards.getCard(cardName, setCode, artIndex);
|
||||||
boolean isCustom = false;
|
boolean isCustom = false;
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ package forge.card;
|
|||||||
*/
|
*/
|
||||||
public class CardAiHints {
|
public class CardAiHints {
|
||||||
|
|
||||||
|
|
||||||
private final boolean isRemovedFromAIDecks;
|
private final boolean isRemovedFromAIDecks;
|
||||||
private final boolean isRemovedFromRandomDecks;
|
private final boolean isRemovedFromRandomDecks;
|
||||||
private final boolean isRemovedFromNonCommanderDecks;
|
private final boolean isRemovedFromNonCommanderDecks;
|
||||||
@@ -15,7 +14,6 @@ public class CardAiHints {
|
|||||||
private final DeckHints deckNeeds;
|
private final DeckHints deckNeeds;
|
||||||
private final DeckHints deckHas;
|
private final DeckHints deckHas;
|
||||||
|
|
||||||
|
|
||||||
public CardAiHints(boolean remAi, boolean remRandom, boolean remUnlessCommander, DeckHints dh, DeckHints dn, DeckHints has) {
|
public CardAiHints(boolean remAi, boolean remRandom, boolean remUnlessCommander, DeckHints dh, DeckHints dn, DeckHints has) {
|
||||||
isRemovedFromAIDecks = remAi;
|
isRemovedFromAIDecks = remAi;
|
||||||
isRemovedFromRandomDecks = remRandom;
|
isRemovedFromRandomDecks = remRandom;
|
||||||
@@ -90,5 +88,4 @@ public class CardAiHints {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ import java.util.Set;
|
|||||||
import java.util.TreeMap;
|
import java.util.TreeMap;
|
||||||
|
|
||||||
import forge.StaticData;
|
import forge.StaticData;
|
||||||
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.apache.commons.lang3.tuple.Pair;
|
import org.apache.commons.lang3.tuple.Pair;
|
||||||
|
|
||||||
@@ -55,6 +56,8 @@ import forge.util.TextUtil;
|
|||||||
public final class CardDb implements ICardDatabase, IDeckGenPool {
|
public final class CardDb implements ICardDatabase, IDeckGenPool {
|
||||||
public final static String foilSuffix = "+";
|
public final static String foilSuffix = "+";
|
||||||
public final static char NameSetSeparator = '|';
|
public final static char NameSetSeparator = '|';
|
||||||
|
private final String exlcudedCardName = "Concentrate";
|
||||||
|
private final String exlcudedCardSet = "DS0";
|
||||||
|
|
||||||
// need this to obtain cardReference by name+set+artindex
|
// 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());
|
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, String> alternateName = Maps.newTreeMap(String.CASE_INSENSITIVE_ORDER);
|
||||||
private final Map<String, Integer> artIds = new HashMap<>();
|
private final Map<String, Integer> artIds = new HashMap<>();
|
||||||
|
|
||||||
private final Collection<PaperCard> roAllCards = Collections.unmodifiableCollection(allCardsByName.values());
|
|
||||||
private final CardEdition.Collection editions;
|
private final CardEdition.Collection editions;
|
||||||
|
private List<String> filtered;
|
||||||
|
|
||||||
public enum SetPreference {
|
public enum SetPreference {
|
||||||
Latest(false),
|
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.rulesByName = rules;
|
||||||
this.editions = editions0;
|
this.editions = editions0;
|
||||||
|
|
||||||
// create faces list from rules
|
// create faces list from rules
|
||||||
for (final CardRules rule : rules.values() ) {
|
for (final CardRules rule : rules.values() ) {
|
||||||
|
if (filteredCards.contains(rule.getName()) && !exlcudedCardName.equalsIgnoreCase(rule.getName()))
|
||||||
|
continue;
|
||||||
final ICardFace main = rule.getMainPart();
|
final ICardFace main = rule.getMainPart();
|
||||||
facesByName.put(main.getName(), main);
|
facesByName.put(main.getName(), main);
|
||||||
if (main.getAltName() != null) {
|
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) {
|
private void addSetCard(CardEdition e, CardInSet cis, CardRules cr) {
|
||||||
int artIdx = 1;
|
int artIdx = 1;
|
||||||
String key = e.getCode() + "/" + cis.name;
|
String key = e.getCode() + "/" + cis.name;
|
||||||
@@ -182,33 +192,24 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
|||||||
reIndex();
|
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<>();
|
Set<String> allMissingCards = new LinkedHashSet<>();
|
||||||
List<String> missingCards = new ArrayList<>();
|
List<String> missingCards = new ArrayList<>();
|
||||||
CardEdition upcomingSet = null;
|
CardEdition upcomingSet = null;
|
||||||
Date today = new Date();
|
Date today = new Date();
|
||||||
List<String> skippedCardName = new ArrayList<>();
|
|
||||||
|
|
||||||
for (CardEdition e : editions.getOrderedEditions()) {
|
for (CardEdition e : editions.getOrderedEditions()) {
|
||||||
boolean coreOrExpSet = e.getType() == CardEdition.Type.CORE || e.getType() == CardEdition.Type.EXPANSION;
|
boolean coreOrExpSet = e.getType() == CardEdition.Type.CORE || e.getType() == CardEdition.Type.EXPANSION;
|
||||||
boolean isCoreExpSet = coreOrExpSet || e.getType() == CardEdition.Type.REPRINT;
|
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) {
|
if (logMissingPerEdition && isCoreExpSet) {
|
||||||
System.out.print(e.getName() + " (" + e.getAllCardsInSet().size() + " cards)");
|
System.out.print(e.getName() + " (" + e.getAllCardsInSet().size() + " cards)");
|
||||||
}
|
}
|
||||||
if (coreOrExpSet && e.getDate().after(today) && upcomingSet == null) {
|
if (coreOrExpSet && e.getDate().after(today) && upcomingSet == null) {
|
||||||
if (skip)
|
|
||||||
upcomingSet = e;
|
upcomingSet = e;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (CardEdition.CardInSet cis : e.getAllCardsInSet()) {
|
for (CardEdition.CardInSet cis : e.getAllCardsInSet()) {
|
||||||
CardRules cr = rulesByName.get(cis.name);
|
CardRules cr = rulesByName.get(cis.name);
|
||||||
if (cr != null && !cr.getType().isBasicLand() && skip) {
|
|
||||||
skippedCardName.add(cis.name);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cr != null) {
|
if (cr != null) {
|
||||||
addSetCard(e, cis, cr);
|
addSetCard(e, cis, cr);
|
||||||
}
|
}
|
||||||
@@ -244,7 +245,7 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
|||||||
if (!contains(cr.getName())) {
|
if (!contains(cr.getName())) {
|
||||||
if (upcomingSet != null) {
|
if (upcomingSet != null) {
|
||||||
addCard(new PaperCard(cr, upcomingSet.getCode(), CardRarity.Unknown, 1));
|
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. ");
|
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));
|
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) {
|
public void addCard(PaperCard paperCard) {
|
||||||
|
if (excludeCard(paperCard.getName(), paperCard.getEdition()))
|
||||||
|
return;
|
||||||
|
|
||||||
allCardsByName.put(paperCard.getName(), paperCard);
|
allCardsByName.put(paperCard.getName(), paperCard);
|
||||||
|
|
||||||
if (paperCard.getRules().getSplitType() == CardSplitType.None) { return; }
|
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);
|
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() {
|
private void reIndex() {
|
||||||
uniqueCardsByName.clear();
|
uniqueCardsByName.clear();
|
||||||
for (Entry<String, Collection<PaperCard>> kv : allCardsByName.asMap().entrySet()) {
|
for (Entry<String, Collection<PaperCard>> kv : getAllCardsByName().asMap().entrySet()) {
|
||||||
uniqueCardsByName.put(kv.getKey(), getFirstWithImage(kv.getValue()));
|
PaperCard pc = getFirstWithImage(kv.getValue());
|
||||||
|
uniqueCardsByName.put(kv.getKey(), pc);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -518,6 +533,8 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
|||||||
@Override
|
@Override
|
||||||
public int getPrintCount(String cardName, String edition) {
|
public int getPrintCount(String cardName, String edition) {
|
||||||
int cnt = 0;
|
int cnt = 0;
|
||||||
|
if (edition == null || cardName == null)
|
||||||
|
return cnt;
|
||||||
for (PaperCard pc : getAllCards(cardName)) {
|
for (PaperCard pc : getAllCards(cardName)) {
|
||||||
if (pc.getEdition().equals(edition)) {
|
if (pc.getEdition().equals(edition)) {
|
||||||
cnt++;
|
cnt++;
|
||||||
@@ -529,6 +546,8 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
|||||||
@Override
|
@Override
|
||||||
public int getMaxPrintCount(String cardName) {
|
public int getMaxPrintCount(String cardName) {
|
||||||
int max = -1;
|
int max = -1;
|
||||||
|
if (cardName == null)
|
||||||
|
return max;
|
||||||
for (PaperCard pc : getAllCards(cardName)) {
|
for (PaperCard pc : getAllCards(cardName)) {
|
||||||
if (max < pc.getArtIndex()) {
|
if (max < pc.getArtIndex()) {
|
||||||
max = pc.getArtIndex();
|
max = pc.getArtIndex();
|
||||||
@@ -540,6 +559,8 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
|||||||
@Override
|
@Override
|
||||||
public int getArtCount(String cardName, String setName) {
|
public int getArtCount(String cardName, String setName) {
|
||||||
int cnt = 0;
|
int cnt = 0;
|
||||||
|
if (cardName == null || setName == null)
|
||||||
|
return cnt;
|
||||||
|
|
||||||
Collection<PaperCard> cards = getAllCards(cardName);
|
Collection<PaperCard> cards = getAllCards(cardName);
|
||||||
if (null == cards) {
|
if (null == cards) {
|
||||||
@@ -561,6 +582,17 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
|||||||
return uniqueCardsByName.values();
|
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) {
|
public PaperCard getUniqueByName(final String name) {
|
||||||
return uniqueCardsByName.get(getName(name));
|
return uniqueCardsByName.get(getName(name));
|
||||||
}
|
}
|
||||||
@@ -575,11 +607,20 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Collection<PaperCard> getAllCards() {
|
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() {
|
public Collection<PaperCard> getAllNonPromoCards() {
|
||||||
return Lists.newArrayList(Iterables.filter(this.roAllCards, new Predicate<PaperCard>() {
|
return Lists.newArrayList(Iterables.filter(getAllCards(), new Predicate<PaperCard>() {
|
||||||
@Override
|
@Override
|
||||||
public boolean apply(final PaperCard paperCard) {
|
public boolean apply(final PaperCard paperCard) {
|
||||||
CardEdition edition = null;
|
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) {
|
public String getName(final String cardName) {
|
||||||
if (alternateName.containsKey(cardName)) {
|
if (alternateName.containsKey(cardName)) {
|
||||||
return alternateName.get(cardName);
|
return alternateName.get(cardName);
|
||||||
@@ -602,13 +660,27 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<PaperCard> getAllCards(String cardName) {
|
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 */
|
/** Returns a modifiable list of cards matching the given predicate */
|
||||||
@Override
|
@Override
|
||||||
public List<PaperCard> getAllCards(Predicate<PaperCard> predicate) {
|
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?
|
// Do I want a foiled version of these cards?
|
||||||
@@ -630,12 +702,12 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean contains(String name) {
|
public boolean contains(String name) {
|
||||||
return allCardsByName.containsKey(getName(name));
|
return getAllCardsByName().containsKey(getName(name));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Iterator<PaperCard> iterator() {
|
public Iterator<PaperCard> iterator() {
|
||||||
return this.roAllCards.iterator();
|
return getAllCards().iterator();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Predicate<? super PaperCard> wasPrintedInSets(List<String> setCodes) {
|
public Predicate<? super PaperCard> wasPrintedInSets(List<String> setCodes) {
|
||||||
@@ -700,7 +772,6 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public PaperCard createUnsupportedCard(String cardName) {
|
public PaperCard createUnsupportedCard(String cardName) {
|
||||||
|
|
||||||
CardRequest request = CardRequest.fromString(cardName);
|
CardRequest request = CardRequest.fromString(cardName);
|
||||||
CardEdition cardEdition = CardEdition.UNKNOWN;
|
CardEdition cardEdition = CardEdition.UNKNOWN;
|
||||||
CardRarity cardRarity = CardRarity.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);
|
return new PaperCard(CardRules.getUnsupportedCardNamed(request.cardName), cardEdition.getCode(), cardRarity, 1);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private final Editor editor = new Editor();
|
private final Editor editor = new Editor();
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ package forge.card;
|
|||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FilenameFilter;
|
import java.io.FilenameFilter;
|
||||||
import java.text.ParseException;
|
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
@@ -71,7 +70,9 @@ import forge.util.storage.StorageReaderFolder;
|
|||||||
* @author Forge
|
* @author Forge
|
||||||
* @version $Id: CardSet.java 9708 2011-08-09 19:34:12Z jendave $
|
* @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 {
|
public enum Type {
|
||||||
UNKNOWN,
|
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
|
// 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
|
// commonly used printsheets with collector number
|
||||||
public enum EditionSectionWithCollectorNumbers {
|
public enum EditionSectionWithCollectorNumbers {
|
||||||
CARDS("cards"),
|
CARDS("cards"),
|
||||||
|
SPECIAL_SLOT("special slot"), //to help with convoluted boosters
|
||||||
PRECON_PRODUCT("precon product"),
|
PRECON_PRODUCT("precon product"),
|
||||||
BORDERLESS("borderless"),
|
BORDERLESS("borderless"),
|
||||||
SHOWCASE("showcase"),
|
SHOWCASE("showcase"),
|
||||||
EXTENDED_ART("extended art"),
|
EXTENDED_ART("extended art"),
|
||||||
ALTERNATE_ART("alternate art"),
|
ALTERNATE_ART("alternate art"),
|
||||||
|
ALTERNATE_FRAME("alternate frame"),
|
||||||
BUY_A_BOX("buy a box"),
|
BUY_A_BOX("buy a box"),
|
||||||
PROMO("promo");
|
PROMO("promo"),
|
||||||
|
BUNDLE("bundle"),
|
||||||
|
BOX_TOPPER("box topper");
|
||||||
|
|
||||||
private final String name;
|
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 CardRarity rarity;
|
||||||
public final String collectorNumber;
|
public final String collectorNumber;
|
||||||
public final String name;
|
public final String name;
|
||||||
@@ -171,6 +176,56 @@ public final class CardEdition implements Comparable<CardEdition> { // immutable
|
|||||||
sb.append(name);
|
sb.append(name);
|
||||||
return sb.toString();
|
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");
|
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 code;
|
||||||
private String code2;
|
private String code2;
|
||||||
private String mciCode;
|
private String mciCode;
|
||||||
|
private String scryfallCode;
|
||||||
|
private String cardsLanguage;
|
||||||
private Type type;
|
private Type type;
|
||||||
private String name;
|
private String name;
|
||||||
private String alias = null;
|
private String alias = null;
|
||||||
@@ -205,6 +262,7 @@ public final class CardEdition implements Comparable<CardEdition> { // immutable
|
|||||||
private String[] chaosDraftThemes = new String[0];
|
private String[] chaosDraftThemes = new String[0];
|
||||||
|
|
||||||
private final ListMultimap<String, CardInSet> cardMap;
|
private final ListMultimap<String, CardInSet> cardMap;
|
||||||
|
private final List<CardInSet> cardsInSet;
|
||||||
private final Map<String, Integer> tokenNormalized;
|
private final Map<String, Integer> tokenNormalized;
|
||||||
// custom print sheets that will be loaded lazily
|
// custom print sheets that will be loaded lazily
|
||||||
private final Map<String, List<String>> customPrintSheetsToParse;
|
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) {
|
private CardEdition(ListMultimap<String, CardInSet> cardMap, Map<String, Integer> tokens, Map<String, List<String>> customPrintSheetsToParse) {
|
||||||
this.cardMap = cardMap;
|
this.cardMap = cardMap;
|
||||||
|
this.cardsInSet = new ArrayList<>(cardMap.values());
|
||||||
|
Collections.sort(cardsInSet);
|
||||||
this.tokenNormalized = tokens;
|
this.tokenNormalized = tokens;
|
||||||
this.customPrintSheetsToParse = customPrintSheetsToParse;
|
this.customPrintSheetsToParse = customPrintSheetsToParse;
|
||||||
}
|
}
|
||||||
|
|
||||||
private CardEdition(CardInSet[] cards, Map<String, Integer> tokens) {
|
private CardEdition(CardInSet[] cards, Map<String, Integer> tokens) {
|
||||||
|
List<CardInSet> cardsList = Arrays.asList(cards);
|
||||||
this.cardMap = ArrayListMultimap.create();
|
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.tokenNormalized = tokens;
|
||||||
this.customPrintSheetsToParse = new HashMap<>();
|
this.customPrintSheetsToParse = new HashMap<>();
|
||||||
}
|
}
|
||||||
@@ -255,7 +318,7 @@ public final class CardEdition implements Comparable<CardEdition> { // immutable
|
|||||||
date = date + "-01";
|
date = date + "-01";
|
||||||
try {
|
try {
|
||||||
return formatter.parse(date);
|
return formatter.parse(date);
|
||||||
} catch (ParseException e) {
|
} catch (Exception e) {
|
||||||
return new Date();
|
return new Date();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -264,6 +327,8 @@ public final class CardEdition implements Comparable<CardEdition> { // immutable
|
|||||||
public String getCode() { return code; }
|
public String getCode() { return code; }
|
||||||
public String getCode2() { return code2; }
|
public String getCode2() { return code2; }
|
||||||
public String getMciCode() { return mciCode; }
|
public String getMciCode() { return mciCode; }
|
||||||
|
public String getScryfallCode() { return scryfallCode.toLowerCase(); }
|
||||||
|
public String getCardsLangCode() { return cardsLanguage.toLowerCase(); }
|
||||||
public Type getType() { return type; }
|
public Type getType() { return type; }
|
||||||
public String getName() { return name; }
|
public String getName() { return name; }
|
||||||
public String getAlias() { return alias; }
|
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> getCards() { return cardMap.get("cards"); }
|
||||||
public List<CardInSet> getAllCardsInSet() {
|
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
|
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) {
|
if (o == null) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
return date.compareTo(o.date);
|
int dateComp = date.compareTo(o.date);
|
||||||
|
if (0 != dateComp)
|
||||||
|
return dateComp;
|
||||||
|
return name.compareTo(o.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -339,7 +407,7 @@ public final class CardEdition implements Comparable<CardEdition> { // immutable
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean isLargeSet() {
|
public boolean isLargeSet() {
|
||||||
return getAllCardsInSet().size() > 200 && !smallSetOverride;
|
return this.cardsInSet.size() > 200 && !smallSetOverride;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getCntBoosterPictures() {
|
public int getCntBoosterPictures() {
|
||||||
@@ -413,7 +481,8 @@ public final class CardEdition implements Comparable<CardEdition> { // immutable
|
|||||||
* rarity - grouping #4
|
* rarity - grouping #4
|
||||||
* name - grouping #5
|
* 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();
|
ListMultimap<String, CardInSet> cardMap = ArrayListMultimap.create();
|
||||||
@@ -479,6 +548,14 @@ public final class CardEdition implements Comparable<CardEdition> { // immutable
|
|||||||
if (res.mciCode == null) {
|
if (res.mciCode == null) {
|
||||||
res.mciCode = res.code2.toLowerCase();
|
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);
|
res.boosterArts = section.getInt("BoosterCovers", 1);
|
||||||
String boosterDesc = section.get("Booster");
|
String boosterDesc = section.get("Booster");
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
*/
|
*/
|
||||||
package forge.card;
|
package forge.card;
|
||||||
|
|
||||||
import java.util.StringTokenizer;
|
import java.util.*;
|
||||||
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
@@ -28,6 +28,9 @@ import forge.card.mana.ManaCost;
|
|||||||
import forge.card.mana.ManaCostShard;
|
import forge.card.mana.ManaCostShard;
|
||||||
import forge.util.TextUtil;
|
import forge.util.TextUtil;
|
||||||
|
|
||||||
|
import static forge.card.MagicColor.Constant.*;
|
||||||
|
import static org.apache.commons.lang3.StringUtils.containsIgnoreCase;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A collection of methods containing full
|
* A collection of methods containing full
|
||||||
* meta and gameplay properties of a card.
|
* meta and gameplay properties of a card.
|
||||||
@@ -42,6 +45,7 @@ public final class CardRules implements ICardCharacteristics {
|
|||||||
private ICardFace otherPart;
|
private ICardFace otherPart;
|
||||||
private CardAiHints aiHints;
|
private CardAiHints aiHints;
|
||||||
private ColorSet colorIdentity;
|
private ColorSet colorIdentity;
|
||||||
|
private ColorSet deckbuildingColors;
|
||||||
private String meldWith;
|
private String meldWith;
|
||||||
private String partnerWith;
|
private String partnerWith;
|
||||||
|
|
||||||
@@ -80,6 +84,12 @@ public final class CardRules implements ICardCharacteristics {
|
|||||||
boolean isReminder = false;
|
boolean isReminder = false;
|
||||||
boolean isSymbol = false;
|
boolean isSymbol = false;
|
||||||
String oracleText = face.getOracleText();
|
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();
|
int len = oracleText.length();
|
||||||
for(int i = 0; i < len; i++) {
|
for(int i = 0; i < len; i++) {
|
||||||
char c = oracleText.charAt(i); // This is to avoid needless allocations performed by toCharArray()
|
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);
|
this.removedFromNonCommanderDecks = "NonCommander".equalsIgnoreCase(value);
|
||||||
}
|
}
|
||||||
} else if ("AlternateMode".equals(key)) {
|
} else if ("AlternateMode".equals(key)) {
|
||||||
//System.out.println(faces[curFace].getName());
|
|
||||||
this.altMode = CardSplitType.smartValueOf(value);
|
this.altMode = CardSplitType.smartValueOf(value);
|
||||||
} else if ("ALTERNATE".equals(key)) {
|
} else if ("ALTERNATE".equals(key)) {
|
||||||
this.curFace = 1;
|
this.curFace = 1;
|
||||||
@@ -535,7 +544,6 @@ public final class CardRules implements ICardCharacteristics {
|
|||||||
@Override
|
@Override
|
||||||
public final ManaCostShard next() {
|
public final ManaCostShard next() {
|
||||||
final String unparsed = st.nextToken();
|
final String unparsed = st.nextToken();
|
||||||
// System.out.println(unparsed);
|
|
||||||
if (StringUtils.isNumeric(unparsed)) {
|
if (StringUtils.isNumeric(unparsed)) {
|
||||||
this.genericCost += Integer.parseInt(unparsed);
|
this.genericCost += Integer.parseInt(unparsed);
|
||||||
return null;
|
return null;
|
||||||
@@ -551,7 +559,7 @@ public final class CardRules implements ICardCharacteristics {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void remove() {
|
public void remove() {
|
||||||
} // unsuported
|
} // unsupported
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -581,4 +589,25 @@ public final class CardRules implements ICardCharacteristics {
|
|||||||
}
|
}
|
||||||
return null;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -91,6 +91,7 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
|
|||||||
public enum Supertype {
|
public enum Supertype {
|
||||||
Basic,
|
Basic,
|
||||||
Elite,
|
Elite,
|
||||||
|
Host,
|
||||||
Legendary,
|
Legendary,
|
||||||
Snow,
|
Snow,
|
||||||
Ongoing,
|
Ongoing,
|
||||||
|
|||||||
@@ -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> 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 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>()
|
public static final ImmutableMap<String, String> ANY_COLOR_CONVERSION = new ImmutableMap.Builder<String, String>()
|
||||||
.put("ManaColorConversion", "Additive")
|
.put("ManaConversion", "AnyType->AnyColor")
|
||||||
.put("WhiteConversion", "Color")
|
|
||||||
.put("BlueConversion", "Color")
|
|
||||||
.put("BlackConversion", "Color")
|
|
||||||
.put("RedConversion", "Color")
|
|
||||||
.put("GreenConversion", "Color")
|
|
||||||
.put("ColorlessConversion", "Color")
|
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
public static final ImmutableMap<String, String> ANY_TYPE_CONVERSION = new ImmutableMap.Builder<String, String>()
|
public static final ImmutableMap<String, String> ANY_TYPE_CONVERSION = new ImmutableMap.Builder<String, String>()
|
||||||
.put("ManaColorConversion", "Additive")
|
.put("ManaConversion", "AnyType->AnyType")
|
||||||
.put("WhiteConversion", "Type")
|
|
||||||
.put("BlueConversion", "Type")
|
|
||||||
.put("BlackConversion", "Type")
|
|
||||||
.put("RedConversion", "Type")
|
|
||||||
.put("GreenConversion", "Type")
|
|
||||||
.put("ColorlessConversion", "Type")
|
|
||||||
.build();
|
.build();
|
||||||
/**
|
/**
|
||||||
* Private constructor to prevent instantiation.
|
* Private constructor to prevent instantiation.
|
||||||
|
|||||||
@@ -31,7 +31,6 @@ public class PrintSheet {
|
|||||||
|
|
||||||
for(CardEdition edition : editions) {
|
for(CardEdition edition : editions) {
|
||||||
for(PrintSheet ps : edition.getPrintSheetsBySection()) {
|
for(PrintSheet ps : edition.getPrintSheetsBySection()) {
|
||||||
System.out.println(ps.name);
|
|
||||||
sheets.add(ps.name, ps);
|
sheets.add(ps.name, ps);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user