mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-19 20:28:00 +00:00
Compare commits
2122 Commits
forge-1.5.
...
forge-1.5.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
18dccd6a98 | ||
|
|
d1767cb425 | ||
|
|
5afea2159f | ||
|
|
f7cb94a0f4 | ||
|
|
bf6d84a954 | ||
|
|
ed8b8e68c1 | ||
|
|
c1d794765e | ||
|
|
c8dc342616 | ||
|
|
dffdaad6b5 | ||
|
|
84d93b7e41 | ||
|
|
9516f2a502 | ||
|
|
a25fcb2f16 | ||
|
|
15f1edfbeb | ||
|
|
dc67f872e8 | ||
|
|
34480385e8 | ||
|
|
96b18b59bb | ||
|
|
002f7d1d95 | ||
|
|
5a522b8e6b | ||
|
|
2b092d7444 | ||
|
|
a707250d33 | ||
|
|
dca3f4e89a | ||
|
|
8b6d8a6bcb | ||
|
|
9689efc1d1 | ||
|
|
b932f37070 | ||
|
|
69ee4a03d8 | ||
|
|
310d34e357 | ||
|
|
73fd1e29f1 | ||
|
|
b480b52bc0 | ||
|
|
48cd9250f9 | ||
|
|
3ac679ebb7 | ||
|
|
7cec807a31 | ||
|
|
e165b64199 | ||
|
|
4463d883df | ||
|
|
3b4964ba4a | ||
|
|
8c884753e1 | ||
|
|
4c49df83fe | ||
|
|
3f14d71f32 | ||
|
|
bee94bbc20 | ||
|
|
7a1097f5e9 | ||
|
|
bcba7b7c3b | ||
|
|
c487103715 | ||
|
|
b1d4b53c55 | ||
|
|
40483b9437 | ||
|
|
6c6d760f19 | ||
|
|
ded19e2505 | ||
|
|
879bc96d52 | ||
|
|
d5620708d4 | ||
|
|
70fedb87df | ||
|
|
9f6e258acb | ||
|
|
7fddd89ca5 | ||
|
|
dbe80258c0 | ||
|
|
61723676ed | ||
|
|
d76588684f | ||
|
|
029e3d640a | ||
|
|
fa83940583 | ||
|
|
288c5308a0 | ||
|
|
8fc7a560a7 | ||
|
|
5252609d7b | ||
|
|
64c167ae44 | ||
|
|
b5c10f7acd | ||
|
|
b758129933 | ||
|
|
d0e9599cc4 | ||
|
|
cdb5440933 | ||
|
|
5a4eccf604 | ||
|
|
42767532ac | ||
|
|
a4b41c94c2 | ||
|
|
9654c025e6 | ||
|
|
d1609866ff | ||
|
|
d8646997b0 | ||
|
|
adb57bed8a | ||
|
|
42c04bdcdb | ||
|
|
f8d8d51957 | ||
|
|
cd3caa920b | ||
|
|
277ba65f2d | ||
|
|
5159250da6 | ||
|
|
4e4702db5d | ||
|
|
39194d7d80 | ||
|
|
e0a9a8f467 | ||
|
|
60d4b4a281 | ||
|
|
54cb352ce0 | ||
|
|
7fba37ca36 | ||
|
|
57bab78676 | ||
|
|
2d56178cfc | ||
|
|
d5e4fb9fd6 | ||
|
|
d1deb9aef8 | ||
|
|
c06522480a | ||
|
|
cc4d4965d7 | ||
|
|
908a0ccfe9 | ||
|
|
b9a1556a2a | ||
|
|
322079030f | ||
|
|
8c6095e65a | ||
|
|
98ef69eea1 | ||
|
|
745aad5c99 | ||
|
|
3cea12e3dc | ||
|
|
ec9331ceb8 | ||
|
|
3f0e238919 | ||
|
|
4d9644fc77 | ||
|
|
d6ab10a98d | ||
|
|
e1f0fab2bc | ||
|
|
b406d7382d | ||
|
|
00db8ef1dc | ||
|
|
5471cd32a4 | ||
|
|
8f95c98a3d | ||
|
|
2afc9d1694 | ||
|
|
0946824d44 | ||
|
|
ce796fa073 | ||
|
|
41c074efb8 | ||
|
|
53828fdcfa | ||
|
|
3d39ce235f | ||
|
|
3c7b8efb72 | ||
|
|
b41bafd584 | ||
|
|
66e5ce2c7e | ||
|
|
e7016a328b | ||
|
|
15e619dee4 | ||
|
|
82aad02edb | ||
|
|
d7aae2e285 | ||
|
|
53d47add26 | ||
|
|
49af3c0075 | ||
|
|
e28a0ce1a8 | ||
|
|
9bb6a6477b | ||
|
|
d1598fbd72 | ||
|
|
402d02b508 | ||
|
|
780ae3e9ea | ||
|
|
c6cc0ab964 | ||
|
|
fdd81ef263 | ||
|
|
0a7bf17e4b | ||
|
|
65304dd2ad | ||
|
|
850efa69c7 | ||
|
|
4861826c9e | ||
|
|
76a1362acd | ||
|
|
64418f4e41 | ||
|
|
51b7c8bf6d | ||
|
|
2bba9c9d8d | ||
|
|
3bbf98b4d4 | ||
|
|
32c50dec13 | ||
|
|
8631916993 | ||
|
|
4afab1c6d1 | ||
|
|
5085b85331 | ||
|
|
d4a5aaa110 | ||
|
|
d4ff26aee5 | ||
|
|
6ccd9da332 | ||
|
|
576d758583 | ||
|
|
118f1ad349 | ||
|
|
ccd19efb56 | ||
|
|
23f7bbac09 | ||
|
|
5572e3fa64 | ||
|
|
8331020dc1 | ||
|
|
7f43f35255 | ||
|
|
b40e607c77 | ||
|
|
4722f915f2 | ||
|
|
0ebe9fb7e6 | ||
|
|
be8acb8333 | ||
|
|
679cd77899 | ||
|
|
7a17a9f3b6 | ||
|
|
8acda1aada | ||
|
|
9687e3e661 | ||
|
|
4ecd5c8e42 | ||
|
|
8ee750b744 | ||
|
|
7b0ca709ac | ||
|
|
b6aede3549 | ||
|
|
01e6cc5c41 | ||
|
|
60574377d6 | ||
|
|
22d1d5b1f0 | ||
|
|
eba88e1a48 | ||
|
|
2962a34aae | ||
|
|
1ef35c1387 | ||
|
|
9e8c3414e4 | ||
|
|
221be3d93d | ||
|
|
53c090def2 | ||
|
|
7ffd2864cf | ||
|
|
f927409335 | ||
|
|
a97ded530a | ||
|
|
f09112f747 | ||
|
|
525a18fbfa | ||
|
|
0b9de60f0b | ||
|
|
87ba4b9f1b | ||
|
|
0193bf6e4d | ||
|
|
bed7240d5c | ||
|
|
a317e10871 | ||
|
|
12b02fefbe | ||
|
|
1fb1c98ef7 | ||
|
|
acb7ff4bac | ||
|
|
0512aa4856 | ||
|
|
9439bbbc57 | ||
|
|
18dd421935 | ||
|
|
3ce7932f48 | ||
|
|
01bb1e0298 | ||
|
|
6bec531abf | ||
|
|
c355b7f2ae | ||
|
|
18433f2244 | ||
|
|
67a5848b26 | ||
|
|
544afe181e | ||
|
|
80fd5f3c32 | ||
|
|
0326c26632 | ||
|
|
58f02d0bc1 | ||
|
|
8de3157cbe | ||
|
|
2df3525918 | ||
|
|
e1d7dae168 | ||
|
|
338bbab074 | ||
|
|
18112f9d31 | ||
|
|
331e1ae505 | ||
|
|
ab6d4c933f | ||
|
|
90166d32e0 | ||
|
|
aca1e1bc4f | ||
|
|
f15bd570cd | ||
|
|
cc833c86bb | ||
|
|
39a7e2b882 | ||
|
|
7d236e0227 | ||
|
|
ee88f0ab95 | ||
|
|
aabb5888fe | ||
|
|
96338eb652 | ||
|
|
a71f9c46eb | ||
|
|
69d1c2ceea | ||
|
|
fd88585bd4 | ||
|
|
b8825ceb8b | ||
|
|
60eee41d72 | ||
|
|
f90b74a864 | ||
|
|
9d2b6a1f9f | ||
|
|
06dcc00517 | ||
|
|
373e7cc9ca | ||
|
|
b8221a6f89 | ||
|
|
083afd6ed5 | ||
|
|
9fbb1eedb8 | ||
|
|
41d864cbe8 | ||
|
|
a0fdf02280 | ||
|
|
0de3d58651 | ||
|
|
cea352d029 | ||
|
|
8e207fd1e0 | ||
|
|
e0b93081c2 | ||
|
|
fb9d515404 | ||
|
|
6f1d20ec5d | ||
|
|
4dc993a3e4 | ||
|
|
a38a733734 | ||
|
|
d5fbed3b98 | ||
|
|
b175dd703a | ||
|
|
be3661edee | ||
|
|
156c0c1a4f | ||
|
|
756b9e58fb | ||
|
|
b42a3e3c54 | ||
|
|
9843d323c5 | ||
|
|
f42830f615 | ||
|
|
44144cc70d | ||
|
|
7803bde1b5 | ||
|
|
8110a19f7b | ||
|
|
42297e47b7 | ||
|
|
8160c3a2de | ||
|
|
8516ca6e03 | ||
|
|
01e88e1047 | ||
|
|
bb1c853f10 | ||
|
|
54f573bcbd | ||
|
|
e31bbe28c5 | ||
|
|
3ec3982e01 | ||
|
|
cf523464b5 | ||
|
|
d25982eebe | ||
|
|
5f30f26d6a | ||
|
|
82aefc0169 | ||
|
|
0a2e6184dc | ||
|
|
13c16e1360 | ||
|
|
d07c327e71 | ||
|
|
bebf1be6db | ||
|
|
f58e85396c | ||
|
|
d7810d6784 | ||
|
|
3f65674a72 | ||
|
|
f778f291df | ||
|
|
6821d83f95 | ||
|
|
4f58ecd08d | ||
|
|
f82e8b277c | ||
|
|
5e36fe0105 | ||
|
|
e746b98ae2 | ||
|
|
6db1e14404 | ||
|
|
fa3bd276ba | ||
|
|
f1eb3b9251 | ||
|
|
624133a1af | ||
|
|
be903a1deb | ||
|
|
e53957f1c4 | ||
|
|
4202b382dc | ||
|
|
df19a14017 | ||
|
|
da2a087a87 | ||
|
|
5735aebb8c | ||
|
|
2371d52c9d | ||
|
|
a629f4c42a | ||
|
|
bb69e27c5a | ||
|
|
4f485594fc | ||
|
|
d43dfb788d | ||
|
|
49a70ccf48 | ||
|
|
9304137b80 | ||
|
|
aca3051029 | ||
|
|
35563a415d | ||
|
|
d5c2b3bacd | ||
|
|
ef6d1f440e | ||
|
|
d11b8fb546 | ||
|
|
d1a1c7f3b1 | ||
|
|
af7d7b6ff8 | ||
|
|
d3a9fb107d | ||
|
|
6f68535120 | ||
|
|
ba430215f6 | ||
|
|
306c7a6678 | ||
|
|
58ecdb59a7 | ||
|
|
c6008dfea4 | ||
|
|
13259c9896 | ||
|
|
b6f245071f | ||
|
|
51a20a21d2 | ||
|
|
cf3425cfde | ||
|
|
5e38f04fa2 | ||
|
|
a3ed2dbb14 | ||
|
|
27e07fd9dd | ||
|
|
2677323657 | ||
|
|
a5899de0e3 | ||
|
|
a7b8410f25 | ||
|
|
6e1c19f660 | ||
|
|
d37c6fc9a4 | ||
|
|
548b080cbc | ||
|
|
58b91ee603 | ||
|
|
1aac485377 | ||
|
|
3469bd4eaa | ||
|
|
9d4bd7489e | ||
|
|
bffa4259a0 | ||
|
|
4a83127c69 | ||
|
|
f470120924 | ||
|
|
748356b8bf | ||
|
|
9f810d131f | ||
|
|
7c3bcda327 | ||
|
|
6bcd655198 | ||
|
|
2c3d96228c | ||
|
|
93c02e9425 | ||
|
|
c787d02de5 | ||
|
|
5cc0b20654 | ||
|
|
7eb4279a6c | ||
|
|
6c747b5857 | ||
|
|
e67a152654 | ||
|
|
8075435210 | ||
|
|
875befff35 | ||
|
|
d933d79ef9 | ||
|
|
2038216017 | ||
|
|
531672aed8 | ||
|
|
41970a601c | ||
|
|
86769d8321 | ||
|
|
d32cc1f5ba | ||
|
|
9cb3ed42d9 | ||
|
|
47f0f3367a | ||
|
|
682a0cff62 | ||
|
|
e03d626ef9 | ||
|
|
9de574b38f | ||
|
|
e485704cb4 | ||
|
|
396f4071de | ||
|
|
565cb20407 | ||
|
|
3a40132ba3 | ||
|
|
33edd69fa4 | ||
|
|
21939e6ba0 | ||
|
|
8fb49522aa | ||
|
|
2a83c6e662 | ||
|
|
8b6f325af2 | ||
|
|
ad8dc923db | ||
|
|
86ee4c0c78 | ||
|
|
3ea49c0a45 | ||
|
|
efa06e6830 | ||
|
|
beb54c9a4d | ||
|
|
30cd3954c3 | ||
|
|
f123611655 | ||
|
|
d6a290d165 | ||
|
|
eee31f1fd4 | ||
|
|
b06d7bc3cc | ||
|
|
4e27765277 | ||
|
|
59761d1fce | ||
|
|
c634f7a6cf | ||
|
|
8aa77b684b | ||
|
|
12ded99dcd | ||
|
|
fb37d4ea57 | ||
|
|
70c89f29c1 | ||
|
|
be44e84f34 | ||
|
|
3794ddbde4 | ||
|
|
42b054238d | ||
|
|
abdd8c85e9 | ||
|
|
4645e7a2ff | ||
|
|
a605fc3851 | ||
|
|
811b733cbe | ||
|
|
1bd2df2d28 | ||
|
|
dc5be10721 | ||
|
|
64e71155af | ||
|
|
a44c27aae5 | ||
|
|
e66c9db6ab | ||
|
|
ae1deffc5e | ||
|
|
fd0640f678 | ||
|
|
2e7547998a | ||
|
|
61dfce9d6b | ||
|
|
dc1b80db1b | ||
|
|
4054d1af71 | ||
|
|
af2db370f7 | ||
|
|
a1b624f3ee | ||
|
|
eb444c2d69 | ||
|
|
f4fbdddab7 | ||
|
|
3f8c55eac3 | ||
|
|
ec8c7ae319 | ||
|
|
df9f85f7be | ||
|
|
2491d0782e | ||
|
|
93d9663e0a | ||
|
|
58b7d546a5 | ||
|
|
61830aabe5 | ||
|
|
c056a98347 | ||
|
|
64f32e0008 | ||
|
|
ba24c30718 | ||
|
|
c6476d2490 | ||
|
|
3fce6a5f2c | ||
|
|
96e03e5fcf | ||
|
|
36d2134352 | ||
|
|
7bdfeefb1d | ||
|
|
89afe46f92 | ||
|
|
02cbd370a8 | ||
|
|
b6e1c02f07 | ||
|
|
62868732c2 | ||
|
|
84ee4b0488 | ||
|
|
779b067eb2 | ||
|
|
4c554d7c39 | ||
|
|
b00d3547bc | ||
|
|
54ba797151 | ||
|
|
09f7632ad2 | ||
|
|
0fbd8248f8 | ||
|
|
2b14baabb5 | ||
|
|
a0b26dfe02 | ||
|
|
00a911412a | ||
|
|
f3d9a76d0d | ||
|
|
c4c2de03f9 | ||
|
|
8e1862e488 | ||
|
|
80998fb488 | ||
|
|
beb5269aad | ||
|
|
a4be2a944d | ||
|
|
891b1fa2fd | ||
|
|
e9b007db19 | ||
|
|
eed7fbbabf | ||
|
|
5a821a7e3f | ||
|
|
00b73bce10 | ||
|
|
3ce0c595ce | ||
|
|
c5ededc74b | ||
|
|
8412792bdf | ||
|
|
cc425d8d88 | ||
|
|
e45fe26e1b | ||
|
|
177d6f3346 | ||
|
|
ba8923ae5c | ||
|
|
a79fd3f240 | ||
|
|
5abb581109 | ||
|
|
ffab69b0b4 | ||
|
|
d5b516a7cb | ||
|
|
03ce7bd1c3 | ||
|
|
dabc4ff638 | ||
|
|
77bdc7a950 | ||
|
|
d2e9b7720e | ||
|
|
551c00eec6 | ||
|
|
1c8c5a398d | ||
|
|
1a5fccc4fb | ||
|
|
8e07c9eaf9 | ||
|
|
128d6304b5 | ||
|
|
3ed3dca6ad | ||
|
|
d7c227e128 | ||
|
|
b9f703c80e | ||
|
|
48f7b96224 | ||
|
|
c1e24d6043 | ||
|
|
4b61238c20 | ||
|
|
517e245c02 | ||
|
|
3d1d4b8acc | ||
|
|
01910deb96 | ||
|
|
6617e14c9b | ||
|
|
c33a0295cc | ||
|
|
00698b1351 | ||
|
|
0119e329c1 | ||
|
|
be209d1cb4 | ||
|
|
48db3e7a36 | ||
|
|
934aaa9956 | ||
|
|
a8b3a9e824 | ||
|
|
054a939be5 | ||
|
|
d8edc8319c | ||
|
|
7772f758d1 | ||
|
|
0180059e78 | ||
|
|
7476e386c2 | ||
|
|
096ea8c62d | ||
|
|
6a10cce6ca | ||
|
|
d03c9d0e44 | ||
|
|
baefd5982f | ||
|
|
21e85be3c2 | ||
|
|
26bdb62676 | ||
|
|
14accbd162 | ||
|
|
63fe555a11 | ||
|
|
50c3eabbf3 | ||
|
|
5365a0df74 | ||
|
|
c084672add | ||
|
|
61aa3e5603 | ||
|
|
24c6ff43ae | ||
|
|
1669cf8323 | ||
|
|
819c9b38f6 | ||
|
|
d5b050985a | ||
|
|
a65507a453 | ||
|
|
3a4cb788c7 | ||
|
|
6219d036cc | ||
|
|
2d2d566c1f | ||
|
|
523addfdde | ||
|
|
8e7dd1a4ac | ||
|
|
f5a8b2706d | ||
|
|
6c67cef9ed | ||
|
|
6576136f39 | ||
|
|
a72d8e0882 | ||
|
|
44da03a8f9 | ||
|
|
d13e4dbdc5 | ||
|
|
cd45fd824d | ||
|
|
34c2d37b46 | ||
|
|
e2b0bb9c19 | ||
|
|
198ae6bbfb | ||
|
|
a7296a35c6 | ||
|
|
cedda9622f | ||
|
|
b4ccd24e75 | ||
|
|
1479e578ea | ||
|
|
b7cb692b29 | ||
|
|
c1989f8fdf | ||
|
|
986dd76696 | ||
|
|
ff48b93222 | ||
|
|
128bde09d6 | ||
|
|
7310305312 | ||
|
|
d53cc62788 | ||
|
|
6323462350 | ||
|
|
0efef5a925 | ||
|
|
a40bf890f6 | ||
|
|
971e4475d9 | ||
|
|
61bb0cd8b6 | ||
|
|
ddae64716b | ||
|
|
06192e71b8 | ||
|
|
4dec8f1329 | ||
|
|
04ad085e82 | ||
|
|
859bc49f0d | ||
|
|
124174a564 | ||
|
|
076783aac4 | ||
|
|
f3aa6a56a6 | ||
|
|
2065010633 | ||
|
|
e0ddddb365 | ||
|
|
664bbdb030 | ||
|
|
684f433878 | ||
|
|
fe2acaef1b | ||
|
|
722be1b5b3 | ||
|
|
e0ddfd50b3 | ||
|
|
4dee0ff93d | ||
|
|
e83c171aaa | ||
|
|
2475856f10 | ||
|
|
5de0bacd5f | ||
|
|
3ae604d414 | ||
|
|
3fb97dfe15 | ||
|
|
91163c6ccf | ||
|
|
fc6584086e | ||
|
|
bef8f2cc69 | ||
|
|
1ce397c463 | ||
|
|
c403326bef | ||
|
|
7b79c8e191 | ||
|
|
0acc06e6d4 | ||
|
|
b8791152be | ||
|
|
72fd1819e3 | ||
|
|
9a22f8d8c3 | ||
|
|
62a90c2d66 | ||
|
|
bf36e8bd2a | ||
|
|
e6706d21b9 | ||
|
|
a161b3901e | ||
|
|
0f51e5944c | ||
|
|
a20686d9cd | ||
|
|
94a916be8c | ||
|
|
7d6e22be67 | ||
|
|
14a9126933 | ||
|
|
5bc21dd200 | ||
|
|
aae7d09d02 | ||
|
|
83363d7491 | ||
|
|
fbbab5110c | ||
|
|
47e70694bf | ||
|
|
3228346ee1 | ||
|
|
9848cfc17d | ||
|
|
9f56438c86 | ||
|
|
5e7e96ad62 | ||
|
|
b88728dcd8 | ||
|
|
bb837d1b3d | ||
|
|
03a8915c83 | ||
|
|
4edbdc052c | ||
|
|
52cac29ca7 | ||
|
|
c7758ca855 | ||
|
|
7f3d5c4234 | ||
|
|
c8f5113aa8 | ||
|
|
dcc798eac9 | ||
|
|
31e5b0d4c0 | ||
|
|
48f53491c6 | ||
|
|
358caeb54e | ||
|
|
5167948b44 | ||
|
|
859e721566 | ||
|
|
d81003d4db | ||
|
|
013467796b | ||
|
|
3e162658ba | ||
|
|
ffc4ee0275 | ||
|
|
59d1f5c967 | ||
|
|
ddf3351313 | ||
|
|
5eefcd35d4 | ||
|
|
ed359d9926 | ||
|
|
18d04acbc9 | ||
|
|
fa65bb10a6 | ||
|
|
2b8ec09fca | ||
|
|
bf3157511f | ||
|
|
9b45f39399 | ||
|
|
87f23e783a | ||
|
|
2d6baafe97 | ||
|
|
4ff887959d | ||
|
|
d72e5d0f5d | ||
|
|
99417f7dc3 | ||
|
|
7d5d593437 | ||
|
|
41d4db29d2 | ||
|
|
fc2fe0b3dc | ||
|
|
a3a7ecaf6f | ||
|
|
205eb398f8 | ||
|
|
1fe9c7dfc3 | ||
|
|
6202c103c6 | ||
|
|
5b12277d59 | ||
|
|
c47376358c | ||
|
|
b48ace0cee | ||
|
|
739462e4fa | ||
|
|
4cced802bc | ||
|
|
799bc25110 | ||
|
|
6aff03bae2 | ||
|
|
cb50a185b6 | ||
|
|
03c32a6292 | ||
|
|
fbddce946b | ||
|
|
03df1df7dc | ||
|
|
e0902eab8a | ||
|
|
f8460f56d0 | ||
|
|
d9a8f6581d | ||
|
|
6d9e785aec | ||
|
|
e33be38fa6 | ||
|
|
734b6f748e | ||
|
|
104e6dc306 | ||
|
|
a336cd7b0e | ||
|
|
f62f83bdda | ||
|
|
08ec2cc9d9 | ||
|
|
4425029892 | ||
|
|
3b571cb7ec | ||
|
|
8edd870602 | ||
|
|
f81c3f40b8 | ||
|
|
0cd306d23b | ||
|
|
cafff3f379 | ||
|
|
04d336f674 | ||
|
|
4f8babf927 | ||
|
|
c2bbd84204 | ||
|
|
f87f3489d1 | ||
|
|
77329e8fe5 | ||
|
|
c947da28c2 | ||
|
|
de51f43301 | ||
|
|
bec8706fcf | ||
|
|
7b6026e17d | ||
|
|
1324b0754b | ||
|
|
7da1d97769 | ||
|
|
1b5ba15321 | ||
|
|
b299711be7 | ||
|
|
164490ffc9 | ||
|
|
eadba12739 | ||
|
|
e52e154774 | ||
|
|
878c037554 | ||
|
|
03cdddce6e | ||
|
|
f23f17be4f | ||
|
|
fa423233ab | ||
|
|
c9c15d0157 | ||
|
|
1b2e23b309 | ||
|
|
acaa61319f | ||
|
|
c476415bfc | ||
|
|
5e3786fe1c | ||
|
|
07915701c3 | ||
|
|
b69228e28b | ||
|
|
42fcd856d0 | ||
|
|
22be9fffb6 | ||
|
|
204f65faac | ||
|
|
7e2b336ecc | ||
|
|
df86422e1b | ||
|
|
89c2130ff6 | ||
|
|
9d90ca99cb | ||
|
|
f8224a0f7e | ||
|
|
eef2e004b3 | ||
|
|
4fda7abaff | ||
|
|
72aefc9152 | ||
|
|
2feac87574 | ||
|
|
adcceb74ee | ||
|
|
9d0db8580e | ||
|
|
7106098c91 | ||
|
|
51acbeeab2 | ||
|
|
77f7031f44 | ||
|
|
447f07ef7f | ||
|
|
83a1b4f35e | ||
|
|
8cbab3a5a7 | ||
|
|
8fdadd61d0 | ||
|
|
1c8b589b1f | ||
|
|
3d360d216b | ||
|
|
2194592016 | ||
|
|
7bccaa769a | ||
|
|
1982f73e3d | ||
|
|
4525633ad8 | ||
|
|
6c75215113 | ||
|
|
d841ed5858 | ||
|
|
255d508437 | ||
|
|
ee2da8d8cd | ||
|
|
c4e4bc1871 | ||
|
|
45ce34ff0b | ||
|
|
f7256824bb | ||
|
|
06f33d21ee | ||
|
|
60ae9a110b | ||
|
|
dbabf696ce | ||
|
|
bcac77bd0d | ||
|
|
030f6c3939 | ||
|
|
9fbf9903ef | ||
|
|
0b67f989b9 | ||
|
|
68e8b47838 | ||
|
|
52965b233f | ||
|
|
a239710a6b | ||
|
|
4f86ee6025 | ||
|
|
102468d2e3 | ||
|
|
ee8f8def8e | ||
|
|
4bc8a89544 | ||
|
|
e66a776f9f | ||
|
|
0961590f20 | ||
|
|
8bf4345ac3 | ||
|
|
e7f0776098 | ||
|
|
35d8ea0949 | ||
|
|
59979056b9 | ||
|
|
dbbb3741d7 | ||
|
|
7ace65b8c9 | ||
|
|
dcccae1f7c | ||
|
|
fa3a140470 | ||
|
|
d9b657acf4 | ||
|
|
b9046d8326 | ||
|
|
5b70f38a9c | ||
|
|
f94fcfc997 | ||
|
|
73e835df10 | ||
|
|
3dc4594173 | ||
|
|
1606bfa2c5 | ||
|
|
b39e0f4052 | ||
|
|
f61ac6b361 | ||
|
|
b447b4ab6b | ||
|
|
968a035672 | ||
|
|
d976bed702 | ||
|
|
720a44b4fd | ||
|
|
fb2287b8c2 | ||
|
|
6468dcc712 | ||
|
|
c5923c31e9 | ||
|
|
7ab28b5226 | ||
|
|
f862a01a78 | ||
|
|
875d019b1c | ||
|
|
a4271de319 | ||
|
|
4d00ab3ecf | ||
|
|
bebe569d10 | ||
|
|
1c147936d7 | ||
|
|
7e9220d414 | ||
|
|
01867e5352 | ||
|
|
09b638075c | ||
|
|
79a61f2e12 | ||
|
|
ed39723ab6 | ||
|
|
b22ce18572 | ||
|
|
9b55d3c36b | ||
|
|
02bb5942b3 | ||
|
|
a72655fc79 | ||
|
|
30fda0e7a8 | ||
|
|
9957c12ff8 | ||
|
|
bfee22e968 | ||
|
|
485aa06339 | ||
|
|
bd5471cb30 | ||
|
|
39c83b4870 | ||
|
|
d32858b4c4 | ||
|
|
de0348592b | ||
|
|
804a8801e5 | ||
|
|
dd34a3aa9b | ||
|
|
0f37de9627 | ||
|
|
14a0e6e3ac | ||
|
|
2f2d24eb3e | ||
|
|
06484f81f6 | ||
|
|
b3f019420f | ||
|
|
2487f7ee93 | ||
|
|
e9c4f0913d | ||
|
|
81c5912add | ||
|
|
767524fb57 | ||
|
|
40f7d164bf | ||
|
|
1ff23dcc56 | ||
|
|
a4e9aa6243 | ||
|
|
50c40311a7 | ||
|
|
d331c87ab5 | ||
|
|
c4e1552b8b | ||
|
|
94783575cc | ||
|
|
a54a0ba985 | ||
|
|
e9da0ec519 | ||
|
|
8a9a13c5d7 | ||
|
|
85d967e5a5 | ||
|
|
453bf76d63 | ||
|
|
72bb290215 | ||
|
|
6230b6f28b | ||
|
|
0f5228259a | ||
|
|
4b5bd3d619 | ||
|
|
1ad1714388 | ||
|
|
f10fd27869 | ||
|
|
1f883c993b | ||
|
|
319b470acd | ||
|
|
e09ec6c165 | ||
|
|
1fa0a2c81c | ||
|
|
a8e54c5052 | ||
|
|
f17fa767c0 | ||
|
|
c8eed79a2a | ||
|
|
537f95c79d | ||
|
|
c923a7d976 | ||
|
|
b0939aeed8 | ||
|
|
a7d359fa10 | ||
|
|
862d8ed857 | ||
|
|
83cebd7e82 | ||
|
|
1eeab52cdf | ||
|
|
686807b0d2 | ||
|
|
f0464395ab | ||
|
|
87de44dd04 | ||
|
|
5fe65ad74b | ||
|
|
58600fbfad | ||
|
|
c92be334ae | ||
|
|
8276b5c51a | ||
|
|
d7224d7515 | ||
|
|
96ff2dc09e | ||
|
|
a40da18132 | ||
|
|
8cb889da68 | ||
|
|
08d2d7f4e8 | ||
|
|
e9c273e40f | ||
|
|
9500b3d951 | ||
|
|
2b4e935373 | ||
|
|
4f9fd821ec | ||
|
|
b221b7eaab | ||
|
|
4b8ed1df67 | ||
|
|
9399097069 | ||
|
|
337f5b7320 | ||
|
|
80c70d321e | ||
|
|
676fee32c6 | ||
|
|
715ee1f9ab | ||
|
|
c5a791f361 | ||
|
|
1ec656f2ed | ||
|
|
97b579a68d | ||
|
|
59f75710dc | ||
|
|
c7e74b2ddc | ||
|
|
6380b4c8fe | ||
|
|
3d4f9e7297 | ||
|
|
bfe48d2611 | ||
|
|
3c08718b4b | ||
|
|
2f033f795f | ||
|
|
acd2ca2f23 | ||
|
|
de2731c21f | ||
|
|
5e0a8a6331 | ||
|
|
b34ca7b951 | ||
|
|
1422748447 | ||
|
|
186f7f70e4 | ||
|
|
4b1eb98e5b | ||
|
|
f7e185be3b | ||
|
|
ea6da0b7dc | ||
|
|
ae23652c0c | ||
|
|
54b86b4db2 | ||
|
|
f45779d2c3 | ||
|
|
480ebbb121 | ||
|
|
427e8c7059 | ||
|
|
86cf1b63f4 | ||
|
|
27ee03494b | ||
|
|
2f5799f453 | ||
|
|
7c419d875d | ||
|
|
d7d87b5c5d | ||
|
|
a11e7bbcb8 | ||
|
|
028e4bc196 | ||
|
|
2252b9908b | ||
|
|
02353704ca | ||
|
|
2bde0ac099 | ||
|
|
2543aabbd4 | ||
|
|
d01c22b5f9 | ||
|
|
08896bdb8e | ||
|
|
46d7491c78 | ||
|
|
9437ea5d90 | ||
|
|
184943da83 | ||
|
|
66e53ad1ed | ||
|
|
6595893e48 | ||
|
|
14ac5860f8 | ||
|
|
4554d94f32 | ||
|
|
737b2eb0f7 | ||
|
|
cb318eb8b9 | ||
|
|
557035e340 | ||
|
|
86bd570e2c | ||
|
|
d406114118 | ||
|
|
e4dd0faf5e | ||
|
|
83aa60a77c | ||
|
|
acfe00d6db | ||
|
|
d4a2ae07df | ||
|
|
926bdc1d5a | ||
|
|
ffee3af0ac | ||
|
|
42e6dffda2 | ||
|
|
c9db375d7c | ||
|
|
06de741370 | ||
|
|
a2287aff5b | ||
|
|
444aad2034 | ||
|
|
0de852043e | ||
|
|
9df1102514 | ||
|
|
8eaa535d6b | ||
|
|
b5e7de67a2 | ||
|
|
f4db8078b8 | ||
|
|
21db65e9ab | ||
|
|
1d0b60427d | ||
|
|
1760b10940 | ||
|
|
ce54805981 | ||
|
|
6df75248d8 | ||
|
|
21331d4189 | ||
|
|
c1e72c380b | ||
|
|
b38346e00c | ||
|
|
9e5cbbb9d1 | ||
|
|
f767ca34ce | ||
|
|
d4bc720dd0 | ||
|
|
1d1950d3cb | ||
|
|
038418ca0d | ||
|
|
4aa2dd4471 | ||
|
|
075a01e3ae | ||
|
|
35ea4c2a50 | ||
|
|
bc851d0b51 | ||
|
|
c11d353540 | ||
|
|
32e6da4a78 | ||
|
|
a96f9be77a | ||
|
|
04a38b79ac | ||
|
|
a1886d51f8 | ||
|
|
9cd2bc595d | ||
|
|
e592d3bc56 | ||
|
|
1da213b157 | ||
|
|
3f349d0eb3 | ||
|
|
84cac99a59 | ||
|
|
de1b198f4c | ||
|
|
5954e7053c | ||
|
|
1b4b9ad32b | ||
|
|
394e96d051 | ||
|
|
2c6151996e | ||
|
|
5c195f3cea | ||
|
|
1af6d101e8 | ||
|
|
b6b220496b | ||
|
|
ff92c4cf31 | ||
|
|
12ed04a7cc | ||
|
|
6575000c28 | ||
|
|
43f4e45827 | ||
|
|
4b6e58ddf0 | ||
|
|
0abdf7a3b5 | ||
|
|
53e0a2ad37 | ||
|
|
7e22527164 | ||
|
|
7e6928d700 | ||
|
|
4fa37ac4b9 | ||
|
|
bbe5e3c556 | ||
|
|
4c7f16baf4 | ||
|
|
cec3d26493 | ||
|
|
86edf9fd66 | ||
|
|
0adcfe55aa | ||
|
|
5a22e6a15d | ||
|
|
4d833b6f38 | ||
|
|
d05ecdf840 | ||
|
|
1f66ec432d | ||
|
|
2fcc5943fa | ||
|
|
08e909ca00 | ||
|
|
4c48e3e089 | ||
|
|
d9d32f7508 | ||
|
|
691c52fff9 | ||
|
|
c3d7a9f075 | ||
|
|
da3b2c1dc1 | ||
|
|
2ea69dde48 | ||
|
|
7c66069ee3 | ||
|
|
fd9ee6f4e1 | ||
|
|
6314550377 | ||
|
|
738fc7f52d | ||
|
|
4c129ffee4 | ||
|
|
c1750989b7 | ||
|
|
2b685348ab | ||
|
|
070fd7695a | ||
|
|
7cf1a52405 | ||
|
|
096f41172d | ||
|
|
59665029d2 | ||
|
|
3249e844f9 | ||
|
|
187c90c7df | ||
|
|
d779034da8 | ||
|
|
429d8dbf30 | ||
|
|
c5d35c9596 | ||
|
|
23265d2623 | ||
|
|
4810518d74 | ||
|
|
1e389fdef8 | ||
|
|
f31550732e | ||
|
|
29876aeb28 | ||
|
|
a90c34aac4 | ||
|
|
78456d6333 | ||
|
|
44edc80abc | ||
|
|
3f6e6f54a1 | ||
|
|
1e576cfde6 | ||
|
|
07c4ab24e7 | ||
|
|
915a6a42c2 | ||
|
|
5216b35688 | ||
|
|
3c53eec798 | ||
|
|
db080d8b10 | ||
|
|
04d129b8d8 | ||
|
|
059e7f7920 | ||
|
|
3bbfe87868 | ||
|
|
8cdc76427e | ||
|
|
11976bf964 | ||
|
|
a5f4f78018 | ||
|
|
0621f2e242 | ||
|
|
5538480862 | ||
|
|
07f126cb52 | ||
|
|
a5263b1375 | ||
|
|
6a52bdf10d | ||
|
|
ad38be34b7 | ||
|
|
a7785b0d1b | ||
|
|
26743d0ca0 | ||
|
|
fccc8f129f | ||
|
|
c1a11dd89a | ||
|
|
0730e2d774 | ||
|
|
a8bf257391 | ||
|
|
86f6a9e3f1 | ||
|
|
41f3b22237 | ||
|
|
7ad5bfb581 | ||
|
|
75e5c8d0ca | ||
|
|
1b9437ad32 | ||
|
|
d5bde7570a | ||
|
|
a0d13578f3 | ||
|
|
ed48547f36 | ||
|
|
73b97fe151 | ||
|
|
f21e1c3f8a | ||
|
|
aa230be806 | ||
|
|
26bb8e5009 | ||
|
|
5322d5a9c2 | ||
|
|
0f5eb7d5f8 | ||
|
|
9379f7283c | ||
|
|
29025cce61 | ||
|
|
7fd201d5e3 | ||
|
|
1f947d5781 | ||
|
|
8dea53bcda | ||
|
|
f5d43d0986 | ||
|
|
cf67fb456e | ||
|
|
308814bdca | ||
|
|
149a9bd7b2 | ||
|
|
e16998beb5 | ||
|
|
a9e12e38ef | ||
|
|
9c7d25f49d | ||
|
|
a52a1f5820 | ||
|
|
e0c3244667 | ||
|
|
32bf18781a | ||
|
|
5bd88d468e | ||
|
|
e758e07658 | ||
|
|
eb0ac0dde9 | ||
|
|
c81a4d50dd | ||
|
|
ae28b04615 | ||
|
|
471623cd10 | ||
|
|
4378bfdbea | ||
|
|
8ff6cbd98e | ||
|
|
f4ffe22d61 | ||
|
|
4573e30e73 | ||
|
|
832b40e235 | ||
|
|
aec0bad678 | ||
|
|
aeedde578d | ||
|
|
dfebb08e1e | ||
|
|
dbf77d8548 | ||
|
|
003c252d8a | ||
|
|
1986df1961 | ||
|
|
11c205f0c9 | ||
|
|
7d2cc8fac5 | ||
|
|
863e7733e6 | ||
|
|
e657a1b07d | ||
|
|
2f29cf7755 | ||
|
|
6d34dbc286 | ||
|
|
28f7f7baaf | ||
|
|
f3b78b1aa3 | ||
|
|
ce82a93afe | ||
|
|
fe9a35b706 | ||
|
|
febba22f45 | ||
|
|
2981668172 | ||
|
|
ed7369c98f | ||
|
|
b6409b11c7 | ||
|
|
98c51e45b4 | ||
|
|
9e6b82d10a | ||
|
|
ee49314502 | ||
|
|
29e73367a6 | ||
|
|
80aa655164 | ||
|
|
f8d7d060b9 | ||
|
|
b198e8a40e | ||
|
|
a3fb6de6b3 | ||
|
|
5252cca5c5 | ||
|
|
4cab5da076 | ||
|
|
2cdf7030f8 | ||
|
|
e30f8d0fb6 | ||
|
|
22fe17048d | ||
|
|
3d70e2e9f6 | ||
|
|
f35dc1f30d | ||
|
|
34b36a8ef2 | ||
|
|
7c105ca398 | ||
|
|
2ec4873552 | ||
|
|
aa4f3de30c | ||
|
|
e4fb939e91 | ||
|
|
68acaf77c5 | ||
|
|
308039739f | ||
|
|
7687637783 | ||
|
|
7b799eb531 | ||
|
|
9c37156906 | ||
|
|
32d6eb3ed5 | ||
|
|
faa2cef0bb | ||
|
|
7e999ea4b3 | ||
|
|
69c569c82b | ||
|
|
bcc5f2c526 | ||
|
|
35bff6a89b | ||
|
|
a26d45793e | ||
|
|
d6577d1d72 | ||
|
|
e8aeec7f87 | ||
|
|
0b75a0ee8e | ||
|
|
4163c56502 | ||
|
|
37d45ffbeb | ||
|
|
f03a1397f1 | ||
|
|
410b23974e | ||
|
|
3366843ca1 | ||
|
|
9bcc6cbc64 | ||
|
|
9636e3d2e4 | ||
|
|
140e44a3bd | ||
|
|
9f76a78b0a | ||
|
|
dd6e8d0884 | ||
|
|
66b1a795c1 | ||
|
|
8b8b9f0cf5 | ||
|
|
d5647c9e98 | ||
|
|
5d1d77f4df | ||
|
|
7a193c0618 | ||
|
|
1709ed07c7 | ||
|
|
23586a027e | ||
|
|
97c74ec2b1 | ||
|
|
961461aa19 | ||
|
|
713a9ecb3b | ||
|
|
8a46d38914 | ||
|
|
423033d2ac | ||
|
|
d01925107f | ||
|
|
47a719147c | ||
|
|
556bb2caa7 | ||
|
|
ec3813fb13 | ||
|
|
926152940e | ||
|
|
7cf186952e | ||
|
|
b2ccb5c689 | ||
|
|
3da6e688e4 | ||
|
|
567ffe84cd | ||
|
|
d97495ed41 | ||
|
|
ceab176d9f | ||
|
|
050887c53c | ||
|
|
2bc61a9e68 | ||
|
|
6b1d9356f3 | ||
|
|
1dbc34fe62 | ||
|
|
d93b041397 | ||
|
|
ecd14e1de4 | ||
|
|
923bddc90d | ||
|
|
97297f291f | ||
|
|
84850c76a9 | ||
|
|
ca8c8d35d9 | ||
|
|
18e96a1028 | ||
|
|
f27b3e469a | ||
|
|
7b47938062 | ||
|
|
d9f3bb8bdd | ||
|
|
8919532b84 | ||
|
|
481ac50726 | ||
|
|
731b3c8c36 | ||
|
|
8393bc02e4 | ||
|
|
85e5b0ff13 | ||
|
|
3b8d779b27 | ||
|
|
89dcef5b67 | ||
|
|
6ced3d38a4 | ||
|
|
7fc97562b2 | ||
|
|
80372f40b5 | ||
|
|
d9a5a27391 | ||
|
|
8c73aa93c8 | ||
|
|
59997f5cf2 | ||
|
|
3ab7db6061 | ||
|
|
3d2ba5a182 | ||
|
|
d7cb5165ef | ||
|
|
3a89700d95 | ||
|
|
f0ce722aa4 | ||
|
|
95709401a3 | ||
|
|
06f030ab73 | ||
|
|
bd0dc07c43 | ||
|
|
0d354f679c | ||
|
|
7ed37ec5f8 | ||
|
|
fa40deaa11 | ||
|
|
117d25b686 | ||
|
|
7491eb0076 | ||
|
|
1acb9f8ab1 | ||
|
|
f87b262194 | ||
|
|
6c82cabe3a | ||
|
|
7e7969988a | ||
|
|
4b51c24b66 | ||
|
|
5e0ad38ffc | ||
|
|
56557101f3 | ||
|
|
1d0cf99e48 | ||
|
|
2f5b534de6 | ||
|
|
00ae4d4824 | ||
|
|
af2a87a207 | ||
|
|
72fe5ad996 | ||
|
|
d00da28a3c | ||
|
|
81363bc537 | ||
|
|
1e98cd4939 | ||
|
|
814a8976fb | ||
|
|
86cea92ca1 | ||
|
|
f9178dcff2 | ||
|
|
fcc4b92efe | ||
|
|
44f79a5db2 | ||
|
|
034f235196 | ||
|
|
a1e16543b2 | ||
|
|
8578e2a538 | ||
|
|
092d3dac40 | ||
|
|
e9ddf8400b | ||
|
|
22b78255bb | ||
|
|
2985b48432 | ||
|
|
b249028b8e | ||
|
|
4cb2077e1a | ||
|
|
88b6223820 | ||
|
|
0b4ef6b1e3 | ||
|
|
8921ce16e6 | ||
|
|
f4606da30f | ||
|
|
41a5727a93 | ||
|
|
19cec963d2 | ||
|
|
156bc84c73 | ||
|
|
eb98eaf5af | ||
|
|
fb6d86af8a | ||
|
|
39aa6bfda1 | ||
|
|
ba59dfb01c | ||
|
|
dd044c30e6 | ||
|
|
f1fcc2c26b | ||
|
|
c0e9d82ff6 | ||
|
|
89598ab48c | ||
|
|
586de9321a | ||
|
|
ae7244a10f | ||
|
|
d95fc7352f | ||
|
|
e4b7c92c6a | ||
|
|
a52a88e0cb | ||
|
|
2ec3c08e72 | ||
|
|
b57e0a9848 | ||
|
|
eabcc78815 | ||
|
|
1b7a2730f0 | ||
|
|
caac7dc2ed | ||
|
|
d8eb3a5c7a | ||
|
|
5973d0d944 | ||
|
|
a512a7ab33 | ||
|
|
e44cada50e | ||
|
|
49e734b66d | ||
|
|
5c4efbcd04 | ||
|
|
f131aafa44 | ||
|
|
f4263e943d | ||
|
|
45cd72f7dc | ||
|
|
634289407b | ||
|
|
01ff93375a | ||
|
|
c64ef5deed | ||
|
|
a5a0c510b5 | ||
|
|
d7f8231717 | ||
|
|
f0f7cfc839 | ||
|
|
03870821c5 | ||
|
|
726084bc06 | ||
|
|
cbbc341472 | ||
|
|
664b707330 | ||
|
|
a47bba59d4 | ||
|
|
e84544afe4 | ||
|
|
cd505adc3d | ||
|
|
5b1dd964ae | ||
|
|
86ef4d0820 | ||
|
|
966e593617 | ||
|
|
eb4a355dc7 | ||
|
|
d65b645a64 | ||
|
|
f899c6e58c | ||
|
|
a554599fd7 | ||
|
|
6046860e15 | ||
|
|
1dda7d72a9 | ||
|
|
3e2f3ab1c5 | ||
|
|
286d7d043c | ||
|
|
563c089b9d | ||
|
|
ca103655bd | ||
|
|
666fb6b35c | ||
|
|
d234975cde | ||
|
|
13a6310ac0 | ||
|
|
62bfb66e11 | ||
|
|
b776929fe8 | ||
|
|
273b7286cf | ||
|
|
5bd450e0c9 | ||
|
|
777dff3c4b | ||
|
|
e3ddb00eee | ||
|
|
870ffe3437 | ||
|
|
b8ea837204 | ||
|
|
4a611fcf58 | ||
|
|
91be7b5a32 | ||
|
|
42cf0def0f | ||
|
|
10fd05478d | ||
|
|
9fbc7a17c3 | ||
|
|
3cd105bac0 | ||
|
|
559c96412a | ||
|
|
ee5187c7e7 | ||
|
|
0cb7573494 | ||
|
|
0f514be363 | ||
|
|
ca07a8103a | ||
|
|
4837bb5740 | ||
|
|
fc7cc33111 | ||
|
|
ce341302a3 | ||
|
|
4cb27c40ac | ||
|
|
84e3fc1be9 | ||
|
|
2522450930 | ||
|
|
e3e761ddc0 | ||
|
|
c95ee53137 | ||
|
|
7f9ef96178 | ||
|
|
95cbb2870c | ||
|
|
17891a62fa | ||
|
|
f1ff893962 | ||
|
|
ce6d092b16 | ||
|
|
f6577217ff | ||
|
|
283af44c48 | ||
|
|
1ea839f5d4 | ||
|
|
4a3e07bbae | ||
|
|
9ba8866b19 | ||
|
|
5dbe15e7e0 | ||
|
|
cbf38aed9c | ||
|
|
a8eb34d201 | ||
|
|
496b12913d | ||
|
|
be47a4bad3 | ||
|
|
9d28e88a24 | ||
|
|
d937a397a7 | ||
|
|
c9daed2056 | ||
|
|
804c36e8b7 | ||
|
|
7274d46865 | ||
|
|
feaec2eb6d | ||
|
|
753c0731a3 | ||
|
|
9a6b1822a9 | ||
|
|
db5e8316ec | ||
|
|
513b9db9b5 | ||
|
|
04248f0166 | ||
|
|
c60c041012 | ||
|
|
f10ec1751f | ||
|
|
561d4deb32 | ||
|
|
3e552ac655 | ||
|
|
12d8b289e5 | ||
|
|
bcf2abdbbc | ||
|
|
8747c163ee | ||
|
|
458bd8f964 | ||
|
|
19a34ba730 | ||
|
|
16294fe54a | ||
|
|
76cdfa9759 | ||
|
|
fc508b3a27 | ||
|
|
a4c2fa3797 | ||
|
|
d1575996b4 | ||
|
|
75293299ed | ||
|
|
e50f31fb5f | ||
|
|
e47cdebf14 | ||
|
|
85eaf866d9 | ||
|
|
800a9b6854 | ||
|
|
94eb94508c | ||
|
|
02a49149fa | ||
|
|
80308ab621 | ||
|
|
cb055269d3 | ||
|
|
1c6ccbb20a | ||
|
|
764310e08b | ||
|
|
673fd2dae3 | ||
|
|
5fe57fcb11 | ||
|
|
0a9617b0c4 | ||
|
|
6b06a6f274 | ||
|
|
e5c3d4595c | ||
|
|
ab0a18cc64 | ||
|
|
b534c4bbcd | ||
|
|
0ee95fae76 | ||
|
|
dcb8c54e4c | ||
|
|
802aed63df | ||
|
|
ba7cd330e6 | ||
|
|
adeec5d6a9 | ||
|
|
8b0e20aa9e | ||
|
|
a22a3dc849 | ||
|
|
f220aef52a | ||
|
|
0175cc1a26 | ||
|
|
59b6e092c9 | ||
|
|
ceb7b13a8e | ||
|
|
f68f8ba157 | ||
|
|
19a58c309e | ||
|
|
62479e1de6 | ||
|
|
04dde1c18d | ||
|
|
c7450034f5 | ||
|
|
9140dab9f1 | ||
|
|
8f4d3792c6 | ||
|
|
9d73236812 | ||
|
|
fbc0404f94 | ||
|
|
a2050ed3e6 | ||
|
|
0d2c13bf80 | ||
|
|
39061439dc | ||
|
|
d9e5d93265 | ||
|
|
b3bec906ef | ||
|
|
6d969373b9 | ||
|
|
c8ac6b9f2d | ||
|
|
45d7b43aa6 | ||
|
|
e954b40d3c | ||
|
|
07f94f9d42 | ||
|
|
18809bc918 | ||
|
|
85d32d49e2 | ||
|
|
64459b414e | ||
|
|
f0ffeefe24 | ||
|
|
df022d5df7 | ||
|
|
a2b75c2072 | ||
|
|
e7a9492200 | ||
|
|
3133dbf8b4 | ||
|
|
8fd2ffc93e | ||
|
|
39675d465d | ||
|
|
6e969ddaaa | ||
|
|
a9f4009e43 | ||
|
|
b28b5e1ec9 | ||
|
|
f4b9dd2f34 | ||
|
|
40c651cd04 | ||
|
|
b6d6da88e8 | ||
|
|
7f95e5f583 | ||
|
|
e179aa404c | ||
|
|
4235a6906e | ||
|
|
618cd6415d | ||
|
|
9d458ce8c5 | ||
|
|
311306e986 | ||
|
|
a1688a7404 | ||
|
|
da421bc5f8 | ||
|
|
db8a71cca2 | ||
|
|
c8352e4968 | ||
|
|
1d8aec2c3b | ||
|
|
459c2cfae4 | ||
|
|
32c52578e2 | ||
|
|
eccadbf6fc | ||
|
|
8d99d2032d | ||
|
|
121db288c1 | ||
|
|
b8b3fff684 | ||
|
|
33a08f67ec | ||
|
|
dffc1b2a42 | ||
|
|
55aa4350b9 | ||
|
|
40bfde20fa | ||
|
|
8ceda2b07b | ||
|
|
a592a44647 | ||
|
|
2a9b59f935 | ||
|
|
5ed0549dc4 | ||
|
|
158da7f60f | ||
|
|
58e529097f | ||
|
|
2de5beb6d1 | ||
|
|
cb5df407fd | ||
|
|
8fd0e873c7 | ||
|
|
fe9ff2f4f9 | ||
|
|
bf05d850fb | ||
|
|
266cbd5dde | ||
|
|
e222c0f7a9 | ||
|
|
12e80be6d1 | ||
|
|
5efb2c2e40 | ||
|
|
2e574caac6 | ||
|
|
74b7a40763 | ||
|
|
1ede72b64a | ||
|
|
c45fe42129 | ||
|
|
ddaffcf7a5 | ||
|
|
0d28620ee3 | ||
|
|
bbab149d3c | ||
|
|
d4e193f39a | ||
|
|
bd4dd064be | ||
|
|
2efb70e3b5 | ||
|
|
92668c3a2a | ||
|
|
3915c3ac2c | ||
|
|
51e521fbb3 | ||
|
|
a81a666b81 | ||
|
|
69a9f0fe43 | ||
|
|
9262ad4d82 | ||
|
|
b6270a71bb | ||
|
|
e31d2ae0d1 | ||
|
|
77510ac6e3 | ||
|
|
0f1e88bb25 | ||
|
|
c5a42f03e6 | ||
|
|
de1124c19e | ||
|
|
60ba4bfec6 | ||
|
|
6372242b0b | ||
|
|
9a8c4a1c8c | ||
|
|
891db98700 | ||
|
|
f7d1b73a6c | ||
|
|
7a906fdc03 | ||
|
|
e7046c9bf5 | ||
|
|
8d87b0256b | ||
|
|
802aa167a9 | ||
|
|
b83bd41716 | ||
|
|
7c8cb3053a | ||
|
|
7c9fb9f6b4 | ||
|
|
68989d3e31 | ||
|
|
51a0745348 | ||
|
|
3505a2bcd5 | ||
|
|
0581a5e42b | ||
|
|
7ea839334c | ||
|
|
be7cce4300 | ||
|
|
c78251a90e | ||
|
|
c3c8fd7186 | ||
|
|
3d97768f71 | ||
|
|
f2c93cb5dd | ||
|
|
689ef0cf8a | ||
|
|
77de4abfd1 | ||
|
|
dc16b75c94 | ||
|
|
3a0fdb4d4f | ||
|
|
32b0f8333d | ||
|
|
446dfd2257 | ||
|
|
faa9947974 | ||
|
|
c75971e8c5 | ||
|
|
2c850cbe53 | ||
|
|
a060f5f322 | ||
|
|
5367811a33 | ||
|
|
7d41ae1331 | ||
|
|
f55abfa576 | ||
|
|
79b8340b80 | ||
|
|
e0b7034f97 | ||
|
|
b79876c959 | ||
|
|
e159c25f6d | ||
|
|
d16eedd1b3 | ||
|
|
a062d61c8d | ||
|
|
471e5b5399 | ||
|
|
577f94a67f | ||
|
|
f7e05491da | ||
|
|
2771b4fe1f | ||
|
|
b5830fac6e | ||
|
|
d8511c92c0 | ||
|
|
1ea41edd6a | ||
|
|
4f79f92940 | ||
|
|
e13865a966 | ||
|
|
05e1161632 | ||
|
|
feae947742 | ||
|
|
9ef69149ee | ||
|
|
04ca668807 | ||
|
|
489af75669 | ||
|
|
c8e1aff861 | ||
|
|
373bcd0351 | ||
|
|
c08e984e35 | ||
|
|
5dee59e8e5 | ||
|
|
350dbeb12f | ||
|
|
fdc2eb66aa | ||
|
|
ef332e4568 | ||
|
|
93c7d28714 | ||
|
|
5d8225f041 | ||
|
|
b501c76f7c | ||
|
|
97526110d0 | ||
|
|
f8a4c5db92 | ||
|
|
2093d23e24 | ||
|
|
7e32b752a0 | ||
|
|
4b02708bb0 | ||
|
|
9684d69bd1 | ||
|
|
f6d07944d8 | ||
|
|
46796041c7 | ||
|
|
196a782f8c | ||
|
|
4839d05b70 | ||
|
|
d8b6140692 | ||
|
|
040e51f124 | ||
|
|
a24161db2b | ||
|
|
117501c511 | ||
|
|
1c122b99e9 | ||
|
|
6970c1e7cf | ||
|
|
d56cecdba4 | ||
|
|
8ebf514dab | ||
|
|
1376f0dde2 | ||
|
|
dbacf597da | ||
|
|
87eaf96fa1 | ||
|
|
13db59aac8 | ||
|
|
9a46768de6 | ||
|
|
3c5010cebf | ||
|
|
d25b197c09 | ||
|
|
b892cfa029 | ||
|
|
23a012364b | ||
|
|
ed0f3600f5 | ||
|
|
dd4e250432 | ||
|
|
2ffb71e053 | ||
|
|
be25cf699c | ||
|
|
1cc15fe50c | ||
|
|
a04e5c1da2 | ||
|
|
5b18ba9058 | ||
|
|
753f4d989b | ||
|
|
8960fa20c3 | ||
|
|
38344027ca | ||
|
|
90ff1abab8 | ||
|
|
7fe3799ea6 | ||
|
|
5ed6fba085 | ||
|
|
6c3573d854 | ||
|
|
d00aeffbda | ||
|
|
e247b8e368 | ||
|
|
3d54fd3b7b | ||
|
|
b19b5daf48 | ||
|
|
f48952aa94 | ||
|
|
e8d4433bb3 | ||
|
|
84b0e17572 | ||
|
|
e4ba6f56dd | ||
|
|
bdfd2e9adf | ||
|
|
849655cb2f | ||
|
|
1c391a7c7e | ||
|
|
cf1990ddf7 | ||
|
|
fad3ea497d | ||
|
|
f586525a61 | ||
|
|
832a290778 | ||
|
|
ecbba9f1c0 | ||
|
|
afeb4eb815 | ||
|
|
d0b5e78f7a | ||
|
|
1837234650 | ||
|
|
c65031ea31 | ||
|
|
14a7176325 | ||
|
|
a0c3a1eb05 | ||
|
|
f7af3865b7 | ||
|
|
51a0c08042 | ||
|
|
abc2118a77 | ||
|
|
776ac3ee2b | ||
|
|
63eaec8348 | ||
|
|
ddb8464c1c | ||
|
|
29ef7a7d3b | ||
|
|
49aa0cc459 | ||
|
|
cdf1b44cb0 | ||
|
|
61fe9f2282 | ||
|
|
0b9d254e12 | ||
|
|
77b241eb68 | ||
|
|
b98ae5844c | ||
|
|
507ce6f220 | ||
|
|
4d2a27f7e8 | ||
|
|
a90841a6b6 | ||
|
|
e9bb6e42f6 | ||
|
|
ed4b11d9ef | ||
|
|
c115f01edf | ||
|
|
8fd6bbef3b | ||
|
|
8fa68731d1 | ||
|
|
b6b586a865 | ||
|
|
457ba444f1 | ||
|
|
31a367c434 | ||
|
|
e83eb92948 | ||
|
|
5ee779846d | ||
|
|
7a3ecf8e38 | ||
|
|
9e2c96a18e | ||
|
|
6550fa28ca | ||
|
|
de9ce7ebb9 | ||
|
|
caae78b6ee | ||
|
|
495701c46c | ||
|
|
ed99ce1694 | ||
|
|
51a37e896d | ||
|
|
b431315e88 | ||
|
|
ea59f120da | ||
|
|
9bd30e8d5d | ||
|
|
39d1ef7e13 | ||
|
|
83a1b70efe | ||
|
|
01b4f803a8 | ||
|
|
5c31890066 | ||
|
|
be7f4e5192 | ||
|
|
4124c45875 | ||
|
|
06e785c75a | ||
|
|
2f384c079c | ||
|
|
e4adada542 | ||
|
|
efa86891cc | ||
|
|
bcc20bb774 | ||
|
|
937a095209 | ||
|
|
1661228168 | ||
|
|
b17b9eb4e1 | ||
|
|
2ffa1510ed | ||
|
|
d1ef107b48 | ||
|
|
aa48e9cacc | ||
|
|
3c0bfa7843 | ||
|
|
803b6331f4 | ||
|
|
fe8af8705d | ||
|
|
624365cda3 | ||
|
|
4fb59b6071 | ||
|
|
d15948e44b | ||
|
|
bf629ff518 | ||
|
|
ee9ee7f207 | ||
|
|
311d4091ff | ||
|
|
4e79f508c0 | ||
|
|
90828e6eab | ||
|
|
7d3afc35cc | ||
|
|
6f4c046732 | ||
|
|
503a8da70f | ||
|
|
1cf9a005d9 | ||
|
|
fe04d56568 | ||
|
|
93b6ac15dd | ||
|
|
500632515c | ||
|
|
598d2da9d6 | ||
|
|
05f7a9b30a | ||
|
|
0b6be04428 | ||
|
|
dcb9553807 | ||
|
|
c9c9be68b1 | ||
|
|
53e6671143 | ||
|
|
3d063d8188 | ||
|
|
8d3633fe63 | ||
|
|
ee7d64c533 | ||
|
|
5009ebe0ab | ||
|
|
9b2f3f3108 | ||
|
|
9ca9dc797e | ||
|
|
2ffc18cc89 | ||
|
|
cfeeda3c93 | ||
|
|
a2277bc2d4 | ||
|
|
694e4e7988 | ||
|
|
1c0e786489 | ||
|
|
7420e94876 | ||
|
|
73c921bb95 | ||
|
|
7a22f4f74a | ||
|
|
cdb76d344e | ||
|
|
6cbded92cb | ||
|
|
bc032a074c | ||
|
|
89a4afdc08 | ||
|
|
be6266d633 | ||
|
|
705264ba9a | ||
|
|
a0b181e6fe | ||
|
|
df50a0a38d | ||
|
|
abd182e17f | ||
|
|
312c15d824 | ||
|
|
3f0cdc9b19 | ||
|
|
5d5c42c843 | ||
|
|
1a8fe19c88 | ||
|
|
7e37495b49 | ||
|
|
3210f06931 | ||
|
|
a457ff539d | ||
|
|
afbcd90ffc | ||
|
|
8acdc6c458 | ||
|
|
4ed8f69da1 | ||
|
|
90fe657da6 | ||
|
|
91fe7b516c | ||
|
|
e3851e3f1d | ||
|
|
301014285f | ||
|
|
e467f44bf2 | ||
|
|
e8e5ac2ca7 | ||
|
|
ebb0a4b2bc | ||
|
|
d21e2666ec | ||
|
|
43140b2db3 | ||
|
|
c898e1321f | ||
|
|
4f04cbbcc8 | ||
|
|
f17e857710 | ||
|
|
2401b9b1ff | ||
|
|
e646ff1d1f | ||
|
|
9bb800ce43 | ||
|
|
6859ad2ad9 | ||
|
|
19152790b2 | ||
|
|
05751dbe1b | ||
|
|
f35f6b3edd | ||
|
|
8af7fadbf6 | ||
|
|
9190ce038f | ||
|
|
bda47748f2 | ||
|
|
96084d7b26 | ||
|
|
e96ff73b50 | ||
|
|
fcb8c4bdd6 | ||
|
|
c7c1d2d302 | ||
|
|
21ea8e377c | ||
|
|
e9409d01a6 | ||
|
|
a8c70a903f | ||
|
|
c66ca97dfc | ||
|
|
5b5517ad29 | ||
|
|
c8de886967 | ||
|
|
2ef19583a6 | ||
|
|
9e0ff5e635 | ||
|
|
427cef758f | ||
|
|
1cc1516feb | ||
|
|
daafa3f5c2 | ||
|
|
9e20af7603 | ||
|
|
306a160df0 | ||
|
|
b5686bcd2f | ||
|
|
33dd245131 | ||
|
|
25334c27f1 | ||
|
|
3f20ebb15b | ||
|
|
37849b88e2 | ||
|
|
800b3f8272 | ||
|
|
b2ecb3cb52 | ||
|
|
8a794f2e02 | ||
|
|
3c05e1674f | ||
|
|
a15b3c2a69 | ||
|
|
10c138d64e | ||
|
|
99d1718498 | ||
|
|
515985dead | ||
|
|
e30c4532b8 | ||
|
|
2ab5f19f79 | ||
|
|
f5054558c2 | ||
|
|
b726053164 | ||
|
|
cc433b6708 | ||
|
|
e95e251161 | ||
|
|
75edc751f3 | ||
|
|
b4ba84a0d1 | ||
|
|
38074a1660 | ||
|
|
ca5e20b1a9 | ||
|
|
c4db463338 | ||
|
|
34c2cb604f | ||
|
|
f058e38004 | ||
|
|
b38a8b5830 | ||
|
|
8e76829a27 | ||
|
|
f3bf092898 | ||
|
|
4dd2b87538 | ||
|
|
4d6930e903 | ||
|
|
3fecd16efd | ||
|
|
29a535c71a | ||
|
|
3fd72f35df | ||
|
|
bb3d637738 | ||
|
|
02fe620360 | ||
|
|
ebe0e350ff | ||
|
|
6dbe638a59 | ||
|
|
31b5f4181a | ||
|
|
72d0cfdbbc | ||
|
|
f934764290 | ||
|
|
e28f809a58 | ||
|
|
a88dd00a0d | ||
|
|
5d9de47fd5 | ||
|
|
7add28ad7d | ||
|
|
17b97aca28 | ||
|
|
86b422230e | ||
|
|
0193671453 | ||
|
|
c4be1b8697 | ||
|
|
52f8880627 | ||
|
|
f537e8cf78 | ||
|
|
482af12b67 | ||
|
|
2f85d50778 | ||
|
|
b51663c130 | ||
|
|
7ebc54352e | ||
|
|
6703e62823 | ||
|
|
e860e9e1af | ||
|
|
de3303abc9 | ||
|
|
a0695c08cc | ||
|
|
c5aeb0785e | ||
|
|
21b376848f | ||
|
|
f8fe7e41b4 | ||
|
|
22caa0a408 | ||
|
|
4d3945f5a4 | ||
|
|
0786012ed8 | ||
|
|
6e9a316460 | ||
|
|
fd7d0e1d99 | ||
|
|
65199c7a81 | ||
|
|
851094c523 | ||
|
|
9b31032016 | ||
|
|
a7fa173e52 | ||
|
|
de4d83372c | ||
|
|
c7f24490f3 | ||
|
|
fabc7a3a27 | ||
|
|
e830527f74 | ||
|
|
5fc5feb636 | ||
|
|
384af0001c | ||
|
|
2abc9b3f7c | ||
|
|
9242f3439c | ||
|
|
b1ba8f0aa2 | ||
|
|
20b3775280 | ||
|
|
c2bb321e17 | ||
|
|
b0160287ba | ||
|
|
284e2257f3 | ||
|
|
ec06d07f35 | ||
|
|
5eca8807d8 | ||
|
|
5b3ee63299 | ||
|
|
a6ffa3ae36 | ||
|
|
e021e66387 | ||
|
|
eb70985551 | ||
|
|
cd6dc5158e | ||
|
|
942f30557c | ||
|
|
1486d61e65 | ||
|
|
eb082d2f49 | ||
|
|
7e6e691771 | ||
|
|
8e4962ceb1 | ||
|
|
414d82bd37 | ||
|
|
4091f1d1d9 | ||
|
|
fe0f4ddf99 | ||
|
|
b0845c5795 | ||
|
|
aeddb37d9d | ||
|
|
1ff1c35486 | ||
|
|
6e585a7afa | ||
|
|
e0df16ee17 | ||
|
|
ae5cb7a17a | ||
|
|
8d79ed527f | ||
|
|
d4bc84c6bd | ||
|
|
9743a7d1b7 | ||
|
|
e5cffc623a | ||
|
|
9c0a559bce | ||
|
|
b794bc5ee0 | ||
|
|
55c71d77e1 | ||
|
|
6914e30443 | ||
|
|
1c52e8e38a | ||
|
|
c38f169d1d | ||
|
|
8f4b8dbf88 | ||
|
|
b68f28b621 | ||
|
|
a23eb2f869 | ||
|
|
8dfbf2b3b6 | ||
|
|
70b0fab66a | ||
|
|
082e2ff2cd | ||
|
|
12af50d473 | ||
|
|
b1cde02dd4 | ||
|
|
2d737ab092 | ||
|
|
8cd9220f36 | ||
|
|
838d4a98aa | ||
|
|
d32bfea94e | ||
|
|
181a7f5b9c | ||
|
|
ee00b544f7 | ||
|
|
5548c71bda | ||
|
|
ae7a6b79b7 | ||
|
|
96d2338bea | ||
|
|
21217e030b | ||
|
|
1d63f4b4a1 | ||
|
|
8843e5bfe7 | ||
|
|
48b97f8632 | ||
|
|
20184fb4c0 | ||
|
|
76000a90fb | ||
|
|
4aebd350ee | ||
|
|
13f4a0abb4 | ||
|
|
35ea297090 | ||
|
|
f11909d6da | ||
|
|
5df82652b6 | ||
|
|
8fde8ebe5f | ||
|
|
6bc20a8830 | ||
|
|
0bbd9c103a | ||
|
|
0b68e2ade6 | ||
|
|
1ca79539b6 | ||
|
|
1c2c3d6d51 | ||
|
|
bdb1243409 | ||
|
|
812d4ca519 | ||
|
|
4e7e14cbf3 | ||
|
|
2064b6517e | ||
|
|
c9f491a483 | ||
|
|
be2ec80f0c | ||
|
|
1c13e8dc10 | ||
|
|
4748aa2262 | ||
|
|
a3376b0502 | ||
|
|
c073d39cd6 | ||
|
|
b508f78f04 | ||
|
|
8424da8311 | ||
|
|
cfd9d55668 | ||
|
|
417e3bb104 | ||
|
|
320d4f62d9 | ||
|
|
6c712d86fb | ||
|
|
a0479f9610 | ||
|
|
374a744a44 | ||
|
|
81416d49de | ||
|
|
31ebd62c83 | ||
|
|
93dd4af6ed | ||
|
|
9f0308dd66 | ||
|
|
f9298703d4 | ||
|
|
3992032e8e | ||
|
|
58dacc0789 | ||
|
|
b3280923d9 | ||
|
|
e83da9ff9c | ||
|
|
8b1d0fc759 | ||
|
|
ff869933be | ||
|
|
fb0af4dc18 | ||
|
|
6f5551d78d | ||
|
|
a90efe4939 | ||
|
|
46c37b9d82 | ||
|
|
3e23eb517b | ||
|
|
9cf75ea844 | ||
|
|
5565b4a4d0 | ||
|
|
bde5967ef4 | ||
|
|
f3560238fe | ||
|
|
f97f489aba | ||
|
|
454b7715c0 | ||
|
|
ac1e279235 | ||
|
|
5950162663 | ||
|
|
6a52b46192 | ||
|
|
f5558eec7f | ||
|
|
0c39f596a3 | ||
|
|
ba3c544484 | ||
|
|
e0063727a9 | ||
|
|
7b68821a8c | ||
|
|
4a84b408d4 | ||
|
|
b9aec4ffb8 | ||
|
|
1a958e758f | ||
|
|
74527319aa | ||
|
|
a66e7ad14f | ||
|
|
e4b91416dd | ||
|
|
7735c705c0 | ||
|
|
31ab48d0d4 | ||
|
|
e5cf9da31d | ||
|
|
9eac64ab7b | ||
|
|
7131cebf7d | ||
|
|
bfb4a5ee57 | ||
|
|
21e250bf36 | ||
|
|
f5b1ac970b | ||
|
|
c85f14f27c | ||
|
|
c0cf6198a4 | ||
|
|
4d3012fee7 | ||
|
|
75726dc272 | ||
|
|
260724af80 | ||
|
|
2b37cf217f | ||
|
|
669ff579c5 | ||
|
|
d0e65fe1bc | ||
|
|
4485e46801 | ||
|
|
2265687c75 | ||
|
|
f70523bf14 | ||
|
|
48013b9339 | ||
|
|
e9e1caf9d1 | ||
|
|
5b17e2beda | ||
|
|
19bc74f88d | ||
|
|
ae0069652a | ||
|
|
5ec7f01b7a | ||
|
|
691f798f90 | ||
|
|
d5bef0861b | ||
|
|
87dd938cde | ||
|
|
c4e0d3e7ce | ||
|
|
c705722bdc | ||
|
|
6424fa151a | ||
|
|
0646e49359 | ||
|
|
6ddae9e9da | ||
|
|
efc7be1637 | ||
|
|
1de80be872 | ||
|
|
f8740f1268 | ||
|
|
d6f30c9220 | ||
|
|
f2d7004b02 | ||
|
|
6ecf800bab | ||
|
|
6f92a834da | ||
|
|
a7a85d22d5 | ||
|
|
178024e248 | ||
|
|
798e93af46 | ||
|
|
3cb125ae4a | ||
|
|
30aed49e87 | ||
|
|
d0173a2341 | ||
|
|
fb7a61ab30 | ||
|
|
13e09c73bc | ||
|
|
6fdaf4fda4 | ||
|
|
ba79089bed | ||
|
|
a8670467be | ||
|
|
4dd84cdb2e | ||
|
|
028d6c0a1f | ||
|
|
ca2584f598 | ||
|
|
17e96250f7 | ||
|
|
79fdd21817 | ||
|
|
a0daa7c4be | ||
|
|
000d0d3119 | ||
|
|
3b01b4f3dd | ||
|
|
f0613e9660 | ||
|
|
1c51ed37c2 | ||
|
|
2bfcd92da9 | ||
|
|
0bf676448b | ||
|
|
5bceff414e | ||
|
|
1b65231e30 | ||
|
|
8b39b69bb7 | ||
|
|
4dfab45544 | ||
|
|
f046dbf3cc | ||
|
|
2e5f9350eb | ||
|
|
938784d6e8 | ||
|
|
02c582a695 | ||
|
|
2851d49c3b | ||
|
|
e88af1f6ba | ||
|
|
da3b6bf19e | ||
|
|
da28e10ac7 | ||
|
|
e6b260cc60 | ||
|
|
2cea9debcf | ||
|
|
768a26a5d4 | ||
|
|
0e251ffb6a | ||
|
|
74c04ea561 | ||
|
|
e92cc2735b | ||
|
|
6a94535b78 | ||
|
|
17ad490f97 | ||
|
|
704e4c4ef7 | ||
|
|
36a301275e | ||
|
|
bb2a01512e | ||
|
|
ad73c9391d | ||
|
|
ce33d146d0 | ||
|
|
055695890e | ||
|
|
028579eed4 | ||
|
|
5c836228f7 | ||
|
|
0663eee49a | ||
|
|
4d9205699b | ||
|
|
d8a5993a28 | ||
|
|
6e7e19fd34 | ||
|
|
9e55ee9be1 | ||
|
|
ae93665645 | ||
|
|
7eb84bb6a3 | ||
|
|
3006cc9431 | ||
|
|
480629953b | ||
|
|
c54ed09d38 | ||
|
|
a098b56339 | ||
|
|
7a9f8d9788 | ||
|
|
7bd822b96a | ||
|
|
d20e9d5629 | ||
|
|
9b61a99dc3 | ||
|
|
4c1f670147 | ||
|
|
c05d299fe4 | ||
|
|
0ba8b530fe | ||
|
|
0a6f7afeff | ||
|
|
483f274bac | ||
|
|
1fd36a4623 | ||
|
|
3b1ab80c1c | ||
|
|
abe0cec346 | ||
|
|
6e1e22d6cb | ||
|
|
7650ec9323 | ||
|
|
fe3fedaf64 | ||
|
|
f21f187890 | ||
|
|
41e164d131 | ||
|
|
adb6a0b7f0 | ||
|
|
31618c1611 | ||
|
|
6eba60638b | ||
|
|
e4040d4a47 | ||
|
|
c53cd32291 | ||
|
|
172ca340ae | ||
|
|
a8451abea4 | ||
|
|
cdf67a3865 | ||
|
|
e2f2fc4804 | ||
|
|
db9a451e32 | ||
|
|
a8cf682f71 | ||
|
|
0c721f1776 | ||
|
|
f8194aac7a | ||
|
|
15dd60ff3e | ||
|
|
d54441a9b2 | ||
|
|
4e75bfb4d0 | ||
|
|
3e350d03c6 | ||
|
|
2fa44e1ae2 | ||
|
|
1e5244cee0 | ||
|
|
6043c2738d | ||
|
|
013cf5eb13 | ||
|
|
a413fe2dc1 | ||
|
|
a2bf02eb82 | ||
|
|
229485494a | ||
|
|
4ea3439667 | ||
|
|
4154d31143 | ||
|
|
30b660f802 | ||
|
|
b79cf45778 | ||
|
|
c6e9cbd69b | ||
|
|
7e493ed129 | ||
|
|
c3ed6ebe79 | ||
|
|
27e33ec1b1 | ||
|
|
7601d5a2bd | ||
|
|
a9fb42eb15 | ||
|
|
cd9cdbf1be | ||
|
|
e68a716587 | ||
|
|
bab5672669 | ||
|
|
80f083e141 | ||
|
|
91d37b38cc | ||
|
|
9349fa9217 | ||
|
|
748523f9cf | ||
|
|
e64336abe9 | ||
|
|
15450a7fba | ||
|
|
ad2f63cd8f | ||
|
|
dbd3d956fb | ||
|
|
aabb4399ed | ||
|
|
a99386ac1d | ||
|
|
b400f22d15 | ||
|
|
8843054b89 | ||
|
|
8c8bde0799 | ||
|
|
109a990b28 | ||
|
|
8e9ac2e3e7 | ||
|
|
c0ad9b1b71 | ||
|
|
01609a1f29 | ||
|
|
2f5219615d | ||
|
|
91cd93783f | ||
|
|
2d8e4e9053 | ||
|
|
9500b3a5ae | ||
|
|
84e124bbf5 | ||
|
|
0d0377ae4a | ||
|
|
9e1ec67253 | ||
|
|
4f8ee5ea52 | ||
|
|
56654ffcb1 | ||
|
|
ac48d3634b | ||
|
|
934e4cbb2c | ||
|
|
53e631d65f | ||
|
|
1483d7c969 | ||
|
|
71ddee1033 | ||
|
|
480bba7a8c | ||
|
|
9c4c529c62 |
@@ -1,8 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<classpath>
|
<classpath>
|
||||||
<classpathentry kind="src" output="target/classes" path="src/main/java"/>
|
|
||||||
<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources"/>
|
|
||||||
<classpathentry kind="src" output="target/test-classes" path="src/test/java"/>
|
|
||||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.7"/>
|
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.7"/>
|
||||||
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER"/>
|
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER"/>
|
||||||
<classpathentry kind="con" path="org.testng.TESTNG_CONTAINER"/>
|
<classpathentry kind="con" path="org.testng.TESTNG_CONTAINER"/>
|
||||||
|
|||||||
32032
.gitattributes
vendored
32032
.gitattributes
vendored
File diff suppressed because it is too large
Load Diff
182
.gitignore
vendored
182
.gitignore
vendored
@@ -2,20 +2,184 @@
|
|||||||
/*.iml
|
/*.iml
|
||||||
/*.tmp
|
/*.tmp
|
||||||
/.metadata
|
/.metadata
|
||||||
|
forge-ai/forge-ai.iml
|
||||||
|
forge-ai/target
|
||||||
|
forge-core/forge-core.iml
|
||||||
|
forge-core/target
|
||||||
|
forge-game/target
|
||||||
|
forge-gui-android/bin
|
||||||
|
forge-gui-android/target
|
||||||
|
forge-gui-desktop/target
|
||||||
|
forge-gui-mobile-dev/bin
|
||||||
|
forge-gui-mobile-dev/res
|
||||||
|
forge-gui-mobile-dev/target
|
||||||
|
forge-gui-mobile/bin
|
||||||
|
forge-gui-mobile/target
|
||||||
|
forge-gui/forge-gui.iml
|
||||||
|
forge-gui/forge.profile.properties
|
||||||
|
forge-gui/res/*.log
|
||||||
|
forge-gui/res/PerSetTrackingResults
|
||||||
|
forge-gui/res/cardsfolder/*.bat
|
||||||
|
forge-gui/res/decks
|
||||||
|
forge-gui/res/layouts
|
||||||
|
forge-gui/res/pics*
|
||||||
|
forge-gui/res/pics_product
|
||||||
|
forge-gui/res/skins/*.log
|
||||||
|
forge-gui/res/skins/PerSetTrackingResults
|
||||||
|
forge-gui/res/skins/Thumbs.db
|
||||||
|
forge-gui/res/skins/arabian_nights/*.log
|
||||||
|
forge-gui/res/skins/arabian_nights/PerSetTrackingResults
|
||||||
|
forge-gui/res/skins/arabian_nights/Thumbs.db
|
||||||
|
forge-gui/res/skins/arabian_nights/decks
|
||||||
|
forge-gui/res/skins/arabian_nights/layouts
|
||||||
|
forge-gui/res/skins/arabian_nights/pics*
|
||||||
|
forge-gui/res/skins/arabian_nights/pics_product
|
||||||
|
forge-gui/res/skins/comic/*.log
|
||||||
|
forge-gui/res/skins/comic/PerSetTrackingResults
|
||||||
|
forge-gui/res/skins/comic/Thumbs.db
|
||||||
|
forge-gui/res/skins/comic/decks
|
||||||
|
forge-gui/res/skins/comic/layouts
|
||||||
|
forge-gui/res/skins/comic/pics*
|
||||||
|
forge-gui/res/skins/comic/pics_product
|
||||||
|
forge-gui/res/skins/dark_ascension/*.log
|
||||||
|
forge-gui/res/skins/dark_ascension/PerSetTrackingResults
|
||||||
|
forge-gui/res/skins/dark_ascension/Thumbs.db
|
||||||
|
forge-gui/res/skins/dark_ascension/decks
|
||||||
|
forge-gui/res/skins/dark_ascension/layouts
|
||||||
|
forge-gui/res/skins/dark_ascension/pics*
|
||||||
|
forge-gui/res/skins/dark_ascension/pics_product
|
||||||
|
forge-gui/res/skins/decks
|
||||||
|
forge-gui/res/skins/default/*.log
|
||||||
|
forge-gui/res/skins/default/PerSetTrackingResults
|
||||||
|
forge-gui/res/skins/default/Thumbs.db
|
||||||
|
forge-gui/res/skins/default/decks
|
||||||
|
forge-gui/res/skins/default/layouts
|
||||||
|
forge-gui/res/skins/default/pics*
|
||||||
|
forge-gui/res/skins/default/pics_product
|
||||||
|
forge-gui/res/skins/firebloom/*.log
|
||||||
|
forge-gui/res/skins/firebloom/PerSetTrackingResults
|
||||||
|
forge-gui/res/skins/firebloom/Thumbs.db
|
||||||
|
forge-gui/res/skins/firebloom/decks
|
||||||
|
forge-gui/res/skins/firebloom/layouts
|
||||||
|
forge-gui/res/skins/firebloom/pics*
|
||||||
|
forge-gui/res/skins/firebloom/pics_product
|
||||||
|
forge-gui/res/skins/inferno/*.log
|
||||||
|
forge-gui/res/skins/inferno/PerSetTrackingResults
|
||||||
|
forge-gui/res/skins/inferno/Thumbs.db
|
||||||
|
forge-gui/res/skins/inferno/decks
|
||||||
|
forge-gui/res/skins/inferno/layouts
|
||||||
|
forge-gui/res/skins/inferno/pics*
|
||||||
|
forge-gui/res/skins/inferno/pics_product
|
||||||
|
forge-gui/res/skins/innistrad/*.log
|
||||||
|
forge-gui/res/skins/innistrad/PerSetTrackingResults
|
||||||
|
forge-gui/res/skins/innistrad/Thumbs.db
|
||||||
|
forge-gui/res/skins/innistrad/decks
|
||||||
|
forge-gui/res/skins/innistrad/layouts
|
||||||
|
forge-gui/res/skins/innistrad/pics*
|
||||||
|
forge-gui/res/skins/innistrad/pics_product
|
||||||
|
forge-gui/res/skins/journeyman/*.log
|
||||||
|
forge-gui/res/skins/journeyman/PerSetTrackingResults
|
||||||
|
forge-gui/res/skins/journeyman/Thumbs.db
|
||||||
|
forge-gui/res/skins/journeyman/decks
|
||||||
|
forge-gui/res/skins/journeyman/layouts
|
||||||
|
forge-gui/res/skins/journeyman/pics*
|
||||||
|
forge-gui/res/skins/journeyman/pics_product
|
||||||
|
forge-gui/res/skins/kamigawa/*.log
|
||||||
|
forge-gui/res/skins/kamigawa/PerSetTrackingResults
|
||||||
|
forge-gui/res/skins/kamigawa/Thumbs.db
|
||||||
|
forge-gui/res/skins/kamigawa/decks
|
||||||
|
forge-gui/res/skins/kamigawa/layouts
|
||||||
|
forge-gui/res/skins/kamigawa/pics*
|
||||||
|
forge-gui/res/skins/kamigawa/pics_product
|
||||||
|
forge-gui/res/skins/layouts
|
||||||
|
forge-gui/res/skins/marble_blue/*.log
|
||||||
|
forge-gui/res/skins/marble_blue/PerSetTrackingResults
|
||||||
|
forge-gui/res/skins/marble_blue/Thumbs.db
|
||||||
|
forge-gui/res/skins/marble_blue/decks
|
||||||
|
forge-gui/res/skins/marble_blue/layouts
|
||||||
|
forge-gui/res/skins/marble_blue/pics*
|
||||||
|
forge-gui/res/skins/marble_blue/pics_product
|
||||||
|
forge-gui/res/skins/metalcraft/*.log
|
||||||
|
forge-gui/res/skins/metalcraft/PerSetTrackingResults
|
||||||
|
forge-gui/res/skins/metalcraft/Thumbs.db
|
||||||
|
forge-gui/res/skins/metalcraft/decks
|
||||||
|
forge-gui/res/skins/metalcraft/layouts
|
||||||
|
forge-gui/res/skins/metalcraft/pics*
|
||||||
|
forge-gui/res/skins/metalcraft/pics_product
|
||||||
|
forge-gui/res/skins/mythic_rare/*.log
|
||||||
|
forge-gui/res/skins/mythic_rare/PerSetTrackingResults
|
||||||
|
forge-gui/res/skins/mythic_rare/Thumbs.db
|
||||||
|
forge-gui/res/skins/mythic_rare/decks
|
||||||
|
forge-gui/res/skins/mythic_rare/layouts
|
||||||
|
forge-gui/res/skins/mythic_rare/pics*
|
||||||
|
forge-gui/res/skins/mythic_rare/pics_product
|
||||||
|
forge-gui/res/skins/phyrexia/*.log
|
||||||
|
forge-gui/res/skins/phyrexia/PerSetTrackingResults
|
||||||
|
forge-gui/res/skins/phyrexia/Thumbs.db
|
||||||
|
forge-gui/res/skins/phyrexia/decks
|
||||||
|
forge-gui/res/skins/phyrexia/layouts
|
||||||
|
forge-gui/res/skins/phyrexia/pics*
|
||||||
|
forge-gui/res/skins/phyrexia/pics_product
|
||||||
|
forge-gui/res/skins/pics*
|
||||||
|
forge-gui/res/skins/pics_product
|
||||||
|
forge-gui/res/skins/ravnica/*.log
|
||||||
|
forge-gui/res/skins/ravnica/PerSetTrackingResults
|
||||||
|
forge-gui/res/skins/ravnica/Thumbs.db
|
||||||
|
forge-gui/res/skins/ravnica/decks
|
||||||
|
forge-gui/res/skins/ravnica/layouts
|
||||||
|
forge-gui/res/skins/ravnica/pics*
|
||||||
|
forge-gui/res/skins/ravnica/pics_product
|
||||||
|
forge-gui/res/skins/rebel/*.log
|
||||||
|
forge-gui/res/skins/rebel/PerSetTrackingResults
|
||||||
|
forge-gui/res/skins/rebel/Thumbs.db
|
||||||
|
forge-gui/res/skins/rebel/decks
|
||||||
|
forge-gui/res/skins/rebel/layouts
|
||||||
|
forge-gui/res/skins/rebel/pics*
|
||||||
|
forge-gui/res/skins/rebel/pics_product
|
||||||
|
forge-gui/res/skins/sleeping_forest/*.log
|
||||||
|
forge-gui/res/skins/sleeping_forest/PerSetTrackingResults
|
||||||
|
forge-gui/res/skins/sleeping_forest/Thumbs.db
|
||||||
|
forge-gui/res/skins/sleeping_forest/decks
|
||||||
|
forge-gui/res/skins/sleeping_forest/layouts
|
||||||
|
forge-gui/res/skins/sleeping_forest/pics*
|
||||||
|
forge-gui/res/skins/sleeping_forest/pics_product
|
||||||
|
forge-gui/res/skins/smith/*.log
|
||||||
|
forge-gui/res/skins/smith/PerSetTrackingResults
|
||||||
|
forge-gui/res/skins/smith/Thumbs.db
|
||||||
|
forge-gui/res/skins/smith/decks
|
||||||
|
forge-gui/res/skins/smith/layouts
|
||||||
|
forge-gui/res/skins/smith/pics*
|
||||||
|
forge-gui/res/skins/smith/pics_product
|
||||||
|
forge-gui/res/skins/the_dale/*.log
|
||||||
|
forge-gui/res/skins/the_dale/PerSetTrackingResults
|
||||||
|
forge-gui/res/skins/the_dale/Thumbs.db
|
||||||
|
forge-gui/res/skins/the_dale/decks
|
||||||
|
forge-gui/res/skins/the_dale/layouts
|
||||||
|
forge-gui/res/skins/the_dale/pics*
|
||||||
|
forge-gui/res/skins/the_dale/pics_product
|
||||||
|
forge-gui/res/skins/the_simpsons/*.log
|
||||||
|
forge-gui/res/skins/the_simpsons/PerSetTrackingResults
|
||||||
|
forge-gui/res/skins/the_simpsons/Thumbs.db
|
||||||
|
forge-gui/res/skins/the_simpsons/decks
|
||||||
|
forge-gui/res/skins/the_simpsons/layouts
|
||||||
|
forge-gui/res/skins/the_simpsons/pics*
|
||||||
|
forge-gui/res/skins/the_simpsons/pics_product
|
||||||
|
forge-gui/res/skins/zendikar/*.log
|
||||||
|
forge-gui/res/skins/zendikar/PerSetTrackingResults
|
||||||
|
forge-gui/res/skins/zendikar/Thumbs.db
|
||||||
|
forge-gui/res/skins/zendikar/decks
|
||||||
|
forge-gui/res/skins/zendikar/layouts
|
||||||
|
forge-gui/res/skins/zendikar/pics*
|
||||||
|
forge-gui/res/skins/zendikar/pics_product
|
||||||
|
forge-gui/target
|
||||||
|
forge-gui/tools/PerSetTrackingResults
|
||||||
|
forge-gui/tools/oracleScript.log
|
||||||
|
forge-net/target
|
||||||
/forge.profile.properties
|
/forge.profile.properties
|
||||||
/nbactions.xml
|
/nbactions.xml
|
||||||
/pom.xml.next
|
/pom.xml.next
|
||||||
/pom.xml.releaseBackup
|
/pom.xml.releaseBackup
|
||||||
/pom.xml.tag
|
/pom.xml.tag
|
||||||
/release.properties
|
/release.properties
|
||||||
res/*.log
|
|
||||||
res/PerSetTrackingResults
|
|
||||||
res/cardsfolder/*.bat
|
|
||||||
res/decks
|
|
||||||
res/layouts
|
|
||||||
res/pics*
|
|
||||||
res/pics_product
|
|
||||||
/target
|
/target
|
||||||
/test-output
|
/test-output
|
||||||
tools/PerSetTrackingResults
|
|
||||||
tools/oracleScript.log
|
|
||||||
|
|||||||
29
.project
29
.project
@@ -4,36 +4,7 @@
|
|||||||
<comment></comment>
|
<comment></comment>
|
||||||
<projects>
|
<projects>
|
||||||
</projects>
|
</projects>
|
||||||
<buildSpec>
|
|
||||||
<buildCommand>
|
|
||||||
<name>org.eclipse.jdt.core.javabuilder</name>
|
|
||||||
<arguments>
|
|
||||||
</arguments>
|
|
||||||
</buildCommand>
|
|
||||||
<buildCommand>
|
|
||||||
<name>org.eclipse.pde.ManifestBuilder</name>
|
|
||||||
<arguments>
|
|
||||||
</arguments>
|
|
||||||
</buildCommand>
|
|
||||||
<buildCommand>
|
|
||||||
<name>org.eclipse.pde.SchemaBuilder</name>
|
|
||||||
<arguments>
|
|
||||||
</arguments>
|
|
||||||
</buildCommand>
|
|
||||||
<buildCommand>
|
|
||||||
<name>net.sf.eclipsecs.core.CheckstyleBuilder</name>
|
|
||||||
<arguments>
|
|
||||||
</arguments>
|
|
||||||
</buildCommand>
|
|
||||||
<buildCommand>
|
|
||||||
<name>org.eclipse.m2e.core.maven2Builder</name>
|
|
||||||
<arguments>
|
|
||||||
</arguments>
|
|
||||||
</buildCommand>
|
|
||||||
</buildSpec>
|
|
||||||
<natures>
|
<natures>
|
||||||
<nature>org.eclipse.m2e.core.maven2Nature</nature>
|
<nature>org.eclipse.m2e.core.maven2Nature</nature>
|
||||||
<nature>org.eclipse.jdt.core.javanature</nature>
|
|
||||||
<nature>net.sf.eclipsecs.core.CheckstyleNature</nature>
|
|
||||||
</natures>
|
</natures>
|
||||||
</projectDescription>
|
</projectDescription>
|
||||||
|
|||||||
BIN
AppIcon.png
Normal file
BIN
AppIcon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.3 KiB |
101
CHANGES.txt
101
CHANGES.txt
@@ -1,101 +0,0 @@
|
|||||||
Forge Beta: 10-01-2013 ver 1.5.1
|
|
||||||
|
|
||||||
|
|
||||||
13328 cards in total.
|
|
||||||
|
|
||||||
|
|
||||||
-------------
|
|
||||||
Release Notes
|
|
||||||
-------------
|
|
||||||
|
|
||||||
- Forge freezing during a match bug -
|
|
||||||
A number of people have reported this bug and we now feel that it may have been fixed in this version. Please play test this version and let us know.
|
|
||||||
|
|
||||||
|
|
||||||
- Skinned titlebar for main window -
|
|
||||||
Titlebar is now skinned instead of displaying using standard OS window titlebar
|
|
||||||
Maximizing window now displays full-screen
|
|
||||||
Can use Layout > View > Titlebar (F11) to toggle visibility of titlebar (will also open full-screen if hiding titlebar)
|
|
||||||
|
|
||||||
|
|
||||||
- Forge now requires Java 7 -
|
|
||||||
Please update your Java runtime environment. At this point Forge versions 1.4.2 and above will no longer run under Java 6. Those who are using Mac OS should install the JDK version rather than the JRE version.
|
|
||||||
|
|
||||||
|
|
||||||
- The Mac OS X application -
|
|
||||||
At this time Forge now requires Java 7 and will no longer run under Java 6.
|
|
||||||
|
|
||||||
Unfortunately, the Mac OS X builder that we were using does not support Java 7. We hope to find and to use a different Mac OS X builder in order to continue releasing a Mac OS bundled application like we have in the past.
|
|
||||||
|
|
||||||
Currently, the windows/unix release of Forge includes a launcher file named "forge.command". Double click on the "forge.command" launcher command file and this will in turn launch the Forge jar file via the terminal application while increasing the Java heap space. This should be a temporary inconvenience.
|
|
||||||
|
|
||||||
|
|
||||||
---------
|
|
||||||
New Cards
|
|
||||||
---------
|
|
||||||
|
|
||||||
Chaos Moon
|
|
||||||
Deep Water
|
|
||||||
Infernal Darkness
|
|
||||||
Mana Reflection
|
|
||||||
Mausoleum Turnkey
|
|
||||||
Naked Singularity
|
|
||||||
Pale Moon
|
|
||||||
Pulse of Llanowar
|
|
||||||
Reality Twist
|
|
||||||
Ritual of Subdual
|
|
||||||
|
|
||||||
|
|
||||||
-----------
|
|
||||||
New Schemes
|
|
||||||
-----------
|
|
||||||
|
|
||||||
Nature Demands an Offering
|
|
||||||
|
|
||||||
|
|
||||||
--------------------
|
|
||||||
New Vanguard Avatars
|
|
||||||
--------------------
|
|
||||||
|
|
||||||
Mirri
|
|
||||||
|
|
||||||
|
|
||||||
------------
|
|
||||||
Known Issues
|
|
||||||
------------
|
|
||||||
|
|
||||||
Several people have noticed that the cards displayed on the battlefield will fail to be displayed when the number of cards on the battlefield increases. Maximizing the human panel can help to re-display the cards.
|
|
||||||
|
|
||||||
Some time was spent turning the static ETB triggers into the proper ETB replacement effects they should be, mainly to interact correctly with each other. This work is not yet finished. As a result there is currently some inconsistencies with "Enters the battlefield with counters" (Not incredibly noticeable).
|
|
||||||
|
|
||||||
A recent contribution to the code base should fix some of the bugs that people noticed with cloning type abilities. At this time there is one remaining issue that we hope will be addressed in the near future:
|
|
||||||
Copies of cards that setup Zone Change triggers via addComesIntoPlayCommand and addLeavesPlayCommand will not function correctly.
|
|
||||||
|
|
||||||
The Forge archive includes a readme.txt file and we ask that you spend a few minutes reading this file as it contains some information that may prove useful. We do tend to update this file at times and you should quickly read this file and look for new information for each and every new release. Thank you.
|
|
||||||
|
|
||||||
The archive format used for the Forge distribution is ".tar.bz2". There are utilities for Windows, Mac OS and the various *nix's that can be used to extract/decompress these ".tar.bz2" archives. We recommend that you extract/decompress the Forge archive into a new and unused folder.
|
|
||||||
|
|
||||||
Some people use the Windows application 7zip. This utility can be found at http://www.7-zip.org/download.html. Mac users can double click on the archive and the application Archive Utility will launch and extract the archive. Mac users do not need to download a separate utility.
|
|
||||||
|
|
||||||
|
|
||||||
----------------------------
|
|
||||||
Contributors to This Release
|
|
||||||
----------------------------
|
|
||||||
|
|
||||||
DrDev
|
|
||||||
Dripton
|
|
||||||
Gos
|
|
||||||
Hellfish
|
|
||||||
Max
|
|
||||||
Sloth
|
|
||||||
spr
|
|
||||||
Swordshine
|
|
||||||
Chris H
|
|
||||||
|
|
||||||
|
|
||||||
(Quest icons used created by Teekatas, from his Legendora set http://raindropmemory.deviantart.com)
|
|
||||||
(Thanks to the MAGE team for permission to use their targeting arrows.)
|
|
||||||
(Thanks to http://www.freesound.org/browse/ for providing some sound files.)
|
|
||||||
|
|
||||||
|
|
||||||
end
|
|
||||||
9
forge-ai/.classpath
Normal file
9
forge-ai/.classpath
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<classpath>
|
||||||
|
<classpathentry kind="src" output="target/classes" path="src/main/java"/>
|
||||||
|
<classpathentry kind="src" output="target/test-classes" path="src/test/java"/>
|
||||||
|
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.7"/>
|
||||||
|
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER"/>
|
||||||
|
<classpathentry kind="con" path="org.testng.TESTNG_CONTAINER"/>
|
||||||
|
<classpathentry kind="output" path="target/classes"/>
|
||||||
|
</classpath>
|
||||||
23
forge-ai/.project
Normal file
23
forge-ai/.project
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<projectDescription>
|
||||||
|
<name>forge-ai</name>
|
||||||
|
<comment></comment>
|
||||||
|
<projects>
|
||||||
|
</projects>
|
||||||
|
<buildSpec>
|
||||||
|
<buildCommand>
|
||||||
|
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||||
|
<arguments>
|
||||||
|
</arguments>
|
||||||
|
</buildCommand>
|
||||||
|
<buildCommand>
|
||||||
|
<name>org.eclipse.m2e.core.maven2Builder</name>
|
||||||
|
<arguments>
|
||||||
|
</arguments>
|
||||||
|
</buildCommand>
|
||||||
|
</buildSpec>
|
||||||
|
<natures>
|
||||||
|
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||||
|
<nature>org.eclipse.m2e.core.maven2Nature</nature>
|
||||||
|
</natures>
|
||||||
|
</projectDescription>
|
||||||
4
forge-ai/.settings/org.eclipse.core.resources.prefs
Normal file
4
forge-ai/.settings/org.eclipse.core.resources.prefs
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
eclipse.preferences.version=1
|
||||||
|
encoding//src/main/java=ISO-8859-1
|
||||||
|
encoding//src/test/java=ISO-8859-1
|
||||||
|
encoding/<project>=ISO-8859-1
|
||||||
5
forge-ai/.settings/org.eclipse.jdt.core.prefs
Normal file
5
forge-ai/.settings/org.eclipse.jdt.core.prefs
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
eclipse.preferences.version=1
|
||||||
|
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7
|
||||||
|
org.eclipse.jdt.core.compiler.compliance=1.7
|
||||||
|
org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
|
||||||
|
org.eclipse.jdt.core.compiler.source=1.7
|
||||||
4
forge-ai/.settings/org.eclipse.m2e.core.prefs
Normal file
4
forge-ai/.settings/org.eclipse.m2e.core.prefs
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
activeProfiles=
|
||||||
|
eclipse.preferences.version=1
|
||||||
|
resolveWorkspaceProjects=true
|
||||||
|
version=1
|
||||||
28
forge-ai/pom.xml
Normal file
28
forge-ai/pom.xml
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<artifactId>forge</artifactId>
|
||||||
|
<groupId>forge</groupId>
|
||||||
|
<version>1.5.16</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<artifactId>forge-ai</artifactId>
|
||||||
|
<name>Forge AI</name>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>forge</groupId>
|
||||||
|
<artifactId>forge-core</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>forge</groupId>
|
||||||
|
<artifactId>forge-game</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
</dependencies>
|
||||||
|
</project>
|
||||||
@@ -15,25 +15,31 @@
|
|||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
package forge.game.ai;
|
package forge.ai;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Random;
|
|
||||||
|
|
||||||
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.Card;
|
import forge.ai.ability.AnimateAi;
|
||||||
import forge.CardLists;
|
import forge.game.GameEntity;
|
||||||
import forge.CounterType;
|
import forge.game.ability.ApiType;
|
||||||
import forge.GameEntity;
|
import forge.game.ability.effects.ProtectEffect;
|
||||||
import forge.card.trigger.Trigger;
|
import forge.game.card.Card;
|
||||||
import forge.card.trigger.TriggerType;
|
import forge.game.card.CardFactory;
|
||||||
|
import forge.game.card.CardLists;
|
||||||
|
import forge.game.card.CounterType;
|
||||||
import forge.game.combat.Combat;
|
import forge.game.combat.Combat;
|
||||||
import forge.game.combat.CombatUtil;
|
import forge.game.combat.CombatUtil;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
|
import forge.game.spellability.SpellAbility;
|
||||||
|
import forge.game.trigger.Trigger;
|
||||||
|
import forge.game.trigger.TriggerType;
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
|
import forge.util.MyRandom;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
|
||||||
//doesHumanAttackAndWin() uses the global variable AllZone.getComputerPlayer()
|
//doesHumanAttackAndWin() uses the global variable AllZone.getComputerPlayer()
|
||||||
@@ -58,6 +64,7 @@ public class AiAttackController {
|
|||||||
private List<Card> myList; // holds computer creatures
|
private List<Card> myList; // holds computer creatures
|
||||||
|
|
||||||
private final Player ai;
|
private final Player ai;
|
||||||
|
private Player defendingOpponent;
|
||||||
|
|
||||||
private int aiAggression = 0; // added by Masher, how aggressive the ai is
|
private int aiAggression = 0; // added by Masher, how aggressive the ai is
|
||||||
// attack will be depending on circumstances
|
// attack will be depending on circumstances
|
||||||
@@ -75,22 +82,69 @@ public class AiAttackController {
|
|||||||
*/
|
*/
|
||||||
public AiAttackController(final Player ai) {
|
public AiAttackController(final Player ai) {
|
||||||
this.ai = ai;
|
this.ai = ai;
|
||||||
Player opponent = ai.getOpponent();
|
this.defendingOpponent = choosePreferredDefenderPlayer();
|
||||||
|
this.oppList = getOpponentCreatures(this.defendingOpponent);
|
||||||
this.oppList = Lists.newArrayList();
|
|
||||||
this.oppList.addAll(opponent.getCreaturesInPlay());
|
|
||||||
this.myList = ai.getCreaturesInPlay();
|
this.myList = ai.getCreaturesInPlay();
|
||||||
|
|
||||||
|
|
||||||
this.attackers = new ArrayList<Card>();
|
this.attackers = new ArrayList<Card>();
|
||||||
for (Card c : myList) {
|
for (Card c : myList) {
|
||||||
if (CombatUtil.canAttack(c, opponent)) {
|
if (CombatUtil.canAttack(c, this.defendingOpponent)) {
|
||||||
attackers.add(c);
|
attackers.add(c);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.blockers = this.getPossibleBlockers(oppList, this.attackers);
|
this.blockers = this.getPossibleBlockers(oppList, this.attackers);
|
||||||
} // constructor
|
} // constructor
|
||||||
|
|
||||||
|
public AiAttackController(final Player ai, Card attacker) {
|
||||||
|
this.ai = ai;
|
||||||
|
this.defendingOpponent = choosePreferredDefenderPlayer();
|
||||||
|
this.oppList = getOpponentCreatures(this.defendingOpponent);
|
||||||
|
this.myList = ai.getCreaturesInPlay();
|
||||||
|
this.attackers = new ArrayList<Card>();
|
||||||
|
if (CombatUtil.canAttack(attacker, this.defendingOpponent)) {
|
||||||
|
attackers.add(attacker);
|
||||||
|
}
|
||||||
|
this.blockers = this.getPossibleBlockers(oppList, this.attackers);
|
||||||
|
} // overloaded constructor to evaluate single specified attacker
|
||||||
|
|
||||||
|
public static List<Card> getOpponentCreatures(final Player defender) {
|
||||||
|
List<Card> defenders = Lists.newArrayList();
|
||||||
|
defenders.addAll(defender.getCreaturesInPlay());
|
||||||
|
Predicate<Card> canAnimate = new Predicate<Card>() {
|
||||||
|
@Override
|
||||||
|
public boolean apply(Card c) {
|
||||||
|
return !c.isCreature() && !c.isPlaneswalker();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
for (Card c : CardLists.filter(defender.getCardsIn(ZoneType.Battlefield), canAnimate)) {
|
||||||
|
if (c.isToken() && !c.isCopiedToken()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (SpellAbility sa : c.getSpellAbilities()) {
|
||||||
|
if (sa.getApi() == ApiType.Animate) {
|
||||||
|
if (ComputerUtilCost.canPayCost(sa, defender)
|
||||||
|
&& sa.getRestrictions().checkOtherRestrictions(c, sa, defender)) {
|
||||||
|
Card animatedCopy = CardFactory.getCard(c.getPaperCard(), defender);
|
||||||
|
AnimateAi.becomeAnimated(animatedCopy, sa);
|
||||||
|
defenders.add(animatedCopy);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return defenders;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Choose opponent for AI to attack here. Expand as necessary. */
|
||||||
|
private Player choosePreferredDefenderPlayer() {
|
||||||
|
Player defender = ai.getWeakestOpponent(); //Gets opponent with the least life
|
||||||
|
|
||||||
|
if (defender.getLife() < 8) { //Concentrate on opponent within easy kill range
|
||||||
|
return defender;
|
||||||
|
} else { //Otherwise choose a random opponent to ensure no ganging up on players
|
||||||
|
defender = ai.getOpponents().get(MyRandom.getRandom().nextInt(ai.getOpponents().size()));
|
||||||
|
}
|
||||||
|
return defender;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>
|
* <p>
|
||||||
* sortAttackers.
|
* sortAttackers.
|
||||||
@@ -129,7 +183,7 @@ public class AiAttackController {
|
|||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param attacker
|
* @param attacker
|
||||||
* a {@link forge.Card} object.
|
* a {@link forge.game.card.Card} object.
|
||||||
* @param combat
|
* @param combat
|
||||||
* a {@link forge.game.combat.Combat} object.
|
* a {@link forge.game.combat.Combat} object.
|
||||||
* @return a boolean.
|
* @return a boolean.
|
||||||
@@ -141,7 +195,7 @@ public class AiAttackController {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
final Player opp = ai.getOpponent();
|
final Player opp = this.defendingOpponent;
|
||||||
if (ComputerUtilCombat.damageIfUnblocked(attacker, opp, combat) > 0) {
|
if (ComputerUtilCombat.damageIfUnblocked(attacker, opp, combat) > 0) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -192,7 +246,7 @@ public class AiAttackController {
|
|||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param c
|
* @param c
|
||||||
* a {@link forge.Card} object.
|
* a {@link forge.game.card.Card} object.
|
||||||
* @param attackers
|
* @param attackers
|
||||||
* a {@link forge.CardList} object.
|
* a {@link forge.CardList} object.
|
||||||
* @return a boolean.
|
* @return a boolean.
|
||||||
@@ -277,7 +331,7 @@ public class AiAttackController {
|
|||||||
return notNeededAsBlockers;
|
return notNeededAsBlockers;
|
||||||
}
|
}
|
||||||
|
|
||||||
final Player opp = ai.getOpponent();
|
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
|
||||||
@@ -385,11 +439,11 @@ public class AiAttackController {
|
|||||||
final List<Card> unblockedAttackers = new ArrayList<Card>();
|
final List<Card> unblockedAttackers = new ArrayList<Card>();
|
||||||
final List<Card> remainingAttackers = new ArrayList<Card>(this.attackers);
|
final List<Card> remainingAttackers = new ArrayList<Card>(this.attackers);
|
||||||
final List<Card> remainingBlockers = new ArrayList<Card>(this.blockers);
|
final List<Card> remainingBlockers = new ArrayList<Card>(this.blockers);
|
||||||
final Player opp = ai.getOpponent();
|
final Player opp = this.defendingOpponent;
|
||||||
|
|
||||||
|
|
||||||
for (Card attacker : attackers) {
|
for (Card attacker : attackers) {
|
||||||
if (!CombatUtil.canBeBlocked(attacker, this.blockers)
|
if (!CombatUtil.canBeBlocked(attacker, this.blockers, null)
|
||||||
|| attacker.hasKeyword("You may have CARDNAME assign its combat damage as though it weren't blocked.")) {
|
|| attacker.hasKeyword("You may have CARDNAME assign its combat damage as though it weren't blocked.")) {
|
||||||
unblockedAttackers.add(attacker);
|
unblockedAttackers.add(attacker);
|
||||||
}
|
}
|
||||||
@@ -449,27 +503,28 @@ public class AiAttackController {
|
|||||||
if (defs.size() == 1) {
|
if (defs.size() == 1) {
|
||||||
return defs.get(0);
|
return defs.get(0);
|
||||||
}
|
}
|
||||||
|
Player prefDefender = (Player) (defs.contains(this.defendingOpponent) ? this.defendingOpponent : defs.get(0));
|
||||||
|
|
||||||
final GameEntity entity = ai.getMustAttackEntity();
|
final GameEntity entity = ai.getMustAttackEntity();
|
||||||
if (null != entity) {
|
if (null != entity) {
|
||||||
int n = defs.indexOf(entity);
|
int n = defs.indexOf(entity);
|
||||||
if (-1 == n) {
|
if (-1 == n) {
|
||||||
System.out.println("getMustAttackEntity() returned something not in defenders.");
|
System.out.println("getMustAttackEntity() returned something not in defenders.");
|
||||||
return defs.get(0);
|
return prefDefender;
|
||||||
} else {
|
} else {
|
||||||
return entity;
|
return entity;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 1. assault the opponent if you can kill him
|
// 1. assault the opponent if you can kill him
|
||||||
if (bAssault) {
|
if (bAssault) {
|
||||||
return getDefendingPlayers(c).get(0);
|
return prefDefender;
|
||||||
}
|
}
|
||||||
// 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 pwDefending.get(0);
|
||||||
} else {
|
} else {
|
||||||
return defs.get(0);
|
return prefDefender;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -524,9 +579,7 @@ public class AiAttackController {
|
|||||||
mustAttack = true;
|
mustAttack = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if ((s.equals("At the beginning of the end step, destroy CARDNAME.")
|
if (attacker.getSVar("EndOfTurnLeavePlay").equals("True")
|
||||||
|| s.equals("At the beginning of the end step, exile CARDNAME.")
|
|
||||||
|| s.equals("At the beginning of the end step, sacrifice CARDNAME."))
|
|
||||||
&& isEffectiveAttacker(ai, attacker, combat)) {
|
&& isEffectiveAttacker(ai, attacker, combat)) {
|
||||||
mustAttack = true;
|
mustAttack = true;
|
||||||
break;
|
break;
|
||||||
@@ -623,7 +676,7 @@ public class AiAttackController {
|
|||||||
aiLifeToPlayerDamageRatio = (double) ai.getLife() / candidateCounterAttackDamage;
|
aiLifeToPlayerDamageRatio = (double) ai.getLife() / candidateCounterAttackDamage;
|
||||||
}
|
}
|
||||||
|
|
||||||
final Player opp = ai.getOpponent();
|
final Player opp = this.defendingOpponent;
|
||||||
// get the potential damage and strength of the AI forces
|
// get the potential damage and strength of the AI forces
|
||||||
final List<Card> candidateAttackers = new ArrayList<Card>();
|
final List<Card> candidateAttackers = new ArrayList<Card>();
|
||||||
int candidateUnblockedDamage = 0;
|
int candidateUnblockedDamage = 0;
|
||||||
@@ -678,7 +731,7 @@ public class AiAttackController {
|
|||||||
}
|
}
|
||||||
// until the attackers are used up or the player would run out of life
|
// until the attackers are used up or the player would run out of life
|
||||||
int attackRounds = 1;
|
int attackRounds = 1;
|
||||||
while (attritionalAttackers.size() > 0 && humanLife > 0 && attackRounds < 99) {
|
while (!attritionalAttackers.isEmpty() && humanLife > 0 && attackRounds < 99) {
|
||||||
// sum attacker damage
|
// sum attacker damage
|
||||||
int damageThisRound = 0;
|
int damageThisRound = 0;
|
||||||
for (int y = 0; y < attritionalAttackers.size(); y++) {
|
for (int y = 0; y < attritionalAttackers.size(); y++) {
|
||||||
@@ -689,7 +742,7 @@ public class AiAttackController {
|
|||||||
// shorten attacker list by the length of the blockers - assuming
|
// shorten attacker list by the length of the blockers - assuming
|
||||||
// all blocked are killed for convenience
|
// all blocked are killed for convenience
|
||||||
for (int z = 0; z < humanForcesForAttritionalAttack; z++) {
|
for (int z = 0; z < humanForcesForAttritionalAttack; z++) {
|
||||||
if (attritionalAttackers.size() > 0) {
|
if (!attritionalAttackers.isEmpty()) {
|
||||||
attritionalAttackers.remove(attritionalAttackers.size() - 1);
|
attritionalAttackers.remove(attritionalAttackers.size() - 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -755,9 +808,9 @@ public class AiAttackController {
|
|||||||
// 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 && (humanLifeToDamageRatio < 2 || outNumber > 0)) {
|
} else if (ratioDiff >= 1 && computerForces > 1 && (humanLifeToDamageRatio < 2 || outNumber > 0)) {
|
||||||
this.aiAggression = 4; // attack expecting to trade or damage player.
|
this.aiAggression = 4; // attack expecting to trade or damage player.
|
||||||
} else if (ratioDiff >= 0) {
|
} else if (ratioDiff >= 0 && computerForces > 1) {
|
||||||
this.aiAggression = 3; // attack expecting to make good trades or damage player.
|
this.aiAggression = 3; // attack expecting to make good trades or damage player.
|
||||||
} else if (ratioDiff + outNumber >= -1 || aiLifeToPlayerDamageRatio > 1
|
} else if (ratioDiff + outNumber >= -1 || aiLifeToPlayerDamageRatio > 1
|
||||||
|| ratioDiff * -1 < turnsUntilDeathByUnblockable) {
|
|| ratioDiff * -1 < turnsUntilDeathByUnblockable) {
|
||||||
@@ -792,7 +845,7 @@ public class AiAttackController {
|
|||||||
for (int i = 0; i < attackersLeft.size(); i++) {
|
for (int i = 0; i < attackersLeft.size(); i++) {
|
||||||
final Card attacker = attackersLeft.get(i);
|
final Card attacker = attackersLeft.get(i);
|
||||||
if (this.aiAggression < 5 && !attacker.hasFirstStrike() && !attacker.hasDoubleStrike()
|
if (this.aiAggression < 5 && !attacker.hasFirstStrike() && !attacker.hasDoubleStrike()
|
||||||
&& ComputerUtilCombat.getTotalFirstStrikeBlockPower(attacker, ai.getOpponent())
|
&& ComputerUtilCombat.getTotalFirstStrikeBlockPower(attacker, this.defendingOpponent)
|
||||||
>= ComputerUtilCombat.getDamageToKill(attacker)) {
|
>= ComputerUtilCombat.getDamageToKill(attacker)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -808,9 +861,9 @@ public class AiAttackController {
|
|||||||
List<Card> attacking = combat.getAttackersOf(defender);
|
List<Card> attacking = combat.getAttackersOf(defender);
|
||||||
CardLists.sortByPowerAsc(attacking);
|
CardLists.sortByPowerAsc(attacking);
|
||||||
for (Card atta : attacking) {
|
for (Card atta : attacking) {
|
||||||
if (attackNum >= blockNum || !CombatUtil.canBeBlocked(attacker, this.blockers)) {
|
if (attackNum >= blockNum || !CombatUtil.canBeBlocked(attacker, this.blockers, combat)) {
|
||||||
damage += ComputerUtilCombat.damageIfUnblocked(atta, opp, null);
|
damage += ComputerUtilCombat.damageIfUnblocked(atta, opp, null);
|
||||||
} else if (CombatUtil.canBeBlocked(attacker, this.blockers)) {
|
} else if (CombatUtil.canBeBlocked(attacker, this.blockers, combat)) {
|
||||||
attackNum++;
|
attackNum++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -859,7 +912,7 @@ public class AiAttackController {
|
|||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param c
|
* @param c
|
||||||
* a {@link forge.Card} object.
|
* a {@link forge.game.card.Card} object.
|
||||||
* @return a int.
|
* @return a int.
|
||||||
*/
|
*/
|
||||||
public final int getAttack(final Card c) {
|
public final int getAttack(final Card c) {
|
||||||
@@ -878,7 +931,7 @@ public class AiAttackController {
|
|||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param attacker
|
* @param attacker
|
||||||
* a {@link forge.Card} object.
|
* a {@link forge.game.card.Card} object.
|
||||||
* @param defenders
|
* @param defenders
|
||||||
* a {@link forge.CardList} object.
|
* a {@link forge.CardList} object.
|
||||||
* @param combat
|
* @param combat
|
||||||
@@ -937,7 +990,7 @@ public class AiAttackController {
|
|||||||
if (!canKillAllDangerous) {
|
if (!canKillAllDangerous) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (defender.getSVar("HasCombatEffect").equals("TRUE")) {
|
if (defender.getSVar("HasCombatEffect").equals("TRUE") || defender.getSVar("HasBlockEffect").equals("TRUE")) {
|
||||||
canKillAllDangerous = false;
|
canKillAllDangerous = false;
|
||||||
} else {
|
} else {
|
||||||
for (String keyword : defender.getKeyword()) {
|
for (String keyword : defender.getKeyword()) {
|
||||||
@@ -961,7 +1014,7 @@ public class AiAttackController {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (numberOfPossibleBlockers > 1 || (numberOfPossibleBlockers == 1 && CombatUtil.canAttackerBeBlockedWithAmount(attacker, 1))) {
|
if (numberOfPossibleBlockers > 1 || (numberOfPossibleBlockers == 1 && CombatUtil.canAttackerBeBlockedWithAmount(attacker, 1, combat))) {
|
||||||
canBeBlocked = true;
|
canBeBlocked = true;
|
||||||
}
|
}
|
||||||
/*System.out.println(attacker + " canBeKilledByOne: " + canBeKilledByOne + " canKillAll: "
|
/*System.out.println(attacker + " canBeKilledByOne: " + canBeKilledByOne + " canKillAll: "
|
||||||
@@ -1018,4 +1071,71 @@ public class AiAttackController {
|
|||||||
return false; // don't attack
|
return false; // don't attack
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private final boolean shouldAttack(final Player ai, final Card attacker, final Combat combat) {
|
||||||
|
aiAggression = 2;
|
||||||
|
return shouldAttack(ai, attacker, oppList, combat);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toProtectAttacker(SpellAbility sa) {
|
||||||
|
if (sa.getApi() != ApiType.Protection || oppList.isEmpty() || getPossibleBlockers(oppList, attackers).isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
final List<String> choices = ProtectEffect.getProtectionList(sa);
|
||||||
|
String color = ComputerUtilCard.getMostProminentColor(oppList), artifact = null;
|
||||||
|
if (choices.contains("artifacts")) {
|
||||||
|
artifact = "artifacts";
|
||||||
|
}
|
||||||
|
if (!choices.contains(color)) {
|
||||||
|
color = null;
|
||||||
|
}
|
||||||
|
for (Card c : oppList) {
|
||||||
|
if (!c.isArtifact()) {
|
||||||
|
artifact = null;
|
||||||
|
}
|
||||||
|
switch (color) {
|
||||||
|
case "black":
|
||||||
|
if (!c.isBlack()) {
|
||||||
|
color = null;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "blue":
|
||||||
|
if (!c.isBlue()) {
|
||||||
|
color = null;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "green":
|
||||||
|
if (!c.isGreen()) {
|
||||||
|
color = null;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "red":
|
||||||
|
if (!c.isRed()) {
|
||||||
|
color = null;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "white":
|
||||||
|
if (!c.isWhite()) {
|
||||||
|
color = null;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (color == null && artifact == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (color != null) {
|
||||||
|
return color;
|
||||||
|
}
|
||||||
|
if (artifact != null) {
|
||||||
|
return artifact;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean shouldThisAttack(final Player ai, Card attacker) {
|
||||||
|
AiAttackController aiAtk = new AiAttackController(ai, attacker);
|
||||||
|
Combat combat = ai.getGame().getCombat();
|
||||||
|
return aiAtk.shouldAttack(ai, attacker, combat);
|
||||||
|
}
|
||||||
|
|
||||||
} // end class ComputerUtil_Attack2
|
} // end class ComputerUtil_Attack2
|
||||||
@@ -15,26 +15,26 @@
|
|||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
package forge.game.ai;
|
package forge.ai;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
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.Card;
|
import forge.game.CardTraitBase;
|
||||||
import forge.CardLists;
|
import forge.game.GameEntity;
|
||||||
import forge.CardPredicates;
|
import forge.game.card.Card;
|
||||||
import forge.CounterType;
|
import forge.game.card.CardLists;
|
||||||
import forge.GameEntity;
|
import forge.game.card.CardPredicates;
|
||||||
import forge.card.TriggerReplacementBase;
|
import forge.game.card.CounterType;
|
||||||
import forge.card.trigger.Trigger;
|
|
||||||
import forge.card.trigger.TriggerType;
|
|
||||||
import forge.game.combat.Combat;
|
import forge.game.combat.Combat;
|
||||||
import forge.game.combat.CombatUtil;
|
import forge.game.combat.CombatUtil;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
|
import forge.game.trigger.Trigger;
|
||||||
|
import forge.game.trigger.TriggerType;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -137,7 +137,7 @@ public class AiBlockController {
|
|||||||
|
|
||||||
|
|
||||||
// Begin with the attackers that pose the biggest threat
|
// Begin with the attackers that pose the biggest threat
|
||||||
CardLists.sortByEvaluateCreature(firstAttacker);
|
ComputerUtilCard.sortByEvaluateCreature(firstAttacker);
|
||||||
CardLists.sortByPowerDesc(firstAttacker);
|
CardLists.sortByPowerDesc(firstAttacker);
|
||||||
|
|
||||||
// If I don't have any planeswalkers than sorting doesn't really matter
|
// If I don't have any planeswalkers than sorting doesn't really matter
|
||||||
@@ -186,7 +186,9 @@ public class AiBlockController {
|
|||||||
|
|
||||||
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.")) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -197,7 +199,7 @@ public class AiBlockController {
|
|||||||
final List<Card> safeBlockers = getSafeBlockers(combat, attacker, blockers);
|
final List<Card> safeBlockers = getSafeBlockers(combat, attacker, blockers);
|
||||||
List<Card> killingBlockers;
|
List<Card> killingBlockers;
|
||||||
|
|
||||||
if (safeBlockers.size() > 0) {
|
if (!safeBlockers.isEmpty()) {
|
||||||
// 1.Blockers that can destroy the attacker but won't get
|
// 1.Blockers that can destroy the attacker but won't get
|
||||||
// destroyed
|
// destroyed
|
||||||
killingBlockers = getKillingBlockers(combat, attacker, safeBlockers);
|
killingBlockers = getKillingBlockers(combat, attacker, safeBlockers);
|
||||||
@@ -205,6 +207,26 @@ public class AiBlockController {
|
|||||||
blocker = ComputerUtilCard.getWorstCreatureAI(killingBlockers);
|
blocker = ComputerUtilCard.getWorstCreatureAI(killingBlockers);
|
||||||
} else if (!attacker.hasKeyword("You may have CARDNAME assign its combat damage as though it weren't blocked.")) {
|
} else if (!attacker.hasKeyword("You may have CARDNAME assign its combat damage as though it weren't blocked.")) {
|
||||||
blocker = ComputerUtilCard.getWorstCreatureAI(safeBlockers);
|
blocker = ComputerUtilCard.getWorstCreatureAI(safeBlockers);
|
||||||
|
if (attacker.hasKeyword("Trample")) {
|
||||||
|
boolean doNotBlock = false;
|
||||||
|
for (Card other : attackersLeft) {
|
||||||
|
if (other.equals(attacker) || !CombatUtil.canBlock(other, blocker)
|
||||||
|
|| ComputerUtilCombat.canDestroyBlocker(ai, blocker, other, combat, false)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
int damageNext = other.getNetAttack();
|
||||||
|
if (other.hasKeyword("Trample")) {
|
||||||
|
damageNext -= blocker.getLethalDamage();
|
||||||
|
}
|
||||||
|
if (damageNext > blocker.getLethalDamage()) {
|
||||||
|
doNotBlock = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (doNotBlock) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
blockedButUnkilled.add(attacker);
|
blockedButUnkilled.add(attacker);
|
||||||
}
|
}
|
||||||
} // no safe blockers
|
} // no safe blockers
|
||||||
@@ -212,20 +234,26 @@ public class AiBlockController {
|
|||||||
// 3.Blockers that can destroy the attacker and have an upside when dying
|
// 3.Blockers that can destroy the attacker and have an upside when dying
|
||||||
killingBlockers = getKillingBlockers(combat, attacker, blockers);
|
killingBlockers = getKillingBlockers(combat, attacker, blockers);
|
||||||
for (Card b : killingBlockers) {
|
for (Card b : killingBlockers) {
|
||||||
if ((b.hasKeyword("Undying") && b.getCounters(CounterType.P1P1) == 0)
|
if ((b.hasKeyword("Undying") && b.getCounters(CounterType.P1P1) == 0) || b.hasSVar("SacMe")) {
|
||||||
|| !b.getSVar("SacMe").equals("")) {
|
|
||||||
blocker = b;
|
blocker = b;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 4.Blockers that can destroy the attacker and are worth less
|
// 4.Blockers that have a big upside when dying
|
||||||
|
for (Card b : blockers) {
|
||||||
|
if (b.hasSVar("SacMe") && Integer.parseInt(b.getSVar("SacMe")) > 3) {
|
||||||
|
blocker = b;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 5.Blockers that can destroy the attacker and are worth less
|
||||||
if (blocker == null && !killingBlockers.isEmpty()) {
|
if (blocker == null && !killingBlockers.isEmpty()) {
|
||||||
final Card worst = ComputerUtilCard.getWorstCreatureAI(killingBlockers);
|
final Card worst = ComputerUtilCard.getWorstCreatureAI(killingBlockers);
|
||||||
int value = ComputerUtilCard.evaluateCreature(attacker);
|
int value = ComputerUtilCard.evaluateCreature(attacker);
|
||||||
|
|
||||||
// check for triggers when unblocked
|
// check for triggers when unblocked
|
||||||
for (Trigger trigger : attacker.getTriggers()) {
|
for (Trigger trigger : attacker.getTriggers()) {
|
||||||
final HashMap<String, String> trigParams = trigger.getMapParams();
|
final Map<String, String> trigParams = trigger.getMapParams();
|
||||||
TriggerType mode = trigger.getMode();
|
TriggerType mode = trigger.getMode();
|
||||||
|
|
||||||
if (!trigger.requirementsCheck(attacker.getGame())) {
|
if (!trigger.requirementsCheck(attacker.getGame())) {
|
||||||
@@ -234,14 +262,14 @@ public class AiBlockController {
|
|||||||
|
|
||||||
if (mode == TriggerType.DamageDone) {
|
if (mode == TriggerType.DamageDone) {
|
||||||
if ((!trigParams.containsKey("ValidSource")
|
if ((!trigParams.containsKey("ValidSource")
|
||||||
|| TriggerReplacementBase.matchesValid(attacker, trigParams.get("ValidSource").split(","), attacker))
|
|| CardTraitBase.matchesValid(attacker, trigParams.get("ValidSource").split(","), attacker))
|
||||||
&& attacker.getNetCombatDamage() > 0
|
&& attacker.getNetCombatDamage() > 0
|
||||||
&& (!trigParams.containsKey("ValidTarget")
|
&& (!trigParams.containsKey("ValidTarget")
|
||||||
|| TriggerReplacementBase.matchesValid(combat.getDefenderByAttacker(attacker), trigParams.get("ValidTarget").split(","), attacker))) {
|
|| CardTraitBase.matchesValid(combat.getDefenderByAttacker(attacker), trigParams.get("ValidTarget").split(","), attacker))) {
|
||||||
value += 50;
|
value += 50;
|
||||||
}
|
}
|
||||||
} else if (mode == TriggerType.AttackerUnblocked) {
|
} else if (mode == TriggerType.AttackerUnblocked) {
|
||||||
if (TriggerReplacementBase.matchesValid(attacker, trigParams.get("ValidCard").split(","), attacker)) {
|
if (CardTraitBase.matchesValid(attacker, trigParams.get("ValidCard").split(","), attacker)) {
|
||||||
value += 50;
|
value += 50;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -278,7 +306,7 @@ public class AiBlockController {
|
|||||||
|
|
||||||
// Try to block an attacker without first strike with a gang of first strikers
|
// Try to block an attacker without first strike with a gang of first strikers
|
||||||
for (final Card attacker : attackersLeft) {
|
for (final Card attacker : attackersLeft) {
|
||||||
if (!attacker.hasKeyword("First Strike") && !attacker.hasKeyword("Double Strike")) {
|
if (!ComputerUtilCombat.dealsFirstStrikeDamage(attacker, false)) {
|
||||||
blockers = getPossibleBlockers(combat, attacker, blockersLeft, false);
|
blockers = getPossibleBlockers(combat, attacker, blockersLeft, false);
|
||||||
final List<Card> firstStrikeBlockers = new ArrayList<Card>();
|
final List<Card> firstStrikeBlockers = new ArrayList<Card>();
|
||||||
final List<Card> blockGang = new ArrayList<Card>();
|
final List<Card> blockGang = new ArrayList<Card>();
|
||||||
@@ -334,8 +362,8 @@ public class AiBlockController {
|
|||||||
usableBlockers = CardLists.filter(blockers, new Predicate<Card>() {
|
usableBlockers = CardLists.filter(blockers, new Predicate<Card>() {
|
||||||
@Override
|
@Override
|
||||||
public boolean apply(final Card c) {
|
public boolean apply(final Card c) {
|
||||||
if ((attacker.hasKeyword("First Strike") || attacker.hasKeyword("Double Strike"))
|
if (ComputerUtilCombat.dealsFirstStrikeDamage(attacker, false)
|
||||||
&& !(c.hasKeyword("First Strike") || c.hasKeyword("Double Strike"))) {
|
&& !ComputerUtilCombat.dealsFirstStrikeDamage(c, false)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return lifeInDanger || (ComputerUtilCard.evaluateCreature(c) + diff) < ComputerUtilCard.evaluateCreature(attacker);
|
return lifeInDanger || (ComputerUtilCard.evaluateCreature(c) + diff) < ComputerUtilCard.evaluateCreature(attacker);
|
||||||
@@ -402,7 +430,9 @@ public class AiBlockController {
|
|||||||
|
|
||||||
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.")) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -437,7 +467,8 @@ public class AiBlockController {
|
|||||||
|
|
||||||
Card attacker = attackers.get(0);
|
Card attacker = attackers.get(0);
|
||||||
|
|
||||||
if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT") || attacker.hasKeyword("You may have CARDNAME assign its combat damage as though it weren't blocked.")) {
|
if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT") || attacker.hasKeyword("You may have CARDNAME assign its combat damage as though it weren't blocked.")
|
||||||
|
|| attacker.hasKeyword("CARDNAME can't be blocked unless all creatures defending player controls block it.")) {
|
||||||
attackers.remove(0);
|
attackers.remove(0);
|
||||||
makeChumpBlocks(combat, attackers);
|
makeChumpBlocks(combat, attackers);
|
||||||
return;
|
return;
|
||||||
@@ -484,11 +515,12 @@ public class AiBlockController {
|
|||||||
|
|
||||||
for (final Card attacker : currentAttackers) {
|
for (final Card attacker : currentAttackers) {
|
||||||
|
|
||||||
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.")) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
List<Card> possibleBlockers = getPossibleBlockers(combat, attacker, blockersLeft, true);
|
List<Card> possibleBlockers = getPossibleBlockers(combat, attacker, blockersLeft, true);
|
||||||
if (!CombatUtil.canAttackerBeBlockedWithAmount(attacker, possibleBlockers.size())) {
|
if (!CombatUtil.canAttackerBeBlockedWithAmount(attacker, possibleBlockers.size(), combat)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
List<Card> usedBlockers = new ArrayList<Card>();
|
List<Card> usedBlockers = new ArrayList<Card>();
|
||||||
@@ -496,12 +528,12 @@ public class AiBlockController {
|
|||||||
if (CombatUtil.canBlock(attacker, blocker, combat)) {
|
if (CombatUtil.canBlock(attacker, blocker, combat)) {
|
||||||
combat.addBlocker(attacker, blocker);
|
combat.addBlocker(attacker, blocker);
|
||||||
usedBlockers.add(blocker);
|
usedBlockers.add(blocker);
|
||||||
if (CombatUtil.canAttackerBeBlockedWithAmount(attacker, usedBlockers.size())) {
|
if (CombatUtil.canAttackerBeBlockedWithAmount(attacker, usedBlockers.size(), combat)) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (CombatUtil.canAttackerBeBlockedWithAmount(attacker, usedBlockers.size())) {
|
if (CombatUtil.canAttackerBeBlockedWithAmount(attacker, usedBlockers.size(), combat)) {
|
||||||
attackersLeft.remove(attacker);
|
attackersLeft.remove(attacker);
|
||||||
} else {
|
} else {
|
||||||
for (Card blocker : usedBlockers) {
|
for (Card blocker : usedBlockers) {
|
||||||
@@ -526,7 +558,8 @@ public class AiBlockController {
|
|||||||
for (final Card attacker : tramplingAttackers) {
|
for (final Card attacker : tramplingAttackers) {
|
||||||
|
|
||||||
if ((attacker.hasStartOfKeyword("CantBeBlockedByAmount LT") && !combat.isBlocked(attacker))
|
if ((attacker.hasStartOfKeyword("CantBeBlockedByAmount LT") && !combat.isBlocked(attacker))
|
||||||
|| attacker.hasKeyword("You may have CARDNAME assign its combat damage as though it weren't blocked.")) {
|
|| attacker.hasKeyword("You may have CARDNAME assign its combat damage as though it weren't blocked.")
|
||||||
|
|| attacker.hasKeyword("CARDNAME can't be blocked unless all creatures defending player controls block it.")) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -579,7 +612,7 @@ public class AiBlockController {
|
|||||||
// than the attacker
|
// than the attacker
|
||||||
// Don't use blockers without First Strike or Double Strike if
|
// Don't use blockers without First Strike or Double Strike if
|
||||||
// attacker has it
|
// attacker has it
|
||||||
if (attacker.hasKeyword("First Strike") || attacker.hasKeyword("Double Strike")) {
|
if (ComputerUtilCombat.dealsFirstStrikeDamage(attacker, false)) {
|
||||||
safeBlockers = CardLists.getKeyword(blockers, "First Strike");
|
safeBlockers = CardLists.getKeyword(blockers, "First Strike");
|
||||||
safeBlockers.addAll(CardLists.getKeyword(blockers, "Double Strike"));
|
safeBlockers.addAll(CardLists.getKeyword(blockers, "Double Strike"));
|
||||||
} else {
|
} else {
|
||||||
@@ -619,8 +652,18 @@ public class AiBlockController {
|
|||||||
|
|
||||||
/** Assigns blockers for the provided combat instance (in favor of player passes to ctor) */
|
/** Assigns blockers for the provided combat instance (in favor of player passes to ctor) */
|
||||||
public void assignBlockers(final Combat combat) {
|
public void assignBlockers(final Combat combat) {
|
||||||
|
assignBlockers(combat, null);
|
||||||
|
}
|
||||||
|
|
||||||
final List<Card> possibleBlockers = ai.getCreaturesInPlay();
|
public void assignBlockers(final Combat combat, Card evalBlocker) {
|
||||||
|
|
||||||
|
List<Card> possibleBlockers = null;
|
||||||
|
if (evalBlocker == null) {
|
||||||
|
possibleBlockers = ai.getCreaturesInPlay();
|
||||||
|
} else {
|
||||||
|
possibleBlockers = new ArrayList<Card>();
|
||||||
|
possibleBlockers.add(evalBlocker);
|
||||||
|
}
|
||||||
|
|
||||||
attackers = sortPotentialAttackers(combat);
|
attackers = sortPotentialAttackers(combat);
|
||||||
|
|
||||||
@@ -756,7 +799,7 @@ public class AiBlockController {
|
|||||||
public static List<Card> orderBlockers(Card attacker, List<Card> blockers) {
|
public static List<Card> orderBlockers(Card attacker, List<Card> blockers) {
|
||||||
// ordering of blockers, sort by evaluate, then try to kill the best
|
// ordering of blockers, sort by evaluate, then try to kill the best
|
||||||
int damage = attacker.getNetCombatDamage();
|
int damage = attacker.getNetCombatDamage();
|
||||||
CardLists.sortByEvaluateCreature(blockers);
|
ComputerUtilCard.sortByEvaluateCreature(blockers);
|
||||||
final List<Card> first = new ArrayList<Card>();
|
final List<Card> first = new ArrayList<Card>();
|
||||||
final List<Card> last = new ArrayList<Card>();
|
final List<Card> last = new ArrayList<Card>();
|
||||||
for (Card blocker : blockers) {
|
for (Card blocker : blockers) {
|
||||||
@@ -780,7 +823,7 @@ public class AiBlockController {
|
|||||||
// This shouldn't really take trample into account, but otherwise should be pretty similar to orderBlockers
|
// This shouldn't really take trample into account, but otherwise should be pretty similar to orderBlockers
|
||||||
// ordering of blockers, sort by evaluate, then try to kill the best
|
// ordering of blockers, sort by evaluate, then try to kill the best
|
||||||
int damage = blocker.getNetCombatDamage();
|
int damage = blocker.getNetCombatDamage();
|
||||||
CardLists.sortByEvaluateCreature(attackers);
|
ComputerUtilCard.sortByEvaluateCreature(attackers);
|
||||||
final List<Card> first = new ArrayList<Card>();
|
final List<Card> first = new ArrayList<Card>();
|
||||||
final List<Card> last = new ArrayList<Card>();
|
final List<Card> last = new ArrayList<Card>();
|
||||||
for (Card attacker : attackers) {
|
for (Card attacker : attackers) {
|
||||||
@@ -799,4 +842,16 @@ public class AiBlockController {
|
|||||||
|
|
||||||
return first;
|
return first;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean shouldThisBlock(final Player ai, Card blocker) {
|
||||||
|
AiBlockController aiBlk = new AiBlockController(ai);
|
||||||
|
Combat combat = ai.getGame().getCombat();
|
||||||
|
aiBlk.assignBlockers(combat, blocker);
|
||||||
|
if (combat.getAllBlockers().isEmpty()) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
combat.removeFromCombat(blocker);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
1511
forge-ai/src/main/java/forge/ai/AiController.java
Normal file
1511
forge-ai/src/main/java/forge/ai/AiController.java
Normal file
File diff suppressed because it is too large
Load Diff
590
forge-ai/src/main/java/forge/ai/AiCostDecision.java
Normal file
590
forge-ai/src/main/java/forge/ai/AiCostDecision.java
Normal file
@@ -0,0 +1,590 @@
|
|||||||
|
package forge.ai;
|
||||||
|
|
||||||
|
import com.google.common.base.Predicate;
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
|
||||||
|
import forge.card.CardType;
|
||||||
|
import forge.game.Game;
|
||||||
|
import forge.game.ability.AbilityUtils;
|
||||||
|
import forge.game.card.Card;
|
||||||
|
import forge.game.card.CardLists;
|
||||||
|
import forge.game.card.CardPredicates;
|
||||||
|
import forge.game.card.CardPredicates.Presets;
|
||||||
|
import forge.game.card.CounterType;
|
||||||
|
import forge.game.cost.*;
|
||||||
|
import forge.game.player.Player;
|
||||||
|
import forge.game.spellability.SpellAbility;
|
||||||
|
import forge.game.zone.ZoneType;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class AiCostDecision extends CostDecisionMakerBase implements ICostVisitor<PaymentDecision> {
|
||||||
|
|
||||||
|
|
||||||
|
private final SpellAbility ability;
|
||||||
|
private final Card source;
|
||||||
|
|
||||||
|
public AiCostDecision(Player ai0, SpellAbility sa) {
|
||||||
|
super(ai0);
|
||||||
|
ability = sa;
|
||||||
|
source = ability.getHostCard();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PaymentDecision visit(CostAddMana cost) {
|
||||||
|
Integer c = cost.convertAmount();
|
||||||
|
|
||||||
|
if (c == null) {
|
||||||
|
c = AbilityUtils.calculateAmount(source, cost.getAmount(), ability);
|
||||||
|
}
|
||||||
|
|
||||||
|
return PaymentDecision.number(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PaymentDecision visit(CostChooseCreatureType cost) {
|
||||||
|
String choice = player.getController().chooseSomeType("Creature", ability, new ArrayList<String>(CardType.getCreatureTypes()), new ArrayList<String>());
|
||||||
|
return PaymentDecision.type(choice);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PaymentDecision visit(CostDiscard cost) {
|
||||||
|
final String type = cost.getType();
|
||||||
|
|
||||||
|
final List<Card> hand = player.getCardsIn(ZoneType.Hand);
|
||||||
|
if (type.equals("LastDrawn")) {
|
||||||
|
if (!hand.contains(player.getLastDrawnCard())) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return PaymentDecision.card(player.getLastDrawnCard());
|
||||||
|
}
|
||||||
|
else if (cost.payCostFromSource()) {
|
||||||
|
if (!hand.contains(source)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return PaymentDecision.card(source);
|
||||||
|
}
|
||||||
|
else if (type.equals("Hand")) {
|
||||||
|
return PaymentDecision.card(hand);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type.contains("WithSameName")) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
Integer c = cost.convertAmount();
|
||||||
|
if (c == null) {
|
||||||
|
final String sVar = ability.getSVar(cost.getAmount());
|
||||||
|
if (sVar.equals("XChoice")) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
c = AbilityUtils.calculateAmount(source, cost.getAmount(), ability);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type.equals("Random")) {
|
||||||
|
return PaymentDecision.card(CardLists.getRandomSubList(hand, c));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
final AiController aic = ((PlayerControllerAi)player.getController()).getAi();
|
||||||
|
return PaymentDecision.card(aic.getCardsToDiscard(c, type.split(";"), ability));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PaymentDecision visit(CostDamage cost) {
|
||||||
|
Integer c = cost.convertAmount();
|
||||||
|
|
||||||
|
if (c == null) {
|
||||||
|
final String sVar = ability.getSVar(cost.getAmount());
|
||||||
|
// Generalize cost
|
||||||
|
if (sVar.equals("XChoice")) {
|
||||||
|
return null; // cannot pay
|
||||||
|
} else {
|
||||||
|
c = AbilityUtils.calculateAmount(source, cost.getAmount(), ability);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return PaymentDecision.number(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PaymentDecision visit(CostDraw cost) {
|
||||||
|
Integer c = cost.convertAmount();
|
||||||
|
|
||||||
|
if (c == null) {
|
||||||
|
c = AbilityUtils.calculateAmount(source, cost.getAmount(), ability);
|
||||||
|
}
|
||||||
|
|
||||||
|
return PaymentDecision.number(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PaymentDecision visit(CostExile cost) {
|
||||||
|
if (cost.payCostFromSource()) {
|
||||||
|
return PaymentDecision.card(source);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cost.getType().equals("All")) {
|
||||||
|
return PaymentDecision.card(player.getCardsIn(cost.getFrom()));
|
||||||
|
}
|
||||||
|
else if (cost.getType().contains("FromTopGrave")) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Integer c = cost.convertAmount();
|
||||||
|
if (c == null) {
|
||||||
|
final String sVar = ability.getSVar(cost.getAmount());
|
||||||
|
// Generalize cost
|
||||||
|
if (sVar.equals("XChoice")) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
c = AbilityUtils.calculateAmount(source, cost.getAmount(), ability);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cost.getFrom().equals(ZoneType.Library)) {
|
||||||
|
return PaymentDecision.card(player.getCardsIn(ZoneType.Library, c));
|
||||||
|
}
|
||||||
|
else if (cost.sameZone) {
|
||||||
|
// TODO Determine exile from same zone for AI
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
List<Card> chosen = ComputerUtil.chooseExileFrom(player, cost.getFrom(), cost.getType(), source, ability.getTargetCard(), c);
|
||||||
|
return null == chosen ? null : PaymentDecision.card(chosen);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PaymentDecision visit(CostExiledMoveToGrave cost) {
|
||||||
|
Integer c = cost.convertAmount();
|
||||||
|
List<Card> chosen = new ArrayList<Card>();
|
||||||
|
|
||||||
|
if (c == null) {
|
||||||
|
c = AbilityUtils.calculateAmount(source, cost.getAmount(), ability);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Card> typeList = player.getGame().getCardsIn(ZoneType.Exile);
|
||||||
|
|
||||||
|
typeList = CardLists.getValidCards(typeList, cost.getType().split(";"), player, source);
|
||||||
|
|
||||||
|
if (typeList.size() < c) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
CardLists.sortByPowerAsc(typeList);
|
||||||
|
Collections.reverse(typeList);
|
||||||
|
|
||||||
|
for (int i = 0; i < c; i++) {
|
||||||
|
chosen.add(typeList.get(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
return chosen.isEmpty() ? null : PaymentDecision.card(chosen);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PaymentDecision visit(CostFlipCoin cost) {
|
||||||
|
Integer c = cost.convertAmount();
|
||||||
|
if (c == null) {
|
||||||
|
final String sVar = ability.getSVar(cost.getAmount());
|
||||||
|
// Generalize cost
|
||||||
|
if (sVar.equals("XChoice")) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
c = AbilityUtils.calculateAmount(source, cost.getAmount(), ability);
|
||||||
|
}
|
||||||
|
return PaymentDecision.number(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PaymentDecision visit(CostGainControl cost) {
|
||||||
|
if (cost.payCostFromSource())
|
||||||
|
return PaymentDecision.card(source);
|
||||||
|
|
||||||
|
Integer c = cost.convertAmount();
|
||||||
|
if (c == null) {
|
||||||
|
c = AbilityUtils.calculateAmount(source, cost.getAmount(), ability);
|
||||||
|
}
|
||||||
|
|
||||||
|
final List<Card> typeList = CardLists.getValidCards(player.getGame().getCardsIn(ZoneType.Battlefield), cost.getType().split(";"), player, source);
|
||||||
|
|
||||||
|
|
||||||
|
if (typeList.size() < c) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
CardLists.sortByPowerAsc(typeList);
|
||||||
|
final List<Card> res = new ArrayList<Card>();
|
||||||
|
|
||||||
|
for (int i = 0; i < c; i++) {
|
||||||
|
res.add(typeList.get(i));
|
||||||
|
}
|
||||||
|
return res.isEmpty() ? null : PaymentDecision.card(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PaymentDecision visit(CostGainLife cost) {
|
||||||
|
final List<Player> oppsThatCanGainLife = new ArrayList<Player>();
|
||||||
|
|
||||||
|
for (final Player opp : cost.getPotentialTargets(player, source)) {
|
||||||
|
if (opp.canGainLife()) {
|
||||||
|
oppsThatCanGainLife.add(opp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (oppsThatCanGainLife.size() == 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return PaymentDecision.players(oppsThatCanGainLife);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PaymentDecision visit(CostMill cost) {
|
||||||
|
Integer c = cost.convertAmount();
|
||||||
|
if (c == null) {
|
||||||
|
final String sVar = ability.getSVar(cost.getAmount());
|
||||||
|
// Generalize cost
|
||||||
|
if (sVar.equals("XChoice")) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
c = AbilityUtils.calculateAmount(source, cost.getAmount(), ability);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Card> topLib = player.getCardsIn(ZoneType.Library, c);
|
||||||
|
return topLib.size() < c ? null : PaymentDecision.card(topLib);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PaymentDecision visit(CostPartMana cost) {
|
||||||
|
return PaymentDecision.number(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PaymentDecision visit(CostPayLife cost) {
|
||||||
|
Integer c = cost.convertAmount();
|
||||||
|
if (c == null) {
|
||||||
|
final String sVar = ability.getSVar(cost.getAmount());
|
||||||
|
// Generalize cost
|
||||||
|
if (sVar.equals("XChoice")) {
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
c = AbilityUtils.calculateAmount(source, cost.getAmount(), ability);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!player.canPayLife(c)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// activator.payLife(c, null);
|
||||||
|
return PaymentDecision.number(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PaymentDecision visit(CostPutCardToLib cost) {
|
||||||
|
Integer c = cost.convertAmount();
|
||||||
|
final Game game = player.getGame();
|
||||||
|
List<Card> chosen = new ArrayList<Card>();
|
||||||
|
List<Card> list;
|
||||||
|
|
||||||
|
if (cost.isSameZone()) {
|
||||||
|
list = new ArrayList<Card>(game.getCardsIn(cost.getFrom()));
|
||||||
|
} else {
|
||||||
|
list = new ArrayList<Card>(player.getCardsIn(cost.getFrom()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (c == null) {
|
||||||
|
final String sVar = ability.getSVar(cost.getAmount());
|
||||||
|
// Generalize cost
|
||||||
|
if (sVar.equals("XChoice")) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
c = AbilityUtils.calculateAmount(source, cost.getAmount(), ability);
|
||||||
|
}
|
||||||
|
|
||||||
|
list = CardLists.getValidCards(list, cost.getType().split(";"), player, source);
|
||||||
|
|
||||||
|
if (cost.isSameZone()) {
|
||||||
|
// Jotun Grunt
|
||||||
|
// TODO: improve AI
|
||||||
|
final List<Player> players = game.getPlayers();
|
||||||
|
for (Player p : players) {
|
||||||
|
List<Card> enoughType = CardLists.filter(list, CardPredicates.isOwner(p));
|
||||||
|
if (enoughType.size() >= c) {
|
||||||
|
chosen.addAll(enoughType);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
chosen = chosen.subList(0, c);
|
||||||
|
} else {
|
||||||
|
chosen = ComputerUtil.choosePutToLibraryFrom(player, cost.getFrom(), cost.getType(), source, ability.getTargetCard(), c);
|
||||||
|
}
|
||||||
|
return chosen.isEmpty() ? null : PaymentDecision.card(chosen);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PaymentDecision visit(CostPutCounter cost) {
|
||||||
|
|
||||||
|
if (cost.payCostFromSource()) {
|
||||||
|
return PaymentDecision.card(source);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
final List<Card> typeList = CardLists.getValidCards(player.getGame().getCardsIn(ZoneType.Battlefield), cost.getType().split(";"), player, source);
|
||||||
|
|
||||||
|
Card card = null;
|
||||||
|
if (cost.getType().equals("Creature.YouCtrl")) {
|
||||||
|
card = ComputerUtilCard.getWorstCreatureAI(typeList);
|
||||||
|
} else {
|
||||||
|
card = ComputerUtilCard.getWorstPermanentAI(typeList, false, false, false, false);
|
||||||
|
}
|
||||||
|
return PaymentDecision.card(card);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PaymentDecision visit(CostTap cost) {
|
||||||
|
return PaymentDecision.number(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PaymentDecision visit(CostTapType cost) {
|
||||||
|
final String amount = cost.getAmount();
|
||||||
|
Integer c = cost.convertAmount();
|
||||||
|
if (c == null) {
|
||||||
|
final String sVar = ability.getSVar(amount);
|
||||||
|
if (sVar.equals("XChoice")) {
|
||||||
|
List<Card> typeList =
|
||||||
|
CardLists.getValidCards(player.getCardsIn(ZoneType.Battlefield), cost.getType().split(";"), ability.getActivatingPlayer(), ability.getHostCard());
|
||||||
|
typeList = CardLists.filter(typeList, Presets.UNTAPPED);
|
||||||
|
c = typeList.size();
|
||||||
|
source.setSVar("ChosenX", "Number$" + Integer.toString(c));
|
||||||
|
} else {
|
||||||
|
c = AbilityUtils.calculateAmount(source, amount, ability);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (cost.getType().contains("sharesCreatureTypeWith") || cost.getType().contains("withTotalPowerGE")) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Card> totap = ComputerUtil.chooseTapType(player, cost.getType(), source, !cost.canTapSource, c);
|
||||||
|
|
||||||
|
|
||||||
|
if (totap == null) {
|
||||||
|
System.out.println("Couldn't find a valid card to tap for: " + source.getName());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return PaymentDecision.card(totap);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PaymentDecision visit(CostSacrifice cost) {
|
||||||
|
if (cost.payCostFromSource()) {
|
||||||
|
return PaymentDecision.card(source);
|
||||||
|
}
|
||||||
|
if (cost.getAmount().equals("All")) {
|
||||||
|
/*List<Card> typeList = new ArrayList<Card>(activator.getCardsIn(ZoneType.Battlefield));
|
||||||
|
typeList = CardLists.getValidCards(typeList, cost.getType().split(";"), activator, source);
|
||||||
|
if (activator.hasKeyword("You can't sacrifice creatures to cast spells or activate abilities.")) {
|
||||||
|
typeList = CardLists.getNotType(typeList, "Creature");
|
||||||
|
}*/
|
||||||
|
// Does the AI want to use Sacrifice All?
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Integer c = cost.convertAmount();
|
||||||
|
if (c == null) {
|
||||||
|
if (ability.getSVar(cost.getAmount()).equals("XChoice")) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
c = AbilityUtils.calculateAmount(source, cost.getAmount(), ability);
|
||||||
|
}
|
||||||
|
List<Card> list = ComputerUtil.chooseSacrificeType(player, cost.getType(), source, ability.getTargetCard(), c);
|
||||||
|
return PaymentDecision.card(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PaymentDecision visit(CostReturn cost) {
|
||||||
|
if (cost.payCostFromSource())
|
||||||
|
return PaymentDecision.card(source);
|
||||||
|
|
||||||
|
Integer c = cost.convertAmount();
|
||||||
|
if (c == null) {
|
||||||
|
c = AbilityUtils.calculateAmount(source, cost.getAmount(), ability);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Card> res = ComputerUtil.chooseReturnType(player, cost.getType(), source, ability.getTargetCard(), c);
|
||||||
|
return res.isEmpty() ? null : PaymentDecision.card(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PaymentDecision visit(CostReveal cost) {
|
||||||
|
|
||||||
|
final String type = cost.getType();
|
||||||
|
List<Card> hand = new ArrayList<Card>(player.getCardsIn(ZoneType.Hand));
|
||||||
|
|
||||||
|
if (cost.payCostFromSource()) {
|
||||||
|
if (!hand.contains(source)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return PaymentDecision.card(source);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cost.getType().equals("Hand"))
|
||||||
|
return PaymentDecision.card(player.getCardsIn(ZoneType.Hand));
|
||||||
|
|
||||||
|
if (cost.getType().equals("SameColor")) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
hand = CardLists.getValidCards(hand, type.split(";"), player, source);
|
||||||
|
Integer c = cost.convertAmount();
|
||||||
|
if (c == null) {
|
||||||
|
final String sVar = ability.getSVar(cost.getAmount());
|
||||||
|
if (sVar.equals("XChoice")) {
|
||||||
|
c = hand.size();
|
||||||
|
} else {
|
||||||
|
c = AbilityUtils.calculateAmount(source, cost.getAmount(), ability);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final AiController aic = ((PlayerControllerAi)player.getController()).getAi();
|
||||||
|
return PaymentDecision.card(aic.getCardsToDiscard(c, type.split(";"), ability));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PaymentDecision visit(CostRemoveAnyCounter cost) {
|
||||||
|
final String amount = cost.getAmount();
|
||||||
|
final int c = AbilityUtils.calculateAmount(source, amount, ability);
|
||||||
|
final String type = cost.getType();
|
||||||
|
|
||||||
|
List<Card> typeList = CardLists.getValidCards(player.getCardsIn(ZoneType.Battlefield), type.split(";"), player, source);
|
||||||
|
List<Card> hperms = CardLists.filter(typeList, new Predicate<Card>() {
|
||||||
|
@Override
|
||||||
|
public boolean apply(final Card crd) {
|
||||||
|
for (final CounterType c1 : CounterType.values()) {
|
||||||
|
if (crd.getCounters(c1) >= c && ComputerUtil.isNegativeCounter(c1, crd)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if(hperms.isEmpty())
|
||||||
|
return null;
|
||||||
|
|
||||||
|
PaymentDecision result = PaymentDecision.card(hperms);
|
||||||
|
Card valid = hperms.get(0);
|
||||||
|
for (CounterType c1 : valid.getCounters().keySet()) {
|
||||||
|
if (valid.getCounters(c1) >= c && ComputerUtil.isNegativeCounter(c1, valid)) {
|
||||||
|
result.ct = c1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Only find cards with enough negative counters
|
||||||
|
// TODO: add ai for Chisei, Heart of Oceans
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PaymentDecision visit(CostRemoveCounter cost) {
|
||||||
|
final String amount = cost.getAmount();
|
||||||
|
Integer c = cost.convertAmount();
|
||||||
|
final String type = cost.getType();
|
||||||
|
|
||||||
|
if (c == null) {
|
||||||
|
final String sVar = ability.getSVar(amount);
|
||||||
|
if (sVar.equals("XChoice")) {
|
||||||
|
c = AbilityUtils.calculateAmount(source, "ChosenX", ability);
|
||||||
|
} else if (amount.equals("All")) {
|
||||||
|
c = source.getCounters(cost.counter);
|
||||||
|
} else {
|
||||||
|
c = AbilityUtils.calculateAmount(source, amount, ability);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!cost.payCostFromSource()) {
|
||||||
|
List<Card> typeList;
|
||||||
|
if (type.equals("OriginalHost")) {
|
||||||
|
typeList = Lists.newArrayList(ability.getOriginalHost());
|
||||||
|
} else {
|
||||||
|
typeList = CardLists.getValidCards(player.getCardsIn(cost.zone), type.split(";"), player, source);
|
||||||
|
}
|
||||||
|
for (Card card : typeList) {
|
||||||
|
if (card.getCounters(cost.counter) >= c) {
|
||||||
|
return PaymentDecision.card(card, c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (c > source.getCounters(cost.counter)) {
|
||||||
|
System.out.println("Not enough " + cost.counter + " on " + source.getName());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return PaymentDecision.card(source, c);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PaymentDecision visit(CostUntapType cost) {
|
||||||
|
final String amount = cost.getAmount();
|
||||||
|
Integer c = cost.convertAmount();
|
||||||
|
if (c == null) {
|
||||||
|
final String sVar = ability.getSVar(amount);
|
||||||
|
if (sVar.equals("XChoice")) {
|
||||||
|
List<Card> typeList = player.getGame().getCardsIn(ZoneType.Battlefield);
|
||||||
|
typeList = CardLists.getValidCards(typeList, cost.getType().split(";"), player, ability.getHostCard());
|
||||||
|
if (!cost.canUntapSource) {
|
||||||
|
typeList.remove(source);
|
||||||
|
}
|
||||||
|
typeList = CardLists.filter(typeList, Presets.TAPPED);
|
||||||
|
c = typeList.size();
|
||||||
|
source.setSVar("ChosenX", "Number$" + Integer.toString(c));
|
||||||
|
} else {
|
||||||
|
c = AbilityUtils.calculateAmount(source, amount, ability);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Card> list = ComputerUtil.chooseUntapType(player, cost.getType(), source, cost.canUntapSource, c);
|
||||||
|
|
||||||
|
if (list == null) {
|
||||||
|
System.out.println("Couldn't find a valid card to untap for: " + source.getName());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return PaymentDecision.card(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PaymentDecision visit(CostUntap cost) {
|
||||||
|
return PaymentDecision.number(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PaymentDecision visit(CostUnattach cost) {
|
||||||
|
Card cardToUnattach = cost.findCardToUnattach(source, (Player) player, ability);
|
||||||
|
if (cardToUnattach == null) {
|
||||||
|
// We really shouldn't be able to get here if there's nothing to unattach
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return PaymentDecision.card(cardToUnattach);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean paysRightAfterDecision() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
21
forge-ai/src/main/java/forge/ai/AiPlayDecision.java
Normal file
21
forge-ai/src/main/java/forge/ai/AiPlayDecision.java
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
package forge.ai;
|
||||||
|
|
||||||
|
public enum AiPlayDecision {
|
||||||
|
WillPlay,
|
||||||
|
CantPlaySa,
|
||||||
|
CantPlayAi,
|
||||||
|
CantAfford,
|
||||||
|
CantAffordX,
|
||||||
|
WaitForMain2,
|
||||||
|
AnotherTime,
|
||||||
|
MissingNeededCards,
|
||||||
|
NeedsToPlayCriteriaNotMet,
|
||||||
|
TargetingFailed,
|
||||||
|
CostNotAcceptable,
|
||||||
|
WouldDestroyLegend,
|
||||||
|
WouldDestroyOtherPlaneswalker,
|
||||||
|
WouldBecomeZeroToughnessCreature,
|
||||||
|
WouldDestroyWorldEnchantment,
|
||||||
|
BadEtbEffects,
|
||||||
|
CurseEffects;
|
||||||
|
}
|
||||||
@@ -15,20 +15,19 @@
|
|||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
package forge.game.ai;
|
package forge.ai;
|
||||||
|
|
||||||
import forge.game.player.LobbyPlayer;
|
import forge.game.player.LobbyPlayer;
|
||||||
import forge.game.player.LobbyPlayerAi;
|
|
||||||
import forge.util.Aggregates;
|
import forge.util.Aggregates;
|
||||||
import forge.util.FileUtil;
|
import forge.util.FileUtil;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.ArrayUtils;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.ArrayList;
|
|
||||||
|
|
||||||
import org.apache.commons.lang.ArrayUtils;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Holds default AI personality profile values in an enum.
|
* Holds default AI personality profile values in an enum.
|
||||||
@@ -41,7 +40,7 @@ import org.apache.commons.lang.ArrayUtils;
|
|||||||
public class AiProfileUtil {
|
public class AiProfileUtil {
|
||||||
private static Map<String, Map<AiProps, String>> loadedProfiles = new HashMap<String, Map<AiProps, String>>();
|
private static Map<String, Map<AiProps, String>> loadedProfiles = new HashMap<String, Map<AiProps, String>>();
|
||||||
|
|
||||||
private static final String AI_PROFILE_DIR = "res/ai";
|
private static String AI_PROFILE_DIR;
|
||||||
private static final String AI_PROFILE_EXT = ".ai";
|
private static final String AI_PROFILE_EXT = ".ai";
|
||||||
|
|
||||||
public static final String AI_PROFILE_RANDOM_MATCH = "* Random (Match) *";
|
public static final String AI_PROFILE_RANDOM_MATCH = "* Random (Match) *";
|
||||||
@@ -59,7 +58,9 @@ public class AiProfileUtil {
|
|||||||
/**
|
/**
|
||||||
* Load all profiles
|
* Load all profiles
|
||||||
*/
|
*/
|
||||||
public static final void loadAllProfiles() {
|
public static final void loadAllProfiles(String aiProfileDir) {
|
||||||
|
AI_PROFILE_DIR = aiProfileDir;
|
||||||
|
|
||||||
loadedProfiles.clear();
|
loadedProfiles.clear();
|
||||||
ArrayList<String> availableProfiles = getAvailableProfiles();
|
ArrayList<String> availableProfiles = getAvailableProfiles();
|
||||||
for (String profile : availableProfiles) {
|
for (String profile : availableProfiles) {
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
package forge.game.ai;
|
package forge.ai;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* AI personality profile settings identifiers, and their default values.
|
* AI personality profile settings identifiers, and their default values.
|
||||||
@@ -28,7 +28,8 @@ public enum AiProps { /** */
|
|||||||
DEFAULT_MIN_TURN_TO_ROLL_PLANAR_DIE ("3"), /** */
|
DEFAULT_MIN_TURN_TO_ROLL_PLANAR_DIE ("3"), /** */
|
||||||
DEFAULT_PLANAR_DIE_ROLL_CHANCE ("50"), /** */
|
DEFAULT_PLANAR_DIE_ROLL_CHANCE ("50"), /** */
|
||||||
MULLIGAN_THRESHOLD ("5"), /** */
|
MULLIGAN_THRESHOLD ("5"), /** */
|
||||||
PLANAR_DIE_ROLL_HESITATION_CHANCE ("10"); /** */
|
PLANAR_DIE_ROLL_HESITATION_CHANCE ("10"),
|
||||||
|
CHEAT_WITH_MANA_ON_SHUFFLE ("FALSE"); /** */
|
||||||
|
|
||||||
private final String strDefaultVal;
|
private final String strDefaultVal;
|
||||||
|
|
||||||
@@ -15,60 +15,45 @@
|
|||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
package forge.game.ai;
|
package forge.ai;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Random;
|
|
||||||
|
|
||||||
import org.apache.commons.lang.StringUtils;
|
|
||||||
|
|
||||||
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.Card;
|
import forge.ai.ability.ProtectAi;
|
||||||
import forge.CardLists;
|
|
||||||
import forge.CardPredicates;
|
|
||||||
import forge.Constant;
|
|
||||||
import forge.CounterType;
|
|
||||||
import forge.CardPredicates.Presets;
|
|
||||||
import forge.CardUtil;
|
|
||||||
import forge.ITargetable;
|
|
||||||
import forge.card.CardType;
|
import forge.card.CardType;
|
||||||
|
import forge.card.CardType.Constant;
|
||||||
import forge.card.MagicColor;
|
import forge.card.MagicColor;
|
||||||
import forge.card.ability.AbilityFactory;
|
|
||||||
import forge.card.ability.AbilityUtils;
|
|
||||||
import forge.card.ability.ApiType;
|
|
||||||
import forge.card.ability.effects.CharmEffect;
|
|
||||||
import forge.card.cost.Cost;
|
|
||||||
import forge.card.cost.CostDiscard;
|
|
||||||
import forge.card.cost.CostPart;
|
|
||||||
import forge.card.cost.CostPayment;
|
|
||||||
import forge.card.cost.CostSacrifice;
|
|
||||||
import forge.card.spellability.AbilityManaPart;
|
|
||||||
import forge.card.spellability.SpellAbility;
|
|
||||||
import forge.card.spellability.SpellAbilityStackInstance;
|
|
||||||
import forge.card.spellability.TargetRestrictions;
|
|
||||||
import forge.card.staticability.StaticAbility;
|
|
||||||
import forge.card.trigger.Trigger;
|
|
||||||
import forge.card.trigger.TriggerType;
|
|
||||||
import forge.error.BugReporter;
|
|
||||||
import forge.game.Game;
|
import forge.game.Game;
|
||||||
|
import forge.game.GameObject;
|
||||||
|
import forge.game.ability.AbilityFactory;
|
||||||
|
import forge.game.ability.AbilityUtils;
|
||||||
|
import forge.game.ability.ApiType;
|
||||||
|
import forge.game.ability.effects.CharmEffect;
|
||||||
|
import forge.game.card.*;
|
||||||
|
import forge.game.card.CardPredicates.Presets;
|
||||||
import forge.game.combat.Combat;
|
import forge.game.combat.Combat;
|
||||||
import forge.game.combat.CombatUtil;
|
import forge.game.combat.CombatUtil;
|
||||||
|
import forge.game.cost.*;
|
||||||
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.PlayerControllerAi;
|
import forge.game.spellability.AbilityManaPart;
|
||||||
|
import forge.game.spellability.SpellAbility;
|
||||||
|
import forge.game.spellability.SpellAbilityStackInstance;
|
||||||
|
import forge.game.spellability.TargetRestrictions;
|
||||||
|
import forge.game.staticability.StaticAbility;
|
||||||
|
import forge.game.trigger.Trigger;
|
||||||
|
import forge.game.trigger.TriggerType;
|
||||||
import forge.game.zone.Zone;
|
import forge.game.zone.Zone;
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
import forge.gui.GuiChoose;
|
|
||||||
import forge.util.Aggregates;
|
import forge.util.Aggregates;
|
||||||
import forge.util.MyRandom;
|
import forge.util.MyRandom;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>
|
* <p>
|
||||||
@@ -86,24 +71,28 @@ public class ComputerUtil {
|
|||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param sa
|
* @param sa
|
||||||
* a {@link forge.card.spellability.SpellAbility} object.
|
* a {@link forge.game.spellability.SpellAbility} object.
|
||||||
* @return a boolean.
|
* @return a boolean.
|
||||||
*/
|
*/
|
||||||
public static boolean handlePlayingSpellAbility(final Player ai, final SpellAbility sa, final Game game) {
|
public static boolean handlePlayingSpellAbility(final Player ai, final SpellAbility sa, final Game game) {
|
||||||
|
|
||||||
game.getStack().freezeStack();
|
game.getStack().freezeStack();
|
||||||
final Card source = sa.getSourceCard();
|
final Card source = sa.getHostCard();
|
||||||
|
|
||||||
if (sa.isSpell() && !source.isCopiedSpell()) {
|
if (sa.isSpell() && !source.isCopiedSpell()) {
|
||||||
sa.setSourceCard(game.getAction().moveToStack(source));
|
sa.setHostCard(game.getAction().moveToStack(source));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sa.getApi() == ApiType.Charm && !sa.isWrapper()) {
|
if (sa.getApi() == ApiType.Charm && !sa.isWrapper()) {
|
||||||
CharmEffect.makeChoices(sa);
|
CharmEffect.makeChoices(sa);
|
||||||
}
|
}
|
||||||
|
|
||||||
final Cost cost = sa.getPayCosts();
|
if (sa.hasParam("Bestow")) {
|
||||||
|
sa.getHostCard().animateBestow();
|
||||||
|
}
|
||||||
|
|
||||||
|
final Cost cost = sa.getPayCosts();
|
||||||
|
// TODO: update mana color conversion for Daxos of Meletis
|
||||||
if (cost == null) {
|
if (cost == null) {
|
||||||
if (ComputerUtilMana.payManaCost(ai, sa)) {
|
if (ComputerUtilMana.payManaCost(ai, sa)) {
|
||||||
game.getStack().addAndUnfreeze(sa);
|
game.getStack().addAndUnfreeze(sa);
|
||||||
@@ -111,18 +100,16 @@ public class ComputerUtil {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
final CostPayment pay = new CostPayment(cost, sa);
|
final CostPayment pay = new CostPayment(cost, sa);
|
||||||
if (pay.payComputerCosts(ai, game)) {
|
if (pay.payComputerCosts(new AiCostDecision(ai, sa))) {
|
||||||
game.getStack().addAndUnfreeze(sa);
|
game.getStack().addAndUnfreeze(sa);
|
||||||
if (sa.getSplicedCards() != null && !sa.getSplicedCards().isEmpty()) {
|
if (sa.getSplicedCards() != null && !sa.getSplicedCards().isEmpty()) {
|
||||||
GuiChoose.oneOrNone("Computer reveals spliced cards:", sa.getSplicedCards());
|
game.getAction().reveal(sa.getSplicedCards(), ai, true, "Computer reveals spliced cards from ");
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
// TODO: solve problems with TapsForMana triggers by adding
|
|
||||||
// sources tapped here if possible (ArsenalNut)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//Should not arrive here
|
//Should not arrive here
|
||||||
System.out.println("AI failed to play " + sa.getSourceCard());
|
System.out.println("AI failed to play " + sa.getHostCard());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -147,7 +134,7 @@ public class ComputerUtil {
|
|||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param sa
|
* @param sa
|
||||||
* a {@link forge.card.spellability.SpellAbility} object.
|
* a {@link forge.game.spellability.SpellAbility} object.
|
||||||
* @return a int.
|
* @return a int.
|
||||||
*/
|
*/
|
||||||
public static int counterSpellRestriction(final Player ai, final SpellAbility sa) {
|
public static int counterSpellRestriction(final Player ai, final SpellAbility sa) {
|
||||||
@@ -156,7 +143,7 @@ public class ComputerUtil {
|
|||||||
|
|
||||||
int restrict = 0;
|
int restrict = 0;
|
||||||
|
|
||||||
final Card source = sa.getSourceCard();
|
final Card source = sa.getHostCard();
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
|
|
||||||
|
|
||||||
@@ -204,7 +191,8 @@ public class ComputerUtil {
|
|||||||
|
|
||||||
// And lastly give some bonus points to least restrictive TargetType
|
// And lastly give some bonus points to least restrictive TargetType
|
||||||
// (Spell,Ability,Triggered)
|
// (Spell,Ability,Triggered)
|
||||||
final String tgtType = tgt.getTargetSpellAbilityType();
|
final String tgtType = sa.getParam("TargetType");
|
||||||
|
if (tgtType != null)
|
||||||
restrict -= (5 * tgtType.split(",").length);
|
restrict -= (5 * tgtType.split(",").length);
|
||||||
|
|
||||||
return restrict;
|
return restrict;
|
||||||
@@ -217,16 +205,16 @@ public class ComputerUtil {
|
|||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param sa
|
* @param sa
|
||||||
* a {@link forge.card.spellability.SpellAbility} object.
|
* a {@link forge.game.spellability.SpellAbility} object.
|
||||||
*/
|
*/
|
||||||
public static final void playStack(final SpellAbility sa, final Player ai, final Game game) {
|
public static final void playStack(final SpellAbility sa, final Player ai, final Game game) {
|
||||||
sa.setActivatingPlayer(ai);
|
sa.setActivatingPlayer(ai);
|
||||||
if (!ComputerUtilCost.canPayCost(sa, ai))
|
if (!ComputerUtilCost.canPayCost(sa, ai))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
final Card source = sa.getSourceCard();
|
final Card source = sa.getHostCard();
|
||||||
if (sa.isSpell() && !source.isCopiedSpell()) {
|
if (sa.isSpell() && !source.isCopiedSpell()) {
|
||||||
sa.setSourceCard(game.getAction().moveToStack(source));
|
sa.setHostCard(game.getAction().moveToStack(source));
|
||||||
}
|
}
|
||||||
final Cost cost = sa.getPayCosts();
|
final Cost cost = sa.getPayCosts();
|
||||||
if (cost == null) {
|
if (cost == null) {
|
||||||
@@ -234,7 +222,7 @@ public class ComputerUtil {
|
|||||||
game.getStack().add(sa);
|
game.getStack().add(sa);
|
||||||
} else {
|
} else {
|
||||||
final CostPayment pay = new CostPayment(cost, sa);
|
final CostPayment pay = new CostPayment(cost, sa);
|
||||||
if (pay.payComputerCosts(ai, game)) {
|
if (pay.payComputerCosts(new AiCostDecision(ai, sa))) {
|
||||||
game.getStack().add(sa);
|
game.getStack().add(sa);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -246,14 +234,14 @@ public class ComputerUtil {
|
|||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param sa
|
* @param sa
|
||||||
* a {@link forge.card.spellability.SpellAbility} object.
|
* a {@link forge.game.spellability.SpellAbility} object.
|
||||||
*/
|
*/
|
||||||
public static final void playSpellAbilityForFree(final Player ai, final SpellAbility sa) {
|
public static final void playSpellAbilityForFree(final Player ai, final SpellAbility sa) {
|
||||||
sa.setActivatingPlayer(ai);
|
sa.setActivatingPlayer(ai);
|
||||||
|
|
||||||
final Card source = sa.getSourceCard();
|
final Card source = sa.getHostCard();
|
||||||
if (sa.isSpell() && !source.isCopiedSpell()) {
|
if (sa.isSpell() && !source.isCopiedSpell()) {
|
||||||
sa.setSourceCard(ai.getGame().getAction().moveToStack(source));
|
sa.setHostCard(ai.getGame().getAction().moveToStack(source));
|
||||||
}
|
}
|
||||||
|
|
||||||
ai.getGame().getStack().add(sa);
|
ai.getGame().getStack().add(sa);
|
||||||
@@ -265,7 +253,7 @@ public class ComputerUtil {
|
|||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param sa
|
* @param sa
|
||||||
* a {@link forge.card.spellability.SpellAbility} object.
|
* a {@link forge.game.spellability.SpellAbility} object.
|
||||||
*/
|
*/
|
||||||
public static final void playSpellAbilityWithoutPayingManaCost(final Player ai, final SpellAbility sa, final Game game) {
|
public static final void playSpellAbilityWithoutPayingManaCost(final Player ai, final SpellAbility sa, final Game game) {
|
||||||
final SpellAbility newSA = sa.copyWithNoManaCost();
|
final SpellAbility newSA = sa.copyWithNoManaCost();
|
||||||
@@ -275,13 +263,13 @@ public class ComputerUtil {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final Card source = newSA.getSourceCard();
|
final Card source = newSA.getHostCard();
|
||||||
if (newSA.isSpell() && !source.isCopiedSpell()) {
|
if (newSA.isSpell() && !source.isCopiedSpell()) {
|
||||||
newSA.setSourceCard(game.getAction().moveToStack(source));
|
newSA.setHostCard(game.getAction().moveToStack(source));
|
||||||
}
|
}
|
||||||
|
|
||||||
final CostPayment pay = new CostPayment(newSA.getPayCosts(), newSA);
|
final CostPayment pay = new CostPayment(newSA.getPayCosts(), newSA);
|
||||||
pay.payComputerCosts(ai, game);
|
pay.payComputerCosts(new AiCostDecision(ai, sa));
|
||||||
|
|
||||||
game.getStack().add(newSA);
|
game.getStack().add(newSA);
|
||||||
}
|
}
|
||||||
@@ -292,15 +280,15 @@ public class ComputerUtil {
|
|||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param sa
|
* @param sa
|
||||||
* a {@link forge.card.spellability.SpellAbility} object.
|
* a {@link forge.game.spellability.SpellAbility} object.
|
||||||
*/
|
*/
|
||||||
public static final void playNoStack(final Player ai, final SpellAbility sa, final Game game) {
|
public static final void playNoStack(final Player ai, final SpellAbility sa, final Game game) {
|
||||||
sa.setActivatingPlayer(ai);
|
sa.setActivatingPlayer(ai);
|
||||||
// TODO: We should really restrict what doesn't use the Stack
|
// TODO: We should really restrict what doesn't use the Stack
|
||||||
if (ComputerUtilCost.canPayCost(sa, ai)) {
|
if (ComputerUtilCost.canPayCost(sa, ai)) {
|
||||||
final Card source = sa.getSourceCard();
|
final Card source = sa.getHostCard();
|
||||||
if (sa.isSpell() && !source.isCopiedSpell()) {
|
if (sa.isSpell() && !source.isCopiedSpell()) {
|
||||||
sa.setSourceCard(game.getAction().moveToStack(source));
|
sa.setHostCard(game.getAction().moveToStack(source));
|
||||||
}
|
}
|
||||||
|
|
||||||
final Cost cost = sa.getPayCosts();
|
final Cost cost = sa.getPayCosts();
|
||||||
@@ -308,7 +296,7 @@ public class ComputerUtil {
|
|||||||
ComputerUtilMana.payManaCost(ai, sa);
|
ComputerUtilMana.payManaCost(ai, sa);
|
||||||
} else {
|
} else {
|
||||||
final CostPayment pay = new CostPayment(cost, sa);
|
final CostPayment pay = new CostPayment(cost, sa);
|
||||||
pay.payComputerCosts(ai, game);
|
pay.payComputerCosts(new AiCostDecision(ai, sa));
|
||||||
}
|
}
|
||||||
|
|
||||||
AbilityUtils.resolve(sa);
|
AbilityUtils.resolve(sa);
|
||||||
@@ -324,12 +312,12 @@ public class ComputerUtil {
|
|||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param activate
|
* @param activate
|
||||||
* a {@link forge.Card} object.
|
* a {@link forge.game.card.Card} object.
|
||||||
* @param pref
|
* @param pref
|
||||||
* a {@link java.lang.String} object.
|
* a {@link java.lang.String} object.
|
||||||
* @param typeList
|
* @param typeList
|
||||||
* a {@link forge.CardList} object.
|
* a {@link forge.CardList} object.
|
||||||
* @return a {@link forge.Card} object.
|
* @return a {@link forge.game.card.Card} object.
|
||||||
*/
|
*/
|
||||||
public static Card getCardPreference(final Player ai, final Card activate, final String pref, final List<Card> typeList) {
|
public static Card getCardPreference(final Player ai, final Card activate, final String pref, final List<Card> typeList) {
|
||||||
|
|
||||||
@@ -414,9 +402,9 @@ public class ComputerUtil {
|
|||||||
* @param type
|
* @param type
|
||||||
* a {@link java.lang.String} object.
|
* a {@link java.lang.String} object.
|
||||||
* @param source
|
* @param source
|
||||||
* a {@link forge.Card} object.
|
* a {@link forge.game.card.Card} object.
|
||||||
* @param target
|
* @param target
|
||||||
* a {@link forge.Card} object.
|
* a {@link forge.game.card.Card} object.
|
||||||
* @param amount
|
* @param amount
|
||||||
* a int.
|
* a int.
|
||||||
* @return a {@link forge.CardList} object.
|
* @return a {@link forge.CardList} object.
|
||||||
@@ -463,9 +451,9 @@ public class ComputerUtil {
|
|||||||
* @param type
|
* @param type
|
||||||
* a {@link java.lang.String} object.
|
* a {@link java.lang.String} object.
|
||||||
* @param activate
|
* @param activate
|
||||||
* a {@link forge.Card} object.
|
* a {@link forge.game.card.Card} object.
|
||||||
* @param target
|
* @param target
|
||||||
* a {@link forge.Card} object.
|
* a {@link forge.game.card.Card} object.
|
||||||
* @param amount
|
* @param amount
|
||||||
* a int.
|
* a int.
|
||||||
* @return a {@link forge.CardList} object.
|
* @return a {@link forge.CardList} object.
|
||||||
@@ -510,9 +498,9 @@ public class ComputerUtil {
|
|||||||
* @param type
|
* @param type
|
||||||
* a {@link java.lang.String} object.
|
* a {@link java.lang.String} object.
|
||||||
* @param activate
|
* @param activate
|
||||||
* a {@link forge.Card} object.
|
* a {@link forge.game.card.Card} object.
|
||||||
* @param target
|
* @param target
|
||||||
* a {@link forge.Card} object.
|
* a {@link forge.game.card.Card} object.
|
||||||
* @param amount
|
* @param amount
|
||||||
* a int.
|
* a int.
|
||||||
* @return a {@link forge.CardList} object.
|
* @return a {@link forge.CardList} object.
|
||||||
@@ -553,7 +541,7 @@ public class ComputerUtil {
|
|||||||
* @param type
|
* @param type
|
||||||
* a {@link java.lang.String} object.
|
* a {@link java.lang.String} object.
|
||||||
* @param activate
|
* @param activate
|
||||||
* a {@link forge.Card} object.
|
* a {@link forge.game.card.Card} object.
|
||||||
* @param tap
|
* @param tap
|
||||||
* a boolean.
|
* a boolean.
|
||||||
* @param amount
|
* @param amount
|
||||||
@@ -593,7 +581,7 @@ public class ComputerUtil {
|
|||||||
* @param type
|
* @param type
|
||||||
* a {@link java.lang.String} object.
|
* a {@link java.lang.String} object.
|
||||||
* @param activate
|
* @param activate
|
||||||
* a {@link forge.Card} object.
|
* a {@link forge.game.card.Card} object.
|
||||||
* @param untap
|
* @param untap
|
||||||
* a boolean.
|
* a boolean.
|
||||||
* @param amount
|
* @param amount
|
||||||
@@ -633,9 +621,9 @@ public class ComputerUtil {
|
|||||||
* @param type
|
* @param type
|
||||||
* a {@link java.lang.String} object.
|
* a {@link java.lang.String} object.
|
||||||
* @param activate
|
* @param activate
|
||||||
* a {@link forge.Card} object.
|
* a {@link forge.game.card.Card} object.
|
||||||
* @param target
|
* @param target
|
||||||
* a {@link forge.Card} object.
|
* a {@link forge.game.card.Card} object.
|
||||||
* @param amount
|
* @param amount
|
||||||
* a int.
|
* a int.
|
||||||
* @return a {@link forge.CardList} object.
|
* @return a {@link forge.CardList} object.
|
||||||
@@ -763,7 +751,7 @@ public class ComputerUtil {
|
|||||||
* @param ai
|
* @param ai
|
||||||
*
|
*
|
||||||
* @param card
|
* @param card
|
||||||
* a {@link forge.Card} object.
|
* a {@link forge.game.card.Card} object.
|
||||||
* @return a boolean.
|
* @return a boolean.
|
||||||
*/
|
*/
|
||||||
public static boolean canRegenerate(Player ai, final Card card) {
|
public static boolean canRegenerate(Player ai, final Card card) {
|
||||||
@@ -807,15 +795,15 @@ public class ComputerUtil {
|
|||||||
|
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
if (tgt != null) {
|
if (tgt != null) {
|
||||||
if (CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), controller, sa.getSourceCard()).contains(card)) {
|
if (CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), controller, sa.getHostCard()).contains(card)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
} else if (AbilityUtils.getDefinedCards(sa.getSourceCard(), sa.getParam("Defined"), sa).contains(card)) {
|
} else if (AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("Defined"), sa).contains(card)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (final Exception ex) {
|
} catch (final Exception ex) {
|
||||||
BugReporter.reportException(ex, "There is an error in the card code for %s:%n", c.getName(), ex.getMessage());
|
throw new RuntimeException(String.format("There is an error in the card code for %s:%n", c.getName(), ex.getMessage()), ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -828,7 +816,7 @@ public class ComputerUtil {
|
|||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param card
|
* @param card
|
||||||
* a {@link forge.Card} object.
|
* a {@link forge.game.card.Card} object.
|
||||||
* @return a int.
|
* @return a int.
|
||||||
*/
|
*/
|
||||||
public static int possibleDamagePrevention(final Card card) {
|
public static int possibleDamagePrevention(final Card card) {
|
||||||
@@ -850,20 +838,19 @@ public class ComputerUtil {
|
|||||||
|
|
||||||
if (sa.getApi() == ApiType.PreventDamage && sa.canPlay()
|
if (sa.getApi() == ApiType.PreventDamage && sa.canPlay()
|
||||||
&& ComputerUtilCost.canPayCost(sa, controller)) {
|
&& ComputerUtilCost.canPayCost(sa, controller)) {
|
||||||
if (AbilityUtils.getDefinedCards(sa.getSourceCard(), sa.getParam("Defined"), sa).contains(card)) {
|
if (AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("Defined"), sa).contains(card)) {
|
||||||
prevented += AbilityUtils.calculateAmount(sa.getSourceCard(), sa.getParam("Amount"), sa);
|
prevented += AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("Amount"), sa);
|
||||||
}
|
}
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
if (tgt != null) {
|
if (tgt != null) {
|
||||||
if (CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), controller, sa.getSourceCard()).contains(card)) {
|
if (CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), controller, sa.getHostCard()).contains(card)) {
|
||||||
prevented += AbilityUtils.calculateAmount(sa.getSourceCard(), sa.getParam("Amount"), sa);
|
prevented += AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("Amount"), sa);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (final Exception ex) {
|
} catch (final Exception ex) {
|
||||||
BugReporter.reportException(ex, "There is an error in the card code for %s:%n", c.getName(),
|
throw new RuntimeException(String.format("There is an error in the card code for %s:%n", c.getName(), ex.getMessage()), ex);
|
||||||
ex.getMessage());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -880,7 +867,7 @@ public class ComputerUtil {
|
|||||||
* @return a boolean.
|
* @return a boolean.
|
||||||
*/
|
*/
|
||||||
public static boolean castPermanentInMain1(final Player ai, final SpellAbility sa) {
|
public static boolean castPermanentInMain1(final Player ai, final SpellAbility sa) {
|
||||||
final Card card = sa.getSourceCard();
|
final Card card = sa.getHostCard();
|
||||||
if ("True".equals(card.getSVar("NonStackingEffect")) && card.getController().isCardInPlay(card.getName())) {
|
if ("True".equals(card.getSVar("NonStackingEffect")) && card.getController().isCardInPlay(card.getName())) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -1044,19 +1031,18 @@ public class ComputerUtil {
|
|||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param sa
|
* @param sa
|
||||||
* a {@link forge.card.spellability.SpellAbility} object.
|
* a {@link forge.game.spellability.SpellAbility} object.
|
||||||
* @return a boolean (returns true if it's better to wait until blockers are declared).
|
* @return a boolean (returns true if it's better to wait until blockers are declared).
|
||||||
*/
|
*/
|
||||||
public static boolean waitForBlocking(final SpellAbility sa) {
|
public static boolean waitForBlocking(final SpellAbility sa) {
|
||||||
final Game game = sa.getActivatingPlayer().getGame();
|
final Game game = sa.getActivatingPlayer().getGame();
|
||||||
final PhaseHandler ph = game.getPhaseHandler();
|
final PhaseHandler ph = game.getPhaseHandler();
|
||||||
|
|
||||||
return (sa.getSourceCard().isCreature()
|
return (sa.getHostCard().isCreature()
|
||||||
&& sa.getPayCosts().hasTapCost()
|
&& sa.getPayCosts().hasTapCost()
|
||||||
&& (ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS)
|
&& (ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS)
|
||||||
|| !ph.getNextTurn().equals(sa.getActivatingPlayer()))
|
|| !ph.getNextTurn().equals(sa.getActivatingPlayer()))
|
||||||
&& !sa.getSourceCard().hasKeyword("At the beginning of the end step, exile CARDNAME.")
|
&& !sa.getHostCard().hasSVar("EndOfTurnLeavePlay"));
|
||||||
&& !sa.getSourceCard().hasKeyword("At the beginning of the end step, sacrifice CARDNAME."));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1065,11 +1051,11 @@ public class ComputerUtil {
|
|||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param sa
|
* @param sa
|
||||||
* a {@link forge.card.spellability.SpellAbility} object.
|
* a {@link forge.game.spellability.SpellAbility} object.
|
||||||
* @return a boolean (returns true if it's better to wait until blockers are declared).
|
* @return a boolean (returns true if it's better to wait until blockers are declared).
|
||||||
*/
|
*/
|
||||||
public static boolean castSpellInMain1(final Player ai, final SpellAbility sa) {
|
public static boolean castSpellInMain1(final Player ai, final SpellAbility sa) {
|
||||||
final Card source = sa.getSourceCard();
|
final Card source = sa.getHostCard();
|
||||||
final SpellAbility sub = sa.getSubAbility();
|
final SpellAbility sub = sa.getSubAbility();
|
||||||
|
|
||||||
// Cipher spells
|
// Cipher spells
|
||||||
@@ -1086,7 +1072,7 @@ public class ComputerUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
final List<Card> buffed = ai.getCardsIn(ZoneType.Battlefield);
|
final List<Card> buffed = ai.getCardsIn(ZoneType.Battlefield);
|
||||||
boolean checkThreshold = sa.isSpell() && !ai.hasThreshold() && !sa.getSourceCard().isInZone(ZoneType.Graveyard);
|
boolean checkThreshold = sa.isSpell() && !ai.hasThreshold() && !sa.getHostCard().isInZone(ZoneType.Graveyard);
|
||||||
for (Card buffedCard : buffed) {
|
for (Card buffedCard : buffed) {
|
||||||
if (buffedCard.hasSVar("BuffedBy")) {
|
if (buffedCard.hasSVar("BuffedBy")) {
|
||||||
final String buffedby = buffedCard.getSVar("BuffedBy");
|
final String buffedby = buffedCard.getSVar("BuffedBy");
|
||||||
@@ -1131,17 +1117,23 @@ public class ComputerUtil {
|
|||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param sa
|
* @param sa
|
||||||
* a {@link forge.card.spellability.SpellAbility} object.
|
* a {@link forge.game.spellability.SpellAbility} object.
|
||||||
* @return a boolean (returns true if the AI should stop using the ability).
|
* @return a boolean (returns true if the AI should stop using the ability).
|
||||||
*/
|
*/
|
||||||
public static boolean preventRunAwayActivations(final SpellAbility sa) {
|
public static boolean preventRunAwayActivations(final SpellAbility sa) {
|
||||||
final int activations = sa.getRestrictions().getNumberTurnActivations();
|
int activations = sa.getRestrictions().getNumberTurnActivations();
|
||||||
|
|
||||||
|
if (sa.isTemporary()) {
|
||||||
|
final Random r = MyRandom.getRandom();
|
||||||
|
return r.nextFloat() >= .95; // Abilities created by static abilities have no memory
|
||||||
|
}
|
||||||
|
|
||||||
if (activations < 10) { //10 activations per turn should still be acceptable
|
if (activations < 10) { //10 activations per turn should still be acceptable
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
final Random r = MyRandom.getRandom();
|
|
||||||
|
|
||||||
return r.nextFloat() <= Math.pow(.95, activations);
|
final Random r = MyRandom.getRandom();
|
||||||
|
return r.nextFloat() >= Math.pow(.95, activations);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1151,7 +1143,7 @@ public class ComputerUtil {
|
|||||||
*/
|
*/
|
||||||
public static boolean activateForCost(SpellAbility sa, final Player ai) {
|
public static boolean activateForCost(SpellAbility sa, final Player ai) {
|
||||||
final Cost abCost = sa.getPayCosts();
|
final Cost abCost = sa.getPayCosts();
|
||||||
final Card source = sa.getSourceCard();
|
final Card source = sa.getHostCard();
|
||||||
if (abCost == null) {
|
if (abCost == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -1163,6 +1155,12 @@ public class ComputerUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if (sa.hasParam("Planeswalker") && ai.getGame().getPhaseHandler().is(PhaseType.MAIN2)) {
|
||||||
|
for (final CostPart part : abCost.getCostParts()) {
|
||||||
|
if (part instanceof CostPutCounter) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
for (final CostPart part : abCost.getCostParts()) {
|
for (final CostPart part : abCost.getCostParts()) {
|
||||||
if (part instanceof CostSacrifice) {
|
if (part instanceof CostSacrifice) {
|
||||||
@@ -1202,7 +1200,7 @@ public class ComputerUtil {
|
|||||||
for (final Card c : all) {
|
for (final Card c : all) {
|
||||||
if (c.isEquipment()) {
|
if (c.isEquipment()) {
|
||||||
for (StaticAbility stAb : c.getStaticAbilities()) {
|
for (StaticAbility stAb : c.getStaticAbilities()) {
|
||||||
HashMap<String, String> params = stAb.getMapParams();
|
Map<String, String> params = stAb.getMapParams();
|
||||||
if ("Continuous".equals(params.get("Mode")) && params.containsKey("AddKeyword")
|
if ("Continuous".equals(params.get("Mode")) && params.containsKey("AddKeyword")
|
||||||
&& params.get("AddKeyword").contains("Haste") && c.getEquippingCard() == null) {
|
&& params.get("AddKeyword").contains("Haste") && c.getEquippingCard() == null) {
|
||||||
return true;
|
return true;
|
||||||
@@ -1241,7 +1239,7 @@ public class ComputerUtil {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
final String numDam = sa.getParam("NumDmg");
|
final String numDam = sa.getParam("NumDmg");
|
||||||
int dmg = AbilityUtils.calculateAmount(sa.getSourceCard(), numDam, sa);
|
int dmg = AbilityUtils.calculateAmount(sa.getHostCard(), numDam, sa);
|
||||||
if (dmg <= damage) {
|
if (dmg <= damage) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -1272,9 +1270,9 @@ public class ComputerUtil {
|
|||||||
* @return a {@link java.util.ArrayList} object.
|
* @return a {@link java.util.ArrayList} object.
|
||||||
* @since 1.0.15
|
* @since 1.0.15
|
||||||
*/
|
*/
|
||||||
public static List<ITargetable> predictThreatenedObjects(final Player aiPlayer, final SpellAbility sa) {
|
public static List<GameObject> predictThreatenedObjects(final Player aiPlayer, final SpellAbility sa) {
|
||||||
final Game game = aiPlayer.getGame();
|
final Game game = aiPlayer.getGame();
|
||||||
final List<ITargetable> objects = new ArrayList<ITargetable>();
|
final List<GameObject> objects = new ArrayList<GameObject>();
|
||||||
if (game.getStack().isEmpty()) {
|
if (game.getStack().isEmpty()) {
|
||||||
return objects;
|
return objects;
|
||||||
}
|
}
|
||||||
@@ -1294,21 +1292,21 @@ public class ComputerUtil {
|
|||||||
* @param saviourAf
|
* @param saviourAf
|
||||||
* a AbilityFactory object
|
* a AbilityFactory object
|
||||||
* @param topStack
|
* @param topStack
|
||||||
* a {@link forge.card.spellability.SpellAbility} object.
|
* a {@link forge.game.spellability.SpellAbility} object.
|
||||||
* @return a {@link java.util.ArrayList} object.
|
* @return a {@link java.util.ArrayList} object.
|
||||||
* @since 1.0.15
|
* @since 1.0.15
|
||||||
*/
|
*/
|
||||||
private static Iterable<? extends ITargetable> predictThreatenedObjects(final Player aiPlayer, final SpellAbility saviour,
|
private static Iterable<? extends GameObject> predictThreatenedObjects(final Player aiPlayer, final SpellAbility saviour,
|
||||||
final SpellAbility topStack) {
|
final SpellAbility topStack) {
|
||||||
Iterable<? extends ITargetable> objects = new ArrayList<ITargetable>();
|
Iterable<? extends GameObject> objects = new ArrayList<GameObject>();
|
||||||
final List<ITargetable> threatened = new ArrayList<ITargetable>();
|
final List<GameObject> threatened = new ArrayList<GameObject>();
|
||||||
ApiType saviourApi = saviour == null ? null : saviour.getApi();
|
ApiType saviourApi = saviour == null ? null : saviour.getApi();
|
||||||
|
|
||||||
if (topStack == null) {
|
if (topStack == null) {
|
||||||
return objects;
|
return objects;
|
||||||
}
|
}
|
||||||
|
|
||||||
final Card source = topStack.getSourceCard();
|
final Card source = topStack.getHostCard();
|
||||||
final ApiType threatApi = topStack.getApi();
|
final ApiType threatApi = topStack.getApi();
|
||||||
|
|
||||||
// Can only Predict things from AFs
|
// Can only Predict things from AFs
|
||||||
@@ -1334,7 +1332,7 @@ public class ComputerUtil {
|
|||||||
// Lethal Damage => prevent damage/regeneration/bounce/shroud
|
// Lethal Damage => prevent damage/regeneration/bounce/shroud
|
||||||
if (threatApi == ApiType.DealDamage || threatApi == ApiType.DamageAll) {
|
if (threatApi == ApiType.DealDamage || threatApi == ApiType.DamageAll) {
|
||||||
// If PredictDamage is >= Lethal Damage
|
// If PredictDamage is >= Lethal Damage
|
||||||
final int dmg = AbilityUtils.calculateAmount(topStack.getSourceCard(),
|
final int dmg = AbilityUtils.calculateAmount(topStack.getHostCard(),
|
||||||
topStack.getParam("NumDmg"), topStack);
|
topStack.getParam("NumDmg"), topStack);
|
||||||
for (final Object o : objects) {
|
for (final Object o : objects) {
|
||||||
if (o instanceof Card) {
|
if (o instanceof Card) {
|
||||||
@@ -1346,7 +1344,7 @@ public class ComputerUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// already regenerated
|
// already regenerated
|
||||||
if (c.getShield() > 0) {
|
if (!c.getShield().isEmpty()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1362,6 +1360,11 @@ public class ComputerUtil {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// cannot protect against source
|
||||||
|
if (saviourApi == ApiType.Protection && (ProtectAi.toProtectFrom(source, saviour) == null)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// don't bounce or blink a permanent that the human
|
// don't bounce or blink a permanent that the human
|
||||||
// player owns or is a token
|
// player owns or is a token
|
||||||
if (saviourApi == ApiType.ChangeZone && (c.getOwner().isOpponentOf(aiPlayer) || c.isToken())) {
|
if (saviourApi == ApiType.ChangeZone && (c.getOwner().isOpponentOf(aiPlayer) || c.isToken())) {
|
||||||
@@ -1388,7 +1391,7 @@ public class ComputerUtil {
|
|||||||
else if ((threatApi == ApiType.Destroy || threatApi == ApiType.DestroyAll)
|
else if ((threatApi == ApiType.Destroy || threatApi == ApiType.DestroyAll)
|
||||||
&& (((saviourApi == ApiType.Regenerate || saviourApi == ApiType.RegenerateAll)
|
&& (((saviourApi == ApiType.Regenerate || saviourApi == ApiType.RegenerateAll)
|
||||||
&& !topStack.hasParam("NoRegen")) || saviourApi == ApiType.ChangeZone || saviourApi == ApiType.Pump
|
&& !topStack.hasParam("NoRegen")) || saviourApi == ApiType.ChangeZone || saviourApi == ApiType.Pump
|
||||||
|| saviourApi == null)) {
|
|| saviourApi == ApiType.Protection || saviourApi == null)) {
|
||||||
for (final Object o : objects) {
|
for (final Object o : objects) {
|
||||||
if (o instanceof Card) {
|
if (o instanceof Card) {
|
||||||
final Card c = (Card) o;
|
final Card c = (Card) o;
|
||||||
@@ -1398,7 +1401,7 @@ public class ComputerUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// already regenerated
|
// already regenerated
|
||||||
if (c.getShield() > 0) {
|
if (!c.getShield().isEmpty()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1408,6 +1411,11 @@ public class ComputerUtil {
|
|||||||
|| saviour.getParam("KW").endsWith("Hexproof"))) {
|
|| saviour.getParam("KW").endsWith("Hexproof"))) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
if (saviourApi == ApiType.Protection) {
|
||||||
|
if (tgt == null || (ProtectAi.toProtectFrom(source, saviour) == null)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// don't bounce or blink a permanent that the human
|
// don't bounce or blink a permanent that the human
|
||||||
// player owns or is a token
|
// player owns or is a token
|
||||||
@@ -1425,7 +1433,8 @@ public class ComputerUtil {
|
|||||||
}
|
}
|
||||||
// Exiling => bounce/shroud
|
// Exiling => bounce/shroud
|
||||||
else if ((threatApi == ApiType.ChangeZone || threatApi == ApiType.ChangeZoneAll)
|
else if ((threatApi == ApiType.ChangeZone || threatApi == ApiType.ChangeZoneAll)
|
||||||
&& (saviourApi == ApiType.ChangeZone || saviourApi == ApiType.Pump || saviourApi == null)
|
&& (saviourApi == ApiType.ChangeZone || saviourApi == ApiType.Pump
|
||||||
|
|| saviourApi == ApiType.Protection || saviourApi == null)
|
||||||
&& topStack.hasParam("Destination")
|
&& topStack.hasParam("Destination")
|
||||||
&& topStack.getParam("Destination").equals("Exile")) {
|
&& topStack.getParam("Destination").equals("Exile")) {
|
||||||
for (final Object o : objects) {
|
for (final Object o : objects) {
|
||||||
@@ -1436,6 +1445,11 @@ public class ComputerUtil {
|
|||||||
&& (saviour.getParam("KW").endsWith("Shroud") || saviour.getParam("KW").endsWith("Hexproof"))) {
|
&& (saviour.getParam("KW").endsWith("Shroud") || saviour.getParam("KW").endsWith("Hexproof"))) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
if (saviourApi == ApiType.Protection) {
|
||||||
|
if (tgt == null || (ProtectAi.toProtectFrom(source, saviour) == null)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// don't bounce or blink a permanent that the human
|
// don't bounce or blink a permanent that the human
|
||||||
// player owns or is a token
|
// player owns or is a token
|
||||||
@@ -1447,19 +1461,45 @@ public class ComputerUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
//GainControl
|
||||||
|
else if (threatApi == ApiType.GainControl
|
||||||
|
&& (saviourApi == ApiType.ChangeZone || saviourApi == ApiType.Pump || saviourApi == ApiType.Protection
|
||||||
|
|| saviourApi == null)) {
|
||||||
|
for (final Object o : objects) {
|
||||||
|
if (o instanceof Card) {
|
||||||
|
final Card c = (Card) o;
|
||||||
|
// give Shroud to targeted creatures
|
||||||
|
if (saviourApi == ApiType.Pump && tgt == null && saviour.hasParam("KW")
|
||||||
|
&& (saviour.getParam("KW").endsWith("Shroud") || saviour.getParam("KW").endsWith("Hexproof"))) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (saviourApi == ApiType.Protection) {
|
||||||
|
if (tgt == null || (ProtectAi.toProtectFrom(source, saviour) == null)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
threatened.add(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Iterables.addAll(threatened, ComputerUtil.predictThreatenedObjects(aiPlayer, saviour, topStack.getSubAbility()));
|
Iterables.addAll(threatened, ComputerUtil.predictThreatenedObjects(aiPlayer, saviour, topStack.getSubAbility()));
|
||||||
return threatened;
|
return threatened;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean playImmediately(Player ai, SpellAbility sa) {
|
public static boolean playImmediately(Player ai, SpellAbility sa) {
|
||||||
final Card source = sa.getSourceCard();
|
final Card source = sa.getHostCard();
|
||||||
final Zone zone = source.getZone();
|
final Zone zone = source.getZone();
|
||||||
|
final Game game = source.getGame();
|
||||||
|
|
||||||
if (zone.getZoneType() == ZoneType.Battlefield) {
|
if (zone.getZoneType() == ZoneType.Battlefield) {
|
||||||
if (predictThreatenedObjects(ai, null).contains(source)) {
|
if (predictThreatenedObjects(ai, null).contains(source)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
if (game.getPhaseHandler().inCombat() &&
|
||||||
|
ComputerUtilCombat.combatantWouldBeDestroyed(ai, source, game.getCombat())) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -1615,6 +1655,22 @@ public class ComputerUtil {
|
|||||||
// 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
|
// otherwise, lib search for most common type left
|
||||||
// then, reveal chosenType to Human
|
// then, reveal chosenType to Human
|
||||||
|
if (game.getPhaseHandler().is(PhaseType.UNTAP) && logic == null) { // Storage Matrix
|
||||||
|
double amount = 0;
|
||||||
|
for (String type : Constant.CARD_TYPES) {
|
||||||
|
if (!invalidTypes.contains(type)) {
|
||||||
|
List<Card> list = CardLists.filter(ai.getCardsIn(ZoneType.Battlefield), CardPredicates.isType(type), Presets.TAPPED);
|
||||||
|
double i = type.equals("Creature") ? list.size() * 1.5 : list.size();
|
||||||
|
if (i > amount) {
|
||||||
|
amount = i;
|
||||||
|
chosen = type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (StringUtils.isEmpty(chosen)) {
|
||||||
|
chosen = "Creature";
|
||||||
|
}
|
||||||
} else if (kindOfType.equals("Creature")) {
|
} else if (kindOfType.equals("Creature")) {
|
||||||
Player opp = ai.getOpponent();
|
Player opp = ai.getOpponent();
|
||||||
if (logic != null ) {
|
if (logic != null ) {
|
||||||
@@ -1646,7 +1702,7 @@ public class ComputerUtil {
|
|||||||
if (logic.equals("MostNeededType")) {
|
if (logic.equals("MostNeededType")) {
|
||||||
// Choose a type that is in the deck, but not in hand or on the battlefield
|
// Choose a type that is in the deck, but not in hand or on the battlefield
|
||||||
final ArrayList<String> basics = new ArrayList<String>();
|
final ArrayList<String> basics = new ArrayList<String>();
|
||||||
basics.addAll(Constant.CardTypes.BASIC_TYPES);
|
basics.addAll(CardType.Constant.BASIC_TYPES);
|
||||||
List<Card> presentCards = ai.getCardsIn(ZoneType.Battlefield);
|
List<Card> presentCards = ai.getCardsIn(ZoneType.Battlefield);
|
||||||
presentCards.addAll(ai.getCardsIn(ZoneType.Hand));
|
presentCards.addAll(ai.getCardsIn(ZoneType.Hand));
|
||||||
List<Card> possibleCards = ai.getAllCards();
|
List<Card> possibleCards = ai.getAllCards();
|
||||||
@@ -1696,8 +1752,6 @@ public class ComputerUtil {
|
|||||||
chosen = "Island";
|
chosen = "Island";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
GuiChoose.one("Computer picked: ", new String[]{chosen});
|
|
||||||
return chosen;
|
return chosen;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1725,7 +1779,7 @@ public class ComputerUtil {
|
|||||||
theTriggers.addAll(card.getTriggers());
|
theTriggers.addAll(card.getTriggers());
|
||||||
}
|
}
|
||||||
for (Trigger trigger : theTriggers) {
|
for (Trigger trigger : theTriggers) {
|
||||||
HashMap<String, String> trigParams = trigger.getMapParams();
|
Map<String, String> trigParams = trigger.getMapParams();
|
||||||
final Card source = trigger.getHostCard();
|
final Card source = trigger.getHostCard();
|
||||||
|
|
||||||
|
|
||||||
@@ -1782,7 +1836,6 @@ public class ComputerUtil {
|
|||||||
return damage;
|
return damage;
|
||||||
}
|
}
|
||||||
|
|
||||||
// although this should be in AI's code
|
|
||||||
public static boolean isNegativeCounter(CounterType type, Card c) {
|
public static boolean isNegativeCounter(CounterType type, Card c) {
|
||||||
return type == CounterType.AGE || type == CounterType.BLAZE || type == CounterType.BRIBERY || type == CounterType.DOOM
|
return type == CounterType.AGE || type == CounterType.BLAZE || type == CounterType.BRIBERY || type == CounterType.DOOM
|
||||||
|| type == CounterType.ICE || type == CounterType.M1M1 || type == CounterType.M0M2 || type == CounterType.M0M1
|
|| type == CounterType.ICE || type == CounterType.M1M1 || type == CounterType.M0M2 || type == CounterType.M0M1
|
||||||
1010
forge-ai/src/main/java/forge/ai/ComputerUtilCard.java
Normal file
1010
forge-ai/src/main/java/forge/ai/ComputerUtilCard.java
Normal file
File diff suppressed because it is too large
Load Diff
@@ -15,38 +15,37 @@
|
|||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
package forge.game.ai;
|
package forge.ai;
|
||||||
|
|
||||||
|
import com.google.common.base.Predicate;
|
||||||
|
import forge.game.CardTraitBase;
|
||||||
|
import forge.game.Game;
|
||||||
|
import forge.game.GameEntity;
|
||||||
|
import forge.game.GlobalRuleChange;
|
||||||
|
import forge.game.ability.AbilityFactory;
|
||||||
|
import forge.game.ability.AbilityUtils;
|
||||||
|
import forge.game.ability.ApiType;
|
||||||
|
import forge.game.card.Card;
|
||||||
|
import forge.game.card.CardFactoryUtil;
|
||||||
|
import forge.game.card.CardLists;
|
||||||
|
import forge.game.card.CounterType;
|
||||||
|
import forge.game.combat.Combat;
|
||||||
|
import forge.game.combat.CombatUtil;
|
||||||
|
import forge.game.player.Player;
|
||||||
|
import forge.game.replacement.ReplacementEffect;
|
||||||
|
import forge.game.spellability.AbilityActivated;
|
||||||
|
import forge.game.spellability.SpellAbility;
|
||||||
|
import forge.game.staticability.StaticAbility;
|
||||||
|
import forge.game.trigger.Trigger;
|
||||||
|
import forge.game.trigger.TriggerHandler;
|
||||||
|
import forge.game.trigger.TriggerType;
|
||||||
|
import forge.game.zone.ZoneType;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import com.google.common.base.Predicate;
|
|
||||||
|
|
||||||
import forge.Card;
|
|
||||||
import forge.CardLists;
|
|
||||||
import forge.CounterType;
|
|
||||||
import forge.GameEntity;
|
|
||||||
import forge.card.TriggerReplacementBase;
|
|
||||||
import forge.card.ability.AbilityFactory;
|
|
||||||
import forge.card.ability.AbilityUtils;
|
|
||||||
import forge.card.ability.ApiType;
|
|
||||||
import forge.card.cardfactory.CardFactoryUtil;
|
|
||||||
import forge.card.replacement.ReplacementEffect;
|
|
||||||
import forge.card.spellability.AbilityActivated;
|
|
||||||
import forge.card.spellability.SpellAbility;
|
|
||||||
import forge.card.staticability.StaticAbility;
|
|
||||||
import forge.card.trigger.Trigger;
|
|
||||||
import forge.card.trigger.TriggerHandler;
|
|
||||||
import forge.card.trigger.TriggerType;
|
|
||||||
import forge.game.Game;
|
|
||||||
import forge.game.GlobalRuleChange;
|
|
||||||
import forge.game.combat.Combat;
|
|
||||||
import forge.game.combat.CombatUtil;
|
|
||||||
import forge.game.player.Player;
|
|
||||||
import forge.game.zone.ZoneType;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>
|
* <p>
|
||||||
@@ -64,7 +63,7 @@ public class ComputerUtilCombat {
|
|||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param attacker
|
* @param attacker
|
||||||
* a {@link forge.Card} object.
|
* a {@link forge.game.card.Card} object.
|
||||||
* @param player
|
* @param player
|
||||||
* a {@link forge.game.player.Player} object.
|
* a {@link forge.game.player.Player} object.
|
||||||
* @return a int.
|
* @return a int.
|
||||||
@@ -91,7 +90,7 @@ public class ComputerUtilCombat {
|
|||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param c
|
* @param c
|
||||||
* a {@link forge.Card} object.
|
* a {@link forge.game.card.Card} object.
|
||||||
* @return a int.
|
* @return a int.
|
||||||
*/
|
*/
|
||||||
public static int getAttack(final Card c) {
|
public static int getAttack(final Card c) {
|
||||||
@@ -112,7 +111,7 @@ public class ComputerUtilCombat {
|
|||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param attacker
|
* @param attacker
|
||||||
* a {@link forge.Card} object.
|
* a {@link forge.game.card.Card} object.
|
||||||
* @param attacked
|
* @param attacked
|
||||||
* a {@link forge.game.player.Player} object.
|
* a {@link forge.game.player.Player} object.
|
||||||
* @param combat
|
* @param combat
|
||||||
@@ -142,7 +141,7 @@ public class ComputerUtilCombat {
|
|||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param attacker
|
* @param attacker
|
||||||
* a {@link forge.Card} object.
|
* a {@link forge.game.card.Card} object.
|
||||||
* @param attacked
|
* @param attacked
|
||||||
* a {@link forge.game.player.Player} object.
|
* a {@link forge.game.player.Player} object.
|
||||||
* @param combat
|
* @param combat
|
||||||
@@ -373,7 +372,7 @@ public class ComputerUtilCombat {
|
|||||||
public static boolean lifeInSeriousDanger(final Player ai, final Combat combat) {
|
public static boolean lifeInSeriousDanger(final Player ai, final Combat combat) {
|
||||||
// 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()) {
|
if (ai.cantLose() || combat == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -412,7 +411,7 @@ public class ComputerUtilCombat {
|
|||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param attacker
|
* @param attacker
|
||||||
* a {@link forge.Card} object.
|
* a {@link forge.game.card.Card} object.
|
||||||
* @param defenders
|
* @param defenders
|
||||||
* a {@link forge.CardList} object.
|
* a {@link forge.CardList} object.
|
||||||
* @return a int.
|
* @return a int.
|
||||||
@@ -434,9 +433,9 @@ public class ComputerUtilCombat {
|
|||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param attacker
|
* @param attacker
|
||||||
* a {@link forge.Card} object.
|
* a {@link forge.game.card.Card} object.
|
||||||
* @param defender
|
* @param defender
|
||||||
* a {@link forge.Card} object.
|
* a {@link forge.game.card.Card} object.
|
||||||
* @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) {
|
||||||
@@ -487,7 +486,7 @@ public class ComputerUtilCombat {
|
|||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param attacker
|
* @param attacker
|
||||||
* a {@link forge.Card} object.
|
* a {@link forge.game.card.Card} object.
|
||||||
* @param defenders
|
* @param defenders
|
||||||
* a {@link forge.CardList} object.
|
* a {@link forge.CardList} object.
|
||||||
* @return a int.
|
* @return a int.
|
||||||
@@ -511,9 +510,9 @@ public class ComputerUtilCombat {
|
|||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param attacker
|
* @param attacker
|
||||||
* a {@link forge.Card} object.
|
* a {@link forge.game.card.Card} object.
|
||||||
* @param defender
|
* @param defender
|
||||||
* a {@link forge.Card} object.
|
* a {@link forge.game.card.Card} object.
|
||||||
* @return a int.
|
* @return a int.
|
||||||
*/
|
*/
|
||||||
public static int shieldDamage(final Card attacker, final Card defender) {
|
public static int shieldDamage(final Card attacker, final Card defender) {
|
||||||
@@ -548,7 +547,7 @@ public class ComputerUtilCombat {
|
|||||||
* @param ai
|
* @param ai
|
||||||
*
|
*
|
||||||
* @param combatant
|
* @param combatant
|
||||||
* a {@link forge.Card} object.
|
* a {@link forge.game.card.Card} object.
|
||||||
* @return a boolean.
|
* @return a boolean.
|
||||||
*/
|
*/
|
||||||
public static boolean combatantWouldBeDestroyed(Player ai, final Card combatant, Combat combat) {
|
public static boolean combatantWouldBeDestroyed(Player ai, final Card combatant, Combat combat) {
|
||||||
@@ -570,7 +569,7 @@ public class ComputerUtilCombat {
|
|||||||
* @param ai
|
* @param ai
|
||||||
*
|
*
|
||||||
* @param attacker
|
* @param attacker
|
||||||
* a {@link forge.Card} object.
|
* a {@link forge.game.card.Card} object.
|
||||||
* @return a boolean.
|
* @return a boolean.
|
||||||
*/
|
*/
|
||||||
public static boolean attackerWouldBeDestroyed(Player ai, final Card attacker, Combat combat) {
|
public static boolean attackerWouldBeDestroyed(Player ai, final Card attacker, Combat combat) {
|
||||||
@@ -593,11 +592,11 @@ public class ComputerUtilCombat {
|
|||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param attacker
|
* @param attacker
|
||||||
* a {@link forge.Card} object.
|
* a {@link forge.game.card.Card} object.
|
||||||
* @param defender
|
* @param defender
|
||||||
* a {@link forge.Card} object.
|
* a {@link forge.game.card.Card} object.
|
||||||
* @param trigger
|
* @param trigger
|
||||||
* a {@link forge.card.trigger.Trigger} object.
|
* a {@link forge.game.trigger.Trigger} object.
|
||||||
* @param combat
|
* @param combat
|
||||||
* a {@link forge.game.combat.Combat} object.
|
* a {@link forge.game.combat.Combat} object.
|
||||||
* @return a boolean.
|
* @return a boolean.
|
||||||
@@ -605,7 +604,7 @@ 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) {
|
Combat combat) {
|
||||||
final Game game = attacker.getGame();
|
final Game game = attacker.getGame();
|
||||||
final HashMap<String, String> trigParams = trigger.getMapParams();
|
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) {
|
||||||
@@ -629,8 +628,8 @@ public class ComputerUtilCombat {
|
|||||||
return false; // The trigger should have triggered already
|
return false; // The trigger should have triggered already
|
||||||
}
|
}
|
||||||
if (trigParams.containsKey("ValidCard")) {
|
if (trigParams.containsKey("ValidCard")) {
|
||||||
if (!TriggerReplacementBase.matchesValid(attacker, trigParams.get("ValidCard").split(","), source)
|
if (!CardTraitBase.matchesValid(attacker, trigParams.get("ValidCard").split(","), source)
|
||||||
&& !(combat.isAttacking(source) && TriggerReplacementBase.matchesValid(source,
|
&& !(combat.isAttacking(source) && CardTraitBase.matchesValid(source,
|
||||||
trigParams.get("ValidCard").split(","), source)
|
trigParams.get("ValidCard").split(","), source)
|
||||||
&& !trigParams.containsKey("Alone"))) {
|
&& !trigParams.containsKey("Alone"))) {
|
||||||
return false;
|
return false;
|
||||||
@@ -642,7 +641,7 @@ public class ComputerUtilCombat {
|
|||||||
if ((defender == null) && mode == TriggerType.AttackerUnblocked) {
|
if ((defender == null) && mode == TriggerType.AttackerUnblocked) {
|
||||||
willTrigger = true;
|
willTrigger = true;
|
||||||
if (trigParams.containsKey("ValidCard")) {
|
if (trigParams.containsKey("ValidCard")) {
|
||||||
if (!TriggerReplacementBase.matchesValid(attacker, trigParams.get("ValidCard").split(","), source)) {
|
if (!CardTraitBase.matchesValid(attacker, trigParams.get("ValidCard").split(","), source)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -664,7 +663,7 @@ public class ComputerUtilCombat {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!TriggerReplacementBase.matchesValid(attacker, validBlocked.split(","), source)) {
|
if (!CardTraitBase.matchesValid(attacker, validBlocked.split(","), source)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -678,35 +677,35 @@ public class ComputerUtilCombat {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!TriggerReplacementBase.matchesValid(defender, validBlocker.split(","), source)) {
|
if (!CardTraitBase.matchesValid(defender, validBlocker.split(","), source)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (mode == TriggerType.AttackerBlocked) {
|
} else if (mode == TriggerType.AttackerBlocked) {
|
||||||
willTrigger = true;
|
willTrigger = true;
|
||||||
if (trigParams.containsKey("ValidBlocker")) {
|
if (trigParams.containsKey("ValidBlocker")) {
|
||||||
if (!TriggerReplacementBase.matchesValid(defender, trigParams.get("ValidBlocker").split(","), source)) {
|
if (!CardTraitBase.matchesValid(defender, trigParams.get("ValidBlocker").split(","), source)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (trigParams.containsKey("ValidCard")) {
|
if (trigParams.containsKey("ValidCard")) {
|
||||||
if (!TriggerReplacementBase.matchesValid(attacker, trigParams.get("ValidCard").split(","), source)) {
|
if (!CardTraitBase.matchesValid(attacker, trigParams.get("ValidCard").split(","), source)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (mode == TriggerType.DamageDone) {
|
} else if (mode == TriggerType.DamageDone) {
|
||||||
willTrigger = true;
|
willTrigger = true;
|
||||||
if (trigParams.containsKey("ValidSource")) {
|
if (trigParams.containsKey("ValidSource")) {
|
||||||
if (TriggerReplacementBase.matchesValid(defender, trigParams.get("ValidSource").split(","), source)
|
if (CardTraitBase.matchesValid(defender, trigParams.get("ValidSource").split(","), source)
|
||||||
&& defender.getNetCombatDamage() > 0
|
&& defender.getNetCombatDamage() > 0
|
||||||
&& (!trigParams.containsKey("ValidTarget")
|
&& (!trigParams.containsKey("ValidTarget")
|
||||||
|| TriggerReplacementBase.matchesValid(attacker, trigParams.get("ValidTarget").split(","), source))) {
|
|| CardTraitBase.matchesValid(attacker, trigParams.get("ValidTarget").split(","), source))) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (TriggerReplacementBase.matchesValid(attacker, trigParams.get("ValidSource").split(","), source)
|
if (CardTraitBase.matchesValid(attacker, trigParams.get("ValidSource").split(","), source)
|
||||||
&& attacker.getNetCombatDamage() > 0
|
&& attacker.getNetCombatDamage() > 0
|
||||||
&& (!trigParams.containsKey("ValidTarget")
|
&& (!trigParams.containsKey("ValidTarget")
|
||||||
|| TriggerReplacementBase.matchesValid(defender, trigParams.get("ValidTarget").split(","), source))) {
|
|| CardTraitBase.matchesValid(defender, trigParams.get("ValidTarget").split(","), source))) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -724,36 +723,44 @@ public class ComputerUtilCombat {
|
|||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param attacker
|
* @param attacker
|
||||||
* a {@link forge.Card} object.
|
* a {@link forge.game.card.Card} object.
|
||||||
* @param defender
|
* @param blocker
|
||||||
* a {@link forge.Card} object.
|
* a {@link forge.game.card.Card} object.
|
||||||
* @return a int.
|
* @return a int.
|
||||||
*/
|
*/
|
||||||
public static int predictPowerBonusOfBlocker(final Card attacker, final Card defender, boolean withoutAbilities) {
|
public static int predictPowerBonusOfBlocker(final Card attacker, final Card blocker, boolean withoutAbilities) {
|
||||||
int power = 0;
|
int power = 0;
|
||||||
|
|
||||||
if (attacker.hasKeyword("Flanking") && !defender.hasKeyword("Flanking")) {
|
if (attacker.hasKeyword("Flanking") && !blocker.hasKeyword("Flanking")) {
|
||||||
power -= attacker.getAmountOfKeyword("Flanking");
|
power -= attacker.getAmountOfKeyword("Flanking");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Serene Master switches power with attacker
|
||||||
|
if (blocker.getName().equals("Serene Master")) {
|
||||||
|
power += attacker.getNetAttack() - blocker.getNetAttack();
|
||||||
|
} else if (blocker.getName().equals("Shape Stealer")) {
|
||||||
|
power += attacker.getNetAttack() - blocker.getNetAttack();
|
||||||
|
}
|
||||||
|
|
||||||
// if the attacker has first strike and wither the blocker will deal
|
// if the attacker has first strike and wither the blocker will deal
|
||||||
// less damage than expected
|
// less damage than expected
|
||||||
if ((attacker.hasKeyword("First Strike") || attacker.hasKeyword("Double Strike"))
|
if (dealsFirstStrikeDamage(attacker, withoutAbilities)
|
||||||
&& (attacker.hasKeyword("Wither") || attacker.hasKeyword("Infect"))
|
&& (attacker.hasKeyword("Wither") || attacker.hasKeyword("Infect"))
|
||||||
&& !(defender.hasKeyword("First Strike") || defender.hasKeyword("Double Strike") || defender
|
&& !dealsFirstStrikeDamage(blocker, withoutAbilities)
|
||||||
.hasKeyword("CARDNAME can't have counters placed on it."))) {
|
&& !blocker.hasKeyword("CARDNAME can't have counters placed on it.")) {
|
||||||
power -= attacker.getNetCombatDamage();
|
power -= attacker.getNetCombatDamage();
|
||||||
}
|
}
|
||||||
|
|
||||||
power += defender.getKeywordMagnitude("Bushido");
|
power += blocker.getKeywordMagnitude("Bushido");
|
||||||
|
|
||||||
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 List<Card> cardList = game.getCardsIn(ZoneType.Battlefield);
|
final List<Card> cardList = game.getCardsIn(ZoneType.Battlefield);
|
||||||
|
cardList.addAll(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 HashMap<String, String> params = stAb.getMapParams();
|
final Map<String, String> params = stAb.getMapParams();
|
||||||
if (!params.get("Mode").equals("Continuous")) {
|
if (!params.get("Mode").equals("Continuous")) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -761,7 +768,7 @@ public class ComputerUtilCombat {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
final String valid = params.get("Affected").replace("blocking", "Creature");
|
final String valid = params.get("Affected").replace("blocking", "Creature");
|
||||||
if (!defender.isValid(valid, card.getController(), card)) {
|
if (!blocker.isValid(valid, card.getController(), card)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (params.containsKey("AddPower")) {
|
if (params.containsKey("AddPower")) {
|
||||||
@@ -780,12 +787,15 @@ public class ComputerUtilCombat {
|
|||||||
for (Card card : game.getCardsIn(ZoneType.Battlefield)) {
|
for (Card card : game.getCardsIn(ZoneType.Battlefield)) {
|
||||||
theTriggers.addAll(card.getTriggers());
|
theTriggers.addAll(card.getTriggers());
|
||||||
}
|
}
|
||||||
|
for (Card card : game.getCardsIn(ZoneType.Command)) {
|
||||||
|
theTriggers.addAll(card.getTriggers());
|
||||||
|
}
|
||||||
theTriggers.addAll(attacker.getTriggers());
|
theTriggers.addAll(attacker.getTriggers());
|
||||||
for (final Trigger trigger : theTriggers) {
|
for (final Trigger trigger : theTriggers) {
|
||||||
final HashMap<String, String> trigParams = trigger.getMapParams();
|
final Map<String, String> trigParams = trigger.getMapParams();
|
||||||
final Card source = trigger.getHostCard();
|
final Card source = trigger.getHostCard();
|
||||||
|
|
||||||
if (!ComputerUtilCombat.combatTriggerWillTrigger(attacker, defender, trigger, null)
|
if (!ComputerUtilCombat.combatTriggerWillTrigger(attacker, blocker, trigger, null)
|
||||||
|| !trigParams.containsKey("Execute")) {
|
|| !trigParams.containsKey("Execute")) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -802,12 +812,12 @@ public class ComputerUtilCombat {
|
|||||||
}
|
}
|
||||||
final List<Card> list = AbilityUtils.getDefinedCards(source, abilityParams.get("Defined"), null);
|
final List<Card> list = AbilityUtils.getDefinedCards(source, abilityParams.get("Defined"), null);
|
||||||
if (abilityParams.containsKey("Defined") && abilityParams.get("Defined").equals("TriggeredBlocker")) {
|
if (abilityParams.containsKey("Defined") && abilityParams.get("Defined").equals("TriggeredBlocker")) {
|
||||||
list.add(defender);
|
list.add(blocker);
|
||||||
}
|
}
|
||||||
if (list.isEmpty()) {
|
if (list.isEmpty()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (!list.contains(defender)) {
|
if (!list.contains(blocker)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (!abilityParams.containsKey("NumAtt")) {
|
if (!abilityParams.containsKey("NumAtt")) {
|
||||||
@@ -828,7 +838,7 @@ public class ComputerUtilCombat {
|
|||||||
if (withoutAbilities) {
|
if (withoutAbilities) {
|
||||||
return power;
|
return power;
|
||||||
}
|
}
|
||||||
for (SpellAbility ability : defender.getAllSpellAbilities()) {
|
for (SpellAbility ability : blocker.getAllSpellAbilities()) {
|
||||||
if (!(ability instanceof AbilityActivated) || ability.getPayCosts() == null) {
|
if (!(ability instanceof AbilityActivated) || ability.getPayCosts() == null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -841,8 +851,8 @@ public class ComputerUtilCombat {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ComputerUtilCost.canPayCost(ability, defender.getController())) {
|
if (ComputerUtilCost.canPayCost(ability, blocker.getController())) {
|
||||||
int pBonus = AbilityUtils.calculateAmount(ability.getSourceCard(), ability.getParam("NumAtt"), ability);
|
int pBonus = AbilityUtils.calculateAmount(ability.getHostCard(), ability.getParam("NumAtt"), ability);
|
||||||
if (pBonus > 0) {
|
if (pBonus > 0) {
|
||||||
power += pBonus;
|
power += pBonus;
|
||||||
}
|
}
|
||||||
@@ -852,14 +862,19 @@ public class ComputerUtilCombat {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ComputerUtilCost.canPayCost(ability, defender.getController())) {
|
if (ability.hasParam("Monstrosity") && blocker.isMonstrous()) {
|
||||||
int pBonus = AbilityUtils.calculateAmount(ability.getSourceCard(), ability.getParam("CounterNum"), ability);
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ComputerUtilCost.canPayCost(ability, blocker.getController())) {
|
||||||
|
int pBonus = AbilityUtils.calculateAmount(ability.getHostCard(), ability.getParam("CounterNum"), ability);
|
||||||
if (pBonus > 0) {
|
if (pBonus > 0) {
|
||||||
power += pBonus;
|
power += pBonus;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return power;
|
return power;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -871,30 +886,37 @@ public class ComputerUtilCombat {
|
|||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param attacker
|
* @param attacker
|
||||||
* a {@link forge.Card} object.
|
* a {@link forge.game.card.Card} object.
|
||||||
* @param defender
|
* @param blocker
|
||||||
* a {@link forge.Card} object.
|
* a {@link forge.game.card.Card} object.
|
||||||
* @return a int.
|
* @return a int.
|
||||||
*/
|
*/
|
||||||
public static int predictToughnessBonusOfBlocker(final Card attacker, final Card defender, boolean withoutAbilities) {
|
public static int predictToughnessBonusOfBlocker(final Card attacker, final Card blocker, boolean withoutAbilities) {
|
||||||
int toughness = 0;
|
int toughness = 0;
|
||||||
|
|
||||||
if (attacker.hasKeyword("Flanking") && !defender.hasKeyword("Flanking")) {
|
if (attacker.hasKeyword("Flanking") && !blocker.hasKeyword("Flanking")) {
|
||||||
toughness -= attacker.getAmountOfKeyword("Flanking");
|
toughness -= attacker.getAmountOfKeyword("Flanking");
|
||||||
}
|
}
|
||||||
|
|
||||||
toughness += defender.getKeywordMagnitude("Bushido");
|
if (blocker.getName().equals("Shape Stealer")) {
|
||||||
|
toughness += attacker.getNetDefense() - blocker.getNetDefense();
|
||||||
|
}
|
||||||
|
|
||||||
|
toughness += blocker.getKeywordMagnitude("Bushido");
|
||||||
final Game game = attacker.getGame();
|
final Game game = attacker.getGame();
|
||||||
final ArrayList<Trigger> theTriggers = new ArrayList<Trigger>();
|
final ArrayList<Trigger> theTriggers = new ArrayList<Trigger>();
|
||||||
for (Card card : game.getCardsIn(ZoneType.Battlefield)) {
|
for (Card card : game.getCardsIn(ZoneType.Battlefield)) {
|
||||||
theTriggers.addAll(card.getTriggers());
|
theTriggers.addAll(card.getTriggers());
|
||||||
}
|
}
|
||||||
|
for (Card card : game.getCardsIn(ZoneType.Command)) {
|
||||||
|
theTriggers.addAll(card.getTriggers());
|
||||||
|
}
|
||||||
theTriggers.addAll(attacker.getTriggers());
|
theTriggers.addAll(attacker.getTriggers());
|
||||||
for (final Trigger trigger : theTriggers) {
|
for (final Trigger trigger : theTriggers) {
|
||||||
final HashMap<String, String> trigParams = trigger.getMapParams();
|
final Map<String, String> trigParams = trigger.getMapParams();
|
||||||
final Card source = trigger.getHostCard();
|
final Card source = trigger.getHostCard();
|
||||||
|
|
||||||
if (!ComputerUtilCombat.combatTriggerWillTrigger(attacker, defender, trigger, null)
|
if (!ComputerUtilCombat.combatTriggerWillTrigger(attacker, blocker, trigger, null)
|
||||||
|| !trigParams.containsKey("Execute")) {
|
|| !trigParams.containsKey("Execute")) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -914,7 +936,7 @@ public class ComputerUtilCombat {
|
|||||||
// can't parse the number (X for example)
|
// can't parse the number (X for example)
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
toughness -= predictDamageTo(defender, damage, 0, source, false);
|
toughness -= predictDamageTo(blocker, damage, 0, source, false);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -930,12 +952,12 @@ public class ComputerUtilCombat {
|
|||||||
}
|
}
|
||||||
final List<Card> list = AbilityUtils.getDefinedCards(source, abilityParams.get("Defined"), null);
|
final List<Card> list = AbilityUtils.getDefinedCards(source, abilityParams.get("Defined"), null);
|
||||||
if (abilityParams.containsKey("Defined") && abilityParams.get("Defined").equals("TriggeredBlocker")) {
|
if (abilityParams.containsKey("Defined") && abilityParams.get("Defined").equals("TriggeredBlocker")) {
|
||||||
list.add(defender);
|
list.add(blocker);
|
||||||
}
|
}
|
||||||
if (list.isEmpty()) {
|
if (list.isEmpty()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (!list.contains(defender)) {
|
if (!list.contains(blocker)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (!abilityParams.containsKey("NumDef")) {
|
if (!abilityParams.containsKey("NumDef")) {
|
||||||
@@ -956,7 +978,7 @@ public class ComputerUtilCombat {
|
|||||||
if (withoutAbilities) {
|
if (withoutAbilities) {
|
||||||
return toughness;
|
return toughness;
|
||||||
}
|
}
|
||||||
for (SpellAbility ability : defender.getAllSpellAbilities()) {
|
for (SpellAbility ability : blocker.getAllSpellAbilities()) {
|
||||||
if (!(ability instanceof AbilityActivated) || ability.getPayCosts() == null) {
|
if (!(ability instanceof AbilityActivated) || ability.getPayCosts() == null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -965,9 +987,13 @@ public class ComputerUtilCombat {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ability.getApi() != ApiType.Pump || !ability.hasParam("NumDef")) {
|
if (ability.getApi() == ApiType.Pump) {
|
||||||
if (ComputerUtilCost.canPayCost(ability, defender.getController())) {
|
if (!ability.hasParam("NumDef")) {
|
||||||
int tBonus = AbilityUtils.calculateAmount(ability.getSourceCard(), ability.getParam("NumDef"), ability);
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ComputerUtilCost.canPayCost(ability, blocker.getController())) {
|
||||||
|
int tBonus = AbilityUtils.calculateAmount(ability.getHostCard(), ability.getParam("NumDef"), ability);
|
||||||
if (tBonus > 0) {
|
if (tBonus > 0) {
|
||||||
toughness += tBonus;
|
toughness += tBonus;
|
||||||
}
|
}
|
||||||
@@ -977,8 +1003,12 @@ public class ComputerUtilCombat {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ComputerUtilCost.canPayCost(ability, defender.getController())) {
|
if (ability.hasParam("Monstrosity") && blocker.isMonstrous()) {
|
||||||
int tBonus = AbilityUtils.calculateAmount(ability.getSourceCard(), ability.getParam("CounterNum"), ability);
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ComputerUtilCost.canPayCost(ability, blocker.getController())) {
|
||||||
|
int tBonus = AbilityUtils.calculateAmount(ability.getHostCard(), ability.getParam("CounterNum"), ability);
|
||||||
if (tBonus > 0) {
|
if (tBonus > 0) {
|
||||||
toughness += tBonus;
|
toughness += tBonus;
|
||||||
}
|
}
|
||||||
@@ -996,14 +1026,14 @@ public class ComputerUtilCombat {
|
|||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param attacker
|
* @param attacker
|
||||||
* a {@link forge.Card} object.
|
* a {@link forge.game.card.Card} object.
|
||||||
* @param defender
|
* @param blocker
|
||||||
* a {@link forge.Card} object.
|
* a {@link forge.game.card.Card} object.
|
||||||
* @param combat
|
* @param combat
|
||||||
* a {@link forge.game.combat.Combat} object.
|
* a {@link forge.game.combat.Combat} object.
|
||||||
* @return a int.
|
* @return a int.
|
||||||
*/
|
*/
|
||||||
public static int predictPowerBonusOfAttacker(final Card attacker, final Card defender, final Combat combat
|
public static int predictPowerBonusOfAttacker(final Card attacker, final Card blocker, final Combat combat
|
||||||
, boolean withoutAbilities) {
|
, boolean withoutAbilities) {
|
||||||
int power = 0;
|
int power = 0;
|
||||||
|
|
||||||
@@ -1015,29 +1045,40 @@ public class ComputerUtilCombat {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Serene Master switches power with attacker
|
||||||
|
if (blocker!= null && blocker.getName().equals("Serene Master")) {
|
||||||
|
power += blocker.getNetAttack() - attacker.getNetAttack();
|
||||||
|
} else if (blocker != null && attacker.getName().equals("Shape Stealer")) {
|
||||||
|
power += blocker.getNetAttack() - attacker.getNetAttack();
|
||||||
|
}
|
||||||
|
|
||||||
final Game game = attacker.getGame();
|
final Game game = attacker.getGame();
|
||||||
final ArrayList<Trigger> theTriggers = new ArrayList<Trigger>();
|
final ArrayList<Trigger> theTriggers = new ArrayList<Trigger>();
|
||||||
for (Card card : game.getCardsIn(ZoneType.Battlefield)) {
|
for (Card card : game.getCardsIn(ZoneType.Battlefield)) {
|
||||||
theTriggers.addAll(card.getTriggers());
|
theTriggers.addAll(card.getTriggers());
|
||||||
}
|
}
|
||||||
|
for (Card card : game.getCardsIn(ZoneType.Command)) {
|
||||||
|
theTriggers.addAll(card.getTriggers());
|
||||||
|
}
|
||||||
// if the defender has first strike and wither the attacker will deal
|
// if the defender has first strike and wither the attacker will deal
|
||||||
// less damage than expected
|
// less damage than expected
|
||||||
if (null != defender) {
|
if (null != blocker) {
|
||||||
if ((defender.hasKeyword("First Strike") || defender.hasKeyword("Double Strike"))
|
if (ComputerUtilCombat.dealsFirstStrikeDamage(blocker, withoutAbilities)
|
||||||
&& (defender.hasKeyword("Wither") || defender.hasKeyword("Infect"))
|
&& (blocker.hasKeyword("Wither") || blocker.hasKeyword("Infect"))
|
||||||
&& !(attacker.hasKeyword("First Strike") || attacker.hasKeyword("Double Strike") || attacker
|
&& !ComputerUtilCombat.dealsFirstStrikeDamage(attacker, withoutAbilities)
|
||||||
.hasKeyword("CARDNAME can't have counters placed on it."))) {
|
&& !attacker.hasKeyword("CARDNAME can't have counters placed on it.")) {
|
||||||
power -= defender.getNetCombatDamage();
|
power -= blocker.getNetCombatDamage();
|
||||||
}
|
}
|
||||||
theTriggers.addAll(defender.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
|
||||||
final List<Card> cardList = game.getCardsIn(ZoneType.Battlefield);
|
final List<Card> cardList = game.getCardsIn(ZoneType.Battlefield);
|
||||||
|
cardList.addAll(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 HashMap<String, String> params = stAb.getMapParams();
|
final Map<String, String> params = stAb.getMapParams();
|
||||||
if (!params.get("Mode").equals("Continuous")) {
|
if (!params.get("Mode").equals("Continuous")) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -1061,10 +1102,10 @@ public class ComputerUtilCombat {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (final Trigger trigger : theTriggers) {
|
for (final Trigger trigger : theTriggers) {
|
||||||
final HashMap<String, String> trigParams = trigger.getMapParams();
|
final Map<String, String> trigParams = trigger.getMapParams();
|
||||||
final Card source = trigger.getHostCard();
|
final Card source = trigger.getHostCard();
|
||||||
|
|
||||||
if (!ComputerUtilCombat.combatTriggerWillTrigger(attacker, defender, trigger, combat)
|
if (!ComputerUtilCombat.combatTriggerWillTrigger(attacker, blocker, trigger, combat)
|
||||||
|| !trigParams.containsKey("Execute")) {
|
|| !trigParams.containsKey("Execute")) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -1137,7 +1178,7 @@ public class ComputerUtilCombat {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!ability.getPayCosts().hasTapCost() && ComputerUtilCost.canPayCost(ability, attacker.getController())) {
|
if (!ability.getPayCosts().hasTapCost() && ComputerUtilCost.canPayCost(ability, attacker.getController())) {
|
||||||
int pBonus = AbilityUtils.calculateAmount(ability.getSourceCard(), ability.getParam("NumAtt"), ability);
|
int pBonus = AbilityUtils.calculateAmount(ability.getHostCard(), ability.getParam("NumAtt"), ability);
|
||||||
if (pBonus > 0) {
|
if (pBonus > 0) {
|
||||||
power += pBonus;
|
power += pBonus;
|
||||||
}
|
}
|
||||||
@@ -1147,8 +1188,12 @@ public class ComputerUtilCombat {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ability.hasParam("Monstrosity") && attacker.isMonstrous()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (!ability.getPayCosts().hasTapCost() && ComputerUtilCost.canPayCost(ability, attacker.getController())) {
|
if (!ability.getPayCosts().hasTapCost() && ComputerUtilCost.canPayCost(ability, attacker.getController())) {
|
||||||
int pBonus = AbilityUtils.calculateAmount(ability.getSourceCard(), ability.getParam("CounterNum"), ability);
|
int pBonus = AbilityUtils.calculateAmount(ability.getHostCard(), ability.getParam("CounterNum"), ability);
|
||||||
if (pBonus > 0) {
|
if (pBonus > 0) {
|
||||||
power += pBonus;
|
power += pBonus;
|
||||||
}
|
}
|
||||||
@@ -1166,14 +1211,14 @@ public class ComputerUtilCombat {
|
|||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param attacker
|
* @param attacker
|
||||||
* a {@link forge.Card} object.
|
* a {@link forge.game.card.Card} object.
|
||||||
* @param defender
|
* @param blocker
|
||||||
* a {@link forge.Card} object.
|
* a {@link forge.game.card.Card} object.
|
||||||
* @param combat
|
* @param combat
|
||||||
* a {@link forge.game.combat.Combat} object.
|
* a {@link forge.game.combat.Combat} object.
|
||||||
* @return a int.
|
* @return a int.
|
||||||
*/
|
*/
|
||||||
public static int predictToughnessBonusOfAttacker(final Card attacker, final Card defender, final Combat combat
|
public static int predictToughnessBonusOfAttacker(final Card attacker, final Card blocker, final Combat combat
|
||||||
, boolean withoutAbilities) {
|
, boolean withoutAbilities) {
|
||||||
int toughness = 0;
|
int toughness = 0;
|
||||||
|
|
||||||
@@ -1184,14 +1229,21 @@ public class ComputerUtilCombat {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (blocker != null && attacker.getName().equals("Shape Stealer")) {
|
||||||
|
toughness += blocker.getNetDefense() - attacker.getNetDefense();
|
||||||
|
}
|
||||||
|
|
||||||
final Game game = attacker.getGame();
|
final Game game = attacker.getGame();
|
||||||
final ArrayList<Trigger> theTriggers = new ArrayList<Trigger>();
|
final ArrayList<Trigger> theTriggers = new ArrayList<Trigger>();
|
||||||
for (Card card : game.getCardsIn(ZoneType.Battlefield)) {
|
for (Card card : game.getCardsIn(ZoneType.Battlefield)) {
|
||||||
theTriggers.addAll(card.getTriggers());
|
theTriggers.addAll(card.getTriggers());
|
||||||
}
|
}
|
||||||
if (defender != null) {
|
for (Card card : game.getCardsIn(ZoneType.Command)) {
|
||||||
|
theTriggers.addAll(card.getTriggers());
|
||||||
|
}
|
||||||
|
if (blocker != null) {
|
||||||
toughness += attacker.getKeywordMagnitude("Bushido");
|
toughness += attacker.getKeywordMagnitude("Bushido");
|
||||||
theTriggers.addAll(defender.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
|
||||||
@@ -1199,7 +1251,7 @@ public class ComputerUtilCombat {
|
|||||||
final List<Card> cardList = game.getCardsIn(ZoneType.Battlefield);
|
final List<Card> cardList = game.getCardsIn(ZoneType.Battlefield);
|
||||||
for (final Card card : cardList) {
|
for (final Card card : cardList) {
|
||||||
for (final StaticAbility stAb : card.getStaticAbilities()) {
|
for (final StaticAbility stAb : card.getStaticAbilities()) {
|
||||||
final HashMap<String, String> params = stAb.getMapParams();
|
final Map<String, String> params = stAb.getMapParams();
|
||||||
if (!params.get("Mode").equals("Continuous")) {
|
if (!params.get("Mode").equals("Continuous")) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -1223,10 +1275,10 @@ public class ComputerUtilCombat {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (final Trigger trigger : theTriggers) {
|
for (final Trigger trigger : theTriggers) {
|
||||||
final HashMap<String, String> trigParams = trigger.getMapParams();
|
final Map<String, String> trigParams = trigger.getMapParams();
|
||||||
final Card source = trigger.getHostCard();
|
final Card source = trigger.getHostCard();
|
||||||
|
|
||||||
if (!ComputerUtilCombat.combatTriggerWillTrigger(attacker, defender, trigger, combat)
|
if (!ComputerUtilCombat.combatTriggerWillTrigger(attacker, blocker, trigger, combat)
|
||||||
|| !trigParams.containsKey("Execute")) {
|
|| !trigParams.containsKey("Execute")) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -1312,9 +1364,13 @@ public class ComputerUtilCombat {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ability.getApi() != ApiType.Pump || !ability.hasParam("NumDef")) {
|
if (ability.getApi() == ApiType.Pump) {
|
||||||
|
if (!ability.hasParam("NumDef")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (!ability.getPayCosts().hasTapCost() && ComputerUtilCost.canPayCost(ability, attacker.getController())) {
|
if (!ability.getPayCosts().hasTapCost() && ComputerUtilCost.canPayCost(ability, attacker.getController())) {
|
||||||
int tBonus = AbilityUtils.calculateAmount(ability.getSourceCard(), ability.getParam("NumDef"), ability);
|
int tBonus = AbilityUtils.calculateAmount(ability.getHostCard(), ability.getParam("NumDef"), ability);
|
||||||
if (tBonus > 0) {
|
if (tBonus > 0) {
|
||||||
toughness += tBonus;
|
toughness += tBonus;
|
||||||
}
|
}
|
||||||
@@ -1324,8 +1380,12 @@ public class ComputerUtilCombat {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ability.hasParam("Monstrosity") && attacker.isMonstrous()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (!ability.getPayCosts().hasTapCost() && ComputerUtilCost.canPayCost(ability, attacker.getController())) {
|
if (!ability.getPayCosts().hasTapCost() && ComputerUtilCost.canPayCost(ability, attacker.getController())) {
|
||||||
int tBonus = AbilityUtils.calculateAmount(ability.getSourceCard(), ability.getParam("CounterNum"), ability);
|
int tBonus = AbilityUtils.calculateAmount(ability.getHostCard(), ability.getParam("CounterNum"), ability);
|
||||||
if (tBonus > 0) {
|
if (tBonus > 0) {
|
||||||
toughness += tBonus;
|
toughness += tBonus;
|
||||||
}
|
}
|
||||||
@@ -1342,9 +1402,9 @@ public class ComputerUtilCombat {
|
|||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param attacker
|
* @param attacker
|
||||||
* a {@link forge.Card} object.
|
* a {@link forge.game.card.Card} object.
|
||||||
* @param defender
|
* @param defender
|
||||||
* a {@link forge.Card} object.
|
* a {@link forge.game.card.Card} object.
|
||||||
* @return a boolean.
|
* @return a boolean.
|
||||||
*/
|
*/
|
||||||
public static boolean checkDestroyBlockerTrigger(final Card attacker, final Card defender) {
|
public static boolean checkDestroyBlockerTrigger(final Card attacker, final Card defender) {
|
||||||
@@ -1354,7 +1414,7 @@ public class ComputerUtilCombat {
|
|||||||
theTriggers.addAll(card.getTriggers());
|
theTriggers.addAll(card.getTriggers());
|
||||||
}
|
}
|
||||||
for (Trigger trigger : theTriggers) {
|
for (Trigger trigger : theTriggers) {
|
||||||
HashMap<String, String> trigParams = trigger.getMapParams();
|
Map<String, String> trigParams = trigger.getMapParams();
|
||||||
final Card source = trigger.getHostCard();
|
final Card source = trigger.getHostCard();
|
||||||
|
|
||||||
if (!ComputerUtilCombat.combatTriggerWillTrigger(attacker, defender, trigger, null)) {
|
if (!ComputerUtilCombat.combatTriggerWillTrigger(attacker, defender, trigger, null)) {
|
||||||
@@ -1398,9 +1458,9 @@ public class ComputerUtilCombat {
|
|||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param attacker
|
* @param attacker
|
||||||
* a {@link forge.Card} object.
|
* a {@link forge.game.card.Card} object.
|
||||||
* @param defender
|
* @param defender
|
||||||
* a {@link forge.Card} object.
|
* a {@link forge.game.card.Card} object.
|
||||||
* @return a boolean.
|
* @return a boolean.
|
||||||
*/
|
*/
|
||||||
public static boolean checkDestroyAttackerTrigger(final Card attacker, final Card defender) {
|
public static boolean checkDestroyAttackerTrigger(final Card attacker, final Card defender) {
|
||||||
@@ -1409,7 +1469,7 @@ public class ComputerUtilCombat {
|
|||||||
theTriggers.addAll(card.getTriggers());
|
theTriggers.addAll(card.getTriggers());
|
||||||
}
|
}
|
||||||
for (Trigger trigger : theTriggers) {
|
for (Trigger trigger : theTriggers) {
|
||||||
HashMap<String, String> trigParams = trigger.getMapParams();
|
Map<String, String> trigParams = trigger.getMapParams();
|
||||||
final Card source = trigger.getHostCard();
|
final Card source = trigger.getHostCard();
|
||||||
|
|
||||||
if (!ComputerUtilCombat.combatTriggerWillTrigger(attacker, defender, trigger, null)) {
|
if (!ComputerUtilCombat.combatTriggerWillTrigger(attacker, defender, trigger, null)) {
|
||||||
@@ -1454,9 +1514,9 @@ public class ComputerUtilCombat {
|
|||||||
* @param ai
|
* @param ai
|
||||||
*
|
*
|
||||||
* @param attacker
|
* @param attacker
|
||||||
* a {@link forge.Card} object.
|
* a {@link forge.game.card.Card} object.
|
||||||
* @param defender
|
* @param defender
|
||||||
* a {@link forge.Card} object.
|
* a {@link forge.game.card.Card} object.
|
||||||
* @param combat
|
* @param combat
|
||||||
* a {@link forge.game.combat.Combat} object.
|
* a {@link forge.game.combat.Combat} object.
|
||||||
* @param withoutAbilities
|
* @param withoutAbilities
|
||||||
@@ -1527,8 +1587,7 @@ public class ComputerUtilCombat {
|
|||||||
+ ComputerUtilCombat.predictToughnessBonusOfAttacker(attacker, defender, combat, withoutAbilities);
|
+ ComputerUtilCombat.predictToughnessBonusOfAttacker(attacker, defender, combat, withoutAbilities);
|
||||||
|
|
||||||
if (defender.hasKeyword("Double Strike")) {
|
if (defender.hasKeyword("Double Strike")) {
|
||||||
if (defenderDamage > 0 && (defender.hasKeyword("Deathtouch")
|
if (defenderDamage > 0 && (canGainKeyword(defender, "Deathtouch") || attacker.hasSVar("DestroyWhenDamaged"))) {
|
||||||
|| attacker.hasStartOfKeyword("When CARDNAME is dealt damage, destroy it."))) {
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (defenderDamage >= attackerLife) {
|
if (defenderDamage >= attackerLife) {
|
||||||
@@ -1541,8 +1600,7 @@ public class ComputerUtilCombat {
|
|||||||
if (attackerDamage >= defenderLife) {
|
if (attackerDamage >= defenderLife) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (attackerDamage > 0 && (attacker.hasKeyword("Deathtouch")
|
if (attackerDamage > 0 && (canGainKeyword(attacker, "Deathtouch") || defender.hasSVar("DestroyWhenDamaged"))) {
|
||||||
|| defender.hasStartOfKeyword("When CARDNAME is dealt damage, destroy it."))) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1554,19 +1612,18 @@ public class ComputerUtilCombat {
|
|||||||
else { // no double strike for defender
|
else { // no double strike for defender
|
||||||
// Attacker may kill the blocker before he can deal any damage
|
// Attacker may kill the blocker before he can deal any damage
|
||||||
if (dealsFirstStrikeDamage(attacker, withoutAbilities)
|
if (dealsFirstStrikeDamage(attacker, withoutAbilities)
|
||||||
&& !defender.hasKeyword("Indestructible") && !defender.hasKeyword("First Strike")) {
|
&& !defender.hasKeyword("Indestructible")
|
||||||
|
&& !dealsFirstStrikeDamage(defender, withoutAbilities)) {
|
||||||
|
|
||||||
if (attackerDamage >= defenderLife) {
|
if (attackerDamage >= defenderLife) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (attackerDamage > 0 && (attacker.hasKeyword("Deathtouch")
|
if (attackerDamage > 0 && (canGainKeyword(attacker, "Deathtouch") || defender.hasSVar("DestroyWhenDamaged"))) {
|
||||||
|| defender.hasStartOfKeyword("When CARDNAME is dealt damage, destroy it."))) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (defenderDamage > 0 && (defender.hasKeyword("Deathtouch")
|
if (defenderDamage > 0 && (canGainKeyword(defender, "Deathtouch") || attacker.hasSVar("DestroyWhenDamaged"))) {
|
||||||
|| attacker.hasStartOfKeyword("When CARDNAME is dealt damage, destroy it."))) {
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1584,7 +1641,7 @@ public class ComputerUtilCombat {
|
|||||||
* @param ai
|
* @param ai
|
||||||
*
|
*
|
||||||
* @param blocker
|
* @param blocker
|
||||||
* a {@link forge.Card} object.
|
* a {@link forge.game.card.Card} object.
|
||||||
* @return a boolean.
|
* @return a boolean.
|
||||||
*/
|
*/
|
||||||
public static boolean blockerWouldBeDestroyed(Player ai, final Card blocker, Combat combat) {
|
public static boolean blockerWouldBeDestroyed(Player ai, final Card blocker, Combat combat) {
|
||||||
@@ -1609,9 +1666,9 @@ public class ComputerUtilCombat {
|
|||||||
* @param ai
|
* @param ai
|
||||||
*
|
*
|
||||||
* @param defender
|
* @param defender
|
||||||
* a {@link forge.Card} object.
|
* a {@link forge.game.card.Card} object.
|
||||||
* @param attacker
|
* @param attacker
|
||||||
* a {@link forge.Card} object.
|
* a {@link forge.game.card.Card} object.
|
||||||
* @param combat
|
* @param combat
|
||||||
* a {@link forge.game.combat.Combat} object.
|
* a {@link forge.game.combat.Combat} object.
|
||||||
* @param withoutAbilities
|
* @param withoutAbilities
|
||||||
@@ -1683,8 +1740,7 @@ public class ComputerUtilCombat {
|
|||||||
+ ComputerUtilCombat.predictToughnessBonusOfAttacker(attacker, defender, combat, withoutAbilities);
|
+ ComputerUtilCombat.predictToughnessBonusOfAttacker(attacker, defender, combat, withoutAbilities);
|
||||||
|
|
||||||
if (attacker.hasKeyword("Double Strike")) {
|
if (attacker.hasKeyword("Double Strike")) {
|
||||||
if (attackerDamage > 0 && (attacker.hasKeyword("Deathtouch")
|
if (attackerDamage > 0 && (canGainKeyword(attacker, "Deathtouch") || defender.hasSVar("DestroyWhenDamaged"))) {
|
||||||
|| defender.hasStartOfKeyword("When CARDNAME is dealt damage, destroy it."))) {
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (attackerDamage >= defenderLife) {
|
if (attackerDamage >= defenderLife) {
|
||||||
@@ -1697,8 +1753,7 @@ public class ComputerUtilCombat {
|
|||||||
if (defenderDamage >= attackerLife) {
|
if (defenderDamage >= attackerLife) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (defenderDamage > 0 && (defender.hasKeyword("Deathtouch")
|
if (defenderDamage > 0 && (canGainKeyword(defender, "Deathtouch") || attacker.hasSVar("DestroyWhenDamaged"))) {
|
||||||
|| attacker.hasStartOfKeyword("When CARDNAME is dealt damage, destroy it."))) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1710,19 +1765,17 @@ public class ComputerUtilCombat {
|
|||||||
else { // no double strike for attacker
|
else { // no double strike for attacker
|
||||||
// Defender may kill the attacker before he can deal any damage
|
// Defender may kill the attacker before he can deal any damage
|
||||||
if (dealsFirstStrikeDamage(defender, withoutAbilities) && !attacker.hasKeyword("Indestructible")
|
if (dealsFirstStrikeDamage(defender, withoutAbilities) && !attacker.hasKeyword("Indestructible")
|
||||||
&& !attacker.hasKeyword("First Strike")) {
|
&& !dealsFirstStrikeDamage(attacker, withoutAbilities)) {
|
||||||
|
|
||||||
if (defenderDamage >= attackerLife) {
|
if (defenderDamage >= attackerLife) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (defenderDamage > 0 && (defender.hasKeyword("Deathtouch")
|
if (defenderDamage > 0 && (canGainKeyword(defender, "Deathtouch") || attacker.hasSVar("DestroyWhenDamaged"))) {
|
||||||
|| attacker.hasStartOfKeyword("When CARDNAME is dealt damage, destroy it."))) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (attackerDamage > 0 && (attacker.hasKeyword("Deathtouch")
|
if (attackerDamage > 0 && (canGainKeyword(attacker, "Deathtouch") || defender.hasSVar("DestroyWhenDamaged"))) {
|
||||||
|| defender.hasStartOfKeyword("When CARDNAME is dealt damage, destroy it."))) {
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1739,7 +1792,7 @@ public class ComputerUtilCombat {
|
|||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param attacker
|
* @param attacker
|
||||||
* a {@link forge.Card} object.
|
* a {@link forge.game.card.Card} object.
|
||||||
* @param block
|
* @param block
|
||||||
* a {@link forge.CardList} object.
|
* a {@link forge.CardList} object.
|
||||||
* @param dmgCanDeal
|
* @param dmgCanDeal
|
||||||
@@ -1834,7 +1887,7 @@ public class ComputerUtilCombat {
|
|||||||
* @param maxDamage
|
* @param maxDamage
|
||||||
* a int.
|
* a int.
|
||||||
* @param source
|
* @param source
|
||||||
* a {@link forge.Card} object.
|
* a {@link forge.game.card.Card} object.
|
||||||
* @param isCombat
|
* @param isCombat
|
||||||
* a boolean.
|
* a boolean.
|
||||||
* @return a int.
|
* @return a int.
|
||||||
@@ -1851,7 +1904,7 @@ public class ComputerUtilCombat {
|
|||||||
* @param maxDamage
|
* @param maxDamage
|
||||||
* a int.
|
* a int.
|
||||||
* @param source
|
* @param source
|
||||||
* a {@link forge.Card} object.
|
* a {@link forge.game.card.Card} object.
|
||||||
* @param isCombat
|
* @param isCombat
|
||||||
* a boolean.
|
* a boolean.
|
||||||
* @param noPrevention
|
* @param noPrevention
|
||||||
@@ -1862,7 +1915,7 @@ public class ComputerUtilCombat {
|
|||||||
final boolean noPrevention) {
|
final boolean noPrevention) {
|
||||||
final int killDamage = ComputerUtilCombat.getDamageToKill(c);
|
final int killDamage = ComputerUtilCombat.getDamageToKill(c);
|
||||||
|
|
||||||
if (c.hasKeyword("Indestructible") || (c.getShield() > 0)) {
|
if (c.hasKeyword("Indestructible") || !c.getShield().isEmpty()) {
|
||||||
if (!(source.hasKeyword("Wither") || source.hasKeyword("Infect"))) {
|
if (!(source.hasKeyword("Wither") || source.hasKeyword("Infect"))) {
|
||||||
return maxDamage + 1;
|
return maxDamage + 1;
|
||||||
}
|
}
|
||||||
@@ -1904,7 +1957,7 @@ public class ComputerUtilCombat {
|
|||||||
public final static int getDamageToKill(final Card c) {
|
public final static int getDamageToKill(final Card c) {
|
||||||
int killDamage = c.getLethalDamage() + c.getPreventNextDamageTotalShields();
|
int killDamage = c.getLethalDamage() + c.getPreventNextDamageTotalShields();
|
||||||
if ((killDamage > c.getPreventNextDamageTotalShields())
|
if ((killDamage > c.getPreventNextDamageTotalShields())
|
||||||
&& c.hasStartOfKeyword("When CARDNAME is dealt damage, destroy it.")) {
|
&& c.hasSVar("DestroyWhenDamaged")) {
|
||||||
killDamage = 1 + c.getPreventNextDamageTotalShields();
|
killDamage = 1 + c.getPreventNextDamageTotalShields();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1920,7 +1973,7 @@ public class ComputerUtilCombat {
|
|||||||
* @param damage
|
* @param damage
|
||||||
* a int.
|
* a int.
|
||||||
* @param source
|
* @param source
|
||||||
* a {@link forge.Card} object.
|
* a {@link forge.game.card.Card} object.
|
||||||
* @param isCombat
|
* @param isCombat
|
||||||
* a boolean.
|
* a boolean.
|
||||||
* @return a int.
|
* @return a int.
|
||||||
@@ -1981,7 +2034,7 @@ public class ComputerUtilCombat {
|
|||||||
* @param damage
|
* @param damage
|
||||||
* a int.
|
* a int.
|
||||||
* @param source
|
* @param source
|
||||||
* a {@link forge.Card} object.
|
* a {@link forge.game.card.Card} object.
|
||||||
* @param isCombat
|
* @param isCombat
|
||||||
* a boolean.
|
* a boolean.
|
||||||
* @return a int.
|
* @return a int.
|
||||||
@@ -2011,7 +2064,7 @@ public class ComputerUtilCombat {
|
|||||||
* @param possiblePrevention
|
* @param possiblePrevention
|
||||||
* a int.
|
* a int.
|
||||||
* @param source
|
* @param source
|
||||||
* a {@link forge.Card} object.
|
* a {@link forge.game.card.Card} object.
|
||||||
* @param isCombat
|
* @param isCombat
|
||||||
* a boolean.
|
* a boolean.
|
||||||
* @return a int.
|
* @return a int.
|
||||||
@@ -2033,6 +2086,26 @@ public class ComputerUtilCombat {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!withoutAbilities) {
|
if (!withoutAbilities) {
|
||||||
|
List<String> keywords = new ArrayList<String>();
|
||||||
|
keywords.add("Double Strike");
|
||||||
|
keywords.add("First Strike");
|
||||||
|
return canGainKeyword(combatant, keywords);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final static boolean canGainKeyword(final Card combatant, final String keyword) {
|
||||||
|
if (combatant.hasKeyword(keyword)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> keywords = new ArrayList<String>();
|
||||||
|
keywords.add(keyword);
|
||||||
|
return canGainKeyword(combatant, keywords);
|
||||||
|
}
|
||||||
|
|
||||||
|
public final static boolean canGainKeyword(final Card combatant, final List<String> keywords) {
|
||||||
for (SpellAbility ability : combatant.getAllSpellAbilities()) {
|
for (SpellAbility ability : combatant.getAllSpellAbilities()) {
|
||||||
if (!(ability instanceof AbilityActivated) || ability.getPayCosts() == null) {
|
if (!(ability instanceof AbilityActivated) || ability.getPayCosts() == null) {
|
||||||
continue;
|
continue;
|
||||||
@@ -2045,11 +2118,11 @@ public class ComputerUtilCombat {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ability.hasParam("KW")) {
|
if (!ability.hasParam("KW") || !ComputerUtilCost.canPayCost(ability, combatant.getController())) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
for (String keyword : keywords) {
|
||||||
if (ability.getParam("KW").contains("First Strike") && ComputerUtilCost.canPayCost(ability, combatant.getController())) {
|
if (ability.getParam("KW").contains(keyword)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,29 +1,22 @@
|
|||||||
package forge.game.ai;
|
package forge.ai;
|
||||||
|
|
||||||
|
import forge.ai.AiAttackController;
|
||||||
|
import forge.game.ability.AbilityUtils;
|
||||||
|
import forge.game.card.Card;
|
||||||
|
import forge.game.card.CardLists;
|
||||||
|
import forge.game.card.CounterType;
|
||||||
|
import forge.game.combat.Combat;
|
||||||
|
import forge.game.cost.*;
|
||||||
|
import forge.game.player.Player;
|
||||||
|
import forge.game.spellability.Spell;
|
||||||
|
import forge.game.spellability.SpellAbility;
|
||||||
|
import forge.game.zone.ZoneType;
|
||||||
|
import forge.util.TextUtil;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
|
|
||||||
import forge.Card;
|
|
||||||
import forge.CardLists;
|
|
||||||
import forge.CounterType;
|
|
||||||
import forge.card.ability.AbilityUtils;
|
|
||||||
import forge.card.cost.Cost;
|
|
||||||
import forge.card.cost.CostDamage;
|
|
||||||
import forge.card.cost.CostDiscard;
|
|
||||||
import forge.card.cost.CostPart;
|
|
||||||
import forge.card.cost.CostPayLife;
|
|
||||||
import forge.card.cost.CostPayment;
|
|
||||||
import forge.card.cost.CostPutCounter;
|
|
||||||
import forge.card.cost.CostRemoveCounter;
|
|
||||||
import forge.card.cost.CostSacrifice;
|
|
||||||
import forge.card.spellability.Spell;
|
|
||||||
import forge.card.spellability.SpellAbility;
|
|
||||||
import forge.game.player.Player;
|
|
||||||
import forge.game.zone.ZoneType;
|
|
||||||
import forge.util.TextUtil;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODO: Write javadoc for this type.
|
* TODO: Write javadoc for this type.
|
||||||
*
|
*
|
||||||
@@ -73,7 +66,7 @@ public class ComputerUtilCost {
|
|||||||
if (part instanceof CostRemoveCounter) {
|
if (part instanceof CostRemoveCounter) {
|
||||||
final CostRemoveCounter remCounter = (CostRemoveCounter) part;
|
final CostRemoveCounter remCounter = (CostRemoveCounter) part;
|
||||||
|
|
||||||
final CounterType type = remCounter.getCounter();
|
final CounterType type = remCounter.counter;
|
||||||
if (!part.payCostFromSource()) {
|
if (!part.payCostFromSource()) {
|
||||||
if (type.name().equals("P1P1")) {
|
if (type.name().equals("P1P1")) {
|
||||||
return false;
|
return false;
|
||||||
@@ -268,6 +261,28 @@ public class ComputerUtilCost {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check creature sacrifice cost.
|
||||||
|
*
|
||||||
|
* @param cost
|
||||||
|
* the cost
|
||||||
|
* @param source
|
||||||
|
* the source
|
||||||
|
* @return true, if successful
|
||||||
|
*/
|
||||||
|
public static boolean checkTapTypeCost(final Player ai, final Cost cost, final Card source) {
|
||||||
|
if (cost == null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
for (final CostPart part : cost.getCostParts()) {
|
||||||
|
if (part instanceof CostTapType) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check sacrifice cost.
|
* Check sacrifice cost.
|
||||||
*
|
*
|
||||||
@@ -287,7 +302,7 @@ public class ComputerUtilCost {
|
|||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param hostCard
|
* @param hostCard
|
||||||
* a {@link forge.Card} object.
|
* a {@link forge.game.card.Card} object.
|
||||||
* @param costString
|
* @param costString
|
||||||
* a {@link java.lang.String} object.
|
* a {@link java.lang.String} object.
|
||||||
* @return a boolean.
|
* @return a boolean.
|
||||||
@@ -315,13 +330,15 @@ public class ComputerUtilCost {
|
|||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param sa
|
* @param sa
|
||||||
* a {@link forge.card.spellability.SpellAbility} object.
|
* a {@link forge.game.spellability.SpellAbility} object.
|
||||||
* @param player
|
* @param player
|
||||||
* a {@link forge.game.player.Player} object.
|
* a {@link forge.game.player.Player} object.
|
||||||
* @return a boolean.
|
* @return a boolean.
|
||||||
*/
|
*/
|
||||||
public static boolean canPayCost(final SpellAbility sa, final Player player) {
|
public static boolean canPayCost(final SpellAbility sa, final Player player) {
|
||||||
|
if (sa.getActivatingPlayer() == null) {
|
||||||
sa.setActivatingPlayer(player); // complaints on NPE had came before this line was added.
|
sa.setActivatingPlayer(player); // complaints on NPE had came before this line was added.
|
||||||
|
}
|
||||||
|
|
||||||
// Check for stuff like Nether Void
|
// Check for stuff like Nether Void
|
||||||
int extraManaNeeded = 0;
|
int extraManaNeeded = 0;
|
||||||
@@ -358,21 +375,26 @@ public class ComputerUtilCost {
|
|||||||
} // canPayCost()
|
} // canPayCost()
|
||||||
|
|
||||||
public static boolean willPayUnlessCost(SpellAbility sa, Player payer, Cost cost, boolean alreadyPaid, List<Player> payers) {
|
public static boolean willPayUnlessCost(SpellAbility sa, Player payer, Cost cost, boolean alreadyPaid, List<Player> payers) {
|
||||||
final Card source = sa.getSourceCard();
|
final Card source = sa.getHostCard();
|
||||||
boolean payForOwnOnly = "OnlyOwn".equals(sa.getParam("UnlessAI"));
|
final String aiLogic = sa.getParam("UnlessAI");
|
||||||
boolean payOwner = sa.hasParam("UnlessAI") ? sa.getParam("UnlessAI").startsWith("Defined") : false;
|
boolean payForOwnOnly = "OnlyOwn".equals(aiLogic);
|
||||||
boolean payNever = "Never".equals(sa.getParam("UnlessAI"));
|
boolean payOwner = sa.hasParam("UnlessAI") ? aiLogic.startsWith("Defined") : false;
|
||||||
boolean shockland = "Shockland".equals(sa.getParam("UnlessAI"));
|
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; }
|
||||||
if (payForOwnOnly && !isMine) { return false; }
|
if (payForOwnOnly && !isMine) { return false; }
|
||||||
if (payOwner) {
|
if (payOwner) {
|
||||||
final String defined = sa.getParam("UnlessAI").substring(7);
|
final String defined = aiLogic.substring(7);
|
||||||
final Player player = AbilityUtils.getDefinedPlayers(source, defined, sa).get(0);
|
final Player player = AbilityUtils.getDefinedPlayers(source, defined, sa).get(0);
|
||||||
if (!payer.equals(player)) {
|
if (!payer.equals(player)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
} else if ("OnlyDontControl".equals(aiLogic)) {
|
||||||
|
if (sa.getHostCard() == null || payer.equals(sa.getHostCard().getController())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
} else if (shockland) {
|
} else if (shockland) {
|
||||||
if (payer.getLife() > 3 && payer.canPayLife(2)) {
|
if (payer.getLife() > 3 && payer.canPayLife(2)) {
|
||||||
// If the new land size would equal the CMC of a card in AIs hand, play it untapped
|
// If the new land size would equal the CMC of a card in AIs hand, play it untapped
|
||||||
@@ -384,17 +406,28 @@ public class ComputerUtilCost {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
} else if ("Paralyze".equals(sa.getParam("UnlessAI"))) {
|
} 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()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else if ("MorePowerful".equals(sa.getParam("UnlessAI"))) {
|
} else if ("MorePowerful".equals(aiLogic)) {
|
||||||
final int sourceCreatures = sa.getActivatingPlayer().getCreaturesInPlay().size();
|
final int sourceCreatures = sa.getActivatingPlayer().getCreaturesInPlay().size();
|
||||||
final int payerCreatures = payer.getCreaturesInPlay().size();
|
final int payerCreatures = payer.getCreaturesInPlay().size();
|
||||||
if (payerCreatures > sourceCreatures + 1) {
|
if (payerCreatures > sourceCreatures + 1) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
} else if ("LifeLE2".equals(aiLogic)) {
|
||||||
|
if (payer.getLife() < 3) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} else if ("WillAttack".equals(aiLogic)) {
|
||||||
|
AiAttackController aiAtk = new AiAttackController(payer);
|
||||||
|
Combat combat = new Combat(payer);
|
||||||
|
aiAtk.declareAttackers(combat);
|
||||||
|
if (combat.getAttackers().isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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
|
||||||
934
forge-ai/src/main/java/forge/ai/ComputerUtilMana.java
Normal file
934
forge-ai/src/main/java/forge/ai/ComputerUtilMana.java
Normal file
@@ -0,0 +1,934 @@
|
|||||||
|
package forge.ai;
|
||||||
|
|
||||||
|
import com.google.common.base.Predicate;
|
||||||
|
import com.google.common.collect.ArrayListMultimap;
|
||||||
|
import com.google.common.collect.Multimap;
|
||||||
|
|
||||||
|
import forge.card.MagicColor;
|
||||||
|
import forge.card.mana.ManaAtom;
|
||||||
|
import forge.card.mana.ManaCost;
|
||||||
|
import forge.card.mana.ManaCostShard;
|
||||||
|
import forge.game.Game;
|
||||||
|
import forge.game.GameActionUtil;
|
||||||
|
import forge.game.ability.AbilityUtils;
|
||||||
|
import forge.game.ability.ApiType;
|
||||||
|
import forge.game.card.Card;
|
||||||
|
import forge.game.card.CardLists;
|
||||||
|
import forge.game.card.CardUtil;
|
||||||
|
import forge.game.cost.Cost;
|
||||||
|
import forge.game.cost.CostPartMana;
|
||||||
|
import forge.game.cost.CostPayment;
|
||||||
|
import forge.game.mana.Mana;
|
||||||
|
import forge.game.mana.ManaCostAdjustment;
|
||||||
|
import forge.game.mana.ManaCostBeingPaid;
|
||||||
|
import forge.game.mana.ManaPool;
|
||||||
|
import forge.game.player.Player;
|
||||||
|
import forge.game.replacement.ReplacementEffect;
|
||||||
|
import forge.game.spellability.AbilityManaPart;
|
||||||
|
import forge.game.spellability.AbilitySub;
|
||||||
|
import forge.game.spellability.SpellAbility;
|
||||||
|
import forge.game.zone.ZoneType;
|
||||||
|
import forge.util.TextUtil;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.apache.commons.lang3.tuple.Pair;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO: Write javadoc for this type.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class ComputerUtilMana {
|
||||||
|
private final static boolean DEBUG_MANA_PAYMENT = false;
|
||||||
|
|
||||||
|
public static boolean canPayManaCost(ManaCostBeingPaid cost, final SpellAbility sa, final Player ai) {
|
||||||
|
cost = new ManaCostBeingPaid(cost); //check copy of cost so it doesn't modify the exist cost being paid
|
||||||
|
return payManaCost(cost, sa, ai, true, 0, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean payManaCost(ManaCostBeingPaid cost, final SpellAbility sa, final Player ai) {
|
||||||
|
return payManaCost(cost, sa, ai, false, 0, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean canPayManaCost(final SpellAbility sa, final Player ai, final int extraMana) {
|
||||||
|
return payManaCost(sa, ai, true, extraMana, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Does not check if mana sources can be used right now, just checks for potential chance.
|
||||||
|
public static boolean hasEnoughManaSourcesToCast(final SpellAbility sa, final Player ai) {
|
||||||
|
sa.setActivatingPlayer(ai);
|
||||||
|
return payManaCost(sa, ai, true, 0, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean payManaCost(final Player ai, final SpellAbility sa) {
|
||||||
|
return payManaCost(sa, ai, false, 0, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void refundMana(List<Mana> manaSpent, Player ai, SpellAbility sa) {
|
||||||
|
if (sa.getHostCard() != null) {
|
||||||
|
sa.getHostCard().setCanCounter(true);
|
||||||
|
}
|
||||||
|
for (final Mana m : manaSpent) {
|
||||||
|
ai.getManaPool().addMana(m);
|
||||||
|
}
|
||||||
|
manaSpent.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static boolean payManaCost(final SpellAbility sa, final Player ai, final boolean test, final int extraMana, boolean checkPlayable) {
|
||||||
|
ManaCostBeingPaid cost = ComputerUtilMana.calculateManaCost(sa, test, extraMana);
|
||||||
|
return payManaCost(cost, sa, ai, test, extraMana, checkPlayable);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean payManaCost(final ManaCostBeingPaid cost, final SpellAbility sa, final Player ai, final boolean test, final int extraMana, boolean checkPlayable) {
|
||||||
|
adjustManaCostToAvoidNegEffects(cost, sa.getHostCard(), ai);
|
||||||
|
List<Mana> manaSpentToPay = test ? new ArrayList<Mana>() : sa.getPayingMana();
|
||||||
|
|
||||||
|
final ManaPool manapool = ai.getManaPool();
|
||||||
|
List<ManaCostShard> unpaidShards = cost.getUnpaidShards();
|
||||||
|
Collections.sort(unpaidShards); // most difficult shards must come first
|
||||||
|
for (ManaCostShard part : unpaidShards) {
|
||||||
|
if (part != ManaCostShard.X) {
|
||||||
|
if (cost.isPaid()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// get a mana of this type from floating, bail if none available
|
||||||
|
final Mana mana = getMana(ai, part, sa, cost.getSourceRestriction());
|
||||||
|
if (mana == null) {
|
||||||
|
continue; // no matching mana in the pool
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (ai.getManaPool().tryPayCostWithMana(sa, cost, mana)) {
|
||||||
|
manaSpentToPay.add(0, mana);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cost.isPaid()) {
|
||||||
|
// refund any mana taken from mana pool when test
|
||||||
|
if(test)
|
||||||
|
refundMana(manaSpentToPay, ai, sa);
|
||||||
|
|
||||||
|
handleOfferingsAI(sa, test, cost.isPaid());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// arrange all mana abilities by color produced.
|
||||||
|
final ArrayListMultimap<Integer, SpellAbility> manaAbilityMap = ComputerUtilMana.groupSourcesByManaColor(ai, checkPlayable);
|
||||||
|
if (manaAbilityMap.isEmpty()) {
|
||||||
|
refundMana(manaSpentToPay, ai, sa);
|
||||||
|
|
||||||
|
handleOfferingsAI(sa, test, cost.isPaid());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (DEBUG_MANA_PAYMENT) {
|
||||||
|
System.out.println("DEBUG_MANA_PAYMENT: manaAbilityMap = " + manaAbilityMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
// select which abilities may be used for each shard
|
||||||
|
ArrayListMultimap<ManaCostShard, SpellAbility> sourcesForShards = ComputerUtilMana.groupAndOrderToPayShards(ai, manaAbilityMap, cost);
|
||||||
|
|
||||||
|
if (DEBUG_MANA_PAYMENT) {
|
||||||
|
System.out.println("DEBUG_MANA_PAYMENT: sourcesForShards = " + sourcesForShards);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> paymentPlan = new ArrayList<String>();
|
||||||
|
|
||||||
|
ManaCostShard toPay = null;
|
||||||
|
// Loop over mana needed
|
||||||
|
while (!cost.isPaid()) {
|
||||||
|
toPay = getNextShardToPay(cost, sourcesForShards);
|
||||||
|
|
||||||
|
Collection<SpellAbility> saList = sourcesForShards.get(toPay);
|
||||||
|
SpellAbility saPayment = null;
|
||||||
|
if (saList != null) {
|
||||||
|
for (final SpellAbility ma : saList) {
|
||||||
|
if (ma.getHostCard() == sa.getHostCard()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
final String typeRes = cost.getSourceRestriction();
|
||||||
|
if (StringUtils.isNotBlank(typeRes) && !ma.getHostCard().isType(typeRes)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (canPayShardWithSpellAbility(toPay, ai, ma, sa, checkPlayable || !test)) {
|
||||||
|
saPayment = ma;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (DEBUG_MANA_PAYMENT) {
|
||||||
|
paymentPlan.add(String.format("%s : (%s) %s", toPay, saPayment == null ? "LIFE" : saPayment.getHostCard(), saPayment));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (saPayment == null) {
|
||||||
|
if (!toPay.isPhyrexian() || !ai.canPayLife(2)) {
|
||||||
|
break; // cannot pay
|
||||||
|
}
|
||||||
|
|
||||||
|
cost.payPhyrexian();
|
||||||
|
if (!test) {
|
||||||
|
ai.payLife(2, sa.getHostCard());
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
setExpressColorChoice(sa, ai, cost, toPay, saPayment);
|
||||||
|
|
||||||
|
if (test) {
|
||||||
|
String manaProduced = toPay.isSnow() ? "S" : GameActionUtil.generatedMana(saPayment);
|
||||||
|
manaProduced = AbilityManaPart.applyManaReplacement(saPayment, manaProduced);
|
||||||
|
//System.out.println(manaProduced);
|
||||||
|
payMultipleMana(cost, manaProduced, ai);
|
||||||
|
|
||||||
|
// remove from available lists
|
||||||
|
Iterator<SpellAbility> itSa = sourcesForShards.values().iterator();
|
||||||
|
while (itSa.hasNext()) {
|
||||||
|
SpellAbility srcSa = itSa.next();
|
||||||
|
if (srcSa.getHostCard().equals(saPayment.getHostCard())) {
|
||||||
|
itSa.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (saPayment.getPayCosts() != null) {
|
||||||
|
final CostPayment pay = new CostPayment(saPayment.getPayCosts(), saPayment);
|
||||||
|
if (!pay.payComputerCosts(new AiCostDecision(ai, saPayment))) {
|
||||||
|
saList.remove(saPayment);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
System.err.println("Ability " + saPayment + " from " + saPayment.getHostCard() + " had NULL as payCost");
|
||||||
|
saPayment.getHostCard().tap();
|
||||||
|
}
|
||||||
|
|
||||||
|
ai.getGame().getStack().addAndUnfreeze(saPayment);
|
||||||
|
// subtract mana from mana pool
|
||||||
|
manapool.payManaFromAbility(sa, cost, saPayment);
|
||||||
|
|
||||||
|
// no need to remove abilities from resource map,
|
||||||
|
// once their costs are paid and consume resources, they can not be used again
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleOfferingsAI(sa, test, cost.isPaid());
|
||||||
|
|
||||||
|
// if (DEBUG_MANA_PAYMENT) {
|
||||||
|
// System.err.printf("%s > [%s] payment has %s (%s +%d) for (%s) %s:%n\t%s%n%n",
|
||||||
|
// FThreads.debugGetCurrThreadId(), test ? "test" : "PROD", cost.isPaid() ? "*PAID*" : "failed", originalCost,
|
||||||
|
// extraMana, sa.getHostCard(), sa.toUnsuppressedString(), StringUtils.join(paymentPlan, "\n\t"));
|
||||||
|
// }
|
||||||
|
|
||||||
|
if (!cost.isPaid()) {
|
||||||
|
refundMana(manaSpentToPay, ai, sa);
|
||||||
|
if (test) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
System.out.println("ComputerUtil : payManaCost() cost was not paid for " + sa.getHostCard().getName() + ". Didn't find what to pay for " + toPay);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (test)
|
||||||
|
refundMana(manaSpentToPay, ai, sa);
|
||||||
|
|
||||||
|
sa.getHostCard().setColorsPaid(cost.getColorsPaid());
|
||||||
|
// if (sa instanceof Spell_Permanent) // should probably add this
|
||||||
|
sa.getHostCard().setSunburstValue(cost.getSunburst());
|
||||||
|
return true;
|
||||||
|
} // payManaCost()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* getManaFrom.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param saBeingPaidFor
|
||||||
|
* a {@link forge.game.spellability.SpellAbility} object.
|
||||||
|
* @return a {@link forge.game.mana.Mana} object.
|
||||||
|
*/
|
||||||
|
private static Mana getMana(final Player ai, final ManaCostShard shard, final SpellAbility saBeingPaidFor, String restriction) {
|
||||||
|
final List<Pair<Mana, Integer>> weightedOptions = selectManaToPayFor(ai.getManaPool(), shard, saBeingPaidFor, restriction);
|
||||||
|
|
||||||
|
// Exclude border case
|
||||||
|
if (weightedOptions.isEmpty()) {
|
||||||
|
return null; // There is no matching mana in the pool
|
||||||
|
}
|
||||||
|
|
||||||
|
// select equal weight possibilities
|
||||||
|
List<Mana> manaChoices = new ArrayList<Mana>();
|
||||||
|
int bestWeight = Integer.MIN_VALUE;
|
||||||
|
for (Pair<Mana, Integer> option : weightedOptions) {
|
||||||
|
int thisWeight = option.getRight();
|
||||||
|
Mana thisMana = option.getLeft();
|
||||||
|
|
||||||
|
if (thisWeight > bestWeight) {
|
||||||
|
manaChoices.clear();
|
||||||
|
bestWeight = thisWeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (thisWeight == bestWeight) {
|
||||||
|
// add only distinct Mana-s
|
||||||
|
boolean haveDuplicate = false;
|
||||||
|
for (Mana m : manaChoices) {
|
||||||
|
if (m.equals(thisMana)) {
|
||||||
|
haveDuplicate = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!haveDuplicate) {
|
||||||
|
manaChoices.add(thisMana);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// got an only one best option?
|
||||||
|
if (manaChoices.size() == 1) {
|
||||||
|
return manaChoices.get(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Let them choose then
|
||||||
|
return ai.getController().chooseManaFromPool(manaChoices);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<Pair<Mana, Integer>> selectManaToPayFor(final ManaPool manapool, final ManaCostShard shard, final SpellAbility saBeingPaidFor, String restriction) {
|
||||||
|
final List<Pair<Mana, Integer>> weightedOptions = new ArrayList<Pair<Mana, Integer>>();
|
||||||
|
for (final Mana thisMana : manapool) {
|
||||||
|
if (!manapool.canPayForShardWithColor(shard, thisMana.getColor())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (thisMana.getManaAbility() != null && !thisMana.getManaAbility().meetsManaRestrictions(saBeingPaidFor)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean canPay = manapool.canPayForShardWithColor(shard, thisMana.getColor());
|
||||||
|
if (!canPay || (shard.isSnow() && !thisMana.isSnow())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (StringUtils.isNotBlank(restriction) && !thisMana.getSourceCard().isType(restriction)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// prefer colorless mana to spend
|
||||||
|
int weight = thisMana.isColorless() ? 5 : 0;
|
||||||
|
|
||||||
|
// prefer restricted mana to spend
|
||||||
|
if (thisMana.isRestricted()) {
|
||||||
|
weight += 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Spend non-snow mana first
|
||||||
|
if (!thisMana.isSnow()) {
|
||||||
|
weight += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
weightedOptions.add(Pair.of(thisMana, weight));
|
||||||
|
}
|
||||||
|
return weightedOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void setExpressColorChoice(final SpellAbility sa, final Player ai, ManaCostBeingPaid cost,
|
||||||
|
ManaCostShard toPay, SpellAbility saPayment) {
|
||||||
|
|
||||||
|
AbilityManaPart m = saPayment.getManaPart();
|
||||||
|
if (m.isComboMana())
|
||||||
|
getComboManaChoice(ai, saPayment, sa, cost);
|
||||||
|
else if (saPayment.getApi() == ApiType.ManaReflected) {
|
||||||
|
System.out.println("Evaluate reflected mana of: " + saPayment.getHostCard());
|
||||||
|
Set<String> reflected = CardUtil.getReflectableManaColors(saPayment);
|
||||||
|
|
||||||
|
for (byte c : MagicColor.WUBRG) {
|
||||||
|
if (ai.getManaPool().canPayForShardWithColor(toPay, c) && reflected.contains(MagicColor.toLongString(c))) {
|
||||||
|
m.setExpressChoice(MagicColor.toShortString(c));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (m.isAnyMana()) {
|
||||||
|
byte colorChoice = 0;
|
||||||
|
if (toPay.isOr2Colorless())
|
||||||
|
colorChoice = toPay.getColorMask();
|
||||||
|
else {
|
||||||
|
for (byte c : MagicColor.WUBRG) {
|
||||||
|
if (ai.getManaPool().canPayForShardWithColor(toPay, c)) {
|
||||||
|
colorChoice = c;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m.setExpressChoice(MagicColor.toShortString(colorChoice));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean canPayShardWithSpellAbility(ManaCostShard toPay, Player ai, SpellAbility ma, SpellAbility sa, boolean checkCosts) {
|
||||||
|
final Card sourceCard = ma.getHostCard();
|
||||||
|
|
||||||
|
if (toPay.isSnow() && !sourceCard.isSnow()) { return false; }
|
||||||
|
|
||||||
|
AbilityManaPart m = ma.getManaPart();
|
||||||
|
if (!m.meetsManaRestrictions(sa)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (checkCosts) {
|
||||||
|
// Check if AI can still play this mana ability
|
||||||
|
ma.setActivatingPlayer(ai);
|
||||||
|
if (ma.getPayCosts() != null) { // if the AI can't pay the additional costs skip the mana ability
|
||||||
|
if (!CostPayment.canPayAdditionalCosts(ma.getPayCosts(), ma)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else if (sourceCard.isTapped()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m.isComboMana()) {
|
||||||
|
for (String s : m.getComboColors().split(" ")) {
|
||||||
|
if ("Any".equals(s) || ai.getManaPool().canPayForShardWithColor(toPay, MagicColor.fromName(s)))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
|
||||||
|
} else if (ma.getApi() == ApiType.ManaReflected) {
|
||||||
|
Set<String> reflected = CardUtil.getReflectableManaColors(ma);
|
||||||
|
|
||||||
|
for (byte c : MagicColor.WUBRG) {
|
||||||
|
if (ai.getManaPool().canPayForShardWithColor(toPay, c) && reflected.contains(MagicColor.toLongString(c))) {
|
||||||
|
m.setExpressChoice(MagicColor.toShortString(c));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static ManaCostShard getNextShardToPay(ManaCostBeingPaid cost, Multimap<ManaCostShard, SpellAbility> sourcesForShards) {
|
||||||
|
// mind the priorities
|
||||||
|
// * Pay mono-colored first,
|
||||||
|
// * Pay 2/C with matching colors
|
||||||
|
// * pay hybrids
|
||||||
|
// * pay phyrexian, keep mana for colorless
|
||||||
|
// * pay colorless
|
||||||
|
|
||||||
|
for (ManaCostShard s : cost.getDistinctShards()) { // should check in which order EnumMap enumerates keys. If it's same as enum member declaration, nothing else needs to be done.
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void adjustManaCostToAvoidNegEffects(ManaCostBeingPaid cost, final Card card, Player ai) {
|
||||||
|
// Make mana needed to avoid negative effect a mandatory cost for the AI
|
||||||
|
for (String manaPart : card.getSVar("ManaNeededToAvoidNegativeEffect").split(",")) {
|
||||||
|
// convert long color strings to short color strings
|
||||||
|
byte mask = MagicColor.fromName(manaPart);
|
||||||
|
|
||||||
|
// make mana mandatory for AI
|
||||||
|
if (!cost.needsColor(mask, ai.getManaPool()) && cost.getColorlessManaAmount() > 0) {
|
||||||
|
ManaCostShard shard = ManaCostShard.valueOf(mask);
|
||||||
|
cost.increaseShard(shard, 1);
|
||||||
|
cost.decreaseColorlessMana(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* getComboManaChoice.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param abMana
|
||||||
|
* a {@link forge.card.spellability.AbilityMana} object.
|
||||||
|
* @param saRoot
|
||||||
|
* a {@link forge.game.spellability.SpellAbility} object.
|
||||||
|
* @param cost
|
||||||
|
* a {@link forge.game.mana.ManaCostBeingPaid} object.
|
||||||
|
* @return String
|
||||||
|
*/
|
||||||
|
private static void getComboManaChoice(final Player ai, final SpellAbility manaAb, final SpellAbility saRoot, final ManaCostBeingPaid cost) {
|
||||||
|
final StringBuilder choiceString = new StringBuilder();
|
||||||
|
final Card source = manaAb.getHostCard();
|
||||||
|
final AbilityManaPart abMana = manaAb.getManaPart();
|
||||||
|
|
||||||
|
if (abMana.isComboMana()) {
|
||||||
|
int amount = manaAb.hasParam("Amount") ? AbilityUtils.calculateAmount(source, manaAb.getParam("Amount"), saRoot) : 1;
|
||||||
|
final ManaCostBeingPaid testCost = new ManaCostBeingPaid(cost);
|
||||||
|
final String[] comboColors = abMana.getComboColors().split(" ");
|
||||||
|
for (int nMana = 1; nMana <= amount; nMana++) {
|
||||||
|
String choice = "";
|
||||||
|
// Use expressChoice first
|
||||||
|
if (!abMana.getExpressChoice().isEmpty()) {
|
||||||
|
choice = abMana.getExpressChoice();
|
||||||
|
abMana.clearExpressChoice();
|
||||||
|
byte colorMask = MagicColor.fromName(choice);
|
||||||
|
if (abMana.canProduce(choice, manaAb) && testCost.isAnyPartPayableWith(colorMask, ai.getManaPool())) {
|
||||||
|
choiceString.append(choice);
|
||||||
|
payMultipleMana(testCost, choice, ai);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// check colors needed for cost
|
||||||
|
if (!testCost.isPaid()) {
|
||||||
|
// Loop over combo colors
|
||||||
|
for (String color : comboColors) {
|
||||||
|
if (testCost.isAnyPartPayableWith(MagicColor.fromName(color), ai.getManaPool())) {
|
||||||
|
payMultipleMana(testCost, color, ai);
|
||||||
|
if (nMana != 1) {
|
||||||
|
choiceString.append(" ");
|
||||||
|
}
|
||||||
|
choiceString.append(color);
|
||||||
|
choice = color;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!choice.isEmpty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// check if combo mana can produce most common color in hand
|
||||||
|
String commonColor = ComputerUtilCard.getMostProminentColor(ai.getCardsIn(
|
||||||
|
ZoneType.Hand));
|
||||||
|
if (!commonColor.isEmpty() && abMana.getComboColors().contains(MagicColor.toShortString(commonColor))) {
|
||||||
|
choice = MagicColor.toShortString(commonColor);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// default to first color
|
||||||
|
choice = comboColors[0];
|
||||||
|
}
|
||||||
|
if (nMana != 1) {
|
||||||
|
choiceString.append(" ");
|
||||||
|
}
|
||||||
|
choiceString.append(choice);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (choiceString.toString().isEmpty()) {
|
||||||
|
choiceString.append("0");
|
||||||
|
}
|
||||||
|
|
||||||
|
abMana.setExpressChoice(choiceString.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* payMultipleMana.
|
||||||
|
* </p>
|
||||||
|
* @param testCost
|
||||||
|
*
|
||||||
|
* @param mana
|
||||||
|
* a {@link java.lang.String} object.
|
||||||
|
* @return a boolean.
|
||||||
|
*/
|
||||||
|
private final static String payMultipleMana(ManaCostBeingPaid testCost, String mana, final Player p) {
|
||||||
|
List<String> unused = new ArrayList<String>(4);
|
||||||
|
for (String manaPart : TextUtil.split(mana, ' ')) {
|
||||||
|
if (StringUtils.isNumeric(manaPart)) {
|
||||||
|
for (int i = Integer.parseInt(manaPart); i > 0; i--) {
|
||||||
|
boolean wasNeeded = testCost.ai_payMana("1", p.getManaPool());
|
||||||
|
if (!wasNeeded) {
|
||||||
|
unused.add(Integer.toString(i));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
String color = MagicColor.toShortString(manaPart);
|
||||||
|
boolean wasNeeded = testCost.ai_payMana(color, p.getManaPool());
|
||||||
|
if (!wasNeeded) {
|
||||||
|
unused.add(color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return unused.isEmpty() ? null : StringUtils.join(unused, ' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find all mana sources.
|
||||||
|
* @param manaAbilityMap
|
||||||
|
* @param partSources
|
||||||
|
* @param partPriority
|
||||||
|
* @param costParts
|
||||||
|
* @param foundAllSources
|
||||||
|
* @return Were all mana sources found?
|
||||||
|
*/
|
||||||
|
private static ArrayListMultimap<ManaCostShard, SpellAbility> groupAndOrderToPayShards(final Player ai, final ArrayListMultimap<Integer, SpellAbility> manaAbilityMap,
|
||||||
|
final ManaCostBeingPaid cost) {
|
||||||
|
ArrayListMultimap<ManaCostShard, SpellAbility> res = ArrayListMultimap.create();
|
||||||
|
|
||||||
|
if (cost.getColorlessManaAmount() > 0 && manaAbilityMap.containsKey(ManaAtom.COLORLESS)) {
|
||||||
|
res.putAll(ManaCostShard.COLORLESS, manaAbilityMap.get(ManaAtom.COLORLESS));
|
||||||
|
}
|
||||||
|
|
||||||
|
// loop over cost parts
|
||||||
|
for (ManaCostShard shard : cost.getDistinctShards()) {
|
||||||
|
if (DEBUG_MANA_PAYMENT) {
|
||||||
|
System.out.println("DEBUG_MANA_PAYMENT: shard = " + shard);
|
||||||
|
}
|
||||||
|
if (shard == ManaCostShard.S) {
|
||||||
|
res.putAll(shard, manaAbilityMap.get(ManaAtom.IS_SNOW));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shard.isOr2Colorless()) {
|
||||||
|
Integer colorKey = Integer.valueOf(shard.getColorMask());
|
||||||
|
if (manaAbilityMap.containsKey(colorKey))
|
||||||
|
res.putAll(shard, manaAbilityMap.get(colorKey));
|
||||||
|
if (manaAbilityMap.containsKey(ManaAtom.COLORLESS))
|
||||||
|
res.putAll(shard, manaAbilityMap.get(ManaAtom.COLORLESS));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shard == ManaCostShard.COLORLESS) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Integer colorint : manaAbilityMap.keySet()) {
|
||||||
|
// apply mana color change matrix here
|
||||||
|
if (ai.getManaPool().canPayForShardWithColor(shard, colorint.byteValue())) {
|
||||||
|
for (SpellAbility sa : manaAbilityMap.get(colorint)) {
|
||||||
|
if (!res.get(shard).contains(sa)) {
|
||||||
|
res.get(shard).add(res.get(shard).size(), sa);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate the ManaCost for the given SpellAbility.
|
||||||
|
* @param sa
|
||||||
|
* @param test
|
||||||
|
* @param extraMana
|
||||||
|
* @return ManaCost
|
||||||
|
*/
|
||||||
|
static ManaCostBeingPaid calculateManaCost(final SpellAbility sa, final boolean test, final int extraMana) {
|
||||||
|
ZoneType castFromBackup = null;
|
||||||
|
if (test && sa.isSpell()) {
|
||||||
|
castFromBackup = sa.getHostCard().getCastFrom();
|
||||||
|
sa.getHostCard().setCastFrom(sa.getHostCard().getZone().getZoneType());
|
||||||
|
}
|
||||||
|
|
||||||
|
Cost payCosts = sa.getPayCosts();
|
||||||
|
CostPartMana manapart = payCosts != null ? payCosts.getCostMana() : null;
|
||||||
|
final ManaCost mana = payCosts != null ? ( manapart == null ? ManaCost.ZERO : manapart.getManaCostFor(sa) ) : ManaCost.NO_COST;
|
||||||
|
|
||||||
|
String restriction = null;
|
||||||
|
if (payCosts != null && payCosts.getCostMana() != null) {
|
||||||
|
restriction = payCosts.getCostMana().getRestiction();
|
||||||
|
}
|
||||||
|
ManaCostBeingPaid cost = new ManaCostBeingPaid(mana, restriction);
|
||||||
|
ManaCostAdjustment.adjust(cost, sa, test);
|
||||||
|
|
||||||
|
final Card card = sa.getHostCard();
|
||||||
|
// Tack xMana Payments into mana here if X is a set value
|
||||||
|
if ((sa.getPayCosts() != null) && (cost.getXcounter() > 0 || extraMana > 0)) {
|
||||||
|
int manaToAdd = 0;
|
||||||
|
if (test && extraMana > 0) {
|
||||||
|
final int multiplicator = Math.max(cost.getXcounter(), 1);
|
||||||
|
manaToAdd = extraMana * multiplicator;
|
||||||
|
} else {
|
||||||
|
// For Count$xPaid set PayX in the AFs then use that here
|
||||||
|
// Else calculate it as appropriate.
|
||||||
|
final String xSvar = card.getSVar("X").startsWith("Count$xPaid") ? "PayX" : "X";
|
||||||
|
if (!card.getSVar(xSvar).equals("")) {
|
||||||
|
if (xSvar.equals("PayX")) {
|
||||||
|
manaToAdd = Integer.parseInt(card.getSVar(xSvar)) * cost.getXcounter(); // X
|
||||||
|
} else {
|
||||||
|
manaToAdd = AbilityUtils.calculateAmount(card, xSvar, sa) * cost.getXcounter();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String manaXColor = sa.getParam("XColor");
|
||||||
|
ManaCostShard shardToGrow = ManaCostShard.parseNonGeneric(manaXColor == null ? "1" : manaXColor);
|
||||||
|
cost.increaseShard(shardToGrow, manaToAdd);
|
||||||
|
|
||||||
|
if (!test) {
|
||||||
|
card.setXManaCostPaid(manaToAdd / cost.getXcounter());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (test && sa.isSpell()) {
|
||||||
|
sa.getHostCard().setCastFrom(castFromBackup);
|
||||||
|
}
|
||||||
|
|
||||||
|
return cost;
|
||||||
|
}
|
||||||
|
|
||||||
|
//This method is currently used by AI to estimate available mana
|
||||||
|
public static ArrayList<Card> getAvailableMana(final Player ai, final boolean checkPlayable) {
|
||||||
|
final ArrayList<Card> list = new ArrayList<Card>();
|
||||||
|
list.addAll(ai.getCardsIn(ZoneType.Battlefield));
|
||||||
|
list.addAll(ai.getCardsIn(ZoneType.Hand));
|
||||||
|
final List<Card> manaSources = CardLists.filter(list, new Predicate<Card>() {
|
||||||
|
@Override
|
||||||
|
public boolean apply(final Card c) {
|
||||||
|
for (final SpellAbility am : getAIPlayableMana(c)) {
|
||||||
|
am.setActivatingPlayer(ai);
|
||||||
|
if (!checkPlayable || am.canPlay()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}); // CardListFilter
|
||||||
|
|
||||||
|
final ArrayList<Card> sortedManaSources = new ArrayList<Card>();
|
||||||
|
final ArrayList<Card> otherManaSources = new ArrayList<Card>();
|
||||||
|
final ArrayList<Card> colorlessManaSources = new ArrayList<Card>();
|
||||||
|
final ArrayList<Card> oneManaSources = new ArrayList<Card>();
|
||||||
|
final ArrayList<Card> twoManaSources = new ArrayList<Card>();
|
||||||
|
final ArrayList<Card> threeManaSources = new ArrayList<Card>();
|
||||||
|
final ArrayList<Card> fourManaSources = new ArrayList<Card>();
|
||||||
|
final ArrayList<Card> fiveManaSources = new ArrayList<Card>();
|
||||||
|
final ArrayList<Card> anyColorManaSources = new ArrayList<Card>();
|
||||||
|
|
||||||
|
// Sort mana sources
|
||||||
|
// 1. Use lands that can only produce colorless mana without
|
||||||
|
// drawback/cost first
|
||||||
|
// 2. Search for mana sources that have a certain number of abilities
|
||||||
|
// 3. Use lands that produce any color many
|
||||||
|
// 4. all other sources (creature, costs, drawback, etc.)
|
||||||
|
for (Card card : manaSources) {
|
||||||
|
if (card.isCreature() || card.isEnchanted()) {
|
||||||
|
otherManaSources.add(card);
|
||||||
|
continue; // don't use creatures before other permanents
|
||||||
|
}
|
||||||
|
|
||||||
|
int usableManaAbilities = 0;
|
||||||
|
boolean needsLimitedResources = false;
|
||||||
|
boolean producesAnyColor = false;
|
||||||
|
final ArrayList<SpellAbility> manaAbilities = getAIPlayableMana(card);
|
||||||
|
|
||||||
|
for (final SpellAbility m : manaAbilities) {
|
||||||
|
if (m.getManaPart().isAnyMana()) {
|
||||||
|
producesAnyColor = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
final Cost cost = m.getPayCosts();
|
||||||
|
if (cost != null) {
|
||||||
|
needsLimitedResources |= !cost.isReusuableResource();
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the AI can't pay the additional costs skip the mana ability
|
||||||
|
if (cost != null) {
|
||||||
|
m.setActivatingPlayer(ai);
|
||||||
|
if (!CostPayment.canPayAdditionalCosts(m.getPayCosts(), m)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// don't use abilities with dangerous drawbacks
|
||||||
|
AbilitySub sub = m.getSubAbility();
|
||||||
|
if (sub != null && !card.getName().equals("Pristine Talisman") && !card.getName().equals("Zhur-Taa Druid")) {
|
||||||
|
if (!SpellApiToAi.Converter.get(sub.getApi()).chkDrawbackWithSubs(ai, sub)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
needsLimitedResources = true; // TODO: check for good drawbacks (gainLife)
|
||||||
|
}
|
||||||
|
usableManaAbilities++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (needsLimitedResources) {
|
||||||
|
otherManaSources.add(card);
|
||||||
|
} else if (producesAnyColor) {
|
||||||
|
anyColorManaSources.add(card);
|
||||||
|
} else if (usableManaAbilities == 1) {
|
||||||
|
if (manaAbilities.get(0).getManaPart().mana().equals("1")) {
|
||||||
|
colorlessManaSources.add(card);
|
||||||
|
} else {
|
||||||
|
oneManaSources.add(card);
|
||||||
|
}
|
||||||
|
} else if (usableManaAbilities == 2) {
|
||||||
|
twoManaSources.add(card);
|
||||||
|
} else if (usableManaAbilities == 3) {
|
||||||
|
threeManaSources.add(card);
|
||||||
|
} else if (usableManaAbilities == 4) {
|
||||||
|
fourManaSources.add(card);
|
||||||
|
} else {
|
||||||
|
fiveManaSources.add(card);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sortedManaSources.addAll(sortedManaSources.size(), colorlessManaSources);
|
||||||
|
sortedManaSources.addAll(sortedManaSources.size(), oneManaSources);
|
||||||
|
sortedManaSources.addAll(sortedManaSources.size(), twoManaSources);
|
||||||
|
sortedManaSources.addAll(sortedManaSources.size(), threeManaSources);
|
||||||
|
sortedManaSources.addAll(sortedManaSources.size(), fourManaSources);
|
||||||
|
sortedManaSources.addAll(sortedManaSources.size(), fiveManaSources);
|
||||||
|
sortedManaSources.addAll(sortedManaSources.size(), anyColorManaSources);
|
||||||
|
//use better creatures later
|
||||||
|
ComputerUtilCard.sortByEvaluateCreature(otherManaSources);
|
||||||
|
Collections.reverse(otherManaSources);
|
||||||
|
sortedManaSources.addAll(sortedManaSources.size(), otherManaSources);
|
||||||
|
|
||||||
|
if (DEBUG_MANA_PAYMENT) {
|
||||||
|
System.out.println("DEBUG_MANA_PAYMENT: manaAbilityMap = " + sortedManaSources);
|
||||||
|
}
|
||||||
|
return sortedManaSources;
|
||||||
|
} // getAvailableMana()
|
||||||
|
|
||||||
|
//This method is currently used by AI to estimate mana available
|
||||||
|
private static ArrayListMultimap<Integer, SpellAbility> groupSourcesByManaColor(final Player ai, boolean checkPlayable) {
|
||||||
|
final ArrayListMultimap<Integer, SpellAbility> manaMap = ArrayListMultimap.create();
|
||||||
|
final Game game = ai.getGame();
|
||||||
|
|
||||||
|
// Loop over all current available mana sources
|
||||||
|
for (final Card sourceCard : getAvailableMana(ai, checkPlayable)) {
|
||||||
|
if (DEBUG_MANA_PAYMENT) {
|
||||||
|
System.out.println("DEBUG_MANA_PAYMENT: groupSourcesByManaColor sourceCard = " + sourceCard);
|
||||||
|
}
|
||||||
|
for (final SpellAbility m : getAIPlayableMana(sourceCard)) {
|
||||||
|
if (DEBUG_MANA_PAYMENT) {
|
||||||
|
System.out.println("DEBUG_MANA_PAYMENT: groupSourcesByManaColor m = " + m);
|
||||||
|
}
|
||||||
|
m.setActivatingPlayer(ai);
|
||||||
|
if (checkPlayable && !m.canPlay()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// don't use abilities with dangerous drawbacks
|
||||||
|
AbilitySub sub = m.getSubAbility();
|
||||||
|
if (sub != null) {
|
||||||
|
if (!SpellApiToAi.Converter.get(sub.getApi()).chkDrawbackWithSubs(ai, sub)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
manaMap.get(ManaAtom.COLORLESS).add(manaMap.get(ManaAtom.COLORLESS).size(), m); // add to colorless source list
|
||||||
|
AbilityManaPart mp = m.getManaPart();
|
||||||
|
|
||||||
|
// setup produce mana replacement effects
|
||||||
|
final Map<String, Object> repParams = new HashMap<String, Object>();
|
||||||
|
repParams.put("Event", "ProduceMana");
|
||||||
|
repParams.put("Mana", mp.getOrigProduced());
|
||||||
|
repParams.put("Affected", sourceCard);
|
||||||
|
repParams.put("Player", ai);
|
||||||
|
repParams.put("AbilityMana", m);
|
||||||
|
|
||||||
|
for (final Player p : game.getPlayers()) {
|
||||||
|
for (final Card crd : p.getAllCards()) {
|
||||||
|
for (final ReplacementEffect replacementEffect : crd.getReplacementEffects()) {
|
||||||
|
if (replacementEffect.requirementsCheck(game)
|
||||||
|
&& replacementEffect.canReplace(repParams)
|
||||||
|
&& replacementEffect.getMapParams().containsKey("ManaReplacement")
|
||||||
|
&& replacementEffect.zonesCheck(game.getZoneOf(crd))) {
|
||||||
|
String repType = crd.getSVar(replacementEffect.getMapParams().get("ManaReplacement"));
|
||||||
|
if (repType.contains("Chosen")) {
|
||||||
|
repType = repType.replace("Chosen", MagicColor.toShortString(crd.getChosenColor().get(0)));
|
||||||
|
}
|
||||||
|
mp.setManaReplaceType(repType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Set<String> reflectedColors = CardUtil.getReflectableManaColors(m);
|
||||||
|
// find possible colors
|
||||||
|
if (mp.canProduce("W", m) || reflectedColors.contains(MagicColor.Constant.WHITE)) {
|
||||||
|
manaMap.get(ManaAtom.WHITE).add(manaMap.get(ManaAtom.WHITE).size(), m);
|
||||||
|
}
|
||||||
|
if (mp.canProduce("U", m) || reflectedColors.contains(MagicColor.Constant.BLUE)) {
|
||||||
|
manaMap.get(ManaAtom.BLUE).add(manaMap.get(ManaAtom.BLUE).size(), m);
|
||||||
|
}
|
||||||
|
if (mp.canProduce("B", m) || reflectedColors.contains(MagicColor.Constant.BLACK)) {
|
||||||
|
manaMap.get(ManaAtom.BLACK).add(manaMap.get(ManaAtom.BLACK).size(), m);
|
||||||
|
}
|
||||||
|
if (mp.canProduce("R", m) || reflectedColors.contains(MagicColor.Constant.RED)) {
|
||||||
|
manaMap.get(ManaAtom.RED).add(manaMap.get(ManaAtom.RED).size(), m);
|
||||||
|
}
|
||||||
|
if (mp.canProduce("G", m) || reflectedColors.contains(MagicColor.Constant.GREEN)) {
|
||||||
|
manaMap.get(ManaAtom.GREEN).add(manaMap.get(ManaAtom.GREEN).size(), m);
|
||||||
|
}
|
||||||
|
if (mp.isSnow()) {
|
||||||
|
manaMap.get(ManaAtom.IS_SNOW).add(manaMap.get(ManaAtom.IS_SNOW).size(), m);
|
||||||
|
}
|
||||||
|
if (DEBUG_MANA_PAYMENT) {
|
||||||
|
System.out.println("DEBUG_MANA_PAYMENT: groupSourcesByManaColor manaMap = " + manaMap);
|
||||||
|
}
|
||||||
|
} // end of mana abilities loop
|
||||||
|
} // end of mana sources loop
|
||||||
|
|
||||||
|
return manaMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* determineLeftoverMana.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param sa
|
||||||
|
* a {@link forge.game.spellability.SpellAbility} object.
|
||||||
|
* @param player
|
||||||
|
* a {@link forge.game.player.Player} object.
|
||||||
|
* @return a int.
|
||||||
|
* @since 1.0.15
|
||||||
|
*/
|
||||||
|
public static int determineLeftoverMana(final SpellAbility sa, final Player player) {
|
||||||
|
for (int i = 1; i < 100; i++)
|
||||||
|
if (!canPayManaCost(sa, player, i))
|
||||||
|
return i - 1;
|
||||||
|
|
||||||
|
return 99;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns basic mana abilities plus "reflected mana" abilities
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* getAIPlayableMana.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @return a {@link java.util.ArrayList} object.
|
||||||
|
*/
|
||||||
|
public static final ArrayList<SpellAbility> getAIPlayableMana(Card c) {
|
||||||
|
final ArrayList<SpellAbility> res = new ArrayList<SpellAbility>();
|
||||||
|
for (final SpellAbility a : c.getManaAbility()) {
|
||||||
|
// if a mana ability has a mana cost the AI will miscalculate
|
||||||
|
// if there is a parent ability the AI can't use it
|
||||||
|
final Cost cost = a.getPayCosts();
|
||||||
|
if (!cost.hasNoManaCost() || (a.getApi() != ApiType.Mana && a.getApi() != ApiType.ManaReflected)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!res.contains(a)) {
|
||||||
|
if (cost.isReusuableResource()) {
|
||||||
|
res.add(0, a);
|
||||||
|
} else {
|
||||||
|
res.add(res.size(), a);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void handleOfferingsAI(final SpellAbility sa, boolean test, boolean costIsPaid) {
|
||||||
|
if (sa.isOffering() && sa.getSacrificedAsOffering() != null) {
|
||||||
|
final Card offering = sa.getSacrificedAsOffering();
|
||||||
|
offering.setUsedToPay(false);
|
||||||
|
if (costIsPaid && !test) {
|
||||||
|
sa.getHostCard().getController().getGame().getAction().sacrifice(offering, sa);
|
||||||
|
}
|
||||||
|
sa.resetSacrificedAsOffering();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
63
forge-ai/src/main/java/forge/ai/LobbyPlayerAi.java
Normal file
63
forge-ai/src/main/java/forge/ai/LobbyPlayerAi.java
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
package forge.ai;
|
||||||
|
|
||||||
|
import forge.game.Game;
|
||||||
|
import forge.game.player.LobbyPlayer;
|
||||||
|
import forge.game.player.Player;
|
||||||
|
|
||||||
|
public class LobbyPlayerAi extends LobbyPlayer {
|
||||||
|
public LobbyPlayerAi(String name) {
|
||||||
|
super(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String aiProfile = "";
|
||||||
|
private boolean rotateProfileEachGame;
|
||||||
|
private boolean allowCheatShuffle;
|
||||||
|
|
||||||
|
public boolean isAllowCheatShuffle() {
|
||||||
|
return allowCheatShuffle;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void setAllowCheatShuffle(boolean allowCheatShuffle) {
|
||||||
|
this.allowCheatShuffle = allowCheatShuffle;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAiProfile(String profileName) {
|
||||||
|
aiProfile = profileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAiProfile() {
|
||||||
|
return aiProfile;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRotateProfileEachGame(boolean rotateProfileEachGame) {
|
||||||
|
this.rotateProfileEachGame = rotateProfileEachGame;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected PlayerType getType() {
|
||||||
|
return PlayerType.COMPUTER;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PlayerControllerAi createControllerFor(Player ai) {
|
||||||
|
PlayerControllerAi result = new PlayerControllerAi(ai.getGame(), ai, this);
|
||||||
|
result.allowCheatShuffle(allowCheatShuffle);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Player createIngamePlayer(Game game) {
|
||||||
|
Player ai = new Player(getName(), game);
|
||||||
|
ai.setFirstController(createControllerFor(ai));
|
||||||
|
|
||||||
|
if( rotateProfileEachGame ) {
|
||||||
|
setAiProfile(AiProfileUtil.getRandomProfile());
|
||||||
|
System.out.println(String.format("AI profile %s was chosen for the lobby player %s.", getAiProfile(), getName()));
|
||||||
|
}
|
||||||
|
return ai;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void hear(LobbyPlayer player, String message) { /* Local AI is deaf. */ }
|
||||||
|
}
|
||||||
814
forge-ai/src/main/java/forge/ai/PlayerControllerAi.java
Normal file
814
forge-ai/src/main/java/forge/ai/PlayerControllerAi.java
Normal file
@@ -0,0 +1,814 @@
|
|||||||
|
package forge.ai;
|
||||||
|
|
||||||
|
import com.esotericsoftware.minlog.Log;
|
||||||
|
import com.google.common.base.Predicate;
|
||||||
|
import com.google.common.base.Predicates;
|
||||||
|
import com.google.common.collect.Iterables;
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
import com.google.common.collect.Multimap;
|
||||||
|
|
||||||
|
import forge.ai.ability.ChangeZoneAi;
|
||||||
|
import forge.ai.ability.CharmAi;
|
||||||
|
import forge.ai.ability.ProtectAi;
|
||||||
|
import forge.card.ColorSet;
|
||||||
|
import forge.card.MagicColor;
|
||||||
|
import forge.card.mana.ManaCost;
|
||||||
|
import forge.card.mana.ManaCostShard;
|
||||||
|
import forge.deck.Deck;
|
||||||
|
import forge.game.Game;
|
||||||
|
import forge.game.GameEntity;
|
||||||
|
import forge.game.GameObject;
|
||||||
|
import forge.game.GameType;
|
||||||
|
import forge.game.ability.AbilityUtils;
|
||||||
|
import forge.game.ability.ApiType;
|
||||||
|
import forge.game.card.*;
|
||||||
|
import forge.game.card.CardPredicates.Presets;
|
||||||
|
import forge.game.combat.Combat;
|
||||||
|
import forge.game.cost.Cost;
|
||||||
|
import forge.game.cost.CostPart;
|
||||||
|
import forge.game.cost.CostPartMana;
|
||||||
|
import forge.game.mana.Mana;
|
||||||
|
import forge.game.mana.ManaCostBeingPaid;
|
||||||
|
import forge.game.phase.PhaseHandler;
|
||||||
|
import forge.game.phase.PhaseType;
|
||||||
|
import forge.game.player.LobbyPlayer;
|
||||||
|
import forge.game.player.Player;
|
||||||
|
import forge.game.player.PlayerActionConfirmMode;
|
||||||
|
import forge.game.player.PlayerController;
|
||||||
|
import forge.game.replacement.ReplacementEffect;
|
||||||
|
import forge.game.spellability.*;
|
||||||
|
import forge.game.trigger.Trigger;
|
||||||
|
import forge.game.trigger.WrappedAbility;
|
||||||
|
import forge.game.zone.ZoneType;
|
||||||
|
import forge.item.PaperCard;
|
||||||
|
import forge.util.Aggregates;
|
||||||
|
import forge.util.MyRandom;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.apache.commons.lang3.tuple.ImmutablePair;
|
||||||
|
import org.apache.commons.lang3.tuple.Pair;
|
||||||
|
|
||||||
|
import java.security.InvalidParameterException;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A prototype for player controller class
|
||||||
|
*
|
||||||
|
* Handles phase skips for now.
|
||||||
|
*/
|
||||||
|
public class PlayerControllerAi extends PlayerController {
|
||||||
|
private final AiController brains;
|
||||||
|
|
||||||
|
public PlayerControllerAi(Game game, Player p, LobbyPlayer lp) {
|
||||||
|
super(game, p, lp);
|
||||||
|
|
||||||
|
brains = new AiController(p, game);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void allowCheatShuffle(boolean value){
|
||||||
|
brains.allowCheatShuffle(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public SpellAbility getAbilityToPlay(List<SpellAbility> abilities, Object triggerEvent) {
|
||||||
|
if (abilities.size() == 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return abilities.get(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO: Write javadoc for this method.
|
||||||
|
* @param c
|
||||||
|
*/
|
||||||
|
/**public void playFromSuspend(Card c) {
|
||||||
|
final List<SpellAbility> choices = c.getBasicSpells();
|
||||||
|
c.setSuspendCast(true);
|
||||||
|
getAi().chooseAndPlaySa(choices, true, true);
|
||||||
|
}**/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO: Write javadoc for this method.
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public AiController getAi() {
|
||||||
|
return brains;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<PaperCard> sideboard(Deck deck, GameType gameType) {
|
||||||
|
// AI does not know how to sideboard
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<Card, Integer> assignCombatDamage(Card attacker, List<Card> blockers, int damageDealt, GameEntity defender, boolean overrideOrder) {
|
||||||
|
return ComputerUtilCombat.distributeAIDamage(attacker, blockers, damageDealt, defender, overrideOrder);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Integer announceRequirements(SpellAbility ability, String announce, boolean allowZero) {
|
||||||
|
// For now, these "announcements" are made within the AI classes of the appropriate SA effects
|
||||||
|
if (ability.getApi() != null) {
|
||||||
|
switch (ability.getApi()) {
|
||||||
|
case ChooseNumber:
|
||||||
|
return ability.getActivatingPlayer().isOpponentOf(player) ? 0 : ComputerUtilMana.determineLeftoverMana(ability, player);
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null; // return incorrect value to indicate that
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Card> choosePermanentsToSacrifice(SpellAbility sa, int min, int max, List<Card> validTargets, String message) {
|
||||||
|
return ComputerUtil.choosePermanentsToSacrifice(player, validTargets, max, sa, false, min == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Card> choosePermanentsToDestroy(SpellAbility sa, int min, int max, List<Card> validTargets, String message) {
|
||||||
|
return ComputerUtil.choosePermanentsToSacrifice(player, validTargets, max, sa, true, min == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Card> chooseCardsForEffect(List<Card> sourceList, SpellAbility sa, String title, int min, int max, boolean isOptional) {
|
||||||
|
return brains.chooseCardsForEffect(sourceList, sa, min, max, isOptional);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T extends GameEntity> T chooseSingleEntityForEffect(Collection<T> options, SpellAbility sa, String title, boolean isOptional, Player targetedPlayer) {
|
||||||
|
ApiType api = sa.getApi();
|
||||||
|
if (null == api) {
|
||||||
|
throw new InvalidParameterException("SA is not api-based, this is not supported yet");
|
||||||
|
}
|
||||||
|
return SpellApiToAi.Converter.get(api).chooseSingleEntity(player, sa, options, isOptional, targetedPlayer);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SpellAbility chooseSingleSpellForEffect(java.util.List<SpellAbility> spells, SpellAbility sa, String title) {
|
||||||
|
ApiType api = sa.getApi();
|
||||||
|
if (null == api) {
|
||||||
|
throw new InvalidParameterException("SA is not api-based, this is not supported yet");
|
||||||
|
}
|
||||||
|
return SpellApiToAi.Converter.get(api).chooseSingleSpellAbility(player, sa, spells);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean confirmAction(SpellAbility sa, PlayerActionConfirmMode mode, String message) {
|
||||||
|
return getAi().confirmAction(sa, mode, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean confirmStaticApplication(Card hostCard, GameEntity affected, String logic, String message) {
|
||||||
|
return getAi().confirmStaticApplication(hostCard, affected, logic, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean confirmTrigger(SpellAbility sa, Trigger regtrig, Map<String, String> triggerParams, boolean isMandatory) {
|
||||||
|
if (triggerParams.containsKey("DelayedTrigger")) {
|
||||||
|
//TODO: The only card with an optional delayed trigger is Shirei, Shizo's Caretaker,
|
||||||
|
// needs to be expanded when a more difficult cards comes up
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// Store/replace target choices more properly to get this SA cleared.
|
||||||
|
TargetChoices tc = null;
|
||||||
|
TargetChoices subtc = null;
|
||||||
|
boolean storeChoices = sa.getTargetRestrictions() != null;
|
||||||
|
final SpellAbility sub = sa.getSubAbility();
|
||||||
|
boolean storeSubChoices = sub != null && sub.getTargetRestrictions() != null;
|
||||||
|
boolean ret = true;
|
||||||
|
|
||||||
|
if (storeChoices) {
|
||||||
|
tc = sa.getTargets();
|
||||||
|
sa.resetTargets();
|
||||||
|
}
|
||||||
|
if (storeSubChoices) {
|
||||||
|
subtc = sub.getTargets();
|
||||||
|
sub.resetTargets();
|
||||||
|
}
|
||||||
|
// There is no way this doTrigger here will have the same target as stored above
|
||||||
|
// So it's possible it's making a different decision here than will actually happen
|
||||||
|
if (!brains.doTrigger(sa, isMandatory)) {
|
||||||
|
ret = false;
|
||||||
|
}
|
||||||
|
if (storeChoices) {
|
||||||
|
sa.resetTargets();
|
||||||
|
sa.setTargets(tc);
|
||||||
|
}
|
||||||
|
if (storeSubChoices) {
|
||||||
|
sub.resetTargets();
|
||||||
|
sub.setTargets(subtc);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean getWillPlayOnFirstTurn(boolean isFirstGame) {
|
||||||
|
return true; // AI is brave :)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Card> orderBlockers(Card attacker, List<Card> blockers) {
|
||||||
|
return AiBlockController.orderBlockers(attacker, blockers);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Card> orderAttackers(Card blocker, List<Card> attackers) {
|
||||||
|
return AiBlockController.orderAttackers(blocker, attackers);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* (non-Javadoc)
|
||||||
|
* @see forge.game.player.PlayerController#reveal(java.lang.String, java.util.List)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void reveal(Collection<Card> cards, ZoneType zone, Player owner, String messagePrefix) {
|
||||||
|
// We don't know how to reveal cards to AI
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ImmutablePair<List<Card>, List<Card>> arrangeForScry(List<Card> topN) {
|
||||||
|
List<Card> toBottom = new ArrayList<Card>();
|
||||||
|
List<Card> toTop = new ArrayList<Card>();
|
||||||
|
|
||||||
|
for (Card c: topN) {
|
||||||
|
if (ComputerUtil.scryWillMoveCardToBottomOfLibrary(player, c)) {
|
||||||
|
toBottom.add(c);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
toTop.add(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// put the rest on top in random order
|
||||||
|
Collections.shuffle(toTop);
|
||||||
|
return ImmutablePair.of(toTop, toBottom);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean willPutCardOnTop(Card c) {
|
||||||
|
return true; // AI does not know what will happen next (another clash or that would become his topdeck)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Card> orderMoveToZoneList(List<Card> cards, ZoneType destinationZone) {
|
||||||
|
//TODO Add logic for AI ordering here
|
||||||
|
return cards;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Card> chooseCardsToDiscardFrom(Player p, SpellAbility sa, List<Card> validCards, int min, int max) {
|
||||||
|
if (p == player) {
|
||||||
|
return brains.getCardsToDiscard(min, max, validCards, sa);
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isTargetFriendly = !p.isOpponentOf(player);
|
||||||
|
|
||||||
|
return isTargetFriendly
|
||||||
|
? ComputerUtil.getCardsToDiscardFromFriend(player, p, sa, validCards, min, max)
|
||||||
|
: ComputerUtil.getCardsToDiscardFromOpponent(player, p, sa, validCards, min, max);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* (non-Javadoc)
|
||||||
|
* @see forge.game.player.PlayerController#mayPlaySpellAbilityForFree(forge.card.spellability.SpellAbility)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void playSpellAbilityForFree(SpellAbility copySA, boolean mayChooseNewTargets) {
|
||||||
|
// Ai is known to set targets in doTrigger, so if it cannot choose new targets, we won't call canPlays
|
||||||
|
if (mayChooseNewTargets) {
|
||||||
|
if (copySA instanceof Spell) {
|
||||||
|
Spell spell = (Spell) copySA;
|
||||||
|
((PlayerControllerAi) player.getController()).getAi().canPlayFromEffectAI(spell, true, true);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
getAi().canPlaySa(copySA);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ComputerUtil.playSpellAbilityForFree(player, copySA);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void playSpellAbilityNoStack(SpellAbility effectSA, boolean canSetupTargets) {
|
||||||
|
if (canSetupTargets)
|
||||||
|
brains.doTrigger(effectSA, true); // first parameter does not matter, since return value won't be used
|
||||||
|
ComputerUtil.playNoStack(player, effectSA, game);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void playMiracle(SpellAbility miracle, Card card) {
|
||||||
|
getAi().chooseAndPlaySa(false, false, miracle);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* (non-Javadoc)
|
||||||
|
* @see forge.game.player.PlayerController#chooseCardsToDelve(int, java.util.List)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public List<Card> chooseCardsToDelve(int colorlessCost, List<Card> grave) {
|
||||||
|
return getAi().chooseCardsToDelve(colorlessCost, grave);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* (non-Javadoc)
|
||||||
|
* @see forge.game.player.PlayerController#chooseTargets(forge.card.spellability.SpellAbility, forge.card.spellability.SpellAbilityStackInstance)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public TargetChoices chooseNewTargetsFor(SpellAbility ability) {
|
||||||
|
// AI currently can't do this. But when it can it will need to be based on Ability API
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* (non-Javadoc)
|
||||||
|
* @see forge.game.player.PlayerController#chooseCardsToDiscardUnlessType(int, java.util.List, java.lang.String, forge.card.spellability.SpellAbility)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public List<Card> chooseCardsToDiscardUnlessType(int num, List<Card> hand, String uType, SpellAbility sa) {
|
||||||
|
final List<Card> cardsOfType = CardLists.getType(hand, uType);
|
||||||
|
if (!cardsOfType.isEmpty()) {
|
||||||
|
Card toDiscard = Aggregates.itemWithMin(cardsOfType, CardPredicates.Accessors.fnGetCmc);
|
||||||
|
return Lists.newArrayList(toDiscard);
|
||||||
|
}
|
||||||
|
return getAi().getCardsToDiscard(num, (String[])null, sa);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mana chooseManaFromPool(List<Mana> manaChoices) {
|
||||||
|
return manaChoices.get(0); // no brains used
|
||||||
|
}
|
||||||
|
|
||||||
|
/* (non-Javadoc)
|
||||||
|
* @see forge.game.player.PlayerController#ChooseSomeType(java.lang.String, java.util.List, java.util.List)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String chooseSomeType(String kindOfType, SpellAbility sa, List<String> validTypes, List<String> invalidTypes, boolean isOptional) {
|
||||||
|
String chosen = ComputerUtil.chooseSomeType(player, kindOfType, sa.getParam("AILogic"), invalidTypes);
|
||||||
|
if (StringUtils.isBlank(chosen) && !validTypes.isEmpty())
|
||||||
|
{
|
||||||
|
chosen = validTypes.get(0);
|
||||||
|
Log.warn("AI has no idea how to choose " + kindOfType +", defaulting to 1st element: chosen");
|
||||||
|
}
|
||||||
|
game.getAction().nofityOfValue(sa, null, "Computer picked: " + chosen, player);
|
||||||
|
return chosen;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* (non-Javadoc)
|
||||||
|
* @see forge.game.player.PlayerController#confirmReplacementEffect(forge.card.replacement.ReplacementEffect, forge.card.spellability.SpellAbility, java.lang.String)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean confirmReplacementEffect(ReplacementEffect replacementEffect, SpellAbility effectSA, String question) {
|
||||||
|
return brains.aiShouldRun(replacementEffect, effectSA);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Card> getCardsToMulligan(boolean isCommander, Player firstPlayer) {
|
||||||
|
if (!ComputerUtil.wantMulligan(player)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isCommander) {
|
||||||
|
return player.getCardsIn(ZoneType.Hand);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return ComputerUtil.getPartialParisCandidates(player);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void declareAttackers(Player attacker, Combat combat) {
|
||||||
|
brains.declareAttackers(attacker, combat);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void declareBlockers(Player defender, Combat combat) {
|
||||||
|
brains.declareBlockersFor(defender, combat);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SpellAbility chooseSpellAbilityToPlay() {
|
||||||
|
return brains.choooseSpellAbilityToPlay();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void playChosenSpellAbility(SpellAbility sa)
|
||||||
|
{
|
||||||
|
// System.out.println("Playing sa: " + sa);
|
||||||
|
if ( sa == Ability.PLAY_LAND_SURROGATE )
|
||||||
|
player.playLand(sa.getHostCard(), false);
|
||||||
|
else
|
||||||
|
ComputerUtil.handlePlayingSpellAbility(player, sa, game);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Card> chooseCardsToDiscardToMaximumHandSize(int numDiscard) {
|
||||||
|
return brains.getCardsToDiscard(numDiscard, (String[])null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Card> chooseCardsToRevealFromHand(int min, int max, List<Card> valid) {
|
||||||
|
int numCardsToReveal = Math.min(max, valid.size());
|
||||||
|
return numCardsToReveal == 0 ? Lists.<Card>newArrayList() : valid.subList(0, numCardsToReveal);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean payManaOptional(Card c, Cost cost, SpellAbility sa, String prompt, ManaPaymentPurpose purpose) {
|
||||||
|
final Ability ability = new AbilityStatic(c, cost, null) { @Override public void resolve() {} };
|
||||||
|
ability.setActivatingPlayer(c.getController());
|
||||||
|
|
||||||
|
if (ComputerUtilCost.canPayCost(ability, c.getController())) {
|
||||||
|
ComputerUtil.playNoStack(c.getController(), ability, game);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<SpellAbility> chooseSaToActivateFromOpeningHand(List<SpellAbility> usableFromOpeningHand) {
|
||||||
|
// AI would play everything. But limits to one copy of (Leyline of Singularity) and (Gemstone Caverns)
|
||||||
|
return brains.chooseSaToActivateFromOpeningHand(usableFromOpeningHand);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int chooseNumber(SpellAbility sa, String title, int min, int max) {
|
||||||
|
return brains.chooseNumber(sa, title, min, max);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int chooseNumber(SpellAbility sa, String title, List<Integer> options, Player relatedPlayer) {
|
||||||
|
return brains.chooseNumber(sa, title, options, relatedPlayer);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* (non-Javadoc)
|
||||||
|
* @see forge.game.player.PlayerController#chooseFlipResult(forge.Card, forge.game.player.Player, java.lang.String[], boolean)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean chooseFlipResult(SpellAbility sa, Player flipper, boolean[] results, boolean call) {
|
||||||
|
if (call) {
|
||||||
|
// Win if possible
|
||||||
|
boolean result = false;
|
||||||
|
for (boolean s : results) {
|
||||||
|
if (s) {
|
||||||
|
result = s;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
} else {
|
||||||
|
// heads or tails, AI doesn't know which is better now
|
||||||
|
int i = MyRandom.getRandom().nextInt(results.length);
|
||||||
|
return results[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Pair<SpellAbilityStackInstance, GameObject> chooseTarget(SpellAbility saSrc, List<Pair<SpellAbilityStackInstance, GameObject>> allTargets) {
|
||||||
|
// TODO Teach AI how to use Spellskite
|
||||||
|
return allTargets.get(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void notifyOfValue(SpellAbility saSource, GameObject realtedTarget, String value) {
|
||||||
|
// AI should take into consideration creature types, numbers and other information (mostly choices) arriving through this channel
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean chooseBinary(SpellAbility sa, String question, BinaryChoiceType kindOfChoice, Boolean defaultVal) {
|
||||||
|
switch(kindOfChoice) {
|
||||||
|
case TapOrUntap: return true;
|
||||||
|
case UntapOrLeaveTapped: return defaultVal != null && defaultVal.booleanValue();
|
||||||
|
case UntapTimeVault: return false; // TODO Should AI skip his turn for time vault?
|
||||||
|
default:
|
||||||
|
return MyRandom.getRandom().nextBoolean();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Card chooseProtectionShield(GameEntity entityBeingDamaged, List<String> options, Map<String, Card> choiceMap) {
|
||||||
|
int i = MyRandom.getRandom().nextInt(options.size());
|
||||||
|
return choiceMap.get(options.get(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* (non-Javadoc)
|
||||||
|
* @see forge.game.player.PlayerController#chooseModeForAbility(forge.card.spellability.SpellAbility, java.util.List, int, int)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public List<AbilitySub> chooseModeForAbility(SpellAbility sa, int min, int num) {
|
||||||
|
return CharmAi.chooseOptionsAi(sa, player, sa.isTrigger(), num, min, !player.equals(sa.getActivatingPlayer()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Pair<CounterType,String> chooseAndRemoveOrPutCounter(Card cardWithCounter) {
|
||||||
|
if (!cardWithCounter.hasCounters()) {
|
||||||
|
System.out.println("chooseCounterType was reached with a card with no counters on it. Consider filtering this card out earlier");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
final Player controller = cardWithCounter.getController();
|
||||||
|
final List<Player> enemies = player.getOpponents();
|
||||||
|
final List<Player> allies = player.getAllies();
|
||||||
|
allies.add(player);
|
||||||
|
|
||||||
|
List<CounterType> countersToIncrease = new ArrayList<CounterType>();
|
||||||
|
List<CounterType> countersToDecrease = new ArrayList<CounterType>();
|
||||||
|
|
||||||
|
for (final CounterType counter : cardWithCounter.getCounters().keySet()) {
|
||||||
|
if ((!ComputerUtil.isNegativeCounter(counter, cardWithCounter) && allies.contains(controller))
|
||||||
|
|| (ComputerUtil.isNegativeCounter(counter, cardWithCounter) && enemies.contains(controller))) {
|
||||||
|
countersToIncrease.add(counter);
|
||||||
|
} else {
|
||||||
|
countersToDecrease.add(counter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!countersToIncrease.isEmpty()) {
|
||||||
|
int random = MyRandom.getRandom().nextInt(countersToIncrease.size());
|
||||||
|
return new ImmutablePair<CounterType,String>(countersToIncrease.get(random),"Put");
|
||||||
|
}
|
||||||
|
else if (!countersToDecrease.isEmpty()) {
|
||||||
|
int random = MyRandom.getRandom().nextInt(countersToDecrease.size());
|
||||||
|
return new ImmutablePair<CounterType,String>(countersToDecrease.get(random),"Remove");
|
||||||
|
}
|
||||||
|
|
||||||
|
// shouldn't reach here but just in case, remove random counter
|
||||||
|
List<CounterType> countersOnCard = new ArrayList<CounterType>();
|
||||||
|
int random = MyRandom.getRandom().nextInt(countersOnCard.size());
|
||||||
|
return new ImmutablePair<CounterType,String>(countersOnCard.get(random),"Remove");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte chooseColorAllowColorless(String message, Card card, ColorSet colors) {
|
||||||
|
final String c = ComputerUtilCard.getMostProminentColor(player.getCardsIn(ZoneType.Hand));
|
||||||
|
byte chosenColorMask = MagicColor.fromName(c);
|
||||||
|
if ((colors.getColor() & chosenColorMask) != 0) {
|
||||||
|
return chosenColorMask;
|
||||||
|
} else {
|
||||||
|
return Iterables.getFirst(colors, (byte)0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte chooseColor(String message, SpellAbility sa, ColorSet colors) {
|
||||||
|
// You may switch on sa.getApi() here and use sa.getParam("AILogic")
|
||||||
|
List<Card> hand = new ArrayList<Card>(player.getCardsIn(ZoneType.Hand));
|
||||||
|
if( sa.getApi() == ApiType.Mana )
|
||||||
|
hand.addAll(player.getCardsIn(ZoneType.Stack));
|
||||||
|
final String c = ComputerUtilCard.getMostProminentColor(hand);
|
||||||
|
byte chosenColorMask = MagicColor.fromName(c);
|
||||||
|
|
||||||
|
|
||||||
|
if ((colors.getColor() & chosenColorMask) != 0) {
|
||||||
|
return chosenColorMask;
|
||||||
|
} else {
|
||||||
|
return Iterables.getFirst(colors, MagicColor.WHITE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PaperCard chooseSinglePaperCard(SpellAbility sa, String message,
|
||||||
|
Predicate<PaperCard> cpp, String name) {
|
||||||
|
throw new UnsupportedOperationException("Should not be called for AI"); // or implement it if you know how
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> chooseColors(String message, SpellAbility sa, int min, int max, List<String> options) {
|
||||||
|
return ComputerUtilCard.chooseColor(sa, min, max, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CounterType chooseCounterType(Collection<CounterType> options, SpellAbility sa, String prompt) {
|
||||||
|
// may write a smarter AI if you need to (with calls to AI-clas for given API ability)
|
||||||
|
|
||||||
|
// TODO: ArsenalNut (06 Feb 12)computer needs
|
||||||
|
// better logic to pick a counter type and probably
|
||||||
|
// an initial target
|
||||||
|
// find first nonzero counter on target
|
||||||
|
return Iterables.getFirst(options, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean confirmPayment(CostPart costPart, String prompt) {
|
||||||
|
return brains.confirmPayment(costPart); // AI is expected to know what it is paying for at the moment (otherwise add another parameter to this method)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ReplacementEffect chooseSingleReplacementEffect(String prompt, List<ReplacementEffect> possibleReplacers, HashMap<String, Object> runParams) {
|
||||||
|
// AI logic for choosing which replacement effect to apply
|
||||||
|
// happens here.
|
||||||
|
return possibleReplacers.get(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String chooseProtectionType(String string, SpellAbility sa, List<String> choices) {
|
||||||
|
String choice = choices.get(0);
|
||||||
|
if (game.stack.size() > 1) {
|
||||||
|
for (SpellAbilityStackInstance si : game.getStack()) {
|
||||||
|
SpellAbility spell = si.getSpellAbility();
|
||||||
|
if (sa != spell) {
|
||||||
|
String s = ProtectAi.toProtectFrom(spell.getHostCard(), sa);
|
||||||
|
if (s != null) {
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
final Combat combat = game.getCombat();
|
||||||
|
if (combat != null ) {
|
||||||
|
Card toSave = sa.getTargetCard();
|
||||||
|
List<Card> threats = null;
|
||||||
|
if (combat.isBlocked(toSave)) {
|
||||||
|
threats = combat.getBlockers(toSave);
|
||||||
|
}
|
||||||
|
if (combat.isBlocking(toSave)) {
|
||||||
|
threats = combat.getAttackersBlockedBy(toSave);
|
||||||
|
}
|
||||||
|
if (threats != null) {
|
||||||
|
ComputerUtilCard.sortByEvaluateCreature(threats);
|
||||||
|
String s = ProtectAi.toProtectFrom(threats.get(0), sa);
|
||||||
|
if (s != null) {
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
final PhaseHandler ph = game.getPhaseHandler();
|
||||||
|
if (ph.getPlayerTurn() == sa.getActivatingPlayer() && ph.getPhase() == PhaseType.MAIN1) {
|
||||||
|
AiAttackController aiAtk = new AiAttackController(sa.getActivatingPlayer(), sa.getTargetCard());
|
||||||
|
String s = aiAtk.toProtectAttacker(sa);
|
||||||
|
if (s != null) {
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
final String logic = sa.getParam("AILogic");
|
||||||
|
if (logic == null || logic.equals("MostProminentHumanCreatures")) {
|
||||||
|
List<Card> list = new ArrayList<Card>();
|
||||||
|
for (Player opp : player.getOpponents()) {
|
||||||
|
list.addAll(opp.getCreaturesInPlay());
|
||||||
|
}
|
||||||
|
if (list.isEmpty()) {
|
||||||
|
list = CardLists.filterControlledBy(game.getCardsInGame(), player.getOpponents());
|
||||||
|
}
|
||||||
|
if (!list.isEmpty()) {
|
||||||
|
choice = ComputerUtilCard.getMostProminentColor(list);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return choice;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean payCostToPreventEffect(Cost cost, SpellAbility sa, boolean alreadyPaid, List<Player> allPayers) {
|
||||||
|
final Card source = sa.getHostCard();
|
||||||
|
final Ability emptyAbility = new AbilityStatic(source, cost, sa.getTargetRestrictions()) { @Override public void resolve() { } };
|
||||||
|
emptyAbility.setActivatingPlayer(player);
|
||||||
|
if (ComputerUtilCost.willPayUnlessCost(sa, player, cost, alreadyPaid, allPayers) && ComputerUtilCost.canPayCost(emptyAbility, player)) {
|
||||||
|
ComputerUtil.playNoStack(player, emptyAbility, game); // AI needs something to resolve to pay that cost
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void orderAndPlaySimultaneousSa(List<SpellAbility> activePlayerSAs) {
|
||||||
|
for (final SpellAbility sa : activePlayerSAs) {
|
||||||
|
prepareSingleSa(sa.getHostCard(),sa,true);
|
||||||
|
ComputerUtil.playStack(sa, player, game);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void prepareSingleSa(final Card host, final SpellAbility sa, boolean isMandatory){
|
||||||
|
if (sa.hasParam("TargetingPlayer")) {
|
||||||
|
Player targetingPlayer = AbilityUtils.getDefinedPlayers(host, sa.getParam("TargetingPlayer"), sa).get(0);
|
||||||
|
sa.setTargetingPlayer(targetingPlayer);
|
||||||
|
targetingPlayer.getController().chooseTargetsFor(sa);
|
||||||
|
} else {
|
||||||
|
brains.doTrigger(sa, isMandatory);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void playTrigger(Card host, WrappedAbility wrapperAbility, boolean isMandatory) {
|
||||||
|
prepareSingleSa(host, wrapperAbility, isMandatory);
|
||||||
|
ComputerUtil.playNoStack(wrapperAbility.getActivatingPlayer(), wrapperAbility, game);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean playSaFromPlayEffect(SpellAbility tgtSA) {
|
||||||
|
boolean optional = tgtSA.hasParam("Optional");
|
||||||
|
boolean noManaCost = tgtSA.hasParam("WithoutManaCost");
|
||||||
|
if (tgtSA instanceof Spell) { // Isn't it ALWAYS a spell?
|
||||||
|
Spell spell = (Spell) tgtSA;
|
||||||
|
if (brains.canPlayFromEffectAI(spell, !optional, noManaCost) == AiPlayDecision.WillPlay || !optional) {
|
||||||
|
if (noManaCost) {
|
||||||
|
ComputerUtil.playSpellAbilityWithoutPayingManaCost(player, tgtSA, game);
|
||||||
|
} else {
|
||||||
|
ComputerUtil.playStack(tgtSA, player, game);
|
||||||
|
}
|
||||||
|
} else
|
||||||
|
return false; // didn't play spell
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<GameEntity, CounterType> chooseProliferation() {
|
||||||
|
return brains.chooseProliferation();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean chooseTargetsFor(SpellAbility currentAbility) {
|
||||||
|
return brains.doTrigger(currentAbility, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean chooseCardsPile(SpellAbility sa, List<Card> pile1, List<Card> pile2, boolean faceUp) {
|
||||||
|
if (!faceUp) {
|
||||||
|
// AI will choose the first pile if it is larger or the same
|
||||||
|
// TODO Improve this to be slightly more random to not be so predictable
|
||||||
|
return pile1.size() >= pile2.size();
|
||||||
|
} else {
|
||||||
|
boolean allCreatures = Iterables.all(Iterables.concat(pile1, pile2), CardPredicates.Presets.CREATURES);
|
||||||
|
int cmc1 = allCreatures ? ComputerUtilCard.evaluateCreatureList(pile1) : ComputerUtilCard.evaluatePermanentList(pile1);
|
||||||
|
int cmc2 = allCreatures ? ComputerUtilCard.evaluateCreatureList(pile2) : ComputerUtilCard.evaluatePermanentList(pile2);
|
||||||
|
System.out.println("value:" + cmc1 + " " + cmc2);
|
||||||
|
|
||||||
|
// for now, this assumes that the outcome will be bad
|
||||||
|
// TODO: This should really have a ChooseLogic param to
|
||||||
|
// figure this out
|
||||||
|
return "Worst".equals(sa.getParam("AILogic")) ^ (cmc1 >= cmc2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void revealAnte(String message, Multimap<Player, PaperCard> removedAnteCards) {
|
||||||
|
// Ai won't understand that anyway
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<? extends PaperCard> complainCardsCantPlayWell(Deck myDeck) {
|
||||||
|
return brains.complainCardsCantPlayWell(myDeck);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Card> cheatShuffle(List<Card> list) {
|
||||||
|
return brains.getBooleanProperty(AiProps.CHEAT_WITH_MANA_ON_SHUFFLE) ? brains.cheatShuffle(list) : list;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CardShields chooseRegenerationShield(Card c) {
|
||||||
|
return Iterables.getFirst(c.getShield(), null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<PaperCard> chooseCardsYouWonToAddToDeck(List<PaperCard> losses) {
|
||||||
|
// TODO AI takes all by default
|
||||||
|
return losses;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean payManaCost(ManaCost toPay, CostPartMana costPartMana, SpellAbility sa, String prompt /* ai needs hints as well */, boolean isActivatedSa ) {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
|
ManaCostBeingPaid cost = isActivatedSa ? ComputerUtilMana.calculateManaCost(sa, false, 0) : new ManaCostBeingPaid(toPay);
|
||||||
|
return ComputerUtilMana.payManaCost(cost, sa, player);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<Card, ManaCostShard> chooseCardsForConvoke(SpellAbility sa, ManaCost manaCost,
|
||||||
|
List<Card> untappedCreats) {
|
||||||
|
// TODO: AI to choose a creature to tap would go here
|
||||||
|
// Probably along with deciding how many creatures to tap
|
||||||
|
return new HashMap<Card, ManaCostShard>();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String chooseCardName(SpellAbility sa, Predicate<PaperCard> cpp, String valid, String message) {
|
||||||
|
if (sa.hasParam("AILogic")) {
|
||||||
|
final String logic = sa.getParam("AILogic");
|
||||||
|
if (logic.equals("MostProminentInComputerDeck")) {
|
||||||
|
return ComputerUtilCard.getMostProminentCardName(player.getCardsIn(ZoneType.Library));
|
||||||
|
} else if (logic.equals("MostProminentInHumanDeck")) {
|
||||||
|
return ComputerUtilCard.getMostProminentCardName(player.getOpponent().getCardsIn(ZoneType.Library));
|
||||||
|
} else if (logic.equals("BestCreatureInComputerDeck")) {
|
||||||
|
return ComputerUtilCard.getBestCreatureAI(player.getCardsIn(ZoneType.Library)).getName();
|
||||||
|
} else if (logic.equals("RandomInComputerDeck")) {
|
||||||
|
return Aggregates.random(player.getCardsIn(ZoneType.Library)).getName();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
List<Card> list = CardLists.filterControlledBy(game.getCardsInGame(), player.getOpponent());
|
||||||
|
list = CardLists.filter(list, Predicates.not(Presets.LANDS));
|
||||||
|
if (!list.isEmpty()) {
|
||||||
|
return list.get(0).getName();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "Morphling";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Card chooseSingleCardForZoneChange(ZoneType destination,
|
||||||
|
List<ZoneType> origin, SpellAbility sa, List<Card> fetchList,
|
||||||
|
String selectPrompt, boolean b, Player decider) {
|
||||||
|
|
||||||
|
return ChangeZoneAi.chooseCardToHiddenOriginChangeZone(destination, origin, sa, fetchList, player, decider);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
175
forge-ai/src/main/java/forge/ai/SpellAbilityAi.java
Normal file
175
forge-ai/src/main/java/forge/ai/SpellAbilityAi.java
Normal file
@@ -0,0 +1,175 @@
|
|||||||
|
package forge.ai;
|
||||||
|
|
||||||
|
|
||||||
|
import com.google.common.collect.Iterables;
|
||||||
|
|
||||||
|
import forge.game.GameEntity;
|
||||||
|
import forge.game.ability.SaTargetRoutines;
|
||||||
|
import forge.game.card.Card;
|
||||||
|
import forge.game.phase.PhaseHandler;
|
||||||
|
import forge.game.phase.PhaseType;
|
||||||
|
import forge.game.player.Player;
|
||||||
|
import forge.game.player.PlayerActionConfirmMode;
|
||||||
|
import forge.game.spellability.AbilitySub;
|
||||||
|
import forge.game.spellability.SpellAbility;
|
||||||
|
import forge.game.spellability.TargetRestrictions;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public abstract class SpellAbilityAi extends SaTargetRoutines {
|
||||||
|
|
||||||
|
public final boolean canPlayAIWithSubs(final Player aiPlayer, final SpellAbility sa) {
|
||||||
|
if (!canPlayAI(aiPlayer, sa)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
final AbilitySub subAb = sa.getSubAbility();
|
||||||
|
return subAb == null || chkDrawbackWithSubs(aiPlayer, subAb);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract boolean canPlayAI(final Player aiPlayer, final SpellAbility sa);
|
||||||
|
|
||||||
|
public final boolean doTriggerAI(final Player aiPlayer, final SpellAbility sa, final boolean mandatory) {
|
||||||
|
if (!ComputerUtilCost.canPayCost(sa, aiPlayer) && !mandatory) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return doTriggerNoCostWithSubs(aiPlayer, sa, mandatory);
|
||||||
|
}
|
||||||
|
|
||||||
|
public final boolean doTriggerNoCostWithSubs(final Player aiPlayer, final SpellAbility sa, final boolean mandatory)
|
||||||
|
{
|
||||||
|
if (!doTriggerAINoCost(aiPlayer, sa, mandatory)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
final AbilitySub subAb = sa.getSubAbility();
|
||||||
|
return subAb == null || chkDrawbackWithSubs(aiPlayer, subAb) || mandatory;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean doTriggerAINoCost(final Player aiPlayer, final SpellAbility sa, final boolean mandatory) {
|
||||||
|
if (canPlayAI(aiPlayer, sa)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (mandatory) {
|
||||||
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
|
final Player opp = aiPlayer.getOpponent();
|
||||||
|
if (tgt != null) {
|
||||||
|
if (opp.canBeTargetedBy(sa)) {
|
||||||
|
sa.resetTargets();
|
||||||
|
sa.getTargets().add(opp);
|
||||||
|
} else if (mandatory) {
|
||||||
|
if (aiPlayer.canBeTargetedBy(sa)) {
|
||||||
|
sa.resetTargets();
|
||||||
|
sa.getTargets().add(opp);
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean chkAIDrawback(final SpellAbility sa, final Player aiPlayer) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* isSorcerySpeed.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param sa
|
||||||
|
* a {@link forge.game.spellability.SpellAbility} object.
|
||||||
|
* @return a boolean.
|
||||||
|
*/
|
||||||
|
protected static boolean isSorcerySpeed(final SpellAbility sa) {
|
||||||
|
return ( sa.isSpell() && sa.getHostCard().isSorcery() )
|
||||||
|
|| ( sa.isAbility() && sa.getRestrictions().isSorcerySpeed() );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* playReusable.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param sa
|
||||||
|
* a {@link forge.game.spellability.SpellAbility} object.
|
||||||
|
* @return a boolean.
|
||||||
|
*/
|
||||||
|
protected static boolean playReusable(final Player ai, final SpellAbility sa) {
|
||||||
|
// TODO probably also consider if winter orb or similar are out
|
||||||
|
|
||||||
|
if (sa.getPayCosts() == null || sa instanceof AbilitySub) {
|
||||||
|
return true; // This is only true for Drawbacks and triggers
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ComputerUtil.playImmediately(ai, sa)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!sa.getPayCosts().isReusuableResource()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sa.getRestrictions().getPlaneswalker() && ai.getGame().getPhaseHandler().is(PhaseType.MAIN2)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (sa.isTrigger()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (sa.isSpell() && !sa.isBuyBackAbility()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
PhaseHandler phase = ai.getGame().getPhaseHandler();
|
||||||
|
return phase.is(PhaseType.END_OF_TURN) && phase.getNextTurn().equals(ai);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO: Write javadoc for this method.
|
||||||
|
* @param ai
|
||||||
|
* @param subAb
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public boolean chkDrawbackWithSubs(Player aiPlayer, AbilitySub ab) {
|
||||||
|
final AbilitySub subAb = ab.getSubAbility();
|
||||||
|
return SpellApiToAi.Converter.get(ab.getApi()).chkAIDrawback(ab, aiPlayer) && (subAb == null || chkDrawbackWithSubs(aiPlayer, subAb));
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
|
||||||
|
System.err.println("Warning: default (ie. inherited from base class) implementation of confirmAction is used for " + this.getClass().getName() + ". Consider declaring an overloaded method");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public <T extends GameEntity> T chooseSingleEntity(Player ai, SpellAbility sa, Collection<T> options, boolean isOptional, Player targetedPlayer) {
|
||||||
|
T firstOption = Iterables.getFirst(options, null);
|
||||||
|
|
||||||
|
if( firstOption instanceof Card)
|
||||||
|
return (T) chooseSingleCard(ai, sa, (Collection<Card>) options, isOptional, targetedPlayer);
|
||||||
|
else if ( firstOption instanceof Player)
|
||||||
|
return (T) chooseSinglePlayer(ai, sa, (Collection<Player>) options);
|
||||||
|
else
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SpellAbility chooseSingleSpellAbility(Player player, SpellAbility sa, List<SpellAbility> spells) {
|
||||||
|
System.err.println("Warning: default (ie. inherited from base class) implementation of chooseSingleSpellAbility is used for " + this.getClass().getName() + ". Consider declaring an overloaded method");
|
||||||
|
return spells.get(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Card chooseSingleCard(Player ai, SpellAbility sa, Collection<Card> options, boolean isOptional, Player targetedPlayer) {
|
||||||
|
System.err.println("Warning: default (ie. inherited from base class) implementation of chooseSingleEntity is used for " + this.getClass().getName() + ". Consider declaring an overloaded method");
|
||||||
|
return Iterables.getFirst(options, null);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Player chooseSinglePlayer(Player ai, SpellAbility sa, Collection<Player> options) {
|
||||||
|
System.err.println("Warning: default (ie. inherited from base class) implementation of chooseSingleEntity is used for " + this.getClass().getName() + ". Consider declaring an overloaded method");
|
||||||
|
return Iterables.getFirst(options, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
156
forge-ai/src/main/java/forge/ai/SpellApiToAi.java
Normal file
156
forge-ai/src/main/java/forge/ai/SpellApiToAi.java
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
package forge.ai;
|
||||||
|
|
||||||
|
import java.util.EnumMap;
|
||||||
|
|
||||||
|
import forge.ai.ability.*;
|
||||||
|
import forge.game.ability.ApiType;
|
||||||
|
import forge.util.ReflectionUtil;
|
||||||
|
|
||||||
|
public enum SpellApiToAi {
|
||||||
|
Converter;
|
||||||
|
|
||||||
|
private final static EnumMap<ApiType, Class<? extends SpellAbilityAi>> apiToClass = new EnumMap<>(ApiType.class);
|
||||||
|
private final EnumMap<ApiType, SpellAbilityAi> apiToInstance = new EnumMap<>(ApiType.class);
|
||||||
|
|
||||||
|
static {
|
||||||
|
apiToClass.put(ApiType.Abandon, AlwaysPlayAi.class);
|
||||||
|
apiToClass.put(ApiType.AddOrRemoveCounter, CountersPutOrRemoveAi.class);
|
||||||
|
apiToClass.put(ApiType.AddPhase, AddPhaseAi.class);
|
||||||
|
apiToClass.put(ApiType.AddTurn, AddTurnAi.class);
|
||||||
|
apiToClass.put(ApiType.Animate, AnimateAi.class);
|
||||||
|
apiToClass.put(ApiType.AnimateAll, AnimateAllAi.class);
|
||||||
|
apiToClass.put(ApiType.Attach, AttachAi.class);
|
||||||
|
apiToClass.put(ApiType.Balance, BalanceAi.class);
|
||||||
|
apiToClass.put(ApiType.BecomesBlocked, BecomesBlockedAi.class);
|
||||||
|
apiToClass.put(ApiType.Bond, BondAi.class);
|
||||||
|
apiToClass.put(ApiType.ChangeTargets, ChangeTargetsAi.class);
|
||||||
|
apiToClass.put(ApiType.ChangeZone, ChangeZoneAi.class);
|
||||||
|
apiToClass.put(ApiType.ChangeZoneAll, ChangeZoneAllAi.class);
|
||||||
|
|
||||||
|
apiToClass.put(ApiType.Charm, CharmAi.class);
|
||||||
|
apiToClass.put(ApiType.ChooseCard, ChooseCardAi.class);
|
||||||
|
apiToClass.put(ApiType.ChooseColor, ChooseColorAi.class);
|
||||||
|
apiToClass.put(ApiType.ChooseNumber, ChooseNumberAi.class);
|
||||||
|
apiToClass.put(ApiType.ChoosePlayer, ChoosePlayerAi.class);
|
||||||
|
apiToClass.put(ApiType.ChooseSource, ChooseSourceAi.class);
|
||||||
|
apiToClass.put(ApiType.ChooseType, ChooseTypeAi.class);
|
||||||
|
apiToClass.put(ApiType.Clash, ClashAi.class);
|
||||||
|
apiToClass.put(ApiType.Cleanup, AlwaysPlayAi.class);
|
||||||
|
apiToClass.put(ApiType.Clone, CloneAi.class);
|
||||||
|
apiToClass.put(ApiType.CopyPermanent, CopyPermanentAi.class);
|
||||||
|
apiToClass.put(ApiType.CopySpellAbility, CanPlayAsDrawbackAi.class);
|
||||||
|
apiToClass.put(ApiType.ControlPlayer, CannotPlayAi.class);
|
||||||
|
apiToClass.put(ApiType.ControlSpell, CannotPlayAi.class);
|
||||||
|
apiToClass.put(ApiType.Counter, CounterAi.class);
|
||||||
|
apiToClass.put(ApiType.DamageAll, DamageAllAi.class);
|
||||||
|
|
||||||
|
apiToClass.put(ApiType.DealDamage, DamageDealAi.class);
|
||||||
|
apiToClass.put(ApiType.Debuff, DebuffAi.class);
|
||||||
|
apiToClass.put(ApiType.DebuffAll, DebuffAllAi.class);
|
||||||
|
apiToClass.put(ApiType.DeclareCombatants, CannotPlayAi.class);
|
||||||
|
apiToClass.put(ApiType.DelayedTrigger, DelayedTriggerAi.class);
|
||||||
|
apiToClass.put(ApiType.Destroy, DestroyAi.class);
|
||||||
|
apiToClass.put(ApiType.DestroyAll, DestroyAllAi.class);
|
||||||
|
apiToClass.put(ApiType.Dig, DigAi.class);
|
||||||
|
apiToClass.put(ApiType.DigUntil, DigUntilAi.class);
|
||||||
|
apiToClass.put(ApiType.Discard, DiscardAi.class);
|
||||||
|
apiToClass.put(ApiType.DrainMana, DrainManaAi.class);
|
||||||
|
apiToClass.put(ApiType.Draw, DrawAi.class);
|
||||||
|
apiToClass.put(ApiType.EachDamage, DamageEachAi.class);
|
||||||
|
apiToClass.put(ApiType.Effect, EffectAi.class);
|
||||||
|
apiToClass.put(ApiType.Encode, EncodeAi.class);
|
||||||
|
apiToClass.put(ApiType.EndTurn, EndTurnAi.class);
|
||||||
|
apiToClass.put(ApiType.ExchangeLife, LifeExchangeAi.class);
|
||||||
|
apiToClass.put(ApiType.ExchangeControl, ControlExchangeAi.class);
|
||||||
|
apiToClass.put(ApiType.ExchangePower, PowerExchangeAi.class);
|
||||||
|
apiToClass.put(ApiType.ExchangeZone, ZoneExchangeAi.class);
|
||||||
|
apiToClass.put(ApiType.Fight, FightAi.class);
|
||||||
|
apiToClass.put(ApiType.FlipACoin, FlipACoinAi.class);
|
||||||
|
apiToClass.put(ApiType.Fog, FogAi.class);
|
||||||
|
apiToClass.put(ApiType.GainControl, ControlGainAi.class);
|
||||||
|
apiToClass.put(ApiType.GainLife, LifeGainAi.class);
|
||||||
|
apiToClass.put(ApiType.GainOwnership, CannotPlayAi.class);
|
||||||
|
apiToClass.put(ApiType.GenericChoice, ChooseGenericEffectAi.class);
|
||||||
|
apiToClass.put(ApiType.LoseLife, LifeLoseAi.class);
|
||||||
|
apiToClass.put(ApiType.LosesGame, GameLossAi.class);
|
||||||
|
apiToClass.put(ApiType.Mana, ManaEffectAi.class);
|
||||||
|
apiToClass.put(ApiType.ManaReflected, CannotPlayAi.class);
|
||||||
|
apiToClass.put(ApiType.Mill, MillAi.class);
|
||||||
|
apiToClass.put(ApiType.MoveCounter, CountersMoveAi.class);
|
||||||
|
apiToClass.put(ApiType.MultiplePiles, CannotPlayAi.class);
|
||||||
|
apiToClass.put(ApiType.MustAttack, MustAttackAi.class);
|
||||||
|
apiToClass.put(ApiType.MustBlock, MustBlockAi.class);
|
||||||
|
apiToClass.put(ApiType.NameCard, ChooseCardNameAi.class);
|
||||||
|
apiToClass.put(ApiType.PeekAndReveal, PeekAndRevealAi.class);
|
||||||
|
apiToClass.put(ApiType.PermanentCreature, PermanentCreatureAi.class);
|
||||||
|
apiToClass.put(ApiType.PermanentNoncreature, PermanentNoncreatureAi.class);
|
||||||
|
apiToClass.put(ApiType.Phases, PhasesAi.class);
|
||||||
|
apiToClass.put(ApiType.Planeswalk, AlwaysPlayAi.class);
|
||||||
|
apiToClass.put(ApiType.Play, PlayAi.class);
|
||||||
|
apiToClass.put(ApiType.PlayLandVariant, CannotPlayAi.class);
|
||||||
|
apiToClass.put(ApiType.Poison, PoisonAi.class);
|
||||||
|
apiToClass.put(ApiType.PreventDamage, DamagePreventAi.class);
|
||||||
|
apiToClass.put(ApiType.PreventDamageAll, DamagePreventAllAi.class);
|
||||||
|
apiToClass.put(ApiType.Proliferate, CountersProliferateAi.class);
|
||||||
|
apiToClass.put(ApiType.Protection, ProtectAi.class);
|
||||||
|
apiToClass.put(ApiType.ProtectionAll, ProtectAllAi.class);
|
||||||
|
apiToClass.put(ApiType.Pump, PumpAi.class);
|
||||||
|
apiToClass.put(ApiType.PumpAll, PumpAllAi.class);
|
||||||
|
apiToClass.put(ApiType.PutCounter, CountersPutAi.class);
|
||||||
|
apiToClass.put(ApiType.PutCounterAll, CountersPutAllAi.class);
|
||||||
|
apiToClass.put(ApiType.RearrangeTopOfLibrary, RearrangeTopOfLibraryAi.class);
|
||||||
|
apiToClass.put(ApiType.Regenerate, RegenerateAi.class);
|
||||||
|
apiToClass.put(ApiType.RegenerateAll, RegenerateAllAi.class);
|
||||||
|
apiToClass.put(ApiType.RemoveCounter, CountersRemoveAi.class);
|
||||||
|
apiToClass.put(ApiType.RemoveCounterAll, CannotPlayAi.class);
|
||||||
|
apiToClass.put(ApiType.RemoveFromCombat, RemoveFromCombatAi.class);
|
||||||
|
apiToClass.put(ApiType.ReorderZone, AlwaysPlayAi.class);
|
||||||
|
apiToClass.put(ApiType.Repeat, RepeatAi.class);
|
||||||
|
apiToClass.put(ApiType.RepeatEach, RepeatEachAi.class);
|
||||||
|
apiToClass.put(ApiType.RestartGame, RestartGameAi.class);
|
||||||
|
apiToClass.put(ApiType.Reveal, RevealAi.class);
|
||||||
|
apiToClass.put(ApiType.RevealHand, RevealHandAi.class);
|
||||||
|
apiToClass.put(ApiType.RollPlanarDice, RollPlanarDiceAi.class);
|
||||||
|
apiToClass.put(ApiType.RunSVarAbility, AlwaysPlayAi.class);
|
||||||
|
apiToClass.put(ApiType.Sacrifice, SacrificeAi.class);
|
||||||
|
apiToClass.put(ApiType.SacrificeAll, SacrificeAllAi.class);
|
||||||
|
apiToClass.put(ApiType.Scry, ScryAi.class);
|
||||||
|
apiToClass.put(ApiType.SetInMotion, AlwaysPlayAi.class);
|
||||||
|
apiToClass.put(ApiType.SetLife, LifeSetAi.class);
|
||||||
|
apiToClass.put(ApiType.SetState, SetStateAi.class);
|
||||||
|
apiToClass.put(ApiType.Shuffle, ShuffleAi.class);
|
||||||
|
apiToClass.put(ApiType.SkipTurn, SkipTurnAi.class);
|
||||||
|
apiToClass.put(ApiType.StoreSVar, StoreSVarAi.class);
|
||||||
|
apiToClass.put(ApiType.Tap, TapAi.class);
|
||||||
|
apiToClass.put(ApiType.TapAll, TapAllAi.class);
|
||||||
|
apiToClass.put(ApiType.TapOrUntap, TapOrUntapAi.class);
|
||||||
|
apiToClass.put(ApiType.TapOrUntapAll, TapOrUntapAllAi.class);
|
||||||
|
apiToClass.put(ApiType.Token, TokenAi.class);
|
||||||
|
apiToClass.put(ApiType.TwoPiles, TwoPilesAi.class);
|
||||||
|
apiToClass.put(ApiType.Unattach, CannotPlayAi.class);
|
||||||
|
apiToClass.put(ApiType.UnattachAll, UnattachAllAi.class);
|
||||||
|
apiToClass.put(ApiType.Untap, UntapAi.class);
|
||||||
|
apiToClass.put(ApiType.UntapAll, UntapAllAi.class);
|
||||||
|
apiToClass.put(ApiType.Vote, CannotPlayAi.class);
|
||||||
|
apiToClass.put(ApiType.WinsGame, GameWinAi.class);
|
||||||
|
|
||||||
|
apiToClass.put(ApiType.InternalEtbReplacement, CanPlayAsDrawbackAi.class);
|
||||||
|
apiToClass.put(ApiType.InternalLegendaryRule, LegendaryRuleAi.class);
|
||||||
|
apiToClass.put(ApiType.InternalHaunt, HauntAi.class);
|
||||||
|
apiToClass.put(ApiType.InternalIgnoreEffect, CannotPlayAi.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SpellAbilityAi get(ApiType api) {
|
||||||
|
SpellAbilityAi result = apiToInstance.get(api);
|
||||||
|
if( null == result ) {
|
||||||
|
Class<? extends SpellAbilityAi> clz = apiToClass.get(api);
|
||||||
|
if(null == clz) {
|
||||||
|
System.err.println("No AI assigned for API: " + api);
|
||||||
|
clz = CannotPlayAi.class;
|
||||||
|
}
|
||||||
|
result = ReflectionUtil.makeDefaultInstanceOf(clz);
|
||||||
|
apiToInstance.put(api, result);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
18
forge-ai/src/main/java/forge/ai/ability/AddPhaseAi.java
Normal file
18
forge-ai/src/main/java/forge/ai/ability/AddPhaseAi.java
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
import forge.ai.SpellAbilityAi;
|
||||||
|
import forge.game.player.Player;
|
||||||
|
import forge.game.spellability.SpellAbility;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO: Write javadoc for this type.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class AddPhaseAi extends SpellAbilityAi {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -15,14 +15,16 @@
|
|||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
package forge.card.ability.ai;
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
import forge.ai.SpellAbilityAi;
|
||||||
|
import forge.game.ability.AbilityUtils;
|
||||||
|
import forge.game.player.Player;
|
||||||
|
import forge.game.spellability.SpellAbility;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import forge.card.ability.AbilityUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import forge.card.ability.SpellAbilityAi;
|
|
||||||
import forge.card.spellability.SpellAbility;
|
|
||||||
import forge.game.player.Player;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>
|
* <p>
|
||||||
@@ -50,19 +52,23 @@ public class AddTurnAi extends SpellAbilityAi {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!sa.getTargetRestrictions().isMinTargetsChosen(sa.getSourceCard(), sa) && sa.canTarget(opp)) {
|
if (!sa.getTargetRestrictions().isMinTargetsChosen(sa.getHostCard(), sa) && sa.canTarget(opp)) {
|
||||||
sa.getTargets().add(opp);
|
sa.getTargets().add(opp);
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
final List<Player> tgtPlayers = AbilityUtils.getDefinedPlayers(sa.getSourceCard(), sa.getParam("Defined"), sa);
|
final List<Player> tgtPlayers = AbilityUtils.getDefinedPlayers(sa.getHostCard(), sa.getParam("Defined"), sa);
|
||||||
for (final Player p : tgtPlayers) {
|
for (final Player p : tgtPlayers) {
|
||||||
if (p.isOpponentOf(ai) && !mandatory) {
|
if (p.isOpponentOf(ai) && !mandatory) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (!StringUtils.isNumeric(sa.getParam("NumTurns"))) {
|
||||||
|
// TODO: improve ai for Sage of Hours
|
||||||
|
return false;
|
||||||
|
}
|
||||||
// not sure if the AI should be playing with cards that give the
|
// not sure if the AI should be playing with cards that give the
|
||||||
// Human more turns.
|
// Human more turns.
|
||||||
}
|
}
|
||||||
16
forge-ai/src/main/java/forge/ai/ability/AlwaysPlayAi.java
Normal file
16
forge-ai/src/main/java/forge/ai/ability/AlwaysPlayAi.java
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
|
||||||
|
import forge.ai.SpellAbilityAi;
|
||||||
|
import forge.game.player.Player;
|
||||||
|
import forge.game.spellability.SpellAbility;
|
||||||
|
|
||||||
|
public class AlwaysPlayAi extends SpellAbilityAi {
|
||||||
|
/* (non-Javadoc)
|
||||||
|
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
482
forge-ai/src/main/java/forge/ai/ability/AnimateAi.java
Normal file
482
forge-ai/src/main/java/forge/ai/ability/AnimateAi.java
Normal file
@@ -0,0 +1,482 @@
|
|||||||
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
import com.google.common.collect.Iterables;
|
||||||
|
|
||||||
|
import forge.ai.AiAttackController;
|
||||||
|
import forge.ai.AiBlockController;
|
||||||
|
import forge.ai.SpellAbilityAi;
|
||||||
|
import forge.game.Game;
|
||||||
|
import forge.game.ability.AbilityFactory;
|
||||||
|
import forge.game.ability.AbilityUtils;
|
||||||
|
import forge.game.card.Card;
|
||||||
|
import forge.game.card.CardFactory;
|
||||||
|
import forge.game.card.CardLists;
|
||||||
|
import forge.game.card.CardPredicates;
|
||||||
|
import forge.game.card.CardUtil;
|
||||||
|
import forge.game.phase.PhaseHandler;
|
||||||
|
import forge.game.phase.PhaseType;
|
||||||
|
import forge.game.player.Player;
|
||||||
|
import forge.game.replacement.ReplacementEffect;
|
||||||
|
import forge.game.replacement.ReplacementHandler;
|
||||||
|
import forge.game.spellability.SpellAbility;
|
||||||
|
import forge.game.spellability.TargetRestrictions;
|
||||||
|
import forge.game.staticability.StaticAbility;
|
||||||
|
import forge.game.staticability.StaticAbilityContinuous;
|
||||||
|
import forge.game.trigger.Trigger;
|
||||||
|
import forge.game.trigger.TriggerHandler;
|
||||||
|
import forge.game.zone.ZoneType;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* AbilityFactoryAnimate class.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @author Forge
|
||||||
|
* @version $Id: AbilityFactoryAnimate.java 17608 2012-10-20 22:27:27Z Max mtg $
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class AnimateAi extends SpellAbilityAi {
|
||||||
|
|
||||||
|
/* (non-Javadoc)
|
||||||
|
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
||||||
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
|
final Card source = sa.getHostCard();
|
||||||
|
final Game game = aiPlayer.getGame();
|
||||||
|
final PhaseHandler ph = game.getPhaseHandler();
|
||||||
|
|
||||||
|
// TODO - add some kind of check to answer
|
||||||
|
// "Am I going to attack with this?"
|
||||||
|
// TODO - add some kind of check for during human turn to answer
|
||||||
|
// "Can I use this to block something?"
|
||||||
|
|
||||||
|
// don't use instant speed animate abilities outside computers
|
||||||
|
// Combat_Begin step
|
||||||
|
if (!ph.is(PhaseType.COMBAT_BEGIN)
|
||||||
|
&& ph.isPlayerTurn(aiPlayer)
|
||||||
|
&& !SpellAbilityAi.isSorcerySpeed(sa)
|
||||||
|
&& !sa.hasParam("ActivationPhases") && !sa.hasParam("Permanent")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Player opponent = aiPlayer.getWeakestOpponent();
|
||||||
|
// don't animate if the AI won't attack anyway
|
||||||
|
if (ph.isPlayerTurn(aiPlayer)
|
||||||
|
&& aiPlayer.getLife() < 6
|
||||||
|
&& opponent.getLife() > 6
|
||||||
|
&& Iterables.any(opponent.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.CREATURES)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// don't use instant speed animate abilities outside humans
|
||||||
|
// Combat_Declare_Attackers_InstantAbility step
|
||||||
|
if (ph.getPlayerTurn().isOpponentOf(aiPlayer) &&
|
||||||
|
(!ph.is(PhaseType.COMBAT_DECLARE_ATTACKERS, opponent) || (game.getCombat() != null && game.getCombat().getAttackersOf(aiPlayer).isEmpty()))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// don't activate during main2 unless this effect is permanent
|
||||||
|
if (ph.is(PhaseType.MAIN2) && !sa.hasParam("Permanent") && !sa.hasParam("UntilYourNextTurn")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (null == tgt) {
|
||||||
|
final List<Card> defined = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa);
|
||||||
|
|
||||||
|
boolean bFlag = false;
|
||||||
|
if (sa.hasParam("AILogic")) {
|
||||||
|
if ("EOT".equals(sa.getParam("AILogic"))) {
|
||||||
|
if (ph.getPhase().isBefore(PhaseType.MAIN2)) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
bFlag = true;
|
||||||
|
}
|
||||||
|
} if ("Never".equals(sa.getParam("AILogic"))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else for (final Card c : defined) {
|
||||||
|
bFlag |= !c.isCreature() && !c.isTapped()
|
||||||
|
&& !(c.getTurnInZone() == game.getPhaseHandler().getTurn())
|
||||||
|
&& !c.isEquipping();
|
||||||
|
|
||||||
|
// for creatures that could be improved (like Figure of Destiny)
|
||||||
|
if (!bFlag && c.isCreature() && (sa.hasParam("Permanent") || (!c.isTapped() && !c.isSick()))) {
|
||||||
|
int power = -5;
|
||||||
|
if (sa.hasParam("Power")) {
|
||||||
|
power = AbilityUtils.calculateAmount(source, sa.getParam("Power"), sa);
|
||||||
|
}
|
||||||
|
int toughness = -5;
|
||||||
|
if (sa.hasParam("Toughness")) {
|
||||||
|
toughness = AbilityUtils.calculateAmount(source, sa.getParam("Toughness"), sa);
|
||||||
|
}
|
||||||
|
if ((power + toughness) > (c.getCurrentPower() + c.getCurrentToughness())) {
|
||||||
|
bFlag = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!SpellAbilityAi.isSorcerySpeed(sa)) {
|
||||||
|
Card animatedCopy = CardFactory.getCard(c.getPaperCard(), aiPlayer);
|
||||||
|
AnimateAi.becomeAnimated(animatedCopy, sa);
|
||||||
|
if (ph.isPlayerTurn(aiPlayer) && !AiAttackController.shouldThisAttack(aiPlayer, animatedCopy)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (ph.getPlayerTurn().isOpponentOf(aiPlayer) && !AiBlockController.shouldThisBlock(aiPlayer, animatedCopy)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!bFlag) { // All of the defined stuff is animated, not very
|
||||||
|
// useful
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sa.resetTargets();
|
||||||
|
if (!animateTgtAI(sa)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// end animateCanPlayAI()
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
|
||||||
|
if (sa.usesTargeting()) {
|
||||||
|
sa.resetTargets();
|
||||||
|
if (!animateTgtAI(sa)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* animateTriggerAI.
|
||||||
|
* </p>
|
||||||
|
* @param sa
|
||||||
|
* a {@link forge.game.spellability.SpellAbility} object.
|
||||||
|
* @param mandatory
|
||||||
|
* a boolean.
|
||||||
|
* @param af
|
||||||
|
* a {@link forge.game.ability.AbilityFactory} object.
|
||||||
|
*
|
||||||
|
* @return a boolean.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
||||||
|
|
||||||
|
if (sa.usesTargeting() && !animateTgtAI(sa) && !mandatory) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Improve AI for triggers. If source is a creature with:
|
||||||
|
// When ETB, sacrifice a creature. Check to see if the AI has something
|
||||||
|
// to sacrifice
|
||||||
|
|
||||||
|
// Eventually, we can call the trigger of ETB abilities with
|
||||||
|
// not mandatory as part of the checks to cast something
|
||||||
|
if (sa.hasParam("AITgts")) {
|
||||||
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
|
final Card animateSource = sa.getHostCard();
|
||||||
|
List<Card> list = aiPlayer.getGame().getCardsIn(tgt.getZone());
|
||||||
|
list = CardLists.getValidCards(list, tgt.getValidTgts(), sa.getActivatingPlayer(), animateSource);
|
||||||
|
List<Card> prefList = CardLists.getValidCards(list, sa.getParam("AITgts"), sa.getActivatingPlayer(), animateSource);
|
||||||
|
CardLists.shuffle(prefList);
|
||||||
|
sa.getTargets().add(prefList.get(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* animateTgtAI.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param af
|
||||||
|
* a {@link forge.game.ability.AbilityFactory} object.
|
||||||
|
* @param sa
|
||||||
|
* a {@link forge.game.spellability.SpellAbility} object.
|
||||||
|
* @return a boolean.
|
||||||
|
*/
|
||||||
|
private boolean animateTgtAI(final SpellAbility sa) {
|
||||||
|
// This is reasonable for now. Kamahl, Fist of Krosa and a sorcery or
|
||||||
|
// two are the only things
|
||||||
|
// that animate a target. Those can just use SVar:RemAIDeck:True until
|
||||||
|
// this can do a reasonably
|
||||||
|
// good job of picking a good target
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void becomeAnimated(Card source, SpellAbility sa) {
|
||||||
|
//duplicating AnimateEffect.resolve
|
||||||
|
final Game game = sa.getActivatingPlayer().getGame();
|
||||||
|
final Map<String, String> svars = source.getSVars();
|
||||||
|
final long timestamp = game.getNextTimestamp();
|
||||||
|
source.setSickness(sa.getHostCard().hasSickness());
|
||||||
|
|
||||||
|
// AF specific sa
|
||||||
|
int power = -1;
|
||||||
|
if (sa.hasParam("Power")) {
|
||||||
|
power = AbilityUtils.calculateAmount(source, sa.getParam("Power"), sa);
|
||||||
|
}
|
||||||
|
int toughness = -1;
|
||||||
|
if (sa.hasParam("Toughness")) {
|
||||||
|
toughness = AbilityUtils.calculateAmount(source, sa.getParam("Toughness"), sa);
|
||||||
|
}
|
||||||
|
|
||||||
|
final ArrayList<String> types = new ArrayList<String>();
|
||||||
|
if (sa.hasParam("Types")) {
|
||||||
|
types.addAll(Arrays.asList(sa.getParam("Types").split(",")));
|
||||||
|
}
|
||||||
|
|
||||||
|
final ArrayList<String> removeTypes = new ArrayList<String>();
|
||||||
|
if (sa.hasParam("RemoveTypes")) {
|
||||||
|
removeTypes.addAll(Arrays.asList(sa.getParam("RemoveTypes").split(",")));
|
||||||
|
}
|
||||||
|
|
||||||
|
// allow ChosenType - overrides anything else specified
|
||||||
|
if (types.contains("ChosenType")) {
|
||||||
|
types.clear();
|
||||||
|
types.add(source.getChosenType());
|
||||||
|
}
|
||||||
|
|
||||||
|
final ArrayList<String> keywords = new ArrayList<String>();
|
||||||
|
if (sa.hasParam("Keywords")) {
|
||||||
|
keywords.addAll(Arrays.asList(sa.getParam("Keywords").split(" & ")));
|
||||||
|
}
|
||||||
|
|
||||||
|
final ArrayList<String> removeKeywords = new ArrayList<String>();
|
||||||
|
if (sa.hasParam("RemoveKeywords")) {
|
||||||
|
removeKeywords.addAll(Arrays.asList(sa.getParam("RemoveKeywords").split(" & ")));
|
||||||
|
}
|
||||||
|
|
||||||
|
final ArrayList<String> hiddenKeywords = new ArrayList<String>();
|
||||||
|
if (sa.hasParam("HiddenKeywords")) {
|
||||||
|
hiddenKeywords.addAll(Arrays.asList(sa.getParam("HiddenKeywords").split(" & ")));
|
||||||
|
}
|
||||||
|
// allow SVar substitution for keywords
|
||||||
|
for (int i = 0; i < keywords.size(); i++) {
|
||||||
|
final String k = keywords.get(i);
|
||||||
|
if (svars.containsKey(k)) {
|
||||||
|
keywords.add(svars.get(k));
|
||||||
|
keywords.remove(k);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// colors to be added or changed to
|
||||||
|
String tmpDesc = "";
|
||||||
|
if (sa.hasParam("Colors")) {
|
||||||
|
final String colors = sa.getParam("Colors");
|
||||||
|
if (colors.equals("ChosenColor")) {
|
||||||
|
|
||||||
|
tmpDesc = CardUtil.getShortColorsString(source.getChosenColor());
|
||||||
|
} else {
|
||||||
|
tmpDesc = CardUtil.getShortColorsString(new ArrayList<String>(Arrays.asList(colors.split(","))));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
final String finalDesc = tmpDesc;
|
||||||
|
|
||||||
|
// abilities to add to the animated being
|
||||||
|
final ArrayList<String> abilities = new ArrayList<String>();
|
||||||
|
if (sa.hasParam("Abilities")) {
|
||||||
|
abilities.addAll(Arrays.asList(sa.getParam("Abilities").split(",")));
|
||||||
|
}
|
||||||
|
|
||||||
|
// replacement effects to add to the animated being
|
||||||
|
final ArrayList<String> replacements = new ArrayList<String>();
|
||||||
|
if (sa.hasParam("Replacements")) {
|
||||||
|
replacements.addAll(Arrays.asList(sa.getParam("Replacements").split(",")));
|
||||||
|
}
|
||||||
|
|
||||||
|
// triggers to add to the animated being
|
||||||
|
final ArrayList<String> triggers = new ArrayList<String>();
|
||||||
|
if (sa.hasParam("Triggers")) {
|
||||||
|
triggers.addAll(Arrays.asList(sa.getParam("Triggers").split(",")));
|
||||||
|
}
|
||||||
|
|
||||||
|
// static abilities to add to the animated being
|
||||||
|
final ArrayList<String> stAbs = new ArrayList<String>();
|
||||||
|
if (sa.hasParam("staticAbilities")) {
|
||||||
|
stAbs.addAll(Arrays.asList(sa.getParam("staticAbilities").split(",")));
|
||||||
|
}
|
||||||
|
|
||||||
|
// sVars to add to the animated being
|
||||||
|
final ArrayList<String> sVars = new ArrayList<String>();
|
||||||
|
if (sa.hasParam("sVars")) {
|
||||||
|
sVars.addAll(Arrays.asList(sa.getParam("sVars").split(",")));
|
||||||
|
}
|
||||||
|
|
||||||
|
//duplicating AnimateEffectBase.doAnimate
|
||||||
|
boolean removeSuperTypes = false;
|
||||||
|
boolean removeCardTypes = false;
|
||||||
|
boolean removeSubTypes = false;
|
||||||
|
boolean removeCreatureTypes = false;
|
||||||
|
|
||||||
|
if (sa.hasParam("OverwriteTypes")) {
|
||||||
|
removeSuperTypes = true;
|
||||||
|
removeCardTypes = true;
|
||||||
|
removeSubTypes = true;
|
||||||
|
removeCreatureTypes = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sa.hasParam("KeepSupertypes")) {
|
||||||
|
removeSuperTypes = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sa.hasParam("KeepCardTypes")) {
|
||||||
|
removeCardTypes = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sa.hasParam("RemoveSuperTypes")) {
|
||||||
|
removeSuperTypes = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sa.hasParam("RemoveCardTypes")) {
|
||||||
|
removeCardTypes = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sa.hasParam("RemoveSubTypes")) {
|
||||||
|
removeSubTypes = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sa.hasParam("RemoveCreatureTypes")) {
|
||||||
|
removeCreatureTypes = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((power != -1) || (toughness != -1)) {
|
||||||
|
source.addNewPT(power, toughness, timestamp);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!types.isEmpty() || !removeTypes.isEmpty() || removeCreatureTypes) {
|
||||||
|
source.addChangedCardTypes(types, removeTypes, removeSuperTypes, removeCardTypes, removeSubTypes,
|
||||||
|
removeCreatureTypes, timestamp);
|
||||||
|
}
|
||||||
|
|
||||||
|
source.addChangedCardKeywords(keywords, removeKeywords, sa.hasParam("RemoveAllAbilities"), timestamp);
|
||||||
|
|
||||||
|
for (final String k : hiddenKeywords) {
|
||||||
|
source.addHiddenExtrinsicKeyword(k);
|
||||||
|
}
|
||||||
|
|
||||||
|
source.addColor(finalDesc, !sa.hasParam("OverwriteColors"), true);
|
||||||
|
|
||||||
|
//back to duplicating AnimateEffect.resolve
|
||||||
|
//TODO will all these abilities/triggers/replacements/etc. lead to memory leaks or unintended effects?
|
||||||
|
// remove abilities
|
||||||
|
final ArrayList<SpellAbility> removedAbilities = new ArrayList<SpellAbility>();
|
||||||
|
boolean clearAbilities = sa.hasParam("OverwriteAbilities");
|
||||||
|
boolean clearSpells = sa.hasParam("OverwriteSpells");
|
||||||
|
boolean removeAll = sa.hasParam("RemoveAllAbilities");
|
||||||
|
|
||||||
|
if (clearAbilities || clearSpells || removeAll) {
|
||||||
|
for (final SpellAbility ab : source.getSpellAbilities()) {
|
||||||
|
if (removeAll || (ab.isAbility() && clearAbilities)
|
||||||
|
|| (ab.isSpell() && clearSpells)) {
|
||||||
|
source.removeSpellAbility(ab);
|
||||||
|
removedAbilities.add(ab);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// give abilities
|
||||||
|
final ArrayList<SpellAbility> addedAbilities = new ArrayList<SpellAbility>();
|
||||||
|
if (abilities.size() > 0) {
|
||||||
|
for (final String s : abilities) {
|
||||||
|
final String actualAbility = source.getSVar(s);
|
||||||
|
final SpellAbility grantedAbility = AbilityFactory.getAbility(actualAbility, source);
|
||||||
|
addedAbilities.add(grantedAbility);
|
||||||
|
source.addSpellAbility(grantedAbility);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Grant triggers
|
||||||
|
final ArrayList<Trigger> addedTriggers = new ArrayList<Trigger>();
|
||||||
|
if (triggers.size() > 0) {
|
||||||
|
for (final String s : triggers) {
|
||||||
|
final String actualTrigger = source.getSVar(s);
|
||||||
|
final Trigger parsedTrigger = TriggerHandler.parseTrigger(actualTrigger, source, false);
|
||||||
|
addedTriggers.add(source.addTrigger(parsedTrigger));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// give replacement effects
|
||||||
|
final ArrayList<ReplacementEffect> addedReplacements = new ArrayList<ReplacementEffect>();
|
||||||
|
if (replacements.size() > 0) {
|
||||||
|
for (final String s : replacements) {
|
||||||
|
final String actualReplacement = source.getSVar(s);
|
||||||
|
final ReplacementEffect parsedReplacement = ReplacementHandler.parseReplacement(actualReplacement, source, false);
|
||||||
|
addedReplacements.add(source.addReplacementEffect(parsedReplacement));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// suppress triggers from the animated card
|
||||||
|
final ArrayList<Trigger> removedTriggers = new ArrayList<Trigger>();
|
||||||
|
if (sa.hasParam("OverwriteTriggers") || removeAll) {
|
||||||
|
final List<Trigger> triggersToRemove = source.getTriggers();
|
||||||
|
for (final Trigger trigger : triggersToRemove) {
|
||||||
|
trigger.setSuppressed(true);
|
||||||
|
removedTriggers.add(trigger);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// give static abilities (should only be used by cards to give
|
||||||
|
// itself a static ability)
|
||||||
|
if (stAbs.size() > 0) {
|
||||||
|
for (final String s : stAbs) {
|
||||||
|
final String actualAbility = source.getSVar(s);
|
||||||
|
StaticAbility stAb = source.addStaticAbility(actualAbility);
|
||||||
|
if ("Continuous".equals(stAb.getMapParams().get("Mode"))) {
|
||||||
|
List<Card> list = new ArrayList<Card>();
|
||||||
|
list.add(source);
|
||||||
|
list = StaticAbilityContinuous.applyContinuousAbility(stAb, list);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// give sVars
|
||||||
|
if (sVars.size() > 0) {
|
||||||
|
for (final String s : sVars) {
|
||||||
|
String actualsVar = source.getSVar(s);
|
||||||
|
String name = s;
|
||||||
|
if (actualsVar.startsWith("SVar:")) {
|
||||||
|
actualsVar = actualsVar.split("SVar:")[1];
|
||||||
|
name = actualsVar.split(":")[0];
|
||||||
|
actualsVar = actualsVar.split(":")[1];
|
||||||
|
}
|
||||||
|
source.setSVar(name, actualsVar);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// suppress static abilities from the animated card
|
||||||
|
final ArrayList<StaticAbility> removedStatics = new ArrayList<StaticAbility>();
|
||||||
|
if (sa.hasParam("OverwriteStatics") || removeAll) {
|
||||||
|
final ArrayList<StaticAbility> staticsToRemove = source.getStaticAbilities();
|
||||||
|
for (final StaticAbility stAb : staticsToRemove) {
|
||||||
|
stAb.setTemporarilySuppressed(true);
|
||||||
|
removedStatics.add(stAb);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// suppress static abilities from the animated card
|
||||||
|
final ArrayList<ReplacementEffect> removedReplacements = new ArrayList<ReplacementEffect>();
|
||||||
|
if (sa.hasParam("OverwriteReplacements") || removeAll) {
|
||||||
|
for (final ReplacementEffect re : source.getReplacementEffects()) {
|
||||||
|
re.setTemporarilySuppressed(true);
|
||||||
|
removedReplacements.add(re);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
19
forge-ai/src/main/java/forge/ai/ability/AnimateAllAi.java
Normal file
19
forge-ai/src/main/java/forge/ai/ability/AnimateAllAi.java
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
import forge.ai.SpellAbilityAi;
|
||||||
|
import forge.game.player.Player;
|
||||||
|
import forge.game.spellability.SpellAbility;
|
||||||
|
|
||||||
|
public class AnimateAllAi extends SpellAbilityAi {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
||||||
|
return false;
|
||||||
|
} // end animateAllCanPlayAI()
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // end class AbilityFactoryAnimate
|
||||||
@@ -1,42 +1,27 @@
|
|||||||
package forge.card.ability.ai;
|
package forge.ai.ability;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Random;
|
|
||||||
|
|
||||||
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.*;
|
||||||
import forge.Card;
|
import forge.game.GameObject;
|
||||||
import forge.CardLists;
|
import forge.game.ability.AbilityUtils;
|
||||||
import forge.CardPredicates;
|
import forge.game.ability.ApiType;
|
||||||
import forge.CardUtil;
|
import forge.game.card.*;
|
||||||
import forge.ITargetable;
|
|
||||||
import forge.card.ability.AbilityUtils;
|
|
||||||
import forge.card.ability.ApiType;
|
|
||||||
import forge.card.ability.SpellAbilityAi;
|
|
||||||
import forge.card.cardfactory.CardFactoryUtil;
|
|
||||||
import forge.card.cost.Cost;
|
|
||||||
import forge.card.spellability.SpellAbility;
|
|
||||||
import forge.card.spellability.TargetRestrictions;
|
|
||||||
import forge.card.staticability.StaticAbility;
|
|
||||||
import forge.card.trigger.Trigger;
|
|
||||||
import forge.card.trigger.TriggerType;
|
|
||||||
import forge.game.ai.ComputerUtil;
|
|
||||||
import forge.game.ai.ComputerUtilCard;
|
|
||||||
import forge.game.ai.ComputerUtilCost;
|
|
||||||
import forge.game.ai.ComputerUtilMana;
|
|
||||||
import forge.game.combat.CombatUtil;
|
import forge.game.combat.CombatUtil;
|
||||||
|
import forge.game.cost.Cost;
|
||||||
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.player.PlayerActionConfirmMode;
|
||||||
|
import forge.game.spellability.SpellAbility;
|
||||||
|
import forge.game.spellability.TargetRestrictions;
|
||||||
|
import forge.game.staticability.StaticAbility;
|
||||||
|
import forge.game.trigger.Trigger;
|
||||||
|
import forge.game.trigger.TriggerType;
|
||||||
import forge.util.MyRandom;
|
import forge.util.MyRandom;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
public class AttachAi extends SpellAbilityAi {
|
public class AttachAi extends SpellAbilityAi {
|
||||||
|
|
||||||
/* (non-Javadoc)
|
/* (non-Javadoc)
|
||||||
@@ -46,7 +31,7 @@ public class AttachAi extends SpellAbilityAi {
|
|||||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||||
final Random r = MyRandom.getRandom();
|
final Random r = MyRandom.getRandom();
|
||||||
final Cost abCost = sa.getPayCosts();
|
final Cost abCost = sa.getPayCosts();
|
||||||
final Card source = sa.getSourceCard();
|
final Card source = sa.getHostCard();
|
||||||
|
|
||||||
if (abCost != null) {
|
if (abCost != null) {
|
||||||
// AI currently disabled for these costs
|
// AI currently disabled for these costs
|
||||||
@@ -169,7 +154,7 @@ public class AttachAi extends SpellAbilityAi {
|
|||||||
String type = "";
|
String type = "";
|
||||||
|
|
||||||
for (final StaticAbility stAb : attachSource.getStaticAbilities()) {
|
for (final StaticAbility stAb : attachSource.getStaticAbilities()) {
|
||||||
final HashMap<String, String> stab = stAb.getMapParams();
|
final Map<String, String> stab = stAb.getMapParams();
|
||||||
if (stab.get("Mode").equals("Continuous") && stab.containsKey("AddType")) {
|
if (stab.get("Mode").equals("Continuous") && stab.containsKey("AddType")) {
|
||||||
type = stab.get("AddType");
|
type = stab.get("AddType");
|
||||||
}
|
}
|
||||||
@@ -334,7 +319,7 @@ public class AttachAi extends SpellAbilityAi {
|
|||||||
final Card attachSource) {
|
final Card attachSource) {
|
||||||
// AI For choosing a Card to Animate.
|
// AI For choosing a Card to Animate.
|
||||||
List<Card> betterList = CardLists.getNotType(list, "Creature");
|
List<Card> betterList = CardLists.getNotType(list, "Creature");
|
||||||
if (sa.getSourceCard().getName().equals("Animate Artifact")) {
|
if (sa.getHostCard().getName().equals("Animate Artifact")) {
|
||||||
betterList = CardLists.filter(betterList, new Predicate<Card>() {
|
betterList = CardLists.filter(betterList, new Predicate<Card>() {
|
||||||
@Override
|
@Override
|
||||||
public boolean apply(final Card c) {
|
public boolean apply(final Card c) {
|
||||||
@@ -400,7 +385,7 @@ public class AttachAi extends SpellAbilityAi {
|
|||||||
// I know this isn't much better than Hardcoding, but some cards need it for now
|
// 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();
|
||||||
Card chosen = null;
|
Card chosen = null;
|
||||||
if ("Guilty Conscience".equals(sa.getSourceCard().getName())) {
|
if ("Guilty Conscience".equals(sa.getHostCard().getName())) {
|
||||||
List<Card> aiStuffies = CardLists.filter(list, new Predicate<Card>() {
|
List<Card> aiStuffies = CardLists.filter(list, new Predicate<Card>() {
|
||||||
@Override
|
@Override
|
||||||
public boolean apply(final Card c) {
|
public boolean apply(final Card c) {
|
||||||
@@ -626,12 +611,12 @@ public class AttachAi extends SpellAbilityAi {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(final Player ai, final SpellAbility sa, final boolean mandatory) {
|
protected boolean doTriggerAINoCost(final Player ai, final SpellAbility sa, final boolean mandatory) {
|
||||||
final Card card = sa.getSourceCard();
|
final Card card = sa.getHostCard();
|
||||||
// Check if there are any valid targets
|
// Check if there are any valid targets
|
||||||
List<ITargetable> targets = new ArrayList<ITargetable>();
|
List<GameObject> targets = new ArrayList<GameObject>();
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
if (tgt == null) {
|
if (tgt == null) {
|
||||||
targets = AbilityUtils.getDefinedObjects(sa.getSourceCard(), sa.getParam("Defined"), sa);
|
targets = AbilityUtils.getDefinedObjects(sa.getHostCard(), sa.getParam("Defined"), sa);
|
||||||
} else {
|
} else {
|
||||||
AttachAi.attachPreference(sa, tgt, mandatory);
|
AttachAi.attachPreference(sa, tgt, mandatory);
|
||||||
targets = sa.getTargets().getTargets();
|
targets = sa.getTargets().getTargets();
|
||||||
@@ -703,7 +688,7 @@ public class AttachAi extends SpellAbilityAi {
|
|||||||
* @return true, if successful
|
* @return true, if successful
|
||||||
*/
|
*/
|
||||||
private static boolean attachPreference(final SpellAbility sa, final TargetRestrictions tgt, final boolean mandatory) {
|
private static boolean attachPreference(final SpellAbility sa, final TargetRestrictions tgt, final boolean mandatory) {
|
||||||
ITargetable o;
|
GameObject o;
|
||||||
if (tgt.canTgtPlayer()) {
|
if (tgt.canTgtPlayer()) {
|
||||||
o = attachToPlayerAIPreferences(sa.getActivatingPlayer(), sa, mandatory);
|
o = attachToPlayerAIPreferences(sa.getActivatingPlayer(), sa, mandatory);
|
||||||
} else {
|
} else {
|
||||||
@@ -778,7 +763,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 HashMap<String, String> params = t.getMapParams();
|
final Map<String, String> params = t.getMapParams();
|
||||||
if ("Card.Self".equals(params.get("TargetsValid")) && "You".equals(params.get("ValidActivatingPlayer"))) {
|
if ("Card.Self".equals(params.get("TargetsValid")) && "You".equals(params.get("ValidActivatingPlayer"))) {
|
||||||
magnetList.add(target);
|
magnetList.add(target);
|
||||||
break;
|
break;
|
||||||
@@ -945,7 +930,7 @@ public class AttachAi extends SpellAbilityAi {
|
|||||||
*/
|
*/
|
||||||
private static Card attachToCardAIPreferences(final Player aiPlayer, final SpellAbility sa, final boolean mandatory) {
|
private static Card attachToCardAIPreferences(final Player aiPlayer, final SpellAbility sa, final boolean mandatory) {
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
final Card attachSource = sa.getSourceCard();
|
final Card attachSource = sa.getHostCard();
|
||||||
// TODO AttachSource is currently set for the Source of the Spell, but
|
// TODO AttachSource is currently set for the Source of the Spell, but
|
||||||
// at some point can support attaching a different card
|
// at some point can support attaching a different card
|
||||||
|
|
||||||
@@ -1171,6 +1156,8 @@ public class AttachAi extends SpellAbilityAi {
|
|||||||
if (card.hasKeyword("Shroud") || card.hasKeyword("Hexproof")) {
|
if (card.hasKeyword("Shroud") || card.hasKeyword("Hexproof")) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
} else if (keyword.equals("Defender")) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -1193,7 +1180,7 @@ public class AttachAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
if (keyword.endsWith("CARDNAME can't attack.") || keyword.equals("Defender")
|
if (keyword.endsWith("CARDNAME can't attack.") || keyword.equals("Defender")
|
||||||
|| keyword.endsWith("CARDNAME can't attack or block.")) {
|
|| keyword.endsWith("CARDNAME can't attack or block.")) {
|
||||||
if (!CombatUtil.canAttackNextTurn(card)) {
|
if (!CombatUtil.canAttackNextTurn(card) || card.getNetCombatDamage() < 1) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else if (keyword.endsWith("CARDNAME attacks each turn if able.")) {
|
} else if (keyword.endsWith("CARDNAME attacks each turn if able.")) {
|
||||||
@@ -1234,12 +1221,12 @@ public class AttachAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Card chooseSingleCard(Player ai, SpellAbility sa, Collection<Card> options, boolean isOptional) {
|
protected Card chooseSingleCard(Player ai, SpellAbility sa, Collection<Card> options, boolean isOptional, Player targetedPlayer) {
|
||||||
return attachToCardAIPreferences(ai, sa, true);
|
return attachToCardAIPreferences(ai, sa, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Player chooseSinglePlayer(Player ai, SpellAbility sa, List<Player> options) {
|
protected Player chooseSinglePlayer(Player ai, SpellAbility sa, Collection<Player> options) {
|
||||||
return attachToPlayerAIPreferences(ai, sa, true);
|
return attachToPlayerAIPreferences(ai, sa, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,17 +1,16 @@
|
|||||||
package forge.card.ability.ai;
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
import forge.ai.SpellAbilityAi;
|
||||||
|
import forge.game.card.Card;
|
||||||
|
import forge.game.card.CardLists;
|
||||||
|
import forge.game.card.CardPredicates;
|
||||||
|
import forge.game.player.Player;
|
||||||
|
import forge.game.spellability.SpellAbility;
|
||||||
|
import forge.game.zone.ZoneType;
|
||||||
|
import forge.util.MyRandom;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.apache.commons.lang.math.RandomUtils;
|
|
||||||
|
|
||||||
import forge.Card;
|
|
||||||
import forge.CardLists;
|
|
||||||
import forge.CardPredicates;
|
|
||||||
import forge.card.ability.SpellAbilityAi;
|
|
||||||
import forge.card.spellability.SpellAbility;
|
|
||||||
import forge.game.player.Player;
|
|
||||||
import forge.game.zone.ZoneType;
|
|
||||||
|
|
||||||
public class BalanceAi extends SpellAbilityAi {
|
public class BalanceAi extends SpellAbilityAi {
|
||||||
|
|
||||||
/* (non-Javadoc)
|
/* (non-Javadoc)
|
||||||
@@ -48,6 +47,6 @@ public class BalanceAi extends SpellAbilityAi {
|
|||||||
diff += 0.5 * (humHand.size() - compHand.size());
|
diff += 0.5 * (humHand.size() - compHand.size());
|
||||||
|
|
||||||
// Larger differential == more chance to actually cast this spell
|
// Larger differential == more chance to actually cast this spell
|
||||||
return diff > 2 && RandomUtils.nextInt(100) < diff*10;
|
return diff > 2 && MyRandom.getRandom().nextInt(100) < diff*10;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
|
||||||
|
import forge.ai.ComputerUtilCard;
|
||||||
|
import forge.ai.SpellAbilityAi;
|
||||||
|
import forge.game.Game;
|
||||||
|
import forge.game.card.Card;
|
||||||
|
import forge.game.card.CardLists;
|
||||||
|
import forge.game.phase.PhaseType;
|
||||||
|
import forge.game.player.Player;
|
||||||
|
import forge.game.spellability.SpellAbility;
|
||||||
|
import forge.game.spellability.TargetRestrictions;
|
||||||
|
import forge.game.zone.ZoneType;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class BecomesBlockedAi extends SpellAbilityAi {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
||||||
|
final Card source = sa.getHostCard();
|
||||||
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
|
final Game game = aiPlayer.getGame();
|
||||||
|
|
||||||
|
if (!game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)
|
||||||
|
|| !game.getPhaseHandler().getPlayerTurn().isOpponentOf(aiPlayer)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tgt != null) {
|
||||||
|
sa.resetTargets();
|
||||||
|
List<Card> list = game.getCardsIn(ZoneType.Battlefield);
|
||||||
|
list = CardLists.filterControlledBy(list, aiPlayer.getOpponents());
|
||||||
|
list = CardLists.getValidCards(list, tgt.getValidTgts(), source.getController(), source);
|
||||||
|
list = CardLists.getTargetableCards(list, sa);
|
||||||
|
list = CardLists.getNotKeyword(list, "Trample");
|
||||||
|
|
||||||
|
while (sa.getTargets().getNumTargeted() < tgt.getMaxTargets(source, sa)) {
|
||||||
|
Card choice = null;
|
||||||
|
|
||||||
|
if (list.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
choice = ComputerUtilCard.getBestCreatureAI(list);
|
||||||
|
|
||||||
|
if (choice == null) { // can't find anything left
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
list.remove(choice);
|
||||||
|
sa.getTargets().add(choice);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
|
||||||
|
|
||||||
|
// TODO - implement AI
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* (non-Javadoc)
|
||||||
|
* @see forge.card.abilityfactory.SpellAiLogic#doTriggerAINoCost(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility, boolean)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
||||||
|
boolean chance;
|
||||||
|
|
||||||
|
// TODO - implement AI
|
||||||
|
chance = false;
|
||||||
|
|
||||||
|
return chance;
|
||||||
|
}
|
||||||
|
}
|
||||||
58
forge-ai/src/main/java/forge/ai/ability/BondAi.java
Normal file
58
forge-ai/src/main/java/forge/ai/ability/BondAi.java
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
/*
|
||||||
|
* Forge: Play Magic: the Gathering.
|
||||||
|
* Copyright (C) 2011 Forge Team
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
import forge.ai.ComputerUtilCard;
|
||||||
|
import forge.ai.SpellAbilityAi;
|
||||||
|
import forge.game.card.Card;
|
||||||
|
import forge.game.player.Player;
|
||||||
|
import forge.game.spellability.SpellAbility;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* AbilityFactoryBond class.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @author Forge
|
||||||
|
* @version $Id: AbilityFactoryBond.java 15090 2012-04-07 12:50:31Z Max mtg $
|
||||||
|
*/
|
||||||
|
public final class BondAi extends SpellAbilityAi {
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* bondCanPlayAI.
|
||||||
|
* </p>
|
||||||
|
* @param sa
|
||||||
|
* a {@link forge.game.spellability.SpellAbility} object.
|
||||||
|
* @param af
|
||||||
|
* a {@link forge.game.ability.AbilityFactory} object.
|
||||||
|
*
|
||||||
|
* @return a boolean.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
||||||
|
return true;
|
||||||
|
} // end bondCanPlayAI()
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Card chooseSingleCard(Player ai, SpellAbility sa, Collection<Card> options, boolean isOptional, Player targetedPlayer) {
|
||||||
|
return ComputerUtilCard.getBestCreatureAI(options);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
package forge.card.ability.ai;
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
|
||||||
|
import forge.ai.SpellAbilityAi;
|
||||||
|
import forge.game.player.Player;
|
||||||
|
import forge.game.spellability.SpellAbility;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import forge.card.ability.SpellAbilityAi;
|
|
||||||
import forge.card.spellability.SpellAbility;
|
|
||||||
import forge.game.player.Player;
|
|
||||||
|
|
||||||
public class CanPlayAsDrawbackAi extends SpellAbilityAi {
|
public class CanPlayAsDrawbackAi extends SpellAbilityAi {
|
||||||
|
|
||||||
/* (non-Javadoc)
|
/* (non-Javadoc)
|
||||||
@@ -22,11 +22,11 @@ public class CanPlayAsDrawbackAi extends SpellAbilityAi {
|
|||||||
* copySpellTriggerAI.
|
* copySpellTriggerAI.
|
||||||
* </p>
|
* </p>
|
||||||
* @param sa
|
* @param sa
|
||||||
* a {@link forge.card.spellability.SpellAbility} object.
|
* a {@link forge.game.spellability.SpellAbility} object.
|
||||||
* @param mandatory
|
* @param mandatory
|
||||||
* a boolean.
|
* a boolean.
|
||||||
* @param af
|
* @param af
|
||||||
* a {@link forge.card.ability.AbilityFactory} object.
|
* a {@link forge.game.ability.AbilityFactory} object.
|
||||||
*
|
*
|
||||||
* @return a boolean.
|
* @return a boolean.
|
||||||
*/
|
*/
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
package forge.card.ability.ai;
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
|
||||||
import forge.card.ability.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.card.spellability.SpellAbility;
|
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
|
import forge.game.spellability.SpellAbility;
|
||||||
|
|
||||||
public class CannotPlayAi extends SpellAbilityAi {
|
public class CannotPlayAi extends SpellAbilityAi {
|
||||||
/* (non-Javadoc)
|
/* (non-Javadoc)
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
package forge.card.ability.ai;
|
package forge.ai.ability;
|
||||||
|
|
||||||
import forge.card.ability.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.card.spellability.SpellAbility;
|
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
|
import forge.game.spellability.SpellAbility;
|
||||||
|
|
||||||
public class ChangeTargetsAi extends SpellAbilityAi {
|
public class ChangeTargetsAi extends SpellAbilityAi {
|
||||||
|
|
||||||
1222
forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java
Normal file
1222
forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,22 +1,22 @@
|
|||||||
package forge.card.ability.ai;
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
import forge.ai.ComputerUtilCard;
|
||||||
|
import forge.ai.ComputerUtilCost;
|
||||||
|
import forge.ai.SpellAbilityAi;
|
||||||
|
import forge.game.ability.AbilityUtils;
|
||||||
|
import forge.game.card.Card;
|
||||||
|
import forge.game.card.CardLists;
|
||||||
|
import forge.game.cost.Cost;
|
||||||
|
import forge.game.phase.PhaseType;
|
||||||
|
import forge.game.player.Player;
|
||||||
|
import forge.game.spellability.SpellAbility;
|
||||||
|
import forge.game.spellability.TargetRestrictions;
|
||||||
|
import forge.game.zone.ZoneType;
|
||||||
|
import forge.util.MyRandom;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
|
|
||||||
import forge.Card;
|
|
||||||
import forge.CardLists;
|
|
||||||
import forge.card.ability.AbilityUtils;
|
|
||||||
import forge.card.ability.SpellAbilityAi;
|
|
||||||
import forge.card.cost.Cost;
|
|
||||||
import forge.card.spellability.SpellAbility;
|
|
||||||
import forge.card.spellability.TargetRestrictions;
|
|
||||||
import forge.game.ai.ComputerUtilCard;
|
|
||||||
import forge.game.ai.ComputerUtilCost;
|
|
||||||
import forge.game.phase.PhaseType;
|
|
||||||
import forge.game.player.Player;
|
|
||||||
import forge.game.zone.ZoneType;
|
|
||||||
import forge.util.MyRandom;
|
|
||||||
|
|
||||||
public class ChangeZoneAllAi extends SpellAbilityAi {
|
public class ChangeZoneAllAi extends SpellAbilityAi {
|
||||||
|
|
||||||
/* (non-Javadoc)
|
/* (non-Javadoc)
|
||||||
@@ -26,7 +26,7 @@ public class ChangeZoneAllAi extends SpellAbilityAi {
|
|||||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||||
// Change Zone All, can be any type moving from one zone to another
|
// Change Zone All, can be any type moving from one zone to another
|
||||||
final Cost abCost = sa.getPayCosts();
|
final Cost abCost = sa.getPayCosts();
|
||||||
final Card source = sa.getSourceCard();
|
final Card source = sa.getHostCard();
|
||||||
final ZoneType destination = ZoneType.smartValueOf(sa.getParam("Destination"));
|
final ZoneType destination = ZoneType.smartValueOf(sa.getParam("Destination"));
|
||||||
final ZoneType origin = ZoneType.listValueOf(sa.getParam("Origin")).get(0);
|
final ZoneType origin = ZoneType.listValueOf(sa.getParam("Origin")).get(0);
|
||||||
|
|
||||||
@@ -159,9 +159,9 @@ public class ChangeZoneAllAi extends SpellAbilityAi {
|
|||||||
* changeZoneAllPlayDrawbackAI.
|
* changeZoneAllPlayDrawbackAI.
|
||||||
* </p>
|
* </p>
|
||||||
* @param sa
|
* @param sa
|
||||||
* a {@link forge.card.spellability.SpellAbility} object.
|
* a {@link forge.game.spellability.SpellAbility} object.
|
||||||
* @param af
|
* @param af
|
||||||
* a {@link forge.card.ability.AbilityFactory} object.
|
* a {@link forge.game.ability.AbilityFactory} object.
|
||||||
*
|
*
|
||||||
* @return a boolean.
|
* @return a boolean.
|
||||||
*/
|
*/
|
||||||
@@ -1,17 +1,21 @@
|
|||||||
package forge.card.ability.ai;
|
package forge.ai.ability;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import forge.ai.AiController;
|
||||||
import java.util.List;
|
import forge.ai.AiPlayDecision;
|
||||||
import java.util.Random;
|
import forge.ai.PlayerControllerAi;
|
||||||
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.card.ability.SpellAbilityAi;
|
import forge.game.ability.effects.CharmEffect;
|
||||||
import forge.card.ability.effects.CharmEffect;
|
|
||||||
import forge.card.spellability.AbilitySub;
|
|
||||||
import forge.card.spellability.SpellAbility;
|
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
|
import forge.game.spellability.AbilitySub;
|
||||||
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.util.Aggregates;
|
import forge.util.Aggregates;
|
||||||
import forge.util.MyRandom;
|
import forge.util.MyRandom;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
public class CharmAi extends SpellAbilityAi {
|
public class CharmAi extends SpellAbilityAi {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -44,18 +48,18 @@ public class CharmAi extends SpellAbilityAi {
|
|||||||
return choices.subList(1, choices.size());
|
return choices.subList(1, choices.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AiController aic = ((PlayerControllerAi)ai.getController()).getAi();
|
||||||
for (int i = 0; i < num; i++) {
|
for (int i = 0; i < num; i++) {
|
||||||
AbilitySub thisPick = null;
|
AbilitySub thisPick = null;
|
||||||
for (SpellAbility sub : choices) {
|
for (SpellAbility sub : choices) {
|
||||||
sub.setActivatingPlayer(ai);
|
sub.setActivatingPlayer(ai);
|
||||||
if (!playNow && sub.canPlayAI()) {
|
if (!playNow && AiPlayDecision.WillPlay == aic.canPlaySa(sub)) {
|
||||||
thisPick = (AbilitySub) sub;
|
thisPick = (AbilitySub) sub;
|
||||||
choices.remove(sub);
|
choices.remove(sub);
|
||||||
playNow = true;
|
playNow = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if ((playNow || i < num - 1) && sub.doTrigger(false, ai)) {
|
if ((playNow || i < num - 1) && aic.doTrigger(sub, false)) {
|
||||||
thisPick = (AbilitySub) sub;
|
thisPick = (AbilitySub) sub;
|
||||||
choices.remove(sub);
|
choices.remove(sub);
|
||||||
break;
|
break;
|
||||||
@@ -70,7 +74,7 @@ public class CharmAi extends SpellAbilityAi {
|
|||||||
AbilitySub thisPick = null;
|
AbilitySub thisPick = null;
|
||||||
for (SpellAbility sub : choices) {
|
for (SpellAbility sub : choices) {
|
||||||
sub.setActivatingPlayer(ai);
|
sub.setActivatingPlayer(ai);
|
||||||
if (sub.doTrigger(true, ai)) {
|
if (aic.doTrigger(sub, true)) {
|
||||||
thisPick = (AbilitySub) sub;
|
thisPick = (AbilitySub) sub;
|
||||||
choices.remove(sub);
|
choices.remove(sub);
|
||||||
break;
|
break;
|
||||||
@@ -88,7 +92,7 @@ public class CharmAi extends SpellAbilityAi {
|
|||||||
* @see forge.card.ability.SpellAbilityAi#chooseSinglePlayer(forge.game.player.Player, forge.card.spellability.SpellAbility, java.util.List)
|
* @see forge.card.ability.SpellAbilityAi#chooseSinglePlayer(forge.game.player.Player, forge.card.spellability.SpellAbility, java.util.List)
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public Player chooseSinglePlayer(Player ai, SpellAbility sa, List<Player> opponents) {
|
public Player chooseSinglePlayer(Player ai, SpellAbility sa, Collection<Player> opponents) {
|
||||||
return Aggregates.random(opponents);
|
return Aggregates.random(opponents);
|
||||||
}
|
}
|
||||||
|
|
||||||
203
forge-ai/src/main/java/forge/ai/ability/ChooseCardAi.java
Normal file
203
forge-ai/src/main/java/forge/ai/ability/ChooseCardAi.java
Normal file
@@ -0,0 +1,203 @@
|
|||||||
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
import com.google.common.base.Predicate;
|
||||||
|
import forge.ai.ComputerUtilCard;
|
||||||
|
import forge.ai.ComputerUtilCombat;
|
||||||
|
import forge.ai.SpellAbilityAi;
|
||||||
|
import forge.game.Game;
|
||||||
|
import forge.game.card.Card;
|
||||||
|
import forge.game.card.CardLists;
|
||||||
|
import forge.game.card.CardPredicates.Presets;
|
||||||
|
import forge.game.card.CounterType;
|
||||||
|
import forge.game.combat.Combat;
|
||||||
|
import forge.game.phase.PhaseType;
|
||||||
|
import forge.game.player.Player;
|
||||||
|
import forge.game.spellability.SpellAbility;
|
||||||
|
import forge.game.spellability.TargetRestrictions;
|
||||||
|
import forge.game.zone.ZoneType;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class ChooseCardAi extends SpellAbilityAi {
|
||||||
|
|
||||||
|
/* (non-Javadoc)
|
||||||
|
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected boolean canPlayAI(final Player ai, SpellAbility sa) {
|
||||||
|
final Card host = sa.getHostCard();
|
||||||
|
final Game game = ai.getGame();
|
||||||
|
|
||||||
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
|
if (tgt != null) {
|
||||||
|
sa.resetTargets();
|
||||||
|
if (sa.canTarget(ai.getOpponent())) {
|
||||||
|
sa.getTargets().add(ai.getOpponent());
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (sa.hasParam("AILogic")) {
|
||||||
|
ZoneType choiceZone = ZoneType.Battlefield;
|
||||||
|
String logic = sa.getParam("AILogic");
|
||||||
|
if (sa.hasParam("ChoiceZone")) {
|
||||||
|
choiceZone = ZoneType.smartValueOf(sa.getParam("ChoiceZone"));
|
||||||
|
}
|
||||||
|
List<Card> choices = ai.getGame().getCardsIn(choiceZone);
|
||||||
|
if (sa.hasParam("Choices")) {
|
||||||
|
choices = CardLists.getValidCards(choices, sa.getParam("Choices"), host.getController(), host);
|
||||||
|
}
|
||||||
|
if (sa.hasParam("TargetControls")) {
|
||||||
|
choices = CardLists.filterControlledBy(choices, ai.getOpponent());
|
||||||
|
}
|
||||||
|
if (logic.equals("AtLeast1") || logic.equals("OppPreferred")) {
|
||||||
|
if (choices.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else if (logic.equals("AtLeast2") || logic.equals("BestBlocker")) {
|
||||||
|
if (choices.size() < 2) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else if (logic.equals("Clone")) {
|
||||||
|
choices = CardLists.getValidCards(choices, "Permanent.YouDontCtrl,Permanent.nonLegendary", host.getController(), host);
|
||||||
|
if (choices.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else if (logic.equals("Never")) {
|
||||||
|
return false;
|
||||||
|
} else if (logic.equals("NeedsPrevention")) {
|
||||||
|
if (!game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
final Combat combat = game.getCombat();
|
||||||
|
choices = CardLists.filter(choices, new Predicate<Card>() {
|
||||||
|
@Override
|
||||||
|
public boolean apply(final Card c) {
|
||||||
|
if (!combat.isAttacking(c, ai) || !combat.isUnblocked(c)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
int ref = host.getName().equals("Forcefield") ? 1 : 0;
|
||||||
|
return ComputerUtilCombat.damageIfUnblocked(c, ai, combat) > ref;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (choices.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else if (logic.equals("Ashiok")) {
|
||||||
|
final int loyalty = host.getCounters(CounterType.LOYALTY) - 1;
|
||||||
|
for (int i = loyalty; i >= 0; i--) {
|
||||||
|
host.setSVar("ChosenX", "Number$" + i);
|
||||||
|
choices = ai.getGame().getCardsIn(choiceZone);
|
||||||
|
choices = CardLists.getValidCards(choices, sa.getParam("Choices"), host.getController(), host);
|
||||||
|
if (!choices.isEmpty()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (choices.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
||||||
|
return canPlayAI(ai, sa);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* (non-Javadoc)
|
||||||
|
* @see forge.card.ability.SpellAbilityAi#chooseSingleCard(forge.card.spellability.SpellAbility, java.util.List, boolean)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Card chooseSingleCard(final Player ai, SpellAbility sa, Collection<Card> options, boolean isOptional, Player targetedPlayer) {
|
||||||
|
final Card host = sa.getHostCard();
|
||||||
|
final String logic = sa.getParam("AILogic");
|
||||||
|
Card choice = null;
|
||||||
|
if (logic == null) {
|
||||||
|
// Base Logic is choose "best"
|
||||||
|
choice = ComputerUtilCard.getBestAI(options);
|
||||||
|
} else if ("WorstCard".equals(logic)) {
|
||||||
|
choice = ComputerUtilCard.getWorstAI(options);
|
||||||
|
} else if (logic.equals("BestBlocker")) {
|
||||||
|
if (!CardLists.filter(options, Presets.UNTAPPED).isEmpty()) {
|
||||||
|
options = CardLists.filter(options, Presets.UNTAPPED);
|
||||||
|
}
|
||||||
|
choice = ComputerUtilCard.getBestCreatureAI(options);
|
||||||
|
} else if (logic.equals("Clone")) {
|
||||||
|
if (!CardLists.getValidCards(options, "Permanent.YouDontCtrl,Permanent.nonLegendary", host.getController(), host).isEmpty()) {
|
||||||
|
options = CardLists.getValidCards(options, "Permanent.YouDontCtrl,Permanent.nonLegendary", host.getController(), host);
|
||||||
|
}
|
||||||
|
choice = ComputerUtilCard.getBestAI(options);
|
||||||
|
} else if (logic.equals("Untap")) {
|
||||||
|
if (!CardLists.getValidCards(options, "Permanent.YouCtrl,Permanent.tapped", host.getController(), host).isEmpty()) {
|
||||||
|
options = CardLists.getValidCards(options, "Permanent.YouCtrl,Permanent.tapped", host.getController(), host);
|
||||||
|
}
|
||||||
|
choice = ComputerUtilCard.getBestAI(options);
|
||||||
|
} else if (logic.equals("NeedsPrevention")) {
|
||||||
|
final Game game = ai.getGame();
|
||||||
|
final Combat combat = game.getCombat();
|
||||||
|
List<Card> better = CardLists.filter(options, new Predicate<Card>() {
|
||||||
|
@Override
|
||||||
|
public boolean apply(final Card c) {
|
||||||
|
if (combat == null || !combat.isAttacking(c, ai) || !combat.isUnblocked(c)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
int ref = host.getName().equals("Forcefield") ? 1 : 0;
|
||||||
|
return ComputerUtilCombat.damageIfUnblocked(c, ai, combat) > ref;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (!better.isEmpty()) {
|
||||||
|
choice = ComputerUtilCard.getBestAI(better);
|
||||||
|
} else {
|
||||||
|
choice = ComputerUtilCard.getBestAI(options);
|
||||||
|
}
|
||||||
|
} else if ("OppPreferred".equals(logic)) {
|
||||||
|
List<Card> oppControlled = CardLists.filterControlledBy(options, ai.getOpponents());
|
||||||
|
if (!oppControlled.isEmpty()) {
|
||||||
|
choice = ComputerUtilCard.getBestAI(oppControlled);
|
||||||
|
} else {
|
||||||
|
List<Card> aiControlled = CardLists.filterControlledBy(options, ai);
|
||||||
|
choice = ComputerUtilCard.getWorstAI(aiControlled);
|
||||||
|
}
|
||||||
|
} else if ("LowestCMCCreature".equals(logic)) {
|
||||||
|
List<Card> creats = CardLists.filter(options, Presets.CREATURES);
|
||||||
|
creats = CardLists.filterToughness(creats, 1);
|
||||||
|
if (creats.isEmpty()) {
|
||||||
|
choice = ComputerUtilCard.getWorstAI(options);
|
||||||
|
} else {
|
||||||
|
CardLists.sortByCmcDesc(creats);
|
||||||
|
Collections.reverse(creats);
|
||||||
|
choice = creats.get(0);
|
||||||
|
}
|
||||||
|
} else if ("TangleWire".equals(logic)) {
|
||||||
|
List<Card> betterList = CardLists.filter(options, new Predicate<Card>() {
|
||||||
|
@Override
|
||||||
|
public boolean apply(final Card c) {
|
||||||
|
if (c.isCreature()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (SpellAbility sa : c.getAllSpellAbilities()) {
|
||||||
|
if (sa.getPayCosts() != null && sa.getPayCosts().hasTapCost()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
System.out.println("Tangle Wire" + options + " - " + betterList);
|
||||||
|
if (!betterList.isEmpty()) {
|
||||||
|
choice = betterList.get(0);
|
||||||
|
} else {
|
||||||
|
choice = ComputerUtilCard.getWorstPermanentAI(options, false, false, false, false);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
choice = ComputerUtilCard.getBestAI(options);
|
||||||
|
}
|
||||||
|
return choice;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,19 +1,19 @@
|
|||||||
package forge.card.ability.ai;
|
package forge.ai.ability;
|
||||||
|
|
||||||
import forge.Card;
|
import forge.ai.ComputerUtil;
|
||||||
import forge.card.ability.SpellAbilityAi;
|
import forge.ai.ComputerUtilMana;
|
||||||
import forge.card.spellability.SpellAbility;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.card.spellability.TargetRestrictions;
|
import forge.game.card.Card;
|
||||||
import forge.game.ai.ComputerUtil;
|
|
||||||
import forge.game.ai.ComputerUtilMana;
|
|
||||||
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.TargetRestrictions;
|
||||||
|
|
||||||
public class ChooseCardNameAi extends SpellAbilityAi {
|
public class ChooseCardNameAi extends SpellAbilityAi {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||||
Card source = sa.getSourceCard();
|
Card source = sa.getHostCard();
|
||||||
if (sa.hasParam("AILogic")) {
|
if (sa.hasParam("AILogic")) {
|
||||||
// Don't tap creatures that may be able to block
|
// Don't tap creatures that may be able to block
|
||||||
if (ComputerUtil.waitForBlocking(sa)) {
|
if (ComputerUtil.waitForBlocking(sa)) {
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
package forge.card.ability.ai;
|
package forge.ai.ability;
|
||||||
|
|
||||||
import forge.card.ability.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.card.spellability.SpellAbility;
|
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.util.MyRandom;
|
import forge.util.MyRandom;
|
||||||
|
|
||||||
public class ChooseColorAi extends SpellAbilityAi {
|
public class ChooseColorAi extends SpellAbilityAi {
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
import forge.ai.ComputerUtilCost;
|
||||||
|
import forge.ai.SpellAbilityAi;
|
||||||
|
import forge.game.cost.Cost;
|
||||||
|
import forge.game.player.Player;
|
||||||
|
import forge.game.spellability.SpellAbility;
|
||||||
|
import forge.util.Aggregates;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import com.google.common.base.Predicate;
|
||||||
|
import com.google.common.collect.Iterables;
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO: Write javadoc for this type.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class ChooseGenericEffectAi extends SpellAbilityAi {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* (non-Javadoc)
|
||||||
|
* @see forge.card.abilityfactory.SpellAiLogic#chkAIDrawback(java.util.Map, forge.card.spellability.SpellAbility, forge.game.player.Player)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
|
||||||
|
return canPlayAI(aiPlayer, sa);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SpellAbility chooseSingleSpellAbility(Player player, SpellAbility sa, List<SpellAbility> spells) {
|
||||||
|
final String logic = sa.getParam("AILogic");
|
||||||
|
if ("Random".equals(logic)) {
|
||||||
|
return Aggregates.random(spells);
|
||||||
|
} else if ("Phasing".equals(logic)) { // Teferi's Realm : keep aggressive
|
||||||
|
List<SpellAbility> filtered = Lists.newArrayList(Iterables.filter(spells, new Predicate<SpellAbility>() {
|
||||||
|
@Override
|
||||||
|
public boolean apply(final SpellAbility sp) {
|
||||||
|
return !sp.getDescription().contains("Creature") && !sp.getDescription().contains("Land");
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
return Aggregates.random(filtered);
|
||||||
|
} else if ("PayUnlessCost".equals(logic)) {
|
||||||
|
for (final SpellAbility sp : spells) {
|
||||||
|
String unlessCost = sp.getParam("UnlessCost");
|
||||||
|
sp.setActivatingPlayer(sa.getActivatingPlayer());
|
||||||
|
Cost unless = new Cost(unlessCost, false);
|
||||||
|
SpellAbility paycost = new SpellAbility.EmptySa(sa.getHostCard(), player);
|
||||||
|
paycost.setPayCosts(unless);
|
||||||
|
if (ComputerUtilCost.willPayUnlessCost(sp, player, unless, false, Lists.newArrayList(player))
|
||||||
|
&& ComputerUtilCost.canPayCost(paycost, player)) {
|
||||||
|
return sp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return spells.get(0);
|
||||||
|
} else {
|
||||||
|
return spells.get(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
34
forge-ai/src/main/java/forge/ai/ability/ChooseNumberAi.java
Normal file
34
forge-ai/src/main/java/forge/ai/ability/ChooseNumberAi.java
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
import forge.ai.SpellAbilityAi;
|
||||||
|
import forge.game.player.Player;
|
||||||
|
import forge.game.spellability.SpellAbility;
|
||||||
|
import forge.game.spellability.TargetRestrictions;
|
||||||
|
import forge.util.MyRandom;
|
||||||
|
|
||||||
|
public class ChooseNumberAi extends SpellAbilityAi {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
||||||
|
if (!sa.hasParam("AILogic")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
|
if (tgt != null) {
|
||||||
|
sa.resetTargets();
|
||||||
|
if (sa.canTarget(aiPlayer.getOpponent())) {
|
||||||
|
sa.getTargets().add(aiPlayer.getOpponent());
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
boolean chance = MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
|
||||||
|
return chance;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
|
return mandatory || canPlayAI(ai, sa);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
87
forge-ai/src/main/java/forge/ai/ability/ChoosePlayerAi.java
Normal file
87
forge-ai/src/main/java/forge/ai/ability/ChoosePlayerAi.java
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
import com.google.common.collect.Iterables;
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
import forge.ai.ComputerUtil;
|
||||||
|
import forge.ai.SpellAbilityAi;
|
||||||
|
import forge.game.player.Player;
|
||||||
|
import forge.game.spellability.SpellAbility;
|
||||||
|
import forge.game.zone.ZoneType;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class ChoosePlayerAi extends SpellAbilityAi {
|
||||||
|
|
||||||
|
/* (non-Javadoc)
|
||||||
|
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
||||||
|
return canPlayAI(ai, sa);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
|
return canPlayAI(ai, sa);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* (non-Javadoc)
|
||||||
|
* @see forge.card.ability.SpellAbilityAi#chooseSinglePlayer(forge.game.player.Player, forge.card.spellability.SpellAbility, java.util.List)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Player chooseSinglePlayer(Player ai, SpellAbility sa, Collection<Player> choices) {
|
||||||
|
Player chosen = null;
|
||||||
|
if ("Curse".equals(sa.getParam("AILogic"))) {
|
||||||
|
for (Player pc : choices) {
|
||||||
|
if (pc.isOpponentOf(ai)) {
|
||||||
|
chosen = pc;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (chosen == null) {
|
||||||
|
chosen = Iterables.getFirst(choices, null);
|
||||||
|
System.out.println("No good curse choices. Picking first available: " + chosen);
|
||||||
|
}
|
||||||
|
} else if ("Pump".equals(sa.getParam("AILogic"))) {
|
||||||
|
chosen = choices.contains(ai) ? ai : Iterables.getFirst(choices, null);
|
||||||
|
} else if ("BestAllyBoardPosition".equals(sa.getParam("AILogic"))) {
|
||||||
|
List<Player> prefChoices = Lists.newArrayList(choices);
|
||||||
|
prefChoices.removeAll(ai.getOpponents());
|
||||||
|
if (!prefChoices.isEmpty()) {
|
||||||
|
chosen = ComputerUtil.evaluateBoardPosition(prefChoices);
|
||||||
|
}
|
||||||
|
if (chosen == null) {
|
||||||
|
chosen = Iterables.getFirst(choices, null);
|
||||||
|
System.out.println("No good curse choices. Picking first available: " + chosen);
|
||||||
|
}
|
||||||
|
} else if ("MostCardsInHand".equals(sa.getParam("AILogic"))) {
|
||||||
|
int cardsInHand = 0;
|
||||||
|
for (final Player p : choices) {
|
||||||
|
int hand = p.getCardsIn(ZoneType.Hand).size();
|
||||||
|
if (hand >= cardsInHand) {
|
||||||
|
chosen = p;
|
||||||
|
cardsInHand = hand;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if ("LeastCreatures".equals(sa.getParam("AILogic"))) {
|
||||||
|
int creats = 50;
|
||||||
|
for (final Player p : choices) {
|
||||||
|
int curr = p.getCreaturesInPlay().size();
|
||||||
|
if (curr <= creats) {
|
||||||
|
chosen = p;
|
||||||
|
creats = curr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
System.out.println("Default player choice logic.");
|
||||||
|
chosen = choices.contains(ai) ? ai : Iterables.getFirst(choices, null);
|
||||||
|
}
|
||||||
|
return chosen;
|
||||||
|
}
|
||||||
|
}
|
||||||
186
forge-ai/src/main/java/forge/ai/ability/ChooseSourceAi.java
Normal file
186
forge-ai/src/main/java/forge/ai/ability/ChooseSourceAi.java
Normal file
@@ -0,0 +1,186 @@
|
|||||||
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
import com.google.common.base.Predicate;
|
||||||
|
import forge.ai.ComputerUtilCard;
|
||||||
|
import forge.ai.ComputerUtilCombat;
|
||||||
|
import forge.ai.ComputerUtilCost;
|
||||||
|
import forge.ai.SpellAbilityAi;
|
||||||
|
import forge.game.Game;
|
||||||
|
import forge.game.GameObject;
|
||||||
|
import forge.game.ability.AbilityUtils;
|
||||||
|
import forge.game.ability.ApiType;
|
||||||
|
import forge.game.card.Card;
|
||||||
|
import forge.game.card.CardLists;
|
||||||
|
import forge.game.combat.Combat;
|
||||||
|
import forge.game.cost.Cost;
|
||||||
|
import forge.game.phase.PhaseType;
|
||||||
|
import forge.game.player.Player;
|
||||||
|
import forge.game.spellability.SpellAbility;
|
||||||
|
import forge.game.spellability.SpellAbilityStackInstance;
|
||||||
|
import forge.game.spellability.TargetRestrictions;
|
||||||
|
import forge.game.zone.ZoneType;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class ChooseSourceAi extends SpellAbilityAi {
|
||||||
|
|
||||||
|
/* (non-Javadoc)
|
||||||
|
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected boolean canPlayAI(final Player ai, SpellAbility sa) {
|
||||||
|
// TODO: AI Support! Currently this is copied from AF ChooseCard.
|
||||||
|
// When implementing AI, I believe AI also needs to be made aware of the damage sources chosen
|
||||||
|
// to be prevented (e.g. so the AI doesn't attack with a creature that will not deal any damage
|
||||||
|
// to the player because a CoP was pre-activated on it - unless, of course, there's another
|
||||||
|
// possible reason to attack with that creature).
|
||||||
|
final Card host = sa.getHostCard();
|
||||||
|
final Cost abCost = sa.getPayCosts();
|
||||||
|
final Card source = sa.getHostCard();
|
||||||
|
|
||||||
|
if (abCost != null) {
|
||||||
|
// AI currently disabled for these costs
|
||||||
|
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, null)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ComputerUtilCost.checkDiscardCost(ai, abCost, source)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ComputerUtilCost.checkSacrificeCost(ai, abCost, source)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ComputerUtilCost.checkRemoveCounterCost(abCost, source)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
|
if (tgt != null) {
|
||||||
|
sa.resetTargets();
|
||||||
|
if (sa.canTarget(ai.getOpponent())) {
|
||||||
|
sa.getTargets().add(ai.getOpponent());
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (sa.hasParam("AILogic")) {
|
||||||
|
final Game game = ai.getGame();
|
||||||
|
if (sa.getParam("AILogic").equals("NeedsPrevention")) {
|
||||||
|
if (!game.getStack().isEmpty()) {
|
||||||
|
final SpellAbility topStack = game.getStack().peekAbility();
|
||||||
|
if (sa.hasParam("Choices") && !topStack.getHostCard().isValid(sa.getParam("Choices"), ai, source)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
final ApiType threatApi = topStack.getApi();
|
||||||
|
if (threatApi != ApiType.DealDamage && threatApi != ApiType.DamageAll) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
final Card threatSource = topStack.getHostCard();
|
||||||
|
List<? extends GameObject> objects = getTargets(topStack);
|
||||||
|
if (!topStack.usesTargeting() && topStack.hasParam("ValidPlayers") && !topStack.hasParam("Defined")) {
|
||||||
|
objects = AbilityUtils.getDefinedPlayers(threatSource, topStack.getParam("ValidPlayers"), topStack);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!objects.contains(ai) || topStack.hasParam("NoPrevention")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
int dmg = AbilityUtils.calculateAmount(threatSource, topStack.getParam("NumDmg"), topStack);
|
||||||
|
if (ComputerUtilCombat.predictDamageTo(ai, dmg, threatSource, false) <= 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (game.getPhaseHandler().getPhase() != PhaseType.COMBAT_DECLARE_BLOCKERS) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
List<Card> choices = game.getCardsIn(ZoneType.Battlefield);
|
||||||
|
if (sa.hasParam("Choices")) {
|
||||||
|
choices = CardLists.getValidCards(choices, sa.getParam("Choices"), host.getController(), host);
|
||||||
|
}
|
||||||
|
final Combat combat = game.getCombat();
|
||||||
|
choices = CardLists.filter(choices, new Predicate<Card>() {
|
||||||
|
@Override
|
||||||
|
public boolean apply(final Card c) {
|
||||||
|
if (combat == null || !combat.isAttacking(c, ai) || !combat.isUnblocked(c)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return ComputerUtilCombat.damageIfUnblocked(c, ai, combat) > 0;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (choices.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Card chooseSingleCard(final Player aiChoser, SpellAbility sa, Collection<Card> options, boolean isOptional, Player targetedPlayer) {
|
||||||
|
if ("NeedsPrevention".equals(sa.getParam("AILogic"))) {
|
||||||
|
final Player ai = sa.getActivatingPlayer();
|
||||||
|
final Game game = ai.getGame();
|
||||||
|
if (!game.getStack().isEmpty()) {
|
||||||
|
Card choseCard = chooseCardOnStack(sa, ai, game);
|
||||||
|
if (choseCard != null) {
|
||||||
|
return choseCard;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final Combat combat = game.getCombat();
|
||||||
|
|
||||||
|
List<Card> permanentSources = CardLists.filter(options, new Predicate<Card>() {
|
||||||
|
@Override
|
||||||
|
public boolean apply(final Card c) {
|
||||||
|
if (c == null || c.getZone() == null || c.getZone().getZoneType() != ZoneType.Battlefield
|
||||||
|
|| combat == null || !combat.isAttacking(c, ai) || !combat.isUnblocked(c)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return ComputerUtilCombat.damageIfUnblocked(c, ai, combat) > 0;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return ComputerUtilCard.getBestCreatureAI(permanentSources);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
return ComputerUtilCard.getBestAI(options);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Card chooseCardOnStack(SpellAbility sa, Player ai, Game game) {
|
||||||
|
for (SpellAbilityStackInstance si : game.getStack()) {
|
||||||
|
final Card source = si.getSourceCard();
|
||||||
|
final SpellAbility abilityOnStack = si.getSpellAbility();
|
||||||
|
|
||||||
|
if (sa.hasParam("Choices") && !abilityOnStack.getHostCard().isValid(sa.getParam("Choices"), ai, sa.getHostCard())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
final ApiType threatApi = abilityOnStack.getApi();
|
||||||
|
if (threatApi != ApiType.DealDamage && threatApi != ApiType.DamageAll) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<? extends GameObject> objects = getTargets(abilityOnStack);
|
||||||
|
|
||||||
|
if (!abilityOnStack.usesTargeting() && !abilityOnStack.hasParam("Defined") && abilityOnStack.hasParam("ValidPlayers"))
|
||||||
|
objects = AbilityUtils.getDefinedPlayers(source, abilityOnStack.getParam("ValidPlayers"), abilityOnStack);
|
||||||
|
|
||||||
|
if (!objects.contains(ai) || abilityOnStack.hasParam("NoPrevention")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
int dmg = AbilityUtils.calculateAmount(source, abilityOnStack.getParam("NumDmg"), abilityOnStack);
|
||||||
|
if (ComputerUtilCombat.predictDamageTo(ai, dmg, source, false) <= 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
return source;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
package forge.card.ability.ai;
|
package forge.ai.ability;
|
||||||
|
|
||||||
import forge.card.ability.AbilityUtils;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.card.ability.SpellAbilityAi;
|
import forge.game.ability.AbilityUtils;
|
||||||
import forge.card.spellability.SpellAbility;
|
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
|
import forge.game.spellability.SpellAbility;
|
||||||
|
|
||||||
public class ChooseTypeAi extends SpellAbilityAi {
|
public class ChooseTypeAi extends SpellAbilityAi {
|
||||||
@Override
|
@Override
|
||||||
@@ -21,7 +21,7 @@ public class ChooseTypeAi extends SpellAbilityAi {
|
|||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
sa.getTargets().add(ai);
|
sa.getTargets().add(ai);
|
||||||
} else {
|
} else {
|
||||||
for (final Player p : AbilityUtils.getDefinedPlayers(sa.getSourceCard(), sa.getParam("Defined"), sa)) {
|
for (final Player p : AbilityUtils.getDefinedPlayers(sa.getHostCard(), sa.getParam("Defined"), sa)) {
|
||||||
if (p.isOpponentOf(ai) && !mandatory) {
|
if (p.isOpponentOf(ai) && !mandatory) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
package forge.card.ability.ai;
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
|
||||||
import forge.card.ability.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.card.spellability.SpellAbility;
|
|
||||||
import forge.card.spellability.TargetRestrictions;
|
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
|
import forge.game.spellability.SpellAbility;
|
||||||
|
import forge.game.spellability.TargetRestrictions;
|
||||||
|
|
||||||
public class ClashAi extends SpellAbilityAi {
|
public class ClashAi extends SpellAbilityAi {
|
||||||
|
|
||||||
@@ -1,24 +1,24 @@
|
|||||||
package forge.card.ability.ai;
|
package forge.ai.ability;
|
||||||
|
|
||||||
import java.util.List;
|
import forge.ai.SpellAbilityAi;
|
||||||
|
|
||||||
import forge.Card;
|
|
||||||
import forge.card.ability.AbilityUtils;
|
|
||||||
import forge.card.ability.SpellAbilityAi;
|
|
||||||
import forge.card.spellability.SpellAbility;
|
|
||||||
import forge.card.spellability.TargetRestrictions;
|
|
||||||
import forge.game.Game;
|
import forge.game.Game;
|
||||||
|
import forge.game.ability.AbilityUtils;
|
||||||
|
import forge.game.card.Card;
|
||||||
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.player.PlayerActionConfirmMode;
|
||||||
|
import forge.game.spellability.SpellAbility;
|
||||||
|
import forge.game.spellability.TargetRestrictions;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
public class CloneAi extends SpellAbilityAi {
|
public class CloneAi extends SpellAbilityAi {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
final Card source = sa.getSourceCard();
|
final Card source = sa.getHostCard();
|
||||||
final Game game = source.getGame();
|
final Game game = source.getGame();
|
||||||
|
|
||||||
boolean useAbility = true;
|
boolean useAbility = true;
|
||||||
@@ -129,9 +129,9 @@ public class CloneAi extends SpellAbilityAi {
|
|||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param af
|
* @param af
|
||||||
* a {@link forge.card.ability.AbilityFactory} object.
|
* a {@link forge.game.ability.AbilityFactory} object.
|
||||||
* @param sa
|
* @param sa
|
||||||
* a {@link forge.card.spellability.SpellAbility} object.
|
* a {@link forge.game.spellability.SpellAbility} object.
|
||||||
* @return a boolean.
|
* @return a boolean.
|
||||||
*/
|
*/
|
||||||
private boolean cloneTgtAI(final SpellAbility sa) {
|
private boolean cloneTgtAI(final SpellAbility sa) {
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
import com.google.common.base.Predicate;
|
||||||
|
import forge.ai.ComputerUtilCard;
|
||||||
|
import forge.ai.SpellAbilityAi;
|
||||||
|
import forge.game.ability.AbilityUtils;
|
||||||
|
import forge.game.card.Card;
|
||||||
|
import forge.game.card.CardLists;
|
||||||
|
import forge.game.player.Player;
|
||||||
|
import forge.game.spellability.SpellAbility;
|
||||||
|
import forge.game.spellability.TargetRestrictions;
|
||||||
|
import forge.game.zone.ZoneType;
|
||||||
|
import forge.util.MyRandom;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class ControlExchangeAi extends SpellAbilityAi {
|
||||||
|
|
||||||
|
/* (non-Javadoc)
|
||||||
|
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected boolean canPlayAI(Player ai, final SpellAbility sa) {
|
||||||
|
Card object1 = null;
|
||||||
|
Card object2 = null;
|
||||||
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
|
sa.resetTargets();
|
||||||
|
|
||||||
|
List<Card> list =
|
||||||
|
CardLists.getValidCards(ai.getOpponent().getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), ai, sa.getHostCard());
|
||||||
|
// AI won't try to grab cards that are filtered out of AI decks on
|
||||||
|
// purpose
|
||||||
|
list = CardLists.filter(list, new Predicate<Card>() {
|
||||||
|
@Override
|
||||||
|
public boolean apply(final Card c) {
|
||||||
|
final Map<String, String> vars = c.getSVars();
|
||||||
|
return !vars.containsKey("RemAIDeck") && c.canBeTargetedBy(sa);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
object1 = ComputerUtilCard.getBestAI(list);
|
||||||
|
if (sa.hasParam("Defined")) {
|
||||||
|
object2 = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("Defined"), sa).get(0);
|
||||||
|
} else if (tgt.getMinTargets(sa.getHostCard(), sa) > 1) {
|
||||||
|
List<Card> list2 = ai.getCardsIn(ZoneType.Battlefield);
|
||||||
|
list2 = CardLists.getValidCards(list2, tgt.getValidTgts(), ai, sa.getHostCard());
|
||||||
|
object2 = ComputerUtilCard.getWorstAI(list2);
|
||||||
|
sa.getTargets().add(object2);
|
||||||
|
}
|
||||||
|
if (object1 == null || object2 == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (ComputerUtilCard.evaluateCreature(object1) > ComputerUtilCard.evaluateCreature(object2) + 40) {
|
||||||
|
sa.getTargets().add(object1);
|
||||||
|
return MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* (non-Javadoc)
|
||||||
|
* @see forge.card.abilityfactory.SpellAiLogic#doTriggerAINoCost(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility, boolean)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
||||||
|
TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
|
if (tgt == null) {
|
||||||
|
if (mandatory) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (mandatory) {
|
||||||
|
List<Card> list2 = aiPlayer.getGame().getCardsIn(ZoneType.Battlefield);
|
||||||
|
list2 = CardLists.getValidCards(list2, tgt.getValidTgts(), aiPlayer, sa.getHostCard());
|
||||||
|
while (!list2.isEmpty()) {
|
||||||
|
Card best = ComputerUtilCard.getBestAI(list2);
|
||||||
|
if (sa.canTarget(best)) {
|
||||||
|
sa.getTargets().add(best);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
list2.remove(best);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
return canPlayAI(aiPlayer, sa);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,26 +15,30 @@
|
|||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
package forge.card.ability.ai;
|
package forge.ai.ability;
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import com.google.common.base.Predicate;
|
import com.google.common.base.Predicate;
|
||||||
|
import com.google.common.collect.Iterables;
|
||||||
|
|
||||||
import forge.Card;
|
import forge.ai.ComputerUtilCard;
|
||||||
import forge.CardLists;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.card.ability.AbilityUtils;
|
|
||||||
import forge.card.ability.SpellAbilityAi;
|
|
||||||
import forge.card.spellability.SpellAbility;
|
|
||||||
import forge.card.spellability.TargetRestrictions;
|
|
||||||
import forge.game.Game;
|
import forge.game.Game;
|
||||||
import forge.game.ai.ComputerUtilCard;
|
import forge.game.ability.AbilityUtils;
|
||||||
|
import forge.game.card.Card;
|
||||||
|
import forge.game.card.CardLists;
|
||||||
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;
|
||||||
|
import forge.game.spellability.SpellAbility;
|
||||||
|
import forge.game.spellability.TargetRestrictions;
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
|
import forge.util.Aggregates;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
|
||||||
//AB:GainControl|ValidTgts$Creature|TgtPrompt$Select target legendary creature|LoseControl$Untap,LoseControl|SpellDescription$Gain control of target xxxxxxx
|
//AB:GainControl|ValidTgts$Creature|TgtPrompt$Select target legendary creature|LoseControl$Untap,LoseControl|SpellDescription$Gain control of target xxxxxxx
|
||||||
@@ -85,13 +89,22 @@ public class ControlGainAi extends SpellAbilityAi {
|
|||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
|
if (sa.hasParam("TargetingPlayer")) {
|
||||||
|
Player targetingPlayer = AbilityUtils.getDefinedPlayers(sa.getHostCard(), sa.getParam("TargetingPlayer"), sa).get(0);
|
||||||
|
sa.setTargetingPlayer(targetingPlayer);
|
||||||
|
return targetingPlayer.getController().chooseTargetsFor(sa);
|
||||||
|
}
|
||||||
if (tgt.canOnlyTgtOpponent()) {
|
if (tgt.canOnlyTgtOpponent()) {
|
||||||
if (!opp.canBeTargetedBy(sa)) {
|
if (!opp.canBeTargetedBy(sa)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (tgt.isRandomTarget()) {
|
||||||
|
sa.getTargets().add(Aggregates.random(tgt.getAllCandidates(sa, false)));
|
||||||
|
} else {
|
||||||
sa.getTargets().add(opp);
|
sa.getTargets().add(opp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 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
|
||||||
@@ -102,7 +115,7 @@ public class ControlGainAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
List<Card> list =
|
List<Card> list =
|
||||||
CardLists.getValidCards(opp.getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), sa.getActivatingPlayer(), sa.getSourceCard());
|
CardLists.getValidCards(opp.getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), sa.getActivatingPlayer(), sa.getHostCard());
|
||||||
|
|
||||||
// AI won't try to grab cards that are filtered out of AI decks on purpose
|
// AI won't try to grab cards that are filtered out of AI decks on purpose
|
||||||
list = CardLists.filter(list, new Predicate<Card>() {
|
list = CardLists.filter(list, new Predicate<Card>() {
|
||||||
@@ -126,7 +139,7 @@ public class ControlGainAi extends SpellAbilityAi {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
while (sa.getTargets().getNumTargeted() < tgt.getMaxTargets(sa.getSourceCard(), sa)) {
|
while (sa.getTargets().getNumTargeted() < tgt.getMaxTargets(sa.getHostCard(), sa)) {
|
||||||
Card t = null;
|
Card t = null;
|
||||||
for (final Card c : list) {
|
for (final Card c : list) {
|
||||||
if (c.isCreature()) {
|
if (c.isCreature()) {
|
||||||
@@ -144,7 +157,7 @@ public class ControlGainAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (list.isEmpty()) {
|
if (list.isEmpty()) {
|
||||||
if ((sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getSourceCard(), sa)) || (sa.getTargets().getNumTargeted() == 0)) {
|
if ((sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa)) || (sa.getTargets().getNumTargeted() == 0)) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
@@ -185,7 +198,7 @@ public class ControlGainAi extends SpellAbilityAi {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if(!this.canPlayAI(ai, sa) && mandatory) {
|
if(sa.hasParam("TargetingPlayer") || (!this.canPlayAI(ai, sa) && mandatory)) {
|
||||||
List<Card> list = CardLists.getTargetableCards(ai.getCardsIn(ZoneType.Battlefield), sa);
|
List<Card> list = CardLists.getTargetableCards(ai.getCardsIn(ZoneType.Battlefield), sa);
|
||||||
if (list.isEmpty()) {
|
if (list.isEmpty()) {
|
||||||
return false;
|
return false;
|
||||||
@@ -221,4 +234,14 @@ public class ControlGainAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
} // pumpDrawbackAI()
|
} // pumpDrawbackAI()
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Player chooseSinglePlayer(Player ai, SpellAbility sa, Collection<Player> options) {
|
||||||
|
final List<Card> cards = new ArrayList<Card>();
|
||||||
|
for (Player p : options) {
|
||||||
|
cards.addAll(p.getCreaturesInPlay());
|
||||||
|
}
|
||||||
|
Card chosen = ComputerUtilCard.getBestCreatureAI(cards);
|
||||||
|
return chosen != null ? chosen.getController() : Iterables.getFirst(options, null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
152
forge-ai/src/main/java/forge/ai/ability/CopyPermanentAi.java
Normal file
152
forge-ai/src/main/java/forge/ai/ability/CopyPermanentAi.java
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
import com.google.common.base.Predicate;
|
||||||
|
import com.google.common.collect.Iterables;
|
||||||
|
|
||||||
|
import forge.ai.ComputerUtil;
|
||||||
|
import forge.ai.ComputerUtilCard;
|
||||||
|
import forge.ai.SpellAbilityAi;
|
||||||
|
import forge.game.ability.AbilityUtils;
|
||||||
|
import forge.game.card.Card;
|
||||||
|
import forge.game.card.CardLists;
|
||||||
|
import forge.game.card.CardPredicates.Presets;
|
||||||
|
import forge.game.phase.PhaseType;
|
||||||
|
import forge.game.player.Player;
|
||||||
|
import forge.game.player.PlayerActionConfirmMode;
|
||||||
|
import forge.game.spellability.SpellAbility;
|
||||||
|
import forge.game.spellability.TargetRestrictions;
|
||||||
|
import forge.game.zone.ZoneType;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class CopyPermanentAi extends SpellAbilityAi {
|
||||||
|
|
||||||
|
/* (non-Javadoc)
|
||||||
|
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
||||||
|
// Card source = sa.getHostCard();
|
||||||
|
// TODO - I'm sure someone can do this AI better
|
||||||
|
|
||||||
|
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sa.hasParam("AtEOT") && !aiPlayer.getGame().getPhaseHandler().is(PhaseType.MAIN1)) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
if (sa.getTargetRestrictions() != null && sa.hasParam("TargetingPlayer")) {
|
||||||
|
sa.resetTargets();
|
||||||
|
Player targetingPlayer = AbilityUtils.getDefinedPlayers(sa.getHostCard(), sa.getParam("TargetingPlayer"), sa).get(0);
|
||||||
|
sa.setTargetingPlayer(targetingPlayer);
|
||||||
|
return targetingPlayer.getController().chooseTargetsFor(sa);
|
||||||
|
} else {
|
||||||
|
return this.doTriggerAINoCost(aiPlayer, sa, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean doTriggerAINoCost(final Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
||||||
|
final Card source = sa.getHostCard();
|
||||||
|
|
||||||
|
// ////
|
||||||
|
// Targeting
|
||||||
|
|
||||||
|
final TargetRestrictions abTgt = sa.getTargetRestrictions();
|
||||||
|
|
||||||
|
if (abTgt != null) {
|
||||||
|
sa.resetTargets();
|
||||||
|
List<Card> list = aiPlayer.getGame().getCardsIn(ZoneType.Battlefield);
|
||||||
|
list = CardLists.getValidCards(list, abTgt.getValidTgts(), source.getController(), source);
|
||||||
|
list = CardLists.getTargetableCards(list, sa);
|
||||||
|
list = CardLists.filter(list, new Predicate<Card>() {
|
||||||
|
@Override
|
||||||
|
public boolean apply(final Card c) {
|
||||||
|
final Map<String, String> vars = c.getSVars();
|
||||||
|
return !vars.containsKey("RemAIDeck");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// target loop
|
||||||
|
while (sa.getTargets().getNumTargeted() < abTgt.getMaxTargets(sa.getHostCard(), sa)) {
|
||||||
|
if (list.isEmpty()) {
|
||||||
|
if ((sa.getTargets().getNumTargeted() < abTgt.getMinTargets(sa.getHostCard(), sa))
|
||||||
|
|| (sa.getTargets().getNumTargeted() == 0)) {
|
||||||
|
sa.resetTargets();
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
// TODO is this good enough? for up to amounts?
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
list = CardLists.filter(list, new Predicate<Card>() {
|
||||||
|
@Override
|
||||||
|
public boolean apply(final Card c) {
|
||||||
|
return !c.isType("Legendary") || c.getController().isOpponentOf(aiPlayer);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Card choice;
|
||||||
|
if (!CardLists.filter(list, Presets.CREATURES).isEmpty()) {
|
||||||
|
if (sa.hasParam("TargetingPlayer")) {
|
||||||
|
choice = ComputerUtilCard.getWorstCreatureAI(list);
|
||||||
|
} else {
|
||||||
|
choice = ComputerUtilCard.getBestCreatureAI(list);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
choice = ComputerUtilCard.getMostExpensivePermanentAI(list, sa, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (choice == null) { // can't find anything left
|
||||||
|
if ((sa.getTargets().getNumTargeted() < abTgt.getMinTargets(sa.getHostCard(), sa))
|
||||||
|
|| (sa.getTargets().getNumTargeted() == 0)) {
|
||||||
|
sa.resetTargets();
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
// TODO is this good enough? for up to amounts?
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
list.remove(choice);
|
||||||
|
sa.getTargets().add(choice);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// if no targeting, it should always be ok
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
//TODO: add logic here
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* (non-Javadoc)
|
||||||
|
* @see forge.card.ability.SpellAbilityAi#chooseSingleCard(forge.game.player.Player, forge.card.spellability.SpellAbility, java.util.List, boolean)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Card chooseSingleCard(Player ai, SpellAbility sa, Collection<Card> options, boolean isOptional, Player targetedPlayer) {
|
||||||
|
// Select a card to attach to
|
||||||
|
return ComputerUtilCard.getBestAI(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Player chooseSinglePlayer(Player ai, SpellAbility sa, Collection<Player> options) {
|
||||||
|
final List<Card> cards = new ArrayList<Card>();
|
||||||
|
for (Player p : options) {
|
||||||
|
cards.addAll(p.getCreaturesInPlay());
|
||||||
|
}
|
||||||
|
Card chosen = ComputerUtilCard.getBestCreatureAI(cards);
|
||||||
|
return chosen != null ? chosen.getController() : Iterables.getFirst(options, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,16 +1,16 @@
|
|||||||
package forge.card.ability.ai;
|
package forge.ai.ability;
|
||||||
|
|
||||||
import forge.Card;
|
import forge.ai.ComputerUtilCost;
|
||||||
import forge.card.ability.AbilityUtils;
|
import forge.ai.ComputerUtilMana;
|
||||||
import forge.card.ability.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.card.cardfactory.CardFactoryUtil;
|
|
||||||
import forge.card.cost.Cost;
|
|
||||||
import forge.card.spellability.SpellAbility;
|
|
||||||
import forge.card.spellability.TargetRestrictions;
|
|
||||||
import forge.game.Game;
|
import forge.game.Game;
|
||||||
import forge.game.ai.ComputerUtilCost;
|
import forge.game.ability.AbilityUtils;
|
||||||
import forge.game.ai.ComputerUtilMana;
|
import forge.game.card.Card;
|
||||||
|
import forge.game.card.CardFactoryUtil;
|
||||||
|
import forge.game.cost.Cost;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
|
import forge.game.spellability.SpellAbility;
|
||||||
|
import forge.game.spellability.TargetRestrictions;
|
||||||
import forge.util.MyRandom;
|
import forge.util.MyRandom;
|
||||||
|
|
||||||
public class CounterAi extends SpellAbilityAi {
|
public class CounterAi extends SpellAbilityAi {
|
||||||
@@ -19,7 +19,7 @@ public class CounterAi extends SpellAbilityAi {
|
|||||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||||
boolean toReturn = true;
|
boolean toReturn = true;
|
||||||
final Cost abCost = sa.getPayCosts();
|
final Cost abCost = sa.getPayCosts();
|
||||||
final Card source = sa.getSourceCard();
|
final Card source = sa.getHostCard();
|
||||||
final Game game = ai.getGame();
|
final Game game = ai.getGame();
|
||||||
if (game.getStack().isEmpty()) {
|
if (game.getStack().isEmpty()) {
|
||||||
return false;
|
return false;
|
||||||
@@ -39,12 +39,12 @@ public class CounterAi extends SpellAbilityAi {
|
|||||||
if (tgt != null) {
|
if (tgt != null) {
|
||||||
|
|
||||||
final SpellAbility topSA = game.getStack().peekAbility();
|
final SpellAbility topSA = game.getStack().peekAbility();
|
||||||
if (!CardFactoryUtil.isCounterableBy(topSA.getSourceCard(), sa) || topSA.getActivatingPlayer() == ai) {
|
if (!CardFactoryUtil.isCounterableBy(topSA.getHostCard(), sa) || topSA.getActivatingPlayer() == ai) {
|
||||||
// might as well check for player's friendliness
|
// might as well check for player's friendliness
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (sa.hasParam("AITgts") && (topSA.getSourceCard() == null
|
if (sa.hasParam("AITgts") && (topSA.getHostCard() == null
|
||||||
|| !topSA.getSourceCard().isValid(sa.getParam("AITgts"), sa.getActivatingPlayer(), source))) {
|
|| !topSA.getHostCard().isValid(sa.getParam("AITgts"), sa.getActivatingPlayer(), source))) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,7 +115,7 @@ public class CounterAi extends SpellAbilityAi {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
final SpellAbility topSA = game.getStack().peekAbility();
|
final SpellAbility topSA = game.getStack().peekAbility();
|
||||||
if (!CardFactoryUtil.isCounterableBy(topSA.getSourceCard(), sa) || topSA.getActivatingPlayer() == ai) {
|
if (!CardFactoryUtil.isCounterableBy(topSA.getHostCard(), sa) || topSA.getActivatingPlayer() == ai) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,7 +128,7 @@ public class CounterAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
String unlessCost = sa.hasParam("UnlessCost") ? sa.getParam("UnlessCost").trim() : null;
|
String unlessCost = sa.hasParam("UnlessCost") ? sa.getParam("UnlessCost").trim() : null;
|
||||||
|
|
||||||
final Card source = sa.getSourceCard();
|
final Card source = sa.getHostCard();
|
||||||
if (unlessCost != null) {
|
if (unlessCost != null) {
|
||||||
// Is this Usable Mana Sources? Or Total Available Mana?
|
// Is this Usable Mana Sources? Or Total Available Mana?
|
||||||
final int usableManaSources = ComputerUtilMana.getAvailableMana(ai.getOpponent(), true).size();
|
final int usableManaSources = ComputerUtilMana.getAvailableMana(ai.getOpponent(), true).size();
|
||||||
@@ -15,18 +15,17 @@
|
|||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
package forge.card.ability.ai;
|
package forge.ai.ability;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import com.google.common.base.Predicate;
|
import com.google.common.base.Predicate;
|
||||||
|
import forge.ai.ComputerUtilCard;
|
||||||
import forge.Card;
|
import forge.game.card.Card;
|
||||||
import forge.CardLists;
|
import forge.game.card.CardLists;
|
||||||
import forge.CounterType;
|
import forge.game.card.CounterType;
|
||||||
import forge.game.ai.ComputerUtilCard;
|
|
||||||
import forge.util.Aggregates;
|
import forge.util.Aggregates;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>
|
* <p>
|
||||||
@@ -50,7 +49,7 @@ public abstract class CountersAi {
|
|||||||
* a {@link java.lang.String} object.
|
* a {@link java.lang.String} object.
|
||||||
* @param amount
|
* @param amount
|
||||||
* a int.
|
* a int.
|
||||||
* @return a {@link forge.Card} object.
|
* @return a {@link forge.game.card.Card} object.
|
||||||
*/
|
*/
|
||||||
public static Card chooseCursedTarget(final List<Card> list, final String type, final int amount) {
|
public static Card chooseCursedTarget(final List<Card> list, final String type, final int amount) {
|
||||||
Card choice;
|
Card choice;
|
||||||
@@ -83,18 +82,17 @@ public abstract class CountersAi {
|
|||||||
* a {@link forge.CardList} object.
|
* a {@link forge.CardList} object.
|
||||||
* @param type
|
* @param type
|
||||||
* a {@link java.lang.String} object.
|
* a {@link java.lang.String} object.
|
||||||
* @return a {@link forge.Card} object.
|
* @return a {@link forge.game.card.Card} object.
|
||||||
*/
|
*/
|
||||||
public static Card chooseBoonTarget(final List<Card> list, final String type) {
|
public static Card chooseBoonTarget(final List<Card> list, final String type) {
|
||||||
Card choice;
|
Card choice;
|
||||||
if (type.equals("P1P1")) {
|
if (type.equals("P1P1")) {
|
||||||
choice = ComputerUtilCard.getBestCreatureAI(list);
|
choice = ComputerUtilCard.getBestCreatureAI(list);
|
||||||
} else if (type.equals("DIVINITY") || type.equals("FATE")) {
|
} else if (type.equals("DIVINITY")) {
|
||||||
final List<Card> boon = CardLists.filter(list, new Predicate<Card>() {
|
final List<Card> boon = CardLists.filter(list, new Predicate<Card>() {
|
||||||
@Override
|
@Override
|
||||||
public boolean apply(final Card c) {
|
public boolean apply(final Card c) {
|
||||||
return c.getCounters(CounterType.DIVINITY) == 0
|
return c.getCounters(CounterType.DIVINITY) == 0;
|
||||||
&& c.getCounters(CounterType.FATE) == 0;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
choice = ComputerUtilCard.getMostExpensivePermanentAI(boon, null, false);
|
choice = ComputerUtilCard.getMostExpensivePermanentAI(boon, null, false);
|
||||||
@@ -1,21 +1,21 @@
|
|||||||
package forge.card.ability.ai;
|
package forge.ai.ability;
|
||||||
|
|
||||||
import java.util.List;
|
import forge.ai.ComputerUtilCard;
|
||||||
import java.util.Random;
|
import forge.ai.SpellAbilityAi;
|
||||||
|
import forge.game.ability.AbilityUtils;
|
||||||
import forge.Card;
|
import forge.game.card.Card;
|
||||||
import forge.CardLists;
|
import forge.game.card.CardLists;
|
||||||
import forge.CounterType;
|
import forge.game.card.CounterType;
|
||||||
import forge.card.ability.AbilityUtils;
|
|
||||||
import forge.card.ability.SpellAbilityAi;
|
|
||||||
import forge.card.spellability.SpellAbility;
|
|
||||||
import forge.card.spellability.TargetRestrictions;
|
|
||||||
import forge.game.ai.ComputerUtilCard;
|
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
|
import forge.game.spellability.SpellAbility;
|
||||||
|
import forge.game.spellability.TargetRestrictions;
|
||||||
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;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
public class CountersMoveAi extends SpellAbilityAi {
|
public class CountersMoveAi extends SpellAbilityAi {
|
||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||||
@@ -28,7 +28,7 @@ public class CountersMoveAi extends SpellAbilityAi {
|
|||||||
// TODO handle proper calculation of X values based on Cost
|
// TODO handle proper calculation of X values based on Cost
|
||||||
int amount = 0;
|
int amount = 0;
|
||||||
if (!sa.getParam("CounterNum").equals("All")) {
|
if (!sa.getParam("CounterNum").equals("All")) {
|
||||||
amount = AbilityUtils.calculateAmount(sa.getSourceCard(), amountStr, sa);
|
amount = AbilityUtils.calculateAmount(sa.getHostCard(), amountStr, sa);
|
||||||
}
|
}
|
||||||
// don't use it if no counters to add
|
// don't use it if no counters to add
|
||||||
if (amount <= 0) {
|
if (amount <= 0) {
|
||||||
@@ -47,13 +47,13 @@ public class CountersMoveAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
final Card host = sa.getSourceCard();
|
final Card host = sa.getHostCard();
|
||||||
final TargetRestrictions abTgt = sa.getTargetRestrictions();
|
final TargetRestrictions abTgt = sa.getTargetRestrictions();
|
||||||
final String type = sa.getParam("CounterType");
|
final String type = sa.getParam("CounterType");
|
||||||
final String amountStr = sa.getParam("CounterNum");
|
final String amountStr = sa.getParam("CounterNum");
|
||||||
int amount = 0;
|
int amount = 0;
|
||||||
if (!sa.getParam("CounterNum").equals("All")) {
|
if (!sa.getParam("CounterNum").equals("All")) {
|
||||||
amount = AbilityUtils.calculateAmount(sa.getSourceCard(), amountStr, sa);
|
amount = AbilityUtils.calculateAmount(sa.getHostCard(), amountStr, sa);
|
||||||
}
|
}
|
||||||
boolean chance = false;
|
boolean chance = false;
|
||||||
boolean preferred = true;
|
boolean preferred = true;
|
||||||
@@ -1,18 +1,17 @@
|
|||||||
package forge.card.ability.ai;
|
package forge.ai.ability;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import com.google.common.base.Predicate;
|
import com.google.common.base.Predicate;
|
||||||
|
import forge.ai.ComputerUtil;
|
||||||
import forge.Card;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.CardLists;
|
import forge.game.card.Card;
|
||||||
import forge.CounterType;
|
import forge.game.card.CardLists;
|
||||||
import forge.card.ability.SpellAbilityAi;
|
import forge.game.card.CounterType;
|
||||||
import forge.card.spellability.SpellAbility;
|
|
||||||
import forge.game.ai.ComputerUtil;
|
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
public class CountersProliferateAi extends SpellAbilityAi {
|
public class CountersProliferateAi extends SpellAbilityAi {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -43,7 +42,7 @@ public class CountersProliferateAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if ((cperms.size() == 0) && (hperms.size() == 0) && (ai.getOpponent().getPoisonCounters() == 0)) {
|
if (cperms.isEmpty() && hperms.isEmpty() && ai.getOpponent().getPoisonCounters() == 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return chance;
|
return chance;
|
||||||
468
forge-ai/src/main/java/forge/ai/ability/CountersPutAi.java
Normal file
468
forge-ai/src/main/java/forge/ai/ability/CountersPutAi.java
Normal file
@@ -0,0 +1,468 @@
|
|||||||
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
import com.google.common.base.Predicate;
|
||||||
|
import com.google.common.collect.Iterables;
|
||||||
|
|
||||||
|
import forge.ai.*;
|
||||||
|
import forge.game.ability.AbilityFactory;
|
||||||
|
import forge.game.ability.AbilityUtils;
|
||||||
|
import forge.game.card.Card;
|
||||||
|
import forge.game.card.CardLists;
|
||||||
|
import forge.game.card.CounterType;
|
||||||
|
import forge.game.combat.CombatUtil;
|
||||||
|
import forge.game.cost.Cost;
|
||||||
|
import forge.game.phase.PhaseType;
|
||||||
|
import forge.game.player.Player;
|
||||||
|
import forge.game.player.PlayerActionConfirmMode;
|
||||||
|
import forge.game.spellability.AbilitySub;
|
||||||
|
import forge.game.spellability.SpellAbility;
|
||||||
|
import forge.game.spellability.TargetRestrictions;
|
||||||
|
import forge.game.trigger.Trigger;
|
||||||
|
import forge.game.trigger.TriggerType;
|
||||||
|
import forge.game.zone.ZoneType;
|
||||||
|
import forge.util.Aggregates;
|
||||||
|
import forge.util.MyRandom;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
public class CountersPutAi extends SpellAbilityAi {
|
||||||
|
@Override
|
||||||
|
protected boolean canPlayAI(Player ai, final SpellAbility sa) {
|
||||||
|
// AI needs to be expanded, since this function can be pretty complex
|
||||||
|
// based on
|
||||||
|
// what the expected targets could be
|
||||||
|
final Random r = MyRandom.getRandom();
|
||||||
|
final Cost abCost = sa.getPayCosts();
|
||||||
|
final TargetRestrictions abTgt = sa.getTargetRestrictions();
|
||||||
|
final Card source = sa.getHostCard();
|
||||||
|
List<Card> list;
|
||||||
|
Card choice = null;
|
||||||
|
final String type = sa.getParam("CounterType");
|
||||||
|
final String amountStr = sa.getParam("CounterNum");
|
||||||
|
final boolean divided = sa.hasParam("DividedAsYouChoose");
|
||||||
|
|
||||||
|
final Player player = sa.isCurse() ? ai.getOpponent() : ai;
|
||||||
|
|
||||||
|
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ("Never".equals(sa.getParam("AILogic"))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (abCost != null) {
|
||||||
|
// AI currently disabled for these costs
|
||||||
|
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, null)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ComputerUtilCost.checkDiscardCost(ai, abCost, source)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ComputerUtilCost.checkSacrificeCost(ai, abCost, source)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ComputerUtilCost.checkCreatureSacrificeCost(ai, abCost, source)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ComputerUtilCost.checkRemoveCounterCost(abCost, source)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sa.hasParam("Monstrosity") && source.isMonstrous()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sa.hasParam("LevelUp")) {
|
||||||
|
// creatures enchanted by curse auras have low priority
|
||||||
|
if (source.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2)) {
|
||||||
|
for (Card aura : source.getEnchantedBy()) {
|
||||||
|
if (aura.getController().isOpponentOf(ai)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int maxLevel = Integer.parseInt(sa.getParam("MaxLevel"));
|
||||||
|
return source.getCounters(CounterType.LEVEL) < maxLevel;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO handle proper calculation of X values based on Cost
|
||||||
|
int amount = AbilityUtils.calculateAmount(source, amountStr, sa);
|
||||||
|
|
||||||
|
if ("Fight".equals(sa.getParam("AILogic"))) {
|
||||||
|
int nPump = 0;
|
||||||
|
if (type.equals("P1P1")) {
|
||||||
|
nPump = amount;
|
||||||
|
}
|
||||||
|
final AbilitySub tgtFight = sa.getSubAbility();
|
||||||
|
List<Card> aiCreatures = ai.getCreaturesInPlay();
|
||||||
|
aiCreatures = CardLists.getTargetableCards(aiCreatures, sa);
|
||||||
|
aiCreatures = ComputerUtil.getSafeTargets(ai, sa, aiCreatures);
|
||||||
|
ComputerUtilCard.sortByEvaluateCreature(aiCreatures);
|
||||||
|
|
||||||
|
List<Card> humCreatures = ai.getOpponent().getCreaturesInPlay();
|
||||||
|
humCreatures = CardLists.getTargetableCards(humCreatures, tgtFight);
|
||||||
|
ComputerUtilCard.sortByEvaluateCreature(humCreatures);
|
||||||
|
if (humCreatures.isEmpty() || aiCreatures.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (Card humanCreature : humCreatures) {
|
||||||
|
for (Card aiCreature : aiCreatures) {
|
||||||
|
if (sa.isSpell()) { //heroic triggers adding counters
|
||||||
|
for (Trigger t : aiCreature.getTriggers()) {
|
||||||
|
if (t.getMode() == TriggerType.SpellCast) {
|
||||||
|
final Map<String, String> params = t.getMapParams();
|
||||||
|
if ("Card.Self".equals(params.get("TargetsValid")) && "You".equals(params.get("ValidActivatingPlayer"))
|
||||||
|
&& params.containsKey("Execute")) {
|
||||||
|
SpellAbility heroic = AbilityFactory.getAbility(aiCreature.getSVar(params.get("Execute")),aiCreature);
|
||||||
|
if ("Self".equals(heroic.getParam("Defined")) && "P1P1".equals(heroic.getParam("CounterType"))) {
|
||||||
|
int n = AbilityUtils.calculateAmount(aiCreature, heroic.getParam("CounterNum"), heroic);
|
||||||
|
nPump += n;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (FightAi.shouldFight(aiCreature, humanCreature, nPump, nPump)) {
|
||||||
|
sa.getTargets().add(aiCreature);
|
||||||
|
tgtFight.getTargets().add(humanCreature);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) {
|
||||||
|
// Set PayX here to maximum value.
|
||||||
|
amount = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||||
|
source.setSVar("PayX", Integer.toString(amount));
|
||||||
|
}
|
||||||
|
if ("Polukranos".equals(sa.getParam("AILogic"))) {
|
||||||
|
List<Card> humCreatures = ai.getOpponent().getCreaturesInPlay();
|
||||||
|
//TODO how to grab target restrictions from subsequent triggered ability?
|
||||||
|
if (!humCreatures.isEmpty()){
|
||||||
|
boolean canSurvive = false;
|
||||||
|
for (Card humanCreature : humCreatures) {
|
||||||
|
if (!FightAi.canKill(humanCreature, source, 0)){
|
||||||
|
canSurvive = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!canSurvive){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// don't use it if no counters to add
|
||||||
|
if (amount <= 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Targeting
|
||||||
|
if (abTgt != null) {
|
||||||
|
sa.resetTargets();
|
||||||
|
// target loop
|
||||||
|
|
||||||
|
list = CardLists.filter(player.getCardsIn(ZoneType.Battlefield), new Predicate<Card>() {
|
||||||
|
@Override
|
||||||
|
public boolean apply(final Card c) {
|
||||||
|
return c.canBeTargetedBy(sa) && c.canReceiveCounters(CounterType.valueOf(type));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
list = CardLists.getValidCards(list, abTgt.getValidTgts(), source.getController(), source);
|
||||||
|
|
||||||
|
if (list.size() < abTgt.getMinTargets(source, sa)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
while (sa.getTargets().getNumTargeted() < abTgt.getMaxTargets(sa.getHostCard(), sa)) {
|
||||||
|
if (list.isEmpty()) {
|
||||||
|
if ((sa.getTargets().getNumTargeted() < abTgt.getMinTargets(sa.getHostCard(), sa))
|
||||||
|
|| (sa.getTargets().getNumTargeted() == 0)) {
|
||||||
|
sa.resetTargets();
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
// TODO is this good enough? for up to amounts?
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sa.isCurse()) {
|
||||||
|
choice = CountersAi.chooseCursedTarget(list, type, amount);
|
||||||
|
} else {
|
||||||
|
choice = CountersAi.chooseBoonTarget(list, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (choice == null) { // can't find anything left
|
||||||
|
if ((sa.getTargets().getNumTargeted() < abTgt.getMinTargets(sa.getHostCard(), sa))
|
||||||
|
|| (sa.getTargets().getNumTargeted() == 0)) {
|
||||||
|
sa.resetTargets();
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
// TODO is this good enough? for up to amounts?
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
list.remove(choice);
|
||||||
|
sa.getTargets().add(choice);
|
||||||
|
|
||||||
|
if (divided) {
|
||||||
|
abTgt.addDividedAllocation(choice, amount);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
final List<Card> cards = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("Defined"), sa);
|
||||||
|
// Don't activate Curse abilities on my cards and non-curse abilites
|
||||||
|
// on my opponents
|
||||||
|
if (cards.isEmpty() || !cards.get(0).getController().equals(player)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
final int currCounters = cards.get(0).getCounters(CounterType.valueOf(type));
|
||||||
|
// each non +1/+1 counter on the card is a 10% chance of not
|
||||||
|
// activating this ability.
|
||||||
|
|
||||||
|
if (!(type.equals("P1P1") || type.equals("M1M1") || type.equals("ICE")) && (r.nextFloat() < (.1 * currCounters))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ComputerUtil.playImmediately(ai, sa)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't use non P1P1/M1M1 counters before main 2 if possible
|
||||||
|
if (ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2)
|
||||||
|
&& !sa.hasParam("ActivationPhases")
|
||||||
|
&& !(type.equals("P1P1") || type.equals("M1M1"))
|
||||||
|
&& !ComputerUtil.castSpellInMain1(ai, sa)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ComputerUtil.waitForBlocking(sa)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} // putCanPlayAI
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean chkAIDrawback(final SpellAbility sa, Player ai) {
|
||||||
|
boolean chance = true;
|
||||||
|
final TargetRestrictions abTgt = sa.getTargetRestrictions();
|
||||||
|
final Card source = sa.getHostCard();
|
||||||
|
Card choice = null;
|
||||||
|
final String type = sa.getParam("CounterType");
|
||||||
|
final String amountStr = sa.getParam("CounterNum");
|
||||||
|
final boolean divided = sa.hasParam("DividedAsYouChoose");
|
||||||
|
final int amount = AbilityUtils.calculateAmount(sa.getHostCard(), amountStr, sa);
|
||||||
|
|
||||||
|
final Player player = sa.isCurse() ? ai.getOpponent() : ai;
|
||||||
|
|
||||||
|
if (abTgt != null) {
|
||||||
|
List<Card> list =
|
||||||
|
CardLists.getValidCards(player.getCardsIn(ZoneType.Battlefield), abTgt.getValidTgts(), source.getController(), source);
|
||||||
|
|
||||||
|
if (list.size() == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
sa.resetTargets();
|
||||||
|
// target loop
|
||||||
|
while (sa.getTargets().getNumTargeted() < abTgt.getMaxTargets(sa.getHostCard(), sa)) {
|
||||||
|
list = CardLists.filter(list, new Predicate<Card>() {
|
||||||
|
@Override
|
||||||
|
public boolean apply(final Card c) {
|
||||||
|
return sa.canTarget(c);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (list.size() == 0) {
|
||||||
|
if ((sa.getTargets().getNumTargeted() < abTgt.getMinTargets(sa.getHostCard(), sa))
|
||||||
|
|| (sa.getTargets().getNumTargeted() == 0)) {
|
||||||
|
sa.resetTargets();
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sa.isCurse()) {
|
||||||
|
choice = CountersAi.chooseCursedTarget(list, type, amount);
|
||||||
|
} else {
|
||||||
|
choice = CountersAi.chooseBoonTarget(list, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (choice == null) { // can't find anything left
|
||||||
|
if ((sa.getTargets().getNumTargeted() < abTgt.getMinTargets(sa.getHostCard(), sa))
|
||||||
|
|| (sa.getTargets().getNumTargeted() == 0)) {
|
||||||
|
sa.resetTargets();
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
// TODO is this good enough? for up to amounts?
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
list.remove(choice);
|
||||||
|
sa.getTargets().add(choice);
|
||||||
|
if (divided) {
|
||||||
|
abTgt.addDividedAllocation(choice, amount);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return chance;
|
||||||
|
} // putPlayDrawbackAI
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
|
final TargetRestrictions abTgt = sa.getTargetRestrictions();
|
||||||
|
final Card source = sa.getHostCard();
|
||||||
|
// boolean chance = true;
|
||||||
|
boolean preferred = true;
|
||||||
|
List<Card> list;
|
||||||
|
boolean isCurse = sa.isCurse();
|
||||||
|
final Player player = isCurse ? ai.getOpponent() : ai;
|
||||||
|
final String type = sa.getParam("CounterType");
|
||||||
|
final String amountStr = sa.getParam("CounterNum");
|
||||||
|
final boolean divided = sa.hasParam("DividedAsYouChoose");
|
||||||
|
final int amount = AbilityUtils.calculateAmount(sa.getHostCard(), amountStr, sa);
|
||||||
|
|
||||||
|
if (abTgt == null) {
|
||||||
|
// No target. So must be defined
|
||||||
|
list = new ArrayList<Card>(AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa));
|
||||||
|
|
||||||
|
if (!mandatory) {
|
||||||
|
// TODO - If Trigger isn't mandatory, when wouldn't we want to
|
||||||
|
// put a counter?
|
||||||
|
// things like Powder Keg, which are way too complex for the AI
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
list = CardLists.getTargetableCards(player.getCardsIn(ZoneType.Battlefield), sa);
|
||||||
|
list = CardLists.getValidCards(list, abTgt.getValidTgts(), source.getController(), source);
|
||||||
|
|
||||||
|
if (list.isEmpty() && mandatory) {
|
||||||
|
// If there isn't any prefered cards to target, gotta choose
|
||||||
|
// non-preferred ones
|
||||||
|
list = player.getOpponent().getCardsIn(ZoneType.Battlefield);
|
||||||
|
list = CardLists.getTargetableCards(list, sa);
|
||||||
|
list = CardLists.getValidCards(list, abTgt.getValidTgts(), source.getController(), source);
|
||||||
|
preferred = false;
|
||||||
|
}
|
||||||
|
// Not mandatory, or the the list was regenerated and is still
|
||||||
|
// empty,
|
||||||
|
// so return false since there are no targets
|
||||||
|
if (list.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Card choice = null;
|
||||||
|
|
||||||
|
// Choose targets here:
|
||||||
|
if (isCurse) {
|
||||||
|
if (preferred) {
|
||||||
|
choice = CountersAi.chooseCursedTarget(list, type, amount);
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
if (type.equals("M1M1")) {
|
||||||
|
choice = ComputerUtilCard.getWorstCreatureAI(list);
|
||||||
|
} else {
|
||||||
|
choice = Aggregates.random(list);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (preferred) {
|
||||||
|
choice = CountersAi.chooseBoonTarget(list, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
if (type.equals("P1P1")) {
|
||||||
|
choice = ComputerUtilCard.getWorstCreatureAI(list);
|
||||||
|
} else {
|
||||||
|
choice = Aggregates.random(list);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (choice != null && divided) {
|
||||||
|
abTgt.addDividedAllocation(choice, amount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO - I think choice can be null here. Is that ok for
|
||||||
|
// addTarget()?
|
||||||
|
sa.getTargets().add(choice);
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
final Card source = sa.getHostCard();
|
||||||
|
if (mode == PlayerActionConfirmMode.Tribute) {
|
||||||
|
// add counter if that opponent has a giant creature
|
||||||
|
final List<Card> creats = player.getCreaturesInPlay();
|
||||||
|
final int tributeAmount = source.getKeywordMagnitude("Tribute");
|
||||||
|
final boolean isHaste = source.hasKeyword("Haste");
|
||||||
|
List<Card> threatening = CardLists.filter(creats, new Predicate<Card>() {
|
||||||
|
@Override
|
||||||
|
public boolean apply(Card c) {
|
||||||
|
return CombatUtil.canBlock(source, c, !isHaste)
|
||||||
|
&& (c.getNetDefense() > source.getNetAttack() + tributeAmount || c.hasKeyword("DeathTouch"));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (!threatening.isEmpty()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (source.hasSVar("TributeAILogic")) {
|
||||||
|
final String logic = source.getSVar("TributeAILogic");
|
||||||
|
if (logic.equals("Always")) {
|
||||||
|
return true;
|
||||||
|
} else if (logic.equals("Never")) {
|
||||||
|
return false;
|
||||||
|
} else if (logic.equals("CanBlockThisTurn")) {
|
||||||
|
// pump haste
|
||||||
|
List<Card> canBlock = CardLists.filter(creats, new Predicate<Card>() {
|
||||||
|
@Override
|
||||||
|
public boolean apply(Card c) {
|
||||||
|
return CombatUtil.canBlock(source, c) && (c.getNetDefense() > source.getNetAttack() || c.hasKeyword("DeathTouch"));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (!canBlock.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else if (logic.equals("DontControlCreatures")) {
|
||||||
|
return !creats.isEmpty();
|
||||||
|
} else if (logic.equals("OppHasCardsInHand")) {
|
||||||
|
return !player.getOpponent().getCardsIn(ZoneType.Hand).isEmpty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return MyRandom.getRandom().nextBoolean();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* (non-Javadoc)
|
||||||
|
* @see forge.card.ability.SpellAbilityAi#chooseSinglePlayer(Player, SpellAbility, Collection<Player>)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Player chooseSinglePlayer(Player ai, SpellAbility sa, Collection<Player> options) {
|
||||||
|
// logic?
|
||||||
|
return Iterables.getFirst(options, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,26 +1,26 @@
|
|||||||
package forge.card.ability.ai;
|
package forge.ai.ability;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Random;
|
|
||||||
|
|
||||||
import com.google.common.base.Predicate;
|
import com.google.common.base.Predicate;
|
||||||
|
import forge.ai.ComputerUtilCost;
|
||||||
import forge.Card;
|
import forge.ai.ComputerUtilMana;
|
||||||
import forge.CardLists;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.card.ability.AbilityUtils;
|
import forge.game.ability.AbilityUtils;
|
||||||
import forge.card.ability.SpellAbilityAi;
|
import forge.game.card.Card;
|
||||||
import forge.card.cost.Cost;
|
import forge.game.card.CardLists;
|
||||||
import forge.card.spellability.AbilitySub;
|
import forge.game.cost.Cost;
|
||||||
import forge.card.spellability.SpellAbility;
|
|
||||||
import forge.card.spellability.TargetRestrictions;
|
|
||||||
import forge.game.ai.ComputerUtilCost;
|
|
||||||
import forge.game.ai.ComputerUtilMana;
|
|
||||||
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.AbilitySub;
|
||||||
|
import forge.game.spellability.SpellAbility;
|
||||||
|
import forge.game.spellability.TargetRestrictions;
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
import forge.util.MyRandom;
|
import forge.util.MyRandom;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
public class CountersPutAllAi extends SpellAbilityAi {
|
public class CountersPutAllAi extends SpellAbilityAi {
|
||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||||
@@ -29,7 +29,7 @@ public class CountersPutAllAi extends SpellAbilityAi {
|
|||||||
// the expected targets could be
|
// the expected targets could be
|
||||||
final Random r = MyRandom.getRandom();
|
final Random r = MyRandom.getRandom();
|
||||||
final Cost abCost = sa.getPayCosts();
|
final Cost abCost = sa.getPayCosts();
|
||||||
final Card source = sa.getSourceCard();
|
final Card source = sa.getHostCard();
|
||||||
List<Card> hList;
|
List<Card> hList;
|
||||||
List<Card> cList;
|
List<Card> cList;
|
||||||
final String type = sa.getParam("CounterType");
|
final String type = sa.getParam("CounterType");
|
||||||
@@ -72,7 +72,7 @@ public class CountersPutAllAi extends SpellAbilityAi {
|
|||||||
amount = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
amount = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||||
source.setSVar("PayX", Integer.toString(amount));
|
source.setSVar("PayX", Integer.toString(amount));
|
||||||
} else {
|
} else {
|
||||||
amount = AbilityUtils.calculateAmount(sa.getSourceCard(), amountStr, sa);
|
amount = AbilityUtils.calculateAmount(sa.getHostCard(), amountStr, sa);
|
||||||
}
|
}
|
||||||
|
|
||||||
// prevent run-away activations - first time will always return true
|
// prevent run-away activations - first time will always return true
|
||||||
@@ -133,4 +133,11 @@ public class CountersPutAllAi extends SpellAbilityAi {
|
|||||||
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
||||||
return canPlayAI(ai, sa);
|
return canPlayAI(ai, sa);
|
||||||
}
|
}
|
||||||
|
/* (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) {
|
||||||
|
return player.getCreaturesInPlay().size() >= player.getOpponent().getCreaturesInPlay().size();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -15,21 +15,20 @@
|
|||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
package forge.card.ability.ai;
|
package forge.ai.ability;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import com.google.common.base.Predicate;
|
import com.google.common.base.Predicate;
|
||||||
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.Card;
|
import forge.game.card.Card;
|
||||||
import forge.CardLists;
|
import forge.game.card.CardLists;
|
||||||
import forge.card.ability.SpellAbilityAi;
|
|
||||||
import forge.card.spellability.SpellAbility;
|
|
||||||
import forge.card.spellability.TargetRestrictions;
|
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
|
import forge.game.spellability.SpellAbility;
|
||||||
|
import forge.game.spellability.TargetRestrictions;
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
import forge.util.MyRandom;
|
import forge.util.MyRandom;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>
|
* <p>
|
||||||
* AbilityFactory_PutOrRemoveCountersAi class.
|
* AbilityFactory_PutOrRemoveCountersAi class.
|
||||||
@@ -52,7 +51,7 @@ public class CountersPutOrRemoveAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
List<ZoneType> zones = ZoneType.listValueOf(sa.getParamOrDefault("TgtZones", "Battlefield"));
|
List<ZoneType> zones = ZoneType.listValueOf(sa.getParamOrDefault("TgtZones", "Battlefield"));
|
||||||
List<Card> validCards = CardLists.getValidCards(ai.getGame().getCardsIn(zones),
|
List<Card> validCards = CardLists.getValidCards(ai.getGame().getCardsIn(zones),
|
||||||
tgt.getValidTgts(), sa.getActivatingPlayer(), sa.getSourceCard());
|
tgt.getValidTgts(), sa.getActivatingPlayer(), sa.getHostCard());
|
||||||
|
|
||||||
if (validCards.isEmpty()) {
|
if (validCards.isEmpty()) {
|
||||||
return false;
|
return false;
|
||||||
@@ -73,9 +72,9 @@ public class CountersPutOrRemoveAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
while (sa.getTargets().getNumTargeted() < tgt.getMaxTargets(sa.getSourceCard(), sa)) {
|
while (sa.getTargets().getNumTargeted() < tgt.getMaxTargets(sa.getHostCard(), sa)) {
|
||||||
Card targetCard = null;
|
Card targetCard = null;
|
||||||
if (cWithCounters.isEmpty() && ((sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getSourceCard(), sa))
|
if (cWithCounters.isEmpty() && ((sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa))
|
||||||
|| (sa.getTargets().getNumTargeted() == 0))) {
|
|| (sa.getTargets().getNumTargeted() == 0))) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
return false;
|
return false;
|
||||||
@@ -1,15 +1,15 @@
|
|||||||
package forge.card.ability.ai;
|
package forge.ai.ability;
|
||||||
|
|
||||||
import forge.Card;
|
import forge.ai.ComputerUtil;
|
||||||
import forge.CounterType;
|
import forge.ai.ComputerUtilCost;
|
||||||
import forge.card.ability.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.card.cost.Cost;
|
import forge.game.card.Card;
|
||||||
import forge.card.spellability.SpellAbility;
|
import forge.game.card.CounterType;
|
||||||
import forge.card.spellability.TargetRestrictions;
|
import forge.game.cost.Cost;
|
||||||
import forge.game.ai.ComputerUtil;
|
|
||||||
import forge.game.ai.ComputerUtilCost;
|
|
||||||
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.TargetRestrictions;
|
||||||
|
|
||||||
public class CountersRemoveAi extends SpellAbilityAi {
|
public class CountersRemoveAi extends SpellAbilityAi {
|
||||||
|
|
||||||
@@ -20,7 +20,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
|||||||
// the expected targets could be
|
// the expected targets could be
|
||||||
final Cost abCost = sa.getPayCosts();
|
final Cost abCost = sa.getPayCosts();
|
||||||
TargetRestrictions abTgt = sa.getTargetRestrictions();
|
TargetRestrictions abTgt = sa.getTargetRestrictions();
|
||||||
final Card source = sa.getSourceCard();
|
final Card source = sa.getHostCard();
|
||||||
// List<Card> list;
|
// List<Card> list;
|
||||||
// Card choice = null;
|
// Card choice = null;
|
||||||
|
|
||||||
@@ -59,7 +59,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO handle proper calculation of X values based on Cost
|
// TODO handle proper calculation of X values based on Cost
|
||||||
// final int amount = calculateAmount(sa.getSourceCard(), amountStr, sa);
|
// final int amount = calculateAmount(sa.getHostCard(), amountStr, sa);
|
||||||
|
|
||||||
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
||||||
return false;
|
return false;
|
||||||
@@ -77,7 +77,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!type.matches("Any")) {
|
if (!type.matches("Any")) {
|
||||||
final int currCounters = sa.getSourceCard().getCounters(CounterType.valueOf(type));
|
final int currCounters = sa.getHostCard().getCounters(CounterType.valueOf(type));
|
||||||
if (currCounters < 1) {
|
if (currCounters < 1) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -1,19 +1,18 @@
|
|||||||
package forge.card.ability.ai;
|
package forge.ai.ability;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
|
import forge.ai.ComputerUtilCombat;
|
||||||
import forge.Card;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.CardPredicates;
|
|
||||||
import forge.card.ability.SpellAbilityAi;
|
|
||||||
import forge.card.spellability.SpellAbility;
|
|
||||||
import forge.game.Game;
|
import forge.game.Game;
|
||||||
import forge.game.ai.ComputerUtilCombat;
|
import forge.game.card.Card;
|
||||||
|
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.spellability.SpellAbility;
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
public abstract class DamageAiBase extends SpellAbilityAi {
|
public abstract class DamageAiBase extends SpellAbilityAi {
|
||||||
protected boolean shouldTgtP(final Player comp, final SpellAbility sa, final int d, final boolean noPrevention) {
|
protected boolean shouldTgtP(final Player comp, final SpellAbility sa, final int d, final boolean noPrevention) {
|
||||||
int restDamage = d;
|
int restDamage = d;
|
||||||
@@ -22,15 +21,19 @@ public abstract class DamageAiBase extends SpellAbilityAi {
|
|||||||
if (!sa.canTarget(enemy)) {
|
if (!sa.canTarget(enemy)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (sa.getTargets() != null && sa.getTargets().getTargets().contains(enemy)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// burn Planeswalkers
|
// burn Planeswalkers
|
||||||
if (Iterables.any(enemy.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.PLANEWALKERS)) {
|
if (Iterables.any(enemy.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.PLANEWALKERS)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!noPrevention) {
|
if (!noPrevention) {
|
||||||
restDamage = ComputerUtilCombat.predictDamageTo(enemy, restDamage, sa.getSourceCard(), false);
|
restDamage = ComputerUtilCombat.predictDamageTo(enemy, restDamage, sa.getHostCard(), false);
|
||||||
} else {
|
} else {
|
||||||
restDamage = enemy.staticReplaceDamage(restDamage, sa.getSourceCard(), false);
|
restDamage = enemy.staticReplaceDamage(restDamage, sa.getHostCard(), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (restDamage == 0) {
|
if (restDamage == 0) {
|
||||||
@@ -1,26 +1,21 @@
|
|||||||
package forge.card.ability.ai;
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
import com.google.common.base.Predicate;
|
||||||
|
import forge.ai.*;
|
||||||
|
import forge.game.ability.AbilityUtils;
|
||||||
|
import forge.game.card.Card;
|
||||||
|
import forge.game.card.CardLists;
|
||||||
|
import forge.game.cost.Cost;
|
||||||
|
import forge.game.player.Player;
|
||||||
|
import forge.game.spellability.SpellAbility;
|
||||||
|
import forge.game.spellability.TargetRestrictions;
|
||||||
|
import forge.game.zone.ZoneType;
|
||||||
|
import forge.util.MyRandom;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
|
|
||||||
import com.google.common.base.Predicate;
|
|
||||||
|
|
||||||
import forge.Card;
|
|
||||||
import forge.CardLists;
|
|
||||||
import forge.card.ability.AbilityUtils;
|
|
||||||
import forge.card.ability.SpellAbilityAi;
|
|
||||||
import forge.card.cost.Cost;
|
|
||||||
import forge.card.spellability.SpellAbility;
|
|
||||||
import forge.card.spellability.TargetRestrictions;
|
|
||||||
import forge.game.ai.ComputerUtilCard;
|
|
||||||
import forge.game.ai.ComputerUtilCombat;
|
|
||||||
import forge.game.ai.ComputerUtilCost;
|
|
||||||
import forge.game.ai.ComputerUtilMana;
|
|
||||||
import forge.game.player.Player;
|
|
||||||
import forge.game.zone.ZoneType;
|
|
||||||
import forge.util.MyRandom;
|
|
||||||
|
|
||||||
public class DamageAllAi extends SpellAbilityAi {
|
public class DamageAllAi extends SpellAbilityAi {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -29,12 +24,12 @@ public class DamageAllAi extends SpellAbilityAi {
|
|||||||
// based on what the expected targets could be
|
// based on what the expected targets could be
|
||||||
final Random r = MyRandom.getRandom();
|
final Random r = MyRandom.getRandom();
|
||||||
final Cost abCost = sa.getPayCosts();
|
final Cost abCost = sa.getPayCosts();
|
||||||
final Card source = sa.getSourceCard();
|
final Card source = sa.getHostCard();
|
||||||
|
|
||||||
String validP = "";
|
String validP = "";
|
||||||
|
|
||||||
final String damage = sa.getParam("NumDmg");
|
final String damage = sa.getParam("NumDmg");
|
||||||
int dmg = AbilityUtils.calculateAmount(sa.getSourceCard(), damage, sa);
|
int dmg = AbilityUtils.calculateAmount(sa.getHostCard(), damage, sa);
|
||||||
|
|
||||||
|
|
||||||
if (damage.equals("X") && sa.getSVar(damage).equals("Count$xPaid")) {
|
if (damage.equals("X") && sa.getSVar(damage).equals("Count$xPaid")) {
|
||||||
@@ -104,11 +99,11 @@ public class DamageAllAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
||||||
final Card source = sa.getSourceCard();
|
final Card source = sa.getHostCard();
|
||||||
String validP = "";
|
String validP = "";
|
||||||
|
|
||||||
final String damage = sa.getParam("NumDmg");
|
final String damage = sa.getParam("NumDmg");
|
||||||
int dmg = AbilityUtils.calculateAmount(sa.getSourceCard(), damage, sa);
|
int dmg = AbilityUtils.calculateAmount(sa.getHostCard(), damage, sa);
|
||||||
|
|
||||||
if (damage.equals("X") && sa.getSVar(damage).equals("Count$xPaid")) {
|
if (damage.equals("X") && sa.getSVar(damage).equals("Count$xPaid")) {
|
||||||
// Set PayX here to maximum value.
|
// Set PayX here to maximum value.
|
||||||
@@ -156,9 +151,9 @@ public class DamageAllAi extends SpellAbilityAi {
|
|||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param af
|
* @param af
|
||||||
* a {@link forge.card.ability.AbilityFactory} object.
|
* a {@link forge.game.ability.AbilityFactory} object.
|
||||||
* @param sa
|
* @param sa
|
||||||
* a {@link forge.card.spellability.SpellAbility} object.
|
* a {@link forge.game.spellability.SpellAbility} object.
|
||||||
* @param player
|
* @param player
|
||||||
* a {@link forge.game.player.Player} object.
|
* a {@link forge.game.player.Player} object.
|
||||||
* @param dmg
|
* @param dmg
|
||||||
@@ -166,7 +161,7 @@ public class DamageAllAi extends SpellAbilityAi {
|
|||||||
* @return a {@link forge.CardList} object.
|
* @return a {@link forge.CardList} object.
|
||||||
*/
|
*/
|
||||||
private List<Card> getKillableCreatures(final SpellAbility sa, final Player player, final int dmg) {
|
private List<Card> getKillableCreatures(final SpellAbility sa, final Player player, final int dmg) {
|
||||||
final Card source = sa.getSourceCard();
|
final Card source = sa.getHostCard();
|
||||||
String validC = sa.hasParam("ValidCards") ? sa.getParam("ValidCards") : "";
|
String validC = sa.hasParam("ValidCards") ? sa.getParam("ValidCards") : "";
|
||||||
|
|
||||||
// TODO: X may be something different than X paid
|
// TODO: X may be something different than X paid
|
||||||
@@ -188,11 +183,11 @@ public class DamageAllAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
final Card source = sa.getSourceCard();
|
final Card source = sa.getHostCard();
|
||||||
String validP = "";
|
String validP = "";
|
||||||
|
|
||||||
final String damage = sa.getParam("NumDmg");
|
final String damage = sa.getParam("NumDmg");
|
||||||
int dmg = AbilityUtils.calculateAmount(sa.getSourceCard(), damage, sa);
|
int dmg = AbilityUtils.calculateAmount(sa.getHostCard(), damage, sa);
|
||||||
|
|
||||||
if (damage.equals("X") && sa.getSVar(damage).equals("Count$xPaid")) {
|
if (damage.equals("X") && sa.getSVar(damage).equals("Count$xPaid")) {
|
||||||
// Set PayX here to maximum value.
|
// Set PayX here to maximum value.
|
||||||
576
forge-ai/src/main/java/forge/ai/ability/DamageDealAi.java
Normal file
576
forge-ai/src/main/java/forge/ai/ability/DamageDealAi.java
Normal file
@@ -0,0 +1,576 @@
|
|||||||
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
import com.google.common.base.Predicate;
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
|
||||||
|
import forge.ai.*;
|
||||||
|
import forge.game.Game;
|
||||||
|
import forge.game.GameObject;
|
||||||
|
import forge.game.ability.AbilityUtils;
|
||||||
|
import forge.game.card.Card;
|
||||||
|
import forge.game.card.CardLists;
|
||||||
|
import forge.game.card.CardPredicates;
|
||||||
|
import forge.game.cost.Cost;
|
||||||
|
import forge.game.phase.PhaseHandler;
|
||||||
|
import forge.game.phase.PhaseType;
|
||||||
|
import forge.game.player.Player;
|
||||||
|
import forge.game.spellability.AbilitySub;
|
||||||
|
import forge.game.spellability.SpellAbility;
|
||||||
|
import forge.game.spellability.TargetChoices;
|
||||||
|
import forge.game.spellability.TargetRestrictions;
|
||||||
|
import forge.game.zone.ZoneType;
|
||||||
|
import forge.util.Aggregates;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class DamageDealAi extends DamageAiBase {
|
||||||
|
@Override
|
||||||
|
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
||||||
|
final String damage = sa.getParam("NumDmg");
|
||||||
|
int dmg = AbilityUtils.calculateAmount(sa.getHostCard(), damage, sa);
|
||||||
|
|
||||||
|
final Card source = sa.getHostCard();
|
||||||
|
|
||||||
|
if (damage.equals("X") && sa.getSVar(damage).equals("Count$xPaid")) {
|
||||||
|
// Set PayX here to maximum value.
|
||||||
|
dmg = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||||
|
source.setSVar("PayX", Integer.toString(dmg));
|
||||||
|
}
|
||||||
|
if (!this.damageTargetAI(ai, sa, dmg)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||||
|
|
||||||
|
final Cost abCost = sa.getPayCosts();
|
||||||
|
final Card source = sa.getHostCard();
|
||||||
|
|
||||||
|
final String damage = sa.getParam("NumDmg");
|
||||||
|
int dmg = AbilityUtils.calculateAmount(sa.getHostCard(), damage, sa);
|
||||||
|
|
||||||
|
if (damage.equals("X") && sa.getSVar(damage).equals("Count$xPaid")) {
|
||||||
|
// Set PayX here to maximum value.
|
||||||
|
dmg = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||||
|
source.setSVar("PayX", Integer.toString(dmg));
|
||||||
|
}
|
||||||
|
String logic = sa.getParam("AILogic");
|
||||||
|
|
||||||
|
if ("DiscardLands".equals(logic)) {
|
||||||
|
dmg = 2;
|
||||||
|
} else if ("WildHunt".equals(logic)) {
|
||||||
|
// This dummy ability will just deal 0 damage, but holds the logic for the AI for Master of Wild Hunt
|
||||||
|
List<Card> wolves = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), "Creature.Wolf+untapped+YouCtrl+Other", ai, source);
|
||||||
|
dmg = Aggregates.sum(wolves, CardPredicates.Accessors.fnGetNetAttack);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dmg <= 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// temporarily disabled until better AI
|
||||||
|
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, null)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ComputerUtilCost.checkSacrificeCost(ai, abCost, source)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ComputerUtilCost.checkRemoveCounterCost(abCost, source)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ("DiscardLands".equals(sa.getParam("AILogic")) && !ComputerUtilCost.checkDiscardCost(ai, abCost, source)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.damageTargetAI(ai, sa, dmg)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (damage.equals("X") && source.getSVar(damage).equals("Count$xPaid")) {
|
||||||
|
// If I can kill my target by paying less mana, do it
|
||||||
|
if (sa.usesTargeting() && !sa.getTargets().isTargetingAnyPlayer() && !sa.hasParam("DividedAsYouChoose")) {
|
||||||
|
int actualPay = 0;
|
||||||
|
final boolean noPrevention = sa.hasParam("NoPrevention");
|
||||||
|
for (final Card c : sa.getTargets().getTargetCards()) {
|
||||||
|
final int adjDamage = ComputerUtilCombat.getEnoughDamageToKill(c, dmg, source, false, noPrevention);
|
||||||
|
if ((adjDamage > actualPay) && (adjDamage <= dmg)) {
|
||||||
|
actualPay = adjDamage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
source.setSVar("PayX", Integer.toString(actualPay));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* dealDamageChooseTgtC.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param d
|
||||||
|
* a int.
|
||||||
|
* @param noPrevention
|
||||||
|
* a boolean.
|
||||||
|
* @param pl
|
||||||
|
* a {@link forge.game.player.Player} object.
|
||||||
|
* @param mandatory
|
||||||
|
* a boolean.
|
||||||
|
* @return a {@link forge.game.card.Card} object.
|
||||||
|
*/
|
||||||
|
private Card dealDamageChooseTgtC(final Player ai, final SpellAbility sa, final int d, final boolean noPrevention,
|
||||||
|
final Player pl, final boolean mandatory) {
|
||||||
|
|
||||||
|
// wait until stack is empty (prevents duplicate kills)
|
||||||
|
if (!sa.isTrigger() && !ai.getGame().getStack().isEmpty()) {
|
||||||
|
//TODO:all removal APIs require a check to prevent duplicate kill/bounce/exile/etc.
|
||||||
|
// The original code is a blunt instrument that also blocks all use of removal as interrupts. The issue is
|
||||||
|
// with the AI not having code to consider what occurred previously in the stack thus it has no memory of
|
||||||
|
// removing a target already if something else is placed on top of the stack. A better solution is to place
|
||||||
|
// the checking mechanism after the target is chosen and determine if the topstack invalidates the earlier
|
||||||
|
// removal (shroud effect, pump against damage) so a new removal can/should be applied if possible.
|
||||||
|
//return null;
|
||||||
|
}
|
||||||
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
|
final Card source = sa.getHostCard();
|
||||||
|
List<Card> hPlay = CardLists.getValidCards(pl.getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), ai, source);
|
||||||
|
|
||||||
|
final List<GameObject> objects = Lists.newArrayList(sa.getTargets().getTargets());
|
||||||
|
if (sa.hasParam("TargetUnique")) {
|
||||||
|
objects.addAll(sa.getUniqueTargets());
|
||||||
|
}
|
||||||
|
for (final Object o : objects) {
|
||||||
|
if (o instanceof Card) {
|
||||||
|
final Card c = (Card) o;
|
||||||
|
if (hPlay.contains(c)) {
|
||||||
|
hPlay.remove(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
hPlay = CardLists.getTargetableCards(hPlay, sa);
|
||||||
|
|
||||||
|
final List<Card> killables = CardLists.filter(hPlay, new Predicate<Card>() {
|
||||||
|
@Override
|
||||||
|
public boolean apply(final Card c) {
|
||||||
|
return (ComputerUtilCombat.getEnoughDamageToKill(c, d, source, false, noPrevention) <= d) && !ComputerUtil.canRegenerate(ai, c)
|
||||||
|
&& !(c.getSVar("SacMe").length() > 0);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Card targetCard;
|
||||||
|
if (pl.isOpponentOf(ai) && !killables.isEmpty()) {
|
||||||
|
targetCard = ComputerUtilCard.getBestCreatureAI(killables);
|
||||||
|
|
||||||
|
return targetCard;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!mandatory) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hPlay.isEmpty()) {
|
||||||
|
if (pl.isOpponentOf(ai)) {
|
||||||
|
targetCard = ComputerUtilCard.getBestCreatureAI(hPlay);
|
||||||
|
} else {
|
||||||
|
targetCard = ComputerUtilCard.getWorstCreatureAI(hPlay);
|
||||||
|
}
|
||||||
|
|
||||||
|
return targetCard;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* damageTargetAI.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param saMe
|
||||||
|
* a {@link forge.game.spellability.SpellAbility} object.
|
||||||
|
* @param dmg
|
||||||
|
* a int.
|
||||||
|
* @return a boolean.
|
||||||
|
*/
|
||||||
|
private boolean damageTargetAI(final Player ai, final SpellAbility saMe, final int dmg) {
|
||||||
|
final TargetRestrictions tgt = saMe.getTargetRestrictions();
|
||||||
|
|
||||||
|
if (tgt == null) {
|
||||||
|
return this.damageChooseNontargeted(ai, saMe, dmg);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tgt.isRandomTarget()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.damageChoosingTargets(ai, saMe, tgt, dmg, false, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* damageChoosingTargets.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param sa
|
||||||
|
* a {@link forge.game.spellability.SpellAbility} object.
|
||||||
|
* @param tgt
|
||||||
|
* a {@link forge.game.spellability.TargetRestrictions} object.
|
||||||
|
* @param dmg
|
||||||
|
* a int.
|
||||||
|
* @param mandatory
|
||||||
|
* a boolean.
|
||||||
|
* @return a boolean.
|
||||||
|
*/
|
||||||
|
private boolean damageChoosingTargets(final Player ai, final SpellAbility sa, final TargetRestrictions tgt, int dmg,
|
||||||
|
final boolean isTrigger, final boolean mandatory) {
|
||||||
|
final Card source = sa.getHostCard();
|
||||||
|
final boolean noPrevention = sa.hasParam("NoPrevention");
|
||||||
|
final Game game = source.getGame();
|
||||||
|
final PhaseHandler phase = game.getPhaseHandler();
|
||||||
|
final boolean divided = sa.hasParam("DividedAsYouChoose");
|
||||||
|
final boolean oppTargetsChoice = sa.hasParam("TargetingPlayer");
|
||||||
|
|
||||||
|
if ("PowerDmg".equals(sa.getParam("AILogic"))) {
|
||||||
|
if (tgt.canTgtCreatureAndPlayer() && this.shouldTgtP(ai, sa, dmg, noPrevention)){
|
||||||
|
sa.resetTargets();
|
||||||
|
sa.getTargets().add(ai.getOpponent());
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// target loop
|
||||||
|
sa.resetTargets();
|
||||||
|
TargetChoices tcs = sa.getTargets();
|
||||||
|
Player enemy = ai.getOpponent();
|
||||||
|
|
||||||
|
if (tgt.getMaxTargets(source, sa) <= 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ("Polukranos".equals(sa.getParam("AILogic"))) {
|
||||||
|
int dmgTaken = 0;
|
||||||
|
List<Card> humCreatures = ai.getOpponent().getCreaturesInPlay();
|
||||||
|
Card lastTgt = null;
|
||||||
|
humCreatures = CardLists.getTargetableCards(humCreatures, sa);
|
||||||
|
ComputerUtilCard.sortByEvaluateCreature(humCreatures);
|
||||||
|
for (Card humanCreature : humCreatures) {
|
||||||
|
if (FightAi.canKill(humanCreature, source, dmgTaken)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
final int assignedDamage = ComputerUtilCombat.getEnoughDamageToKill(humanCreature, dmg, source, false, noPrevention);
|
||||||
|
if (assignedDamage <= dmg
|
||||||
|
&& humanCreature.getShield().isEmpty() && !ComputerUtil.canRegenerate(humanCreature.getController(), humanCreature)) {
|
||||||
|
tcs.add(humanCreature);
|
||||||
|
tgt.addDividedAllocation(humanCreature, assignedDamage);
|
||||||
|
lastTgt = humanCreature;
|
||||||
|
}
|
||||||
|
dmg -= assignedDamage;
|
||||||
|
if (!source.hasProtectionFrom(humanCreature)) {
|
||||||
|
dmgTaken += humanCreature.getNetAttack();
|
||||||
|
}
|
||||||
|
if (dmg <= 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (dmg > 0 && lastTgt != null) {
|
||||||
|
tgt.addDividedAllocation(lastTgt, tgt.getDividedValue(lastTgt) + dmg);
|
||||||
|
dmg = 0;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
//fall back into generic targeting due to failure to restrict targets in CountersPutAi or change in board state
|
||||||
|
}
|
||||||
|
while (tcs.getNumTargeted() < tgt.getMaxTargets(source, sa)) {
|
||||||
|
if (oppTargetsChoice && sa.getActivatingPlayer().equals(ai) && !sa.isTrigger()) {
|
||||||
|
// canPlayAI (sa activated by ai)
|
||||||
|
Player targetingPlayer = AbilityUtils.getDefinedPlayers(source, sa.getParam("TargetingPlayer"), sa).get(0);
|
||||||
|
sa.setTargetingPlayer(targetingPlayer);
|
||||||
|
return targetingPlayer.getController().chooseTargetsFor(sa);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tgt.canTgtCreatureAndPlayer()) {
|
||||||
|
|
||||||
|
if (this.shouldTgtP(ai, sa, dmg, noPrevention)) {
|
||||||
|
tcs.add(enemy);
|
||||||
|
if (divided) {
|
||||||
|
tgt.addDividedAllocation(enemy, dmg);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
final Card c = this.dealDamageChooseTgtC(ai, sa, dmg, noPrevention, enemy, false);
|
||||||
|
if (c != null) {
|
||||||
|
//option to hold removal instead only applies for single targeted removal
|
||||||
|
if (sa.isSpell() && tgt.getMaxTargets(sa.getHostCard(), sa) == 1 && !divided) {
|
||||||
|
if (!ComputerUtilCard.useRemovalNow(sa, c, dmg, ZoneType.Graveyard)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tcs.add(c);
|
||||||
|
if (divided) {
|
||||||
|
final int assignedDamage = ComputerUtilCombat.getEnoughDamageToKill(c, dmg, source, false, noPrevention);
|
||||||
|
if (assignedDamage <= dmg) {
|
||||||
|
tgt.addDividedAllocation(c, assignedDamage);
|
||||||
|
}
|
||||||
|
dmg = dmg - assignedDamage;
|
||||||
|
if (dmg <= 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// When giving priority to targeting Creatures for mandatory
|
||||||
|
// triggers
|
||||||
|
// feel free to add the Human after we run out of good targets
|
||||||
|
|
||||||
|
// TODO: add check here if card is about to die from something
|
||||||
|
// on the stack
|
||||||
|
// or from taking combat damage
|
||||||
|
boolean freePing = isTrigger || sa.getPayCosts() == null || sa.getTargets().getNumTargeted() > 0;
|
||||||
|
|
||||||
|
if (phase.is(PhaseType.END_OF_TURN) && sa.isAbility()) {
|
||||||
|
if (phase.getNextTurn().equals(ai))
|
||||||
|
freePing = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (phase.is(PhaseType.MAIN2) && sa.isAbility()) {
|
||||||
|
if (sa.getRestrictions().getPlaneswalker() || source.hasSVar("EndOfTurnLeavePlay"))
|
||||||
|
freePing = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
freePing |= ComputerUtil.playImmediately(ai, sa);
|
||||||
|
|
||||||
|
if (freePing && sa.canTarget(enemy)) {
|
||||||
|
tcs.add(enemy);
|
||||||
|
if (divided) {
|
||||||
|
tgt.addDividedAllocation(enemy, dmg);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (tgt.canTgtCreature()) {
|
||||||
|
final Card c = this.dealDamageChooseTgtC(ai, sa, dmg, noPrevention, enemy, mandatory);
|
||||||
|
if (c != null) {
|
||||||
|
//option to hold removal instead only applies for single targeted removal
|
||||||
|
if (sa.isSpell() && tgt.getMaxTargets(sa.getHostCard(), sa) == 1 && !divided) {
|
||||||
|
if (!ComputerUtilCard.useRemovalNow(sa, c, dmg, ZoneType.Graveyard)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tcs.add(c);
|
||||||
|
if (divided) {
|
||||||
|
final int assignedDamage = ComputerUtilCombat.getEnoughDamageToKill(c, dmg, source, false, noPrevention);
|
||||||
|
if (assignedDamage <= dmg) {
|
||||||
|
tgt.addDividedAllocation(c, assignedDamage);
|
||||||
|
} else {
|
||||||
|
tgt.addDividedAllocation(c, dmg);
|
||||||
|
}
|
||||||
|
dmg = dmg - assignedDamage;
|
||||||
|
if (dmg <= 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Improve Damage, we shouldn't just target the player just
|
||||||
|
// because we can
|
||||||
|
else if (sa.canTarget(enemy)) {
|
||||||
|
if ((phase.is(PhaseType.END_OF_TURN) && phase.getNextTurn().equals(ai))
|
||||||
|
|| (SpellAbilityAi.isSorcerySpeed(sa) && phase.is(PhaseType.MAIN2))
|
||||||
|
|| sa.getPayCosts() == null || isTrigger
|
||||||
|
|| this.shouldTgtP(ai, sa, dmg, noPrevention)) {
|
||||||
|
sa.getTargets().add(enemy);
|
||||||
|
if (divided) {
|
||||||
|
tgt.addDividedAllocation(enemy, dmg);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// fell through all the choices, no targets left?
|
||||||
|
if (sa.getTargets().getNumTargeted() < tgt.getMinTargets(source, sa) || sa.getTargets().getNumTargeted() == 0) {
|
||||||
|
if (!mandatory) {
|
||||||
|
sa.resetTargets();
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
// If the trigger is mandatory, gotta choose my own stuff now
|
||||||
|
return this.damageChooseRequiredTargets(ai, sa, tgt, dmg, mandatory);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// TODO is this good enough? for up to amounts?
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* damageChooseNontargeted.
|
||||||
|
* </p>
|
||||||
|
* @param ai
|
||||||
|
*
|
||||||
|
* @param saMe
|
||||||
|
* a {@link forge.game.spellability.SpellAbility} object.
|
||||||
|
* @param dmg
|
||||||
|
* a int.
|
||||||
|
* @return a boolean.
|
||||||
|
*/
|
||||||
|
private boolean damageChooseNontargeted(Player ai, final SpellAbility saMe, final int dmg) {
|
||||||
|
// TODO: Improve circumstances where the Defined Damage is unwanted
|
||||||
|
final List<GameObject> objects = AbilityUtils.getDefinedObjects(saMe.getHostCard(), saMe.getParam("Defined"), saMe);
|
||||||
|
boolean urgent = false; // can it wait?
|
||||||
|
boolean positive = false;
|
||||||
|
|
||||||
|
for (final Object o : objects) {
|
||||||
|
if (o instanceof Card) {
|
||||||
|
Card c = (Card) o;
|
||||||
|
final int restDamage = ComputerUtilCombat.predictDamageTo(c, dmg, saMe.getHostCard(), false);
|
||||||
|
if (!c.hasKeyword("Indestructible") && ComputerUtilCombat.getDamageToKill(c) <= restDamage) {
|
||||||
|
if (c.getController().equals(ai)) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
urgent = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (c.getController().isOpponentOf(ai) ^ c.getName().equals("Stuffy Doll")) {
|
||||||
|
positive = true;
|
||||||
|
}
|
||||||
|
} else if (o instanceof Player) {
|
||||||
|
final Player p = (Player) o;
|
||||||
|
final int restDamage = ComputerUtilCombat.predictDamageTo(p, dmg, saMe.getHostCard(), false);
|
||||||
|
if (!p.isOpponentOf(ai) && p.canLoseLife() && restDamage + 3 >= p.getLife() && restDamage > 0) {
|
||||||
|
// from this spell will kill me
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (p.isOpponentOf(ai) && p.canLoseLife()) {
|
||||||
|
positive = true;
|
||||||
|
if (p.getLife() + 3 <= restDamage) {
|
||||||
|
urgent = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!positive && !(saMe instanceof AbilitySub)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!urgent && !SpellAbilityAi.playReusable(ai, saMe)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* damageChooseRequiredTargets.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param sa
|
||||||
|
* a {@link forge.game.spellability.SpellAbility} object.
|
||||||
|
* @param tgt
|
||||||
|
* a {@link forge.game.spellability.TargetRestrictions} object.
|
||||||
|
* @param dmg
|
||||||
|
* a int.
|
||||||
|
* @param mandatory
|
||||||
|
* a boolean.
|
||||||
|
* @return a boolean.
|
||||||
|
*/
|
||||||
|
private boolean damageChooseRequiredTargets(final Player ai, final SpellAbility sa, final TargetRestrictions tgt, final int dmg,
|
||||||
|
final boolean mandatory) {
|
||||||
|
// this is for Triggered targets that are mandatory
|
||||||
|
final boolean noPrevention = sa.hasParam("NoPrevention");
|
||||||
|
final boolean divided = sa.hasParam("DividedAsYouChoose");
|
||||||
|
|
||||||
|
while (sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa)) {
|
||||||
|
// TODO: Consider targeting the planeswalker
|
||||||
|
if (tgt.canTgtCreature()) {
|
||||||
|
final Card c = this.dealDamageChooseTgtC(ai, sa, dmg, noPrevention, ai, mandatory);
|
||||||
|
if (c != null) {
|
||||||
|
sa.getTargets().add(c);
|
||||||
|
if (divided) {
|
||||||
|
tgt.addDividedAllocation(c, dmg);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sa.canTarget(ai)) {
|
||||||
|
if (sa.getTargets().add(ai)) {
|
||||||
|
if (divided) {
|
||||||
|
tgt.addDividedAllocation(ai, dmg);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if we get here then there isn't enough targets, this is the only
|
||||||
|
// time we can return false
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
|
|
||||||
|
final Card source = sa.getHostCard();
|
||||||
|
final String damage = sa.getParam("NumDmg");
|
||||||
|
int dmg = AbilityUtils.calculateAmount(source, damage, sa);
|
||||||
|
|
||||||
|
// Remove all damage
|
||||||
|
if (sa.hasParam("Remove")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (damage.equals("X") && sa.getSVar(damage).equals("Count$xPaid")) {
|
||||||
|
// Set PayX here to maximum value.
|
||||||
|
dmg = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||||
|
source.setSVar("PayX", Integer.toString(dmg));
|
||||||
|
}
|
||||||
|
|
||||||
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
|
if (tgt == null) {
|
||||||
|
// If it's not mandatory check a few things
|
||||||
|
if (!mandatory && !this.damageChooseNontargeted(ai, sa, dmg)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!this.damageChoosingTargets(ai, sa, tgt, dmg, true, mandatory) && !mandatory) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (damage.equals("X") && source.getSVar(damage).equals("Count$xPaid") && !sa.hasParam("DividedAsYouChoose")) {
|
||||||
|
// If I can kill my target by paying less mana, do it
|
||||||
|
int actualPay = 0;
|
||||||
|
final boolean noPrevention = sa.hasParam("NoPrevention");
|
||||||
|
|
||||||
|
//target is a player
|
||||||
|
if (!sa.getTargets().isTargetingAnyCard()) {
|
||||||
|
actualPay = dmg;
|
||||||
|
}
|
||||||
|
for (final Card c : sa.getTargets().getTargetCards()) {
|
||||||
|
final int adjDamage = ComputerUtilCombat.getEnoughDamageToKill(c, dmg, source, false, noPrevention);
|
||||||
|
if (adjDamage > actualPay) {
|
||||||
|
actualPay = adjDamage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
source.setSVar("PayX", Integer.toString(actualPay));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
package forge.card.ability.ai;
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
|
||||||
import forge.card.ability.AbilityUtils;
|
import forge.game.ability.AbilityUtils;
|
||||||
import forge.card.spellability.SpellAbility;
|
|
||||||
import forge.card.spellability.TargetRestrictions;
|
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
|
import forge.game.spellability.SpellAbility;
|
||||||
|
import forge.game.spellability.TargetRestrictions;
|
||||||
|
|
||||||
public class DamageEachAi extends DamageAiBase {
|
public class DamageEachAi extends DamageAiBase {
|
||||||
|
|
||||||
@@ -21,7 +21,7 @@ public class DamageEachAi extends DamageAiBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final String damage = sa.getParam("NumDmg");
|
final String damage = sa.getParam("NumDmg");
|
||||||
final int iDmg = AbilityUtils.calculateAmount(sa.getSourceCard(), damage, sa);
|
final int iDmg = AbilityUtils.calculateAmount(sa.getHostCard(), damage, sa);
|
||||||
return this.shouldTgtP(ai, sa, iDmg, false);
|
return this.shouldTgtP(ai, sa, iDmg, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1,33 +1,30 @@
|
|||||||
package forge.card.ability.ai;
|
package forge.ai.ability;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import forge.ai.*;
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import forge.Card;
|
|
||||||
import forge.CardLists;
|
|
||||||
import forge.CardPredicates;
|
|
||||||
import forge.ITargetable;
|
|
||||||
import forge.card.ability.AbilityUtils;
|
|
||||||
import forge.card.ability.SpellAbilityAi;
|
|
||||||
import forge.card.cost.Cost;
|
|
||||||
import forge.card.spellability.SpellAbility;
|
|
||||||
import forge.card.spellability.TargetRestrictions;
|
|
||||||
import forge.game.Game;
|
import forge.game.Game;
|
||||||
import forge.game.ai.ComputerUtil;
|
import forge.game.GameObject;
|
||||||
import forge.game.ai.ComputerUtilCard;
|
import forge.game.ability.AbilityUtils;
|
||||||
import forge.game.ai.ComputerUtilCombat;
|
import forge.game.card.Card;
|
||||||
import forge.game.ai.ComputerUtilCost;
|
import forge.game.card.CardLists;
|
||||||
|
import forge.game.card.CardPredicates;
|
||||||
import forge.game.combat.Combat;
|
import forge.game.combat.Combat;
|
||||||
|
import forge.game.cost.Cost;
|
||||||
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.spellability.SpellAbility;
|
||||||
|
import forge.game.spellability.TargetChoices;
|
||||||
|
import forge.game.spellability.TargetRestrictions;
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
public class DamagePreventAi extends SpellAbilityAi {
|
public class DamagePreventAi extends SpellAbilityAi {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||||
final Card hostCard = sa.getSourceCard();
|
final Card hostCard = sa.getHostCard();
|
||||||
final Game game = ai.getGame();
|
final Game game = ai.getGame();
|
||||||
final Combat combat = game.getCombat();
|
final Combat combat = game.getCombat();
|
||||||
boolean chance = false;
|
boolean chance = false;
|
||||||
@@ -55,11 +52,11 @@ public class DamagePreventAi extends SpellAbilityAi {
|
|||||||
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<ITargetable> objects = AbilityUtils.getDefinedObjects(sa.getSourceCard(), sa.getParam("Defined"), sa);
|
final List<GameObject> objects = AbilityUtils.getDefinedObjects(sa.getHostCard(), sa.getParam("Defined"), sa);
|
||||||
|
|
||||||
// react to threats on the stack
|
// react to threats on the stack
|
||||||
if (!game.getStack().isEmpty()) {
|
if (!game.getStack().isEmpty()) {
|
||||||
final List<ITargetable> threatenedObjects = ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), sa);
|
final List<GameObject> threatenedObjects = ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), sa);
|
||||||
for (final Object o : objects) {
|
for (final Object o : objects) {
|
||||||
if (threatenedObjects.contains(o)) {
|
if (threatenedObjects.contains(o)) {
|
||||||
chance = true;
|
chance = true;
|
||||||
@@ -93,11 +90,12 @@ public class DamagePreventAi extends SpellAbilityAi {
|
|||||||
// react to threats on the stack
|
// react to threats on the stack
|
||||||
else if (!game.getStack().isEmpty()) {
|
else if (!game.getStack().isEmpty()) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
|
final TargetChoices tcs = sa.getTargets();
|
||||||
// check stack for something on the stack will kill anything i control
|
// check stack for something on the stack will kill anything i control
|
||||||
final List<ITargetable> objects = ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), sa);
|
final List<GameObject> objects = ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), sa);
|
||||||
|
|
||||||
if (objects.contains(ai)) {
|
if (objects.contains(ai)) {
|
||||||
sa.getTargets().add(ai);
|
tcs.add(ai);
|
||||||
chance = true;
|
chance = true;
|
||||||
}
|
}
|
||||||
final List<Card> threatenedTargets = new ArrayList<Card>();
|
final List<Card> threatenedTargets = new ArrayList<Card>();
|
||||||
@@ -113,17 +111,18 @@ public class DamagePreventAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
if (!threatenedTargets.isEmpty()) {
|
if (!threatenedTargets.isEmpty()) {
|
||||||
// Choose "best" of the remaining to save
|
// Choose "best" of the remaining to save
|
||||||
sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(threatenedTargets));
|
tcs.add(ComputerUtilCard.getBestCreatureAI(threatenedTargets));
|
||||||
chance = true;
|
chance = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // Protect combatants
|
} // Protect combatants
|
||||||
else if (game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
else if (game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
|
final TargetChoices tcs = sa.getTargets();
|
||||||
if (sa.canTarget(ai) && ComputerUtilCombat.wouldLoseLife(ai, combat)
|
if (sa.canTarget(ai) && ComputerUtilCombat.wouldLoseLife(ai, combat)
|
||||||
&& (ComputerUtilCombat.lifeInDanger(ai, combat) || sa.isAbility() || sa.isTrigger())
|
&& (ComputerUtilCombat.lifeInDanger(ai, combat) || sa.isAbility() || sa.isTrigger())
|
||||||
&& game.getPhaseHandler().getPlayerTurn().isOpponentOf(ai)) {
|
&& game.getPhaseHandler().getPlayerTurn().isOpponentOf(ai)) {
|
||||||
sa.getTargets().add(ai);
|
tcs.add(ai);
|
||||||
chance = true;
|
chance = true;
|
||||||
} else {
|
} else {
|
||||||
// filter AIs battlefield by what I can target
|
// filter AIs battlefield by what I can target
|
||||||
@@ -135,18 +134,18 @@ public class DamagePreventAi extends SpellAbilityAi {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
final List<Card> combatants = CardLists.filter(targetables, CardPredicates.Presets.CREATURES);
|
final List<Card> combatants = CardLists.filter(targetables, CardPredicates.Presets.CREATURES);
|
||||||
CardLists.sortByEvaluateCreature(combatants);
|
ComputerUtilCard.sortByEvaluateCreature(combatants);
|
||||||
|
|
||||||
for (final Card c : combatants) {
|
for (final Card c : combatants) {
|
||||||
if (ComputerUtilCombat.combatantWouldBeDestroyed(ai, c, combat)) {
|
if (ComputerUtilCombat.combatantWouldBeDestroyed(ai, c, combat) && tcs.getNumTargeted() < tgt.getMaxTargets(hostCard, sa)) {
|
||||||
sa.getTargets().add(c);
|
tcs.add(c);
|
||||||
chance = true;
|
chance = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (sa.hasParam("DividedAsYouChoose") && sa.getTargets() != null && !sa.getTargets().getTargets().isEmpty()) {
|
if (sa.hasParam("DividedAsYouChoose") && sa.getTargets() != null && !sa.getTargets().getTargets().isEmpty()) {
|
||||||
tgt.addDividedAllocation(sa.getTargets().getTargets().get(0), AbilityUtils.calculateAmount(sa.getSourceCard(), sa.getParam("Amount"), sa));
|
tgt.addDividedAllocation(sa.getTargets().getTargets().get(0), AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("Amount"), sa));
|
||||||
}
|
}
|
||||||
|
|
||||||
return chance;
|
return chance;
|
||||||
@@ -172,9 +171,9 @@ public class DamagePreventAi extends SpellAbilityAi {
|
|||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param af
|
* @param af
|
||||||
* a {@link forge.card.ability.AbilityFactory} object.
|
* a {@link forge.game.ability.AbilityFactory} object.
|
||||||
* @param sa
|
* @param sa
|
||||||
* a {@link forge.card.spellability.SpellAbility} object.
|
* a {@link forge.game.spellability.SpellAbility} object.
|
||||||
* @param mandatory
|
* @param mandatory
|
||||||
* a boolean.
|
* a boolean.
|
||||||
* @return a boolean.
|
* @return a boolean.
|
||||||
@@ -185,7 +184,7 @@ public class DamagePreventAi extends SpellAbilityAi {
|
|||||||
// filter AIs battlefield by what I can target
|
// filter AIs battlefield by what I can target
|
||||||
final Game game = ai.getGame();
|
final Game game = ai.getGame();
|
||||||
List<Card> targetables = game.getCardsIn(ZoneType.Battlefield);
|
List<Card> targetables = game.getCardsIn(ZoneType.Battlefield);
|
||||||
targetables = CardLists.getValidCards(targetables, tgt.getValidTgts(), ai, sa.getSourceCard());
|
targetables = CardLists.getValidCards(targetables, tgt.getValidTgts(), ai, sa.getHostCard());
|
||||||
final List<Card> compTargetables = CardLists.filterControlledBy(targetables, ai);
|
final List<Card> compTargetables = CardLists.filterControlledBy(targetables, ai);
|
||||||
Card target = null;
|
Card target = null;
|
||||||
|
|
||||||
@@ -199,7 +198,7 @@ public class DamagePreventAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
if (!compTargetables.isEmpty()) {
|
if (!compTargetables.isEmpty()) {
|
||||||
final List<Card> combatants = CardLists.filter(compTargetables, CardPredicates.Presets.CREATURES);
|
final List<Card> combatants = CardLists.filter(compTargetables, CardPredicates.Presets.CREATURES);
|
||||||
CardLists.sortByEvaluateCreature(combatants);
|
ComputerUtilCard.sortByEvaluateCreature(combatants);
|
||||||
if (game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
if (game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
||||||
Combat combat = game.getCombat();
|
Combat combat = game.getCombat();
|
||||||
for (final Card c : combatants) {
|
for (final Card c : combatants) {
|
||||||
@@ -217,7 +216,7 @@ public class DamagePreventAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
sa.getTargets().add(target);
|
sa.getTargets().add(target);
|
||||||
if (sa.hasParam("DividedAsYouChoose")) {
|
if (sa.hasParam("DividedAsYouChoose")) {
|
||||||
tgt.addDividedAllocation(target, AbilityUtils.calculateAmount(sa.getSourceCard(), sa.getParam("Amount"), sa));
|
tgt.addDividedAllocation(target, AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("Amount"), sa));
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
package forge.card.ability.ai;
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
|
||||||
import forge.Card;
|
import forge.ai.ComputerUtilCost;
|
||||||
import forge.card.ability.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.card.cost.Cost;
|
import forge.game.card.Card;
|
||||||
import forge.card.spellability.SpellAbility;
|
import forge.game.cost.Cost;
|
||||||
import forge.game.ai.ComputerUtilCost;
|
|
||||||
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;
|
||||||
|
|
||||||
public class DamagePreventAllAi extends SpellAbilityAi {
|
public class DamagePreventAllAi extends SpellAbilityAi {
|
||||||
|
|
||||||
@@ -16,7 +16,7 @@ public class DamagePreventAllAi extends SpellAbilityAi {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||||
final Card hostCard = sa.getSourceCard();
|
final Card hostCard = sa.getHostCard();
|
||||||
boolean chance = false;
|
boolean chance = false;
|
||||||
|
|
||||||
final Cost cost = sa.getPayCosts();
|
final Cost cost = sa.getPayCosts();
|
||||||
@@ -1,30 +1,29 @@
|
|||||||
package forge.card.ability.ai;
|
package forge.ai.ability;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
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 com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
|
|
||||||
import forge.Card;
|
import forge.ai.ComputerUtilCard;
|
||||||
import forge.CardLists;
|
import forge.ai.ComputerUtilCost;
|
||||||
import forge.card.ability.AbilityUtils;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.card.ability.SpellAbilityAi;
|
|
||||||
import forge.card.cost.Cost;
|
|
||||||
import forge.card.spellability.SpellAbility;
|
|
||||||
import forge.card.spellability.SpellAbilityRestriction;
|
|
||||||
import forge.card.spellability.TargetRestrictions;
|
|
||||||
import forge.game.Game;
|
import forge.game.Game;
|
||||||
import forge.game.ai.ComputerUtilCard;
|
import forge.game.ability.AbilityUtils;
|
||||||
import forge.game.ai.ComputerUtilCost;
|
import forge.game.card.Card;
|
||||||
|
import forge.game.card.CardLists;
|
||||||
import forge.game.combat.Combat;
|
import forge.game.combat.Combat;
|
||||||
|
import forge.game.cost.Cost;
|
||||||
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.spellability.SpellAbility;
|
||||||
|
import forge.game.spellability.TargetRestrictions;
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
public class DebuffAi extends SpellAbilityAi {
|
public class DebuffAi extends SpellAbilityAi {
|
||||||
// *************************************************************************
|
// *************************************************************************
|
||||||
// ***************************** Debuff ************************************
|
// ***************************** Debuff ************************************
|
||||||
@@ -33,7 +32,7 @@ public class DebuffAi extends SpellAbilityAi {
|
|||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(final Player ai, final SpellAbility sa) {
|
protected boolean canPlayAI(final Player ai, final SpellAbility sa) {
|
||||||
// if there is no target and host card isn't in play, don't activate
|
// if there is no target and host card isn't in play, don't activate
|
||||||
final Card source = sa.getSourceCard();
|
final Card source = sa.getHostCard();
|
||||||
final Game game = ai.getGame();
|
final Game game = ai.getGame();
|
||||||
if ((sa.getTargetRestrictions() == null) && !source.isInPlay()) {
|
if ((sa.getTargetRestrictions() == null) && !source.isInPlay()) {
|
||||||
return false;
|
return false;
|
||||||
@@ -54,7 +53,6 @@ public class DebuffAi extends SpellAbilityAi {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
final SpellAbilityRestriction restrict = sa.getRestrictions();
|
|
||||||
final PhaseHandler ph = game.getPhaseHandler();
|
final PhaseHandler ph = game.getPhaseHandler();
|
||||||
|
|
||||||
// Phase Restrictions
|
// Phase Restrictions
|
||||||
@@ -68,15 +66,8 @@ public class DebuffAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final int activations = restrict.getNumberTurnActivations();
|
|
||||||
final int sacActivations = restrict.getActivationNumberSacrifice();
|
|
||||||
// don't risk sacrificing a creature just to pump it
|
|
||||||
if ((sacActivations != -1) && (activations >= (sacActivations - 1))) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!sa.usesTargeting() || !sa.getTargetRestrictions().doesTarget()) {
|
if (!sa.usesTargeting() || !sa.getTargetRestrictions().doesTarget()) {
|
||||||
List<Card> cards = AbilityUtils.getDefinedCards(sa.getSourceCard(), sa.getParam("Defined"), sa);
|
List<Card> cards = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("Defined"), sa);
|
||||||
|
|
||||||
|
|
||||||
final Combat combat = game.getCombat();
|
final Combat combat = game.getCombat();
|
||||||
@@ -117,9 +108,9 @@ public class DebuffAi extends SpellAbilityAi {
|
|||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param af
|
* @param af
|
||||||
* a {@link forge.card.ability.AbilityFactory} object.
|
* a {@link forge.game.ability.AbilityFactory} object.
|
||||||
* @param sa
|
* @param sa
|
||||||
* a {@link forge.card.spellability.SpellAbility} object.
|
* a {@link forge.game.spellability.SpellAbility} object.
|
||||||
* @param kws
|
* @param kws
|
||||||
* a {@link java.util.ArrayList} object.
|
* a {@link java.util.ArrayList} object.
|
||||||
* @param mandatory
|
* @param mandatory
|
||||||
@@ -135,7 +126,7 @@ public class DebuffAi extends SpellAbilityAi {
|
|||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
List<Card> list = getCurseCreatures(ai, sa, kws == null ? Lists.<String>newArrayList() : kws);
|
List<Card> list = getCurseCreatures(ai, sa, kws == null ? Lists.<String>newArrayList() : kws);
|
||||||
list = CardLists.getValidCards(list, tgt.getValidTgts(), sa.getActivatingPlayer(), sa.getSourceCard());
|
list = CardLists.getValidCards(list, tgt.getValidTgts(), sa.getActivatingPlayer(), sa.getHostCard());
|
||||||
|
|
||||||
// several uses here:
|
// several uses here:
|
||||||
// 1. make human creatures lose evasion when they are attacking
|
// 1. make human creatures lose evasion when they are attacking
|
||||||
@@ -148,12 +139,12 @@ public class DebuffAi extends SpellAbilityAi {
|
|||||||
return mandatory && debuffMandatoryTarget(ai, sa, mandatory);
|
return mandatory && debuffMandatoryTarget(ai, sa, mandatory);
|
||||||
}
|
}
|
||||||
|
|
||||||
while (sa.getTargets().getNumTargeted() < tgt.getMaxTargets(sa.getSourceCard(), sa)) {
|
while (sa.getTargets().getNumTargeted() < tgt.getMaxTargets(sa.getHostCard(), sa)) {
|
||||||
Card t = null;
|
Card t = null;
|
||||||
// boolean goodt = false;
|
// boolean goodt = false;
|
||||||
|
|
||||||
if (list.isEmpty()) {
|
if (list.isEmpty()) {
|
||||||
if ((sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getSourceCard(), sa)) || (sa.getTargets().getNumTargeted() == 0)) {
|
if ((sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa)) || (sa.getTargets().getNumTargeted() == 0)) {
|
||||||
if (mandatory) {
|
if (mandatory) {
|
||||||
return debuffMandatoryTarget(ai, sa, mandatory);
|
return debuffMandatoryTarget(ai, sa, mandatory);
|
||||||
}
|
}
|
||||||
@@ -180,9 +171,9 @@ public class DebuffAi extends SpellAbilityAi {
|
|||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param af
|
* @param af
|
||||||
* a {@link forge.card.ability.AbilityFactory} object.
|
* a {@link forge.game.ability.AbilityFactory} object.
|
||||||
* @param sa
|
* @param sa
|
||||||
* a {@link forge.card.spellability.SpellAbility} object.
|
* a {@link forge.game.spellability.SpellAbility} object.
|
||||||
* @param kws
|
* @param kws
|
||||||
* a {@link java.util.ArrayList} object.
|
* a {@link java.util.ArrayList} object.
|
||||||
* @return a {@link forge.CardList} object.
|
* @return a {@link forge.CardList} object.
|
||||||
@@ -211,9 +202,9 @@ public class DebuffAi extends SpellAbilityAi {
|
|||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param af
|
* @param af
|
||||||
* a {@link forge.card.ability.AbilityFactory} object.
|
* a {@link forge.game.ability.AbilityFactory} object.
|
||||||
* @param sa
|
* @param sa
|
||||||
* a {@link forge.card.spellability.SpellAbility} object.
|
* a {@link forge.game.spellability.SpellAbility} object.
|
||||||
* @param mandatory
|
* @param mandatory
|
||||||
* a boolean.
|
* a boolean.
|
||||||
* @return a boolean.
|
* @return a boolean.
|
||||||
@@ -221,9 +212,9 @@ public class DebuffAi extends SpellAbilityAi {
|
|||||||
private boolean debuffMandatoryTarget(final Player ai, final SpellAbility sa, final boolean mandatory) {
|
private boolean debuffMandatoryTarget(final Player ai, final SpellAbility sa, final boolean mandatory) {
|
||||||
List<Card> list = ai.getGame().getCardsIn(ZoneType.Battlefield);
|
List<Card> list = ai.getGame().getCardsIn(ZoneType.Battlefield);
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
list = CardLists.getValidCards(list, tgt.getValidTgts(), sa.getActivatingPlayer(), sa.getSourceCard());
|
list = CardLists.getValidCards(list, tgt.getValidTgts(), sa.getActivatingPlayer(), sa.getHostCard());
|
||||||
|
|
||||||
if (list.size() < tgt.getMinTargets(sa.getSourceCard(), sa)) {
|
if (list.size() < tgt.getMinTargets(sa.getHostCard(), sa)) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -235,7 +226,7 @@ public class DebuffAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
final List<Card> pref = CardLists.filterControlledBy(list, ai.getOpponent());
|
final List<Card> pref = CardLists.filterControlledBy(list, ai.getOpponent());
|
||||||
final List<Card> forced = CardLists.filterControlledBy(list, ai);
|
final List<Card> forced = CardLists.filterControlledBy(list, ai);
|
||||||
final Card source = sa.getSourceCard();
|
final Card source = sa.getHostCard();
|
||||||
|
|
||||||
while (sa.getTargets().getNumTargeted() < tgt.getMaxTargets(source, sa)) {
|
while (sa.getTargets().getNumTargeted() < tgt.getMaxTargets(source, sa)) {
|
||||||
if (pref.isEmpty()) {
|
if (pref.isEmpty()) {
|
||||||
@@ -254,7 +245,7 @@ public class DebuffAi extends SpellAbilityAi {
|
|||||||
sa.getTargets().add(c);
|
sa.getTargets().add(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
while (sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getSourceCard(), sa)) {
|
while (sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa)) {
|
||||||
if (forced.isEmpty()) {
|
if (forced.isEmpty()) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -273,7 +264,7 @@ public class DebuffAi extends SpellAbilityAi {
|
|||||||
sa.getTargets().add(c);
|
sa.getTargets().add(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getSourceCard(), sa)) {
|
if (sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa)) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -1,27 +1,26 @@
|
|||||||
package forge.card.ability.ai;
|
package forge.ai.ability;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Random;
|
|
||||||
|
|
||||||
import com.google.common.base.Predicate;
|
import com.google.common.base.Predicate;
|
||||||
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.Card;
|
import forge.game.card.Card;
|
||||||
import forge.CardLists;
|
import forge.game.card.CardLists;
|
||||||
import forge.card.ability.SpellAbilityAi;
|
|
||||||
import forge.card.spellability.SpellAbility;
|
|
||||||
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;
|
||||||
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
import forge.util.MyRandom;
|
import forge.util.MyRandom;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
public class DebuffAllAi extends SpellAbilityAi {
|
public class DebuffAllAi extends SpellAbilityAi {
|
||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||||
String valid = "";
|
String valid = "";
|
||||||
final Random r = MyRandom.getRandom();
|
final Random r = MyRandom.getRandom();
|
||||||
// final Card source = sa.getSourceCard();
|
// final Card source = sa.getHostCard();
|
||||||
final Card hostCard = sa.getSourceCard();
|
final Card hostCard = sa.getHostCard();
|
||||||
final Player opp = ai.getOpponent();
|
final Player opp = ai.getOpponent();
|
||||||
|
|
||||||
final boolean chance = r.nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn()); // to
|
final boolean chance = r.nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn()); // to
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
import forge.ai.AiController;
|
||||||
|
import forge.ai.AiPlayDecision;
|
||||||
|
import forge.ai.PlayerControllerAi;
|
||||||
|
import forge.ai.SpellAbilityAi;
|
||||||
|
import forge.ai.SpellApiToAi;
|
||||||
|
import forge.game.ability.AbilityFactory;
|
||||||
|
import forge.game.player.Player;
|
||||||
|
import forge.game.spellability.AbilitySub;
|
||||||
|
import forge.game.spellability.SpellAbility;
|
||||||
|
|
||||||
|
public class DelayedTriggerAi extends SpellAbilityAi {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
||||||
|
if ("Always".equals(sa.getParam("AILogic"))) {
|
||||||
|
// TODO: improve ai
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
final String svarName = sa.getParam("Execute");
|
||||||
|
final SpellAbility trigsa = AbilityFactory.getAbility(sa.getHostCard().getSVar(svarName), sa.getHostCard());
|
||||||
|
trigsa.setActivatingPlayer(ai);
|
||||||
|
|
||||||
|
if (trigsa instanceof AbilitySub) {
|
||||||
|
return SpellApiToAi.Converter.get(((AbilitySub) trigsa).getApi()).chkDrawbackWithSubs(ai, (AbilitySub)trigsa);
|
||||||
|
} else {
|
||||||
|
return AiPlayDecision.WillPlay == ((PlayerControllerAi)ai.getController()).getAi().canPlaySa(trigsa);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
|
final String svarName = sa.getParam("Execute");
|
||||||
|
final SpellAbility trigsa = AbilityFactory.getAbility(sa.getHostCard().getSVar(svarName), sa.getHostCard());
|
||||||
|
AiController aic = ((PlayerControllerAi)ai.getController()).getAi();
|
||||||
|
trigsa.setActivatingPlayer(ai);
|
||||||
|
|
||||||
|
if (!sa.hasParam("OptionalDecider")) {
|
||||||
|
return aic.doTrigger(trigsa, true);
|
||||||
|
} else {
|
||||||
|
return aic.doTrigger(trigsa, !sa.getParam("OptionalDecider").equals("You"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||||
|
final String svarName = sa.getParam("Execute");
|
||||||
|
final SpellAbility trigsa = AbilityFactory.getAbility(sa.getHostCard().getSVar(svarName), sa.getHostCard());
|
||||||
|
trigsa.setActivatingPlayer(ai);
|
||||||
|
return AiPlayDecision.WillPlay == ((PlayerControllerAi)ai.getController()).getAi().canPlaySa(trigsa);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
299
forge-ai/src/main/java/forge/ai/ability/DestroyAi.java
Normal file
299
forge-ai/src/main/java/forge/ai/ability/DestroyAi.java
Normal file
@@ -0,0 +1,299 @@
|
|||||||
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
import com.google.common.base.Predicate;
|
||||||
|
|
||||||
|
import forge.ai.ComputerUtil;
|
||||||
|
import forge.ai.ComputerUtilCard;
|
||||||
|
import forge.ai.ComputerUtilCost;
|
||||||
|
import forge.ai.SpellAbilityAi;
|
||||||
|
import forge.game.ability.AbilityUtils;
|
||||||
|
import forge.game.ability.ApiType;
|
||||||
|
import forge.game.card.Card;
|
||||||
|
import forge.game.card.CardLists;
|
||||||
|
import forge.game.card.CounterType;
|
||||||
|
import forge.game.cost.Cost;
|
||||||
|
import forge.game.cost.CostPart;
|
||||||
|
import forge.game.cost.CostSacrifice;
|
||||||
|
import forge.game.player.Player;
|
||||||
|
import forge.game.spellability.SpellAbility;
|
||||||
|
import forge.game.spellability.TargetRestrictions;
|
||||||
|
import forge.game.zone.ZoneType;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class DestroyAi extends SpellAbilityAi {
|
||||||
|
|
||||||
|
/* (non-Javadoc)
|
||||||
|
* @see forge.card.abilityfactory.SpellAiLogic#chkAIDrawback(java.util.Map, forge.card.spellability.SpellAbility, forge.game.player.Player)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
||||||
|
return canPlayAI(ai, sa);
|
||||||
|
}
|
||||||
|
/* (non-Javadoc)
|
||||||
|
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected boolean canPlayAI(final Player ai, SpellAbility sa) {
|
||||||
|
// AI needs to be expanded, since this function can be pretty complex
|
||||||
|
// based on what the expected targets could be
|
||||||
|
final Cost abCost = sa.getPayCosts();
|
||||||
|
final TargetRestrictions abTgt = sa.getTargetRestrictions();
|
||||||
|
final Card source = sa.getHostCard();
|
||||||
|
final boolean noRegen = sa.hasParam("NoRegen");
|
||||||
|
final String logic = sa.getParam("AILogic");
|
||||||
|
List<Card> list;
|
||||||
|
|
||||||
|
if (abCost != null) {
|
||||||
|
if (!ComputerUtilCost.checkSacrificeCost(ai, abCost, source)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, null)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ComputerUtilCost.checkDiscardCost(ai, abCost, source)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Targeting
|
||||||
|
if (abTgt != null) {
|
||||||
|
sa.resetTargets();
|
||||||
|
if (sa.hasParam("TargetingPlayer")) {
|
||||||
|
Player targetingPlayer = AbilityUtils.getDefinedPlayers(source, sa.getParam("TargetingPlayer"), sa).get(0);
|
||||||
|
sa.setTargetingPlayer(targetingPlayer);
|
||||||
|
return targetingPlayer.getController().chooseTargetsFor(sa);
|
||||||
|
}
|
||||||
|
list = CardLists.getTargetableCards(ai.getOpponent().getCardsIn(ZoneType.Battlefield), sa);
|
||||||
|
list = CardLists.getValidCards(list, abTgt.getValidTgts(), source.getController(), source);
|
||||||
|
if (sa.hasParam("AITgts")) {
|
||||||
|
list = CardLists.getValidCards(list, sa.getParam("AITgts"), sa.getActivatingPlayer(), source);
|
||||||
|
}
|
||||||
|
list = CardLists.getNotKeyword(list, "Indestructible");
|
||||||
|
if (!SpellAbilityAi.playReusable(ai, sa)) {
|
||||||
|
list = CardLists.filter(list, new Predicate<Card>() {
|
||||||
|
@Override
|
||||||
|
public boolean apply(final Card c) {
|
||||||
|
//Check for cards that can be sacrificed in response
|
||||||
|
for (final SpellAbility ability : c.getAllSpellAbilities()) {
|
||||||
|
if (ability.isAbility()) {
|
||||||
|
final Cost cost = ability.getPayCosts();
|
||||||
|
for (final CostPart part : cost.getCostParts()) {
|
||||||
|
if (!(part instanceof CostSacrifice)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
CostSacrifice sacCost = (CostSacrifice) part;
|
||||||
|
if (sacCost.payCostFromSource() && ComputerUtilCost.canPayCost(ability, c.getController())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (c.hasSVar("SacMe")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
//Check for undying
|
||||||
|
return (!c.hasKeyword("Undying") || c.getCounters(CounterType.P1P1) > 0);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// If NoRegen is not set, filter out creatures that have a
|
||||||
|
// regeneration shield
|
||||||
|
if (!noRegen) {
|
||||||
|
// TODO filter out things that might be tougher?
|
||||||
|
list = CardLists.filter(list, new Predicate<Card>() {
|
||||||
|
@Override
|
||||||
|
public boolean apply(final Card c) {
|
||||||
|
return (c.getShield().isEmpty() && !ComputerUtil.canRegenerate(ai, c));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (list.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// target loop
|
||||||
|
while (sa.getTargets().getNumTargeted() < abTgt.getMaxTargets(sa.getHostCard(), sa)) {
|
||||||
|
if (list.size() == 0) {
|
||||||
|
if ((sa.getTargets().getNumTargeted() < abTgt.getMinTargets(sa.getHostCard(), sa))
|
||||||
|
|| (sa.getTargets().getNumTargeted() == 0)) {
|
||||||
|
sa.resetTargets();
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
// TODO is this good enough? for up to amounts?
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Card choice = null;
|
||||||
|
// If the targets are only of one type, take the best
|
||||||
|
if (CardLists.getNotType(list, "Creature").isEmpty()) {
|
||||||
|
choice = ComputerUtilCard.getBestCreatureAI(list);
|
||||||
|
if ("OppDestroyYours".equals(logic)) {
|
||||||
|
Card aiBest = ComputerUtilCard.getBestCreatureAI(ai.getCreaturesInPlay());
|
||||||
|
if (ComputerUtilCard.evaluateCreature(aiBest) > ComputerUtilCard.evaluateCreature(choice) - 40) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (CardLists.getNotType(list, "Land").isEmpty()) {
|
||||||
|
choice = ComputerUtilCard.getBestLandAI(list);
|
||||||
|
} else {
|
||||||
|
choice = ComputerUtilCard.getMostExpensivePermanentAI(list, sa, true);
|
||||||
|
}
|
||||||
|
//option to hold removal instead only applies for single targeted removal
|
||||||
|
if (sa.isSpell() && abTgt.getMaxTargets(sa.getHostCard(), sa) == 1) {
|
||||||
|
if (!ComputerUtilCard.useRemovalNow(sa, choice, 0, ZoneType.Graveyard)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (choice == null) { // can't find anything left
|
||||||
|
if ((sa.getTargets().getNumTargeted() < abTgt.getMinTargets(sa.getHostCard(), sa))
|
||||||
|
|| (sa.getTargets().getNumTargeted() == 0)) {
|
||||||
|
sa.resetTargets();
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
// TODO is this good enough? for up to amounts?
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Don't destroy stolen permanents when the stealing aura can be destroyed
|
||||||
|
if (choice.getOwner() == ai) {
|
||||||
|
for (Card aura : choice.getEnchantedBy()) {
|
||||||
|
SpellAbility sp = aura.getFirstSpellAbility();
|
||||||
|
if (sp != null && "GainControl".equals(sp.getParam("AILogic"))
|
||||||
|
&& aura.getController() != ai && sa.canTarget(aura)) {
|
||||||
|
choice = aura;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
list.remove(choice);
|
||||||
|
sa.getTargets().add(choice);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (sa.hasParam("Defined")) {
|
||||||
|
list = new ArrayList<Card>(AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa));
|
||||||
|
if ("WillSkipTurn".equals(logic) && !sa.getHostCard().getController().equals(ai)
|
||||||
|
&& (ai.getCreaturesInPlay().size() >= ai.getOpponent().getCreaturesInPlay().size() || ai.getLife() > 5)) {
|
||||||
|
// Basic ai logic for Lethal Vapors
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (list.isEmpty()
|
||||||
|
|| !CardLists.filterControlledBy(list, ai).isEmpty()
|
||||||
|
|| CardLists.getNotKeyword(list, "Indestructible").isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
|
final Card source = sa.getHostCard();
|
||||||
|
final boolean noRegen = sa.hasParam("NoRegen");
|
||||||
|
if (tgt != null) {
|
||||||
|
List<Card> list;
|
||||||
|
list = ai.getGame().getCardsIn(ZoneType.Battlefield);
|
||||||
|
list = CardLists.getTargetableCards(list, sa);
|
||||||
|
list = CardLists.getValidCards(list, tgt.getValidTgts(), source.getController(), source);
|
||||||
|
|
||||||
|
if (list.isEmpty() || list.size() < tgt.getMinTargets(sa.getHostCard(), sa)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
sa.resetTargets();
|
||||||
|
|
||||||
|
List<Card> preferred = CardLists.getNotKeyword(list, "Indestructible");
|
||||||
|
preferred = CardLists.filterControlledBy(preferred, ai.getOpponents());
|
||||||
|
|
||||||
|
// If NoRegen is not set, filter out creatures that have a
|
||||||
|
// regeneration shield
|
||||||
|
if (!noRegen) {
|
||||||
|
// TODO filter out things that could regenerate in response?
|
||||||
|
// might be tougher?
|
||||||
|
preferred = CardLists.filter(preferred, new Predicate<Card>() {
|
||||||
|
@Override
|
||||||
|
public boolean apply(final Card c) {
|
||||||
|
return c.getShield().isEmpty();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
for (final Card c : preferred) {
|
||||||
|
list.remove(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
while (sa.getTargets().getNumTargeted() < tgt.getMaxTargets(sa.getHostCard(), sa)) {
|
||||||
|
if (preferred.isEmpty()) {
|
||||||
|
if ((sa.getTargets().getNumTargeted() == 0)
|
||||||
|
|| (sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa))) {
|
||||||
|
if (!mandatory) {
|
||||||
|
sa.resetTargets();
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Card c;
|
||||||
|
if (CardLists.getNotType(preferred, "Creature").isEmpty()) {
|
||||||
|
c = ComputerUtilCard.getBestCreatureAI(preferred);
|
||||||
|
} else if (CardLists.getNotType(preferred, "Land").isEmpty()) {
|
||||||
|
c = ComputerUtilCard.getBestLandAI(preferred);
|
||||||
|
} else {
|
||||||
|
c = ComputerUtilCard.getMostExpensivePermanentAI(preferred, sa, false);
|
||||||
|
}
|
||||||
|
sa.getTargets().add(c);
|
||||||
|
preferred.remove(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
while (sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa)) {
|
||||||
|
if (list.isEmpty()) {
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
Card c;
|
||||||
|
if (CardLists.getNotType(list, "Creature").isEmpty()) {
|
||||||
|
if (!sa.getUniqueTargets().isEmpty() && sa.getParent().getApi() == ApiType.Destroy
|
||||||
|
&& sa.getUniqueTargets().get(0) instanceof Card) {
|
||||||
|
// basic ai for Diaochan
|
||||||
|
c = (Card) sa.getUniqueTargets().get(0);
|
||||||
|
} else {
|
||||||
|
c = ComputerUtilCard.getWorstCreatureAI(list);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
c = ComputerUtilCard.getCheapestPermanentAI(list, sa, false);
|
||||||
|
}
|
||||||
|
sa.getTargets().add(c);
|
||||||
|
list.remove(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!mandatory) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,21 +1,20 @@
|
|||||||
package forge.card.ability.ai;
|
package forge.ai.ability;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Random;
|
|
||||||
|
|
||||||
import com.google.common.base.Predicate;
|
import com.google.common.base.Predicate;
|
||||||
|
|
||||||
import forge.Card;
|
import forge.ai.ComputerUtil;
|
||||||
import forge.CardLists;
|
import forge.ai.ComputerUtilCard;
|
||||||
import forge.card.ability.SpellAbilityAi;
|
import forge.ai.ComputerUtilCost;
|
||||||
import forge.card.cost.Cost;
|
import forge.ai.ComputerUtilMana;
|
||||||
import forge.card.spellability.SpellAbility;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.ai.ComputerUtilCard;
|
import forge.game.card.Card;
|
||||||
import forge.game.ai.ComputerUtilCost;
|
import forge.game.card.CardLists;
|
||||||
import forge.game.ai.ComputerUtilMana;
|
import forge.game.cost.Cost;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
import forge.util.MyRandom;
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
public class DestroyAllAi extends SpellAbilityAi {
|
public class DestroyAllAi extends SpellAbilityAi {
|
||||||
|
|
||||||
@@ -31,7 +30,7 @@ public class DestroyAllAi extends SpellAbilityAi {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
final Card source = sa.getSourceCard();
|
final Card source = sa.getHostCard();
|
||||||
String valid = "";
|
String valid = "";
|
||||||
if (mandatory) {
|
if (mandatory) {
|
||||||
return true;
|
return true;
|
||||||
@@ -79,9 +78,8 @@ public class DestroyAllAi extends SpellAbilityAi {
|
|||||||
protected boolean canPlayAI(final Player ai, SpellAbility sa) {
|
protected boolean canPlayAI(final Player ai, SpellAbility sa) {
|
||||||
// AI needs to be expanded, since this function can be pretty complex
|
// AI needs to be expanded, since this function can be pretty complex
|
||||||
// based on what the expected targets could be
|
// based on what the expected targets could be
|
||||||
final Random r = MyRandom.getRandom();
|
|
||||||
final Cost abCost = sa.getPayCosts();
|
final Cost abCost = sa.getPayCosts();
|
||||||
final Card source = sa.getSourceCard();
|
final Card source = sa.getHostCard();
|
||||||
String valid = "";
|
String valid = "";
|
||||||
|
|
||||||
if (sa.hasParam("ValidCards")) {
|
if (sa.hasParam("ValidCards")) {
|
||||||
@@ -115,29 +113,31 @@ public class DestroyAllAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// prevent run-away activations - first time will always return true
|
// prevent run-away activations - first time will always return true
|
||||||
boolean chance = r.nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
|
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// 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(humanlist, "Creature").size() == 0) && (CardLists.getNotType(computerlist, "Creature").size() == 0)) {
|
if (CardLists.getNotType(humanlist, "Creature").isEmpty() && CardLists.getNotType(computerlist, "Creature").isEmpty()) {
|
||||||
if ((ComputerUtilCard.evaluateCreatureList(computerlist) + 200) >= ComputerUtilCard
|
if (ComputerUtilCard.evaluateCreatureList(computerlist) + 200 >= ComputerUtilCard.evaluateCreatureList(humanlist)) {
|
||||||
.evaluateCreatureList(humanlist)) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} // only lands involved
|
} // only lands involved
|
||||||
else if ((CardLists.getNotType(humanlist, "Land").size() == 0) && (CardLists.getNotType(computerlist, "Land").size() == 0)) {
|
else if (CardLists.getNotType(humanlist, "Land").isEmpty() && CardLists.getNotType(computerlist, "Land").isEmpty()) {
|
||||||
if ((ComputerUtilCard.evaluatePermanentList(computerlist) + 1) >= ComputerUtilCard
|
if (ai.isCardInPlay("Crucible of Worlds") && !ai.getOpponent().isCardInPlay("Crucible of Worlds") && !humanlist.isEmpty()) {
|
||||||
.evaluatePermanentList(humanlist)) {
|
return true;
|
||||||
|
}
|
||||||
|
if (ComputerUtilCard.evaluatePermanentList(computerlist) + 1 >= ComputerUtilCard.evaluatePermanentList(humanlist)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} // otherwise evaluate both lists by CMC and pass only if human
|
} // otherwise evaluate both lists by CMC and pass only if human permanents are more valuable
|
||||||
// permanents are more valuable
|
|
||||||
else if ((ComputerUtilCard.evaluatePermanentList(computerlist) + 3) >= ComputerUtilCard
|
else if ((ComputerUtilCard.evaluatePermanentList(computerlist) + 3) >= ComputerUtilCard
|
||||||
.evaluatePermanentList(humanlist)) {
|
.evaluatePermanentList(humanlist)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return chance;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,18 +1,20 @@
|
|||||||
package forge.card.ability.ai;
|
package forge.ai.ability;
|
||||||
|
|
||||||
import java.util.Random;
|
import forge.ai.ComputerUtil;
|
||||||
|
import forge.ai.ComputerUtilCard;
|
||||||
import forge.Card;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.card.ability.AbilityUtils;
|
import forge.game.ability.AbilityUtils;
|
||||||
import forge.card.ability.SpellAbilityAi;
|
import forge.game.card.Card;
|
||||||
import forge.card.spellability.SpellAbility;
|
|
||||||
import forge.game.ai.ComputerUtil;
|
|
||||||
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.spellability.SpellAbility;
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
import forge.util.MyRandom;
|
import forge.util.MyRandom;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
|
||||||
public class DigAi extends SpellAbilityAi {
|
public class DigAi extends SpellAbilityAi {
|
||||||
/* (non-Javadoc)
|
/* (non-Javadoc)
|
||||||
@@ -22,7 +24,7 @@ public class DigAi extends SpellAbilityAi {
|
|||||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||||
|
|
||||||
Player opp = ai.getOpponent();
|
Player opp = ai.getOpponent();
|
||||||
final Card host = sa.getSourceCard();
|
final Card host = sa.getHostCard();
|
||||||
Player libraryOwner = ai;
|
Player libraryOwner = ai;
|
||||||
|
|
||||||
if (sa.usesTargeting()) {
|
if (sa.usesTargeting()) {
|
||||||
@@ -40,6 +42,11 @@ public class DigAi extends SpellAbilityAi {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// return false if nothing to dig into
|
||||||
|
if ("Never".equals(sa.getParam("AILogic"))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// don't deck yourself
|
// don't deck yourself
|
||||||
if (sa.hasParam("DestinationZone2") && !"Library".equals(sa.getParam("DestinationZone2"))) {
|
if (sa.hasParam("DestinationZone2") && !"Library".equals(sa.getParam("DestinationZone2"))) {
|
||||||
int numToDig = AbilityUtils.calculateAmount(host, sa.getParam("DigNum"), sa);
|
int numToDig = AbilityUtils.calculateAmount(host, sa.getParam("DigNum"), sa);
|
||||||
@@ -79,6 +86,15 @@ public class DigAi extends SpellAbilityAi {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Card chooseSingleCard(Player ai, SpellAbility sa, Collection<Card> valid, boolean isOptional, Player relatedPlayer) {
|
||||||
|
Card chosen = ComputerUtilCard.getBestAI(valid);
|
||||||
|
if (sa.getActivatingPlayer().isOpponentOf(ai) && relatedPlayer.isOpponentOf(ai)) {
|
||||||
|
return ComputerUtilCard.getWorstAI(valid);
|
||||||
|
}
|
||||||
|
return chosen;
|
||||||
|
}
|
||||||
|
|
||||||
/* (non-Javadoc)
|
/* (non-Javadoc)
|
||||||
* @see forge.card.ability.SpellAbilityAi#confirmAction(forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String)
|
* @see forge.card.ability.SpellAbilityAi#confirmAction(forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String)
|
||||||
*/
|
*/
|
||||||
@@ -1,25 +1,25 @@
|
|||||||
package forge.card.ability.ai;
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
import forge.ai.ComputerUtilMana;
|
||||||
|
import forge.ai.SpellAbilityAi;
|
||||||
|
import forge.game.card.Card;
|
||||||
|
import forge.game.card.CardLists;
|
||||||
|
import forge.game.card.CardPredicates;
|
||||||
|
import forge.game.player.Player;
|
||||||
|
import forge.game.player.PlayerActionConfirmMode;
|
||||||
|
import forge.game.spellability.AbilitySub;
|
||||||
|
import forge.game.spellability.SpellAbility;
|
||||||
|
import forge.game.zone.ZoneType;
|
||||||
|
import forge.util.MyRandom;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
|
|
||||||
import forge.Card;
|
|
||||||
import forge.CardLists;
|
|
||||||
import forge.CardPredicates;
|
|
||||||
import forge.card.ability.SpellAbilityAi;
|
|
||||||
import forge.card.spellability.AbilitySub;
|
|
||||||
import forge.card.spellability.SpellAbility;
|
|
||||||
import forge.game.ai.ComputerUtilMana;
|
|
||||||
import forge.game.player.Player;
|
|
||||||
import forge.game.player.PlayerActionConfirmMode;
|
|
||||||
import forge.game.zone.ZoneType;
|
|
||||||
import forge.util.MyRandom;
|
|
||||||
|
|
||||||
public class DigUntilAi extends SpellAbilityAi {
|
public class DigUntilAi extends SpellAbilityAi {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||||
Card source = sa.getSourceCard();
|
Card source = sa.getHostCard();
|
||||||
double chance = .4; // 40 percent chance with instant speed stuff
|
double chance = .4; // 40 percent chance with instant speed 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
|
||||||
@@ -1,29 +1,29 @@
|
|||||||
package forge.card.ability.ai;
|
package forge.ai.ability;
|
||||||
|
|
||||||
import java.util.List;
|
import forge.ai.ComputerUtil;
|
||||||
import java.util.Random;
|
import forge.ai.ComputerUtilCost;
|
||||||
|
import forge.ai.ComputerUtilMana;
|
||||||
import forge.Card;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.card.ability.AbilityUtils;
|
import forge.game.ability.AbilityUtils;
|
||||||
import forge.card.ability.SpellAbilityAi;
|
import forge.game.card.Card;
|
||||||
import forge.card.cost.Cost;
|
import forge.game.cost.Cost;
|
||||||
import forge.card.spellability.SpellAbility;
|
|
||||||
import forge.card.spellability.TargetRestrictions;
|
|
||||||
import forge.game.ai.ComputerUtil;
|
|
||||||
import forge.game.ai.ComputerUtilCost;
|
|
||||||
import forge.game.ai.ComputerUtilMana;
|
|
||||||
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.spellability.SpellAbility;
|
||||||
|
import forge.game.spellability.TargetRestrictions;
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
import forge.util.MyRandom;
|
import forge.util.MyRandom;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
public class DiscardAi extends SpellAbilityAi {
|
public class DiscardAi extends SpellAbilityAi {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
final Card source = sa.getSourceCard();
|
final Card source = sa.getHostCard();
|
||||||
final Cost abCost = sa.getPayCosts();
|
final Cost abCost = sa.getPayCosts();
|
||||||
|
|
||||||
if (abCost != null) {
|
if (abCost != null) {
|
||||||
@@ -54,7 +54,7 @@ public class DiscardAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// TODO: Add appropriate restrictions
|
// TODO: Add appropriate restrictions
|
||||||
final List<Player> players = AbilityUtils.getDefinedPlayers(sa.getSourceCard(),
|
final List<Player> players = AbilityUtils.getDefinedPlayers(sa.getHostCard(),
|
||||||
sa.getParam("Defined"), sa);
|
sa.getParam("Defined"), sa);
|
||||||
|
|
||||||
if (players.size() == 1) {
|
if (players.size() == 1) {
|
||||||
@@ -85,7 +85,7 @@ public class DiscardAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
source.setSVar("PayX", Integer.toString(cardsToDiscard));
|
source.setSVar("PayX", Integer.toString(cardsToDiscard));
|
||||||
} else {
|
} else {
|
||||||
if (AbilityUtils.calculateAmount(sa.getSourceCard(), sa.getParam("NumCards"), sa) < 1) {
|
if (AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("NumCards"), sa) < 1) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -115,7 +115,7 @@ public class DiscardAi extends SpellAbilityAi {
|
|||||||
private boolean discardTargetAI(final Player ai, final SpellAbility sa) {
|
private boolean discardTargetAI(final Player ai, final SpellAbility sa) {
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
Player opp = ai.getOpponent();
|
Player opp = ai.getOpponent();
|
||||||
if (opp.getCardsIn(ZoneType.Hand).isEmpty()) {
|
if (opp.getCardsIn(ZoneType.Hand).isEmpty() && !ComputerUtil.activateForCost(sa, ai)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (tgt != null) {
|
if (tgt != null) {
|
||||||
@@ -144,11 +144,11 @@ public class DiscardAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if ("X".equals(sa.getParam("RevealNumber")) && sa.getSourceCard().getSVar("X").equals("Count$xPaid")) {
|
if ("X".equals(sa.getParam("RevealNumber")) && sa.getHostCard().getSVar("X").equals("Count$xPaid")) {
|
||||||
// Set PayX here to maximum value.
|
// Set PayX here to maximum value.
|
||||||
final int cardsToDiscard = Math.min(ComputerUtilMana.determineLeftoverMana(sa, ai), ai.getOpponent()
|
final int cardsToDiscard = Math.min(ComputerUtilMana.determineLeftoverMana(sa, ai), ai.getOpponent()
|
||||||
.getCardsIn(ZoneType.Hand).size());
|
.getCardsIn(ZoneType.Hand).size());
|
||||||
sa.getSourceCard().setSVar("PayX", Integer.toString(cardsToDiscard));
|
sa.getHostCard().setSVar("PayX", Integer.toString(cardsToDiscard));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1,16 +1,16 @@
|
|||||||
package forge.card.ability.ai;
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
import forge.ai.SpellAbilityAi;
|
||||||
|
import forge.game.ability.AbilityUtils;
|
||||||
|
import forge.game.card.Card;
|
||||||
|
import forge.game.player.Player;
|
||||||
|
import forge.game.spellability.SpellAbility;
|
||||||
|
import forge.game.spellability.TargetRestrictions;
|
||||||
|
import forge.util.MyRandom;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
|
|
||||||
import forge.Card;
|
|
||||||
import forge.card.ability.AbilityUtils;
|
|
||||||
import forge.card.ability.SpellAbilityAi;
|
|
||||||
import forge.card.spellability.SpellAbility;
|
|
||||||
import forge.card.spellability.TargetRestrictions;
|
|
||||||
import forge.game.player.Player;
|
|
||||||
import forge.util.MyRandom;
|
|
||||||
|
|
||||||
public class DrainManaAi extends SpellAbilityAi {
|
public class DrainManaAi extends SpellAbilityAi {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -18,7 +18,7 @@ public class DrainManaAi extends SpellAbilityAi {
|
|||||||
// AI cannot use this properly until he can use SAs during Humans turn
|
// AI cannot use this properly until he can use SAs during Humans turn
|
||||||
|
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
final Card source = sa.getSourceCard();
|
final Card source = sa.getHostCard();
|
||||||
final Player opp = ai.getOpponent();
|
final Player opp = ai.getOpponent();
|
||||||
final Random r = MyRandom.getRandom();
|
final Random r = MyRandom.getRandom();
|
||||||
boolean randomReturn = r.nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
|
boolean randomReturn = r.nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
|
||||||
@@ -45,7 +45,7 @@ public class DrainManaAi extends SpellAbilityAi {
|
|||||||
final Player opp = ai.getOpponent();
|
final Player opp = ai.getOpponent();
|
||||||
|
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
final Card source = sa.getSourceCard();
|
final Card source = sa.getHostCard();
|
||||||
|
|
||||||
if (null == tgt) {
|
if (null == tgt) {
|
||||||
if (mandatory) {
|
if (mandatory) {
|
||||||
@@ -71,7 +71,7 @@ public class DrainManaAi extends SpellAbilityAi {
|
|||||||
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
||||||
// AI cannot use this properly until he can use SAs during Humans turn
|
// AI cannot use this properly until he can use SAs during Humans turn
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
final Card source = sa.getSourceCard();
|
final Card source = sa.getHostCard();
|
||||||
|
|
||||||
boolean randomReturn = true;
|
boolean randomReturn = true;
|
||||||
|
|
||||||
@@ -16,25 +16,22 @@
|
|||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
package forge.card.ability.ai;
|
package forge.ai.ability;
|
||||||
|
|
||||||
import forge.Card;
|
import forge.ai.*;
|
||||||
import forge.card.ability.AbilityUtils;
|
|
||||||
import forge.card.ability.SpellAbilityAi;
|
|
||||||
import forge.card.cost.Cost;
|
|
||||||
import forge.card.cost.CostDiscard;
|
|
||||||
import forge.card.cost.CostPart;
|
|
||||||
import forge.card.cost.PaymentDecision;
|
|
||||||
import forge.card.spellability.AbilitySub;
|
|
||||||
import forge.card.spellability.SpellAbility;
|
|
||||||
import forge.card.spellability.TargetRestrictions;
|
|
||||||
import forge.game.Game;
|
import forge.game.Game;
|
||||||
import forge.game.ai.ComputerUtil;
|
import forge.game.ability.AbilityUtils;
|
||||||
import forge.game.ai.ComputerUtilCost;
|
import forge.game.card.Card;
|
||||||
import forge.game.ai.ComputerUtilMana;
|
import forge.game.cost.Cost;
|
||||||
|
import forge.game.cost.CostDiscard;
|
||||||
|
import forge.game.cost.CostPart;
|
||||||
|
import forge.game.cost.PaymentDecision;
|
||||||
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.spellability.AbilitySub;
|
||||||
|
import forge.game.spellability.SpellAbility;
|
||||||
|
import forge.game.spellability.TargetRestrictions;
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
|
|
||||||
public class DrawAi extends SpellAbilityAi {
|
public class DrawAi extends SpellAbilityAi {
|
||||||
@@ -50,7 +47,7 @@ public class DrawAi extends SpellAbilityAi {
|
|||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
final Card source = sa.getSourceCard();
|
final Card source = sa.getHostCard();
|
||||||
final Cost abCost = sa.getPayCosts();
|
final Cost abCost = sa.getPayCosts();
|
||||||
final Game game = ai.getGame();
|
final Game game = ai.getGame();
|
||||||
|
|
||||||
@@ -65,10 +62,10 @@ public class DrawAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!ComputerUtilCost.checkDiscardCost(ai, abCost, source)) {
|
if (!ComputerUtilCost.checkDiscardCost(ai, abCost, source)) {
|
||||||
|
AiCostDecision aiDecisions = new AiCostDecision(ai, sa);
|
||||||
for (final CostPart part : abCost.getCostParts()) {
|
for (final CostPart part : abCost.getCostParts()) {
|
||||||
if (part instanceof CostDiscard) {
|
if (part instanceof CostDiscard) {
|
||||||
CostDiscard cd = (CostDiscard) part;
|
PaymentDecision decision = part.accept(aiDecisions);
|
||||||
PaymentDecision decision = cd.decideAIPayment(ai, sa, sa.getSourceCard());
|
|
||||||
if ( null == decision )
|
if ( null == decision )
|
||||||
return false;
|
return false;
|
||||||
for (Card discard : decision.cards) {
|
for (Card discard : decision.cards) {
|
||||||
@@ -106,6 +103,10 @@ public class DrawAi extends SpellAbilityAi {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (sa.getConditions() != null && !sa.getConditions().areMet(sa) && sa.getSubAbility() == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// Don't use draw abilities before main 2 if possible
|
// Don't use draw abilities before main 2 if possible
|
||||||
if (game.getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2)
|
if (game.getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2)
|
||||||
&& !sa.hasParam("ActivationPhases") && !ComputerUtil.castSpellInMain1(ai, sa)) {
|
&& !sa.hasParam("ActivationPhases") && !ComputerUtil.castSpellInMain1(ai, sa)) {
|
||||||
@@ -129,7 +130,7 @@ public class DrawAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
private boolean targetAI(final Player ai, final SpellAbility sa, final boolean mandatory) {
|
private boolean targetAI(final Player ai, final SpellAbility sa, final boolean mandatory) {
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
final Card source = sa.getSourceCard();
|
final Card source = sa.getHostCard();
|
||||||
final boolean drawback = (sa instanceof AbilitySub);
|
final boolean drawback = (sa instanceof AbilitySub);
|
||||||
final Game game = ai.getGame();
|
final Game game = ai.getGame();
|
||||||
Player opp = ai.getOpponent();
|
Player opp = ai.getOpponent();
|
||||||
@@ -146,7 +147,7 @@ public class DrawAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
int numCards = 1;
|
int numCards = 1;
|
||||||
if (sa.hasParam("NumCards")) {
|
if (sa.hasParam("NumCards")) {
|
||||||
numCards = AbilityUtils.calculateAmount(sa.getSourceCard(), sa.getParam("NumCards"), sa);
|
numCards = AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("NumCards"), sa);
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean xPaid = false;
|
boolean xPaid = false;
|
||||||
@@ -164,8 +165,7 @@ public class DrawAi extends SpellAbilityAi {
|
|||||||
//if (n)
|
//if (n)
|
||||||
|
|
||||||
// TODO: if xPaid and one of the below reasons would fail, instead of
|
// TODO: if xPaid and one of the below reasons would fail, instead of
|
||||||
// bailing
|
// bailing reduce toPay amount to acceptable level
|
||||||
// reduce toPay amount to acceptable level
|
|
||||||
|
|
||||||
if (tgt != null) {
|
if (tgt != null) {
|
||||||
// ability is targeted
|
// ability is targeted
|
||||||
@@ -264,7 +264,7 @@ public class DrawAi extends SpellAbilityAi {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
|
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
|
||||||
int numCards = sa.hasParam("NumCards") ? AbilityUtils.calculateAmount(sa.getSourceCard(), sa.getParam("NumCards"), sa) : 1;
|
int numCards = sa.hasParam("NumCards") ? AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("NumCards"), sa) : 1;
|
||||||
// AI shouldn't mill itself
|
// AI shouldn't mill itself
|
||||||
return numCards < player.getZone(ZoneType.Library).size();
|
return numCards < player.getZone(ZoneType.Library).size();
|
||||||
}
|
}
|
||||||
219
forge-ai/src/main/java/forge/ai/ability/EffectAi.java
Normal file
219
forge-ai/src/main/java/forge/ai/ability/EffectAi.java
Normal file
@@ -0,0 +1,219 @@
|
|||||||
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
import com.google.common.base.Predicate;
|
||||||
|
import com.google.common.collect.Iterables;
|
||||||
|
|
||||||
|
import forge.ai.ComputerUtil;
|
||||||
|
import forge.ai.ComputerUtilCard;
|
||||||
|
import forge.ai.ComputerUtilCombat;
|
||||||
|
import forge.ai.SpellAbilityAi;
|
||||||
|
import forge.game.Game;
|
||||||
|
import forge.game.ability.AbilityFactory;
|
||||||
|
import forge.game.ability.AbilityUtils;
|
||||||
|
import forge.game.ability.ApiType;
|
||||||
|
import forge.game.card.Card;
|
||||||
|
import forge.game.card.CardLists;
|
||||||
|
import forge.game.combat.CombatUtil;
|
||||||
|
import forge.game.phase.PhaseHandler;
|
||||||
|
import forge.game.phase.PhaseType;
|
||||||
|
import forge.game.player.Player;
|
||||||
|
import forge.game.spellability.AbilitySub;
|
||||||
|
import forge.game.spellability.SpellAbility;
|
||||||
|
import forge.game.spellability.SpellAbilityStackInstance;
|
||||||
|
import forge.game.spellability.TargetRestrictions;
|
||||||
|
import forge.game.trigger.Trigger;
|
||||||
|
import forge.game.trigger.TriggerType;
|
||||||
|
import forge.game.zone.ZoneType;
|
||||||
|
import forge.util.MyRandom;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
public class EffectAi extends SpellAbilityAi {
|
||||||
|
@Override
|
||||||
|
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||||
|
final Game game = ai.getGame();
|
||||||
|
final Random r = MyRandom.getRandom();
|
||||||
|
boolean randomReturn = r.nextFloat() <= .6667;
|
||||||
|
final Player opp = ai.getOpponent();
|
||||||
|
String logic = "";
|
||||||
|
|
||||||
|
if (sa.hasParam("AILogic")) {
|
||||||
|
logic = sa.getParam("AILogic");
|
||||||
|
final PhaseHandler phase = game.getPhaseHandler();
|
||||||
|
if (logic.equals("BeginningOfOppTurn")) {
|
||||||
|
if (phase.isPlayerTurn(ai) || phase.getPhase().isAfter(PhaseType.DRAW)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
randomReturn = true;
|
||||||
|
} else if (logic.equals("EndOfOppTurn")) {
|
||||||
|
if (phase.isPlayerTurn(ai) || phase.getPhase().isBefore(PhaseType.END_OF_TURN)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
randomReturn = true;
|
||||||
|
} else if (logic.equals("Fog")) {
|
||||||
|
if (game.getPhaseHandler().isPlayerTurn(sa.getActivatingPlayer())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!game.getStack().isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (game.getPhaseHandler().isPreventCombatDamageThisTurn()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!ComputerUtilCombat.lifeInDanger(ai, game.getCombat())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
|
if (tgt != null) {
|
||||||
|
sa.resetTargets();
|
||||||
|
List<Card> list = game.getCombat().getAttackers();
|
||||||
|
list = CardLists.getValidCards(list, tgt.getValidTgts(), sa.getActivatingPlayer(), sa.getHostCard());
|
||||||
|
list = CardLists.getTargetableCards(list, sa);
|
||||||
|
Card target = ComputerUtilCard.getBestCreatureAI(list);
|
||||||
|
if (target == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
sa.getTargets().add(target);
|
||||||
|
}
|
||||||
|
randomReturn = true;
|
||||||
|
} else if (logic.equals("Always")) {
|
||||||
|
randomReturn = true;
|
||||||
|
} else if (logic.equals("Main2")) {
|
||||||
|
if (phase.getPhase().isBefore(PhaseType.MAIN2)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
randomReturn = true;
|
||||||
|
} else if (logic.equals("Evasion")) {
|
||||||
|
|
||||||
|
if (!phase.isPlayerTurn(ai)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Card> comp = ai.getCreaturesInPlay();
|
||||||
|
List<Card> human = opp.getCreaturesInPlay();
|
||||||
|
|
||||||
|
// only count creatures that can attack or block
|
||||||
|
comp = CardLists.filter(comp, new Predicate<Card>() {
|
||||||
|
@Override
|
||||||
|
public boolean apply(final Card c) {
|
||||||
|
return CombatUtil.canAttack(c, opp);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
human = CardLists.filter(human, new Predicate<Card>() {
|
||||||
|
@Override
|
||||||
|
public boolean apply(final Card c) {
|
||||||
|
return CombatUtil.canBlock(c);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (comp.size() < 2 || human.size() < 1) {
|
||||||
|
randomReturn = false;
|
||||||
|
}
|
||||||
|
} else if (logic.equals("RedirectSpellDamageFromPlayer")) {
|
||||||
|
if (game.getStack().isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
boolean threatened = false;
|
||||||
|
for (final SpellAbilityStackInstance stackSA : game.getStack()) {
|
||||||
|
if (!stackSA.isSpell()) { continue; }
|
||||||
|
if (stackSA.getSpellAbility().getApi() == ApiType.DealDamage) {
|
||||||
|
final SpellAbility saTargeting = stackSA.getSpellAbility().getSATargetingPlayer();
|
||||||
|
if (saTargeting != null && Iterables.contains(saTargeting.getTargets().getTargetPlayers(), ai)) {
|
||||||
|
threatened = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
randomReturn = threatened;
|
||||||
|
} else if (logic.equals("Fight")) {
|
||||||
|
List<Card> humCreatures = ai.getOpponent().getCreaturesInPlay();
|
||||||
|
humCreatures = CardLists.getTargetableCards(humCreatures, sa);
|
||||||
|
ComputerUtilCard.sortByEvaluateCreature(humCreatures);
|
||||||
|
|
||||||
|
final AbilitySub tgtFight = sa.getSubAbility();
|
||||||
|
List<Card> aiCreatures = ai.getCreaturesInPlay();
|
||||||
|
aiCreatures = CardLists.getTargetableCards(aiCreatures, tgtFight);
|
||||||
|
aiCreatures = ComputerUtil.getSafeTargets(ai, tgtFight, aiCreatures);
|
||||||
|
ComputerUtilCard.sortByEvaluateCreature(aiCreatures);
|
||||||
|
|
||||||
|
if (humCreatures.isEmpty() || aiCreatures.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
int buffedAtk = 0, buffedDef = 0;
|
||||||
|
for (Card humanCreature : humCreatures) {
|
||||||
|
for (Card aiCreature : aiCreatures) {
|
||||||
|
if (sa.isSpell()) { //heroic triggers adding counters
|
||||||
|
for (Trigger t : aiCreature.getTriggers()) {
|
||||||
|
if (t.getMode() == TriggerType.SpellCast) {
|
||||||
|
final Map<String, String> params = t.getMapParams();
|
||||||
|
if ("Card.Self".equals(params.get("TargetsValid")) && "You".equals(params.get("ValidActivatingPlayer"))
|
||||||
|
&& params.containsKey("Execute")) {
|
||||||
|
SpellAbility heroic = AbilityFactory.getAbility(aiCreature.getSVar(params.get("Execute")),aiCreature);
|
||||||
|
if ("Self".equals(heroic.getParam("Defined")) && "P1P1".equals(heroic.getParam("CounterType"))) {
|
||||||
|
int amount = AbilityUtils.calculateAmount(aiCreature, heroic.getParam("CounterNum"), heroic);
|
||||||
|
buffedAtk += amount;
|
||||||
|
buffedDef += amount;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (FightAi.shouldFight(aiCreature, humanCreature, buffedAtk, buffedDef)) {
|
||||||
|
tgtFight.getTargets().add(aiCreature);
|
||||||
|
sa.getTargets().add(humanCreature);
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else { //no AILogic
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ("False".equals(sa.getParam("Stackable"))) {
|
||||||
|
String name = sa.getParam("Name");
|
||||||
|
if (name == null) {
|
||||||
|
name = sa.getHostCard().getName() + "'s Effect";
|
||||||
|
}
|
||||||
|
final List<Card> list = sa.getActivatingPlayer().getCardsIn(ZoneType.Command, name);
|
||||||
|
if (!list.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
|
if (tgt != null && tgt.canTgtPlayer()) {
|
||||||
|
sa.resetTargets();
|
||||||
|
if (tgt.canOnlyTgtOpponent() || logic.equals("BeginningOfOppTurn")) {
|
||||||
|
sa.getTargets().add(ai.getOpponent());
|
||||||
|
} else {
|
||||||
|
sa.getTargets().add(ai);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return randomReturn;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
||||||
|
|
||||||
|
final Player opp = aiPlayer.getOpponent();
|
||||||
|
|
||||||
|
if (sa.usesTargeting()) {
|
||||||
|
sa.resetTargets();
|
||||||
|
if (mandatory && sa.canTarget(opp)) {
|
||||||
|
sa.getTargets().add(opp);
|
||||||
|
} else if (mandatory && sa.canTarget(aiPlayer)) {
|
||||||
|
sa.getTargets().add(aiPlayer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,21 +15,20 @@
|
|||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
package forge.card.ability.ai;
|
package forge.ai.ability;
|
||||||
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import com.google.common.base.Predicate;
|
import com.google.common.base.Predicate;
|
||||||
|
import forge.ai.ComputerUtilCard;
|
||||||
import forge.Card;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.CardLists;
|
import forge.game.card.Card;
|
||||||
import forge.card.ability.SpellAbilityAi;
|
import forge.game.card.CardLists;
|
||||||
import forge.card.spellability.SpellAbility;
|
|
||||||
import forge.game.ai.ComputerUtilCard;
|
|
||||||
import forge.game.combat.CombatUtil;
|
import forge.game.combat.CombatUtil;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.player.PlayerActionConfirmMode;
|
import forge.game.player.PlayerActionConfirmMode;
|
||||||
|
import forge.game.spellability.SpellAbility;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>
|
* <p>
|
||||||
@@ -45,9 +44,9 @@ public final class EncodeAi extends SpellAbilityAi {
|
|||||||
* bondCanPlayAI.
|
* bondCanPlayAI.
|
||||||
* </p>
|
* </p>
|
||||||
* @param sa
|
* @param sa
|
||||||
* a {@link forge.card.spellability.SpellAbility} object.
|
* a {@link forge.game.spellability.SpellAbility} object.
|
||||||
* @param af
|
* @param af
|
||||||
* a {@link forge.card.ability.AbilityFactory} object.
|
* a {@link forge.game.ability.AbilityFactory} object.
|
||||||
*
|
*
|
||||||
* @return a boolean.
|
* @return a boolean.
|
||||||
*/
|
*/
|
||||||
@@ -72,7 +71,7 @@ public final class EncodeAi extends SpellAbilityAi {
|
|||||||
* @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)
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public Card chooseSingleCard(final Player ai, SpellAbility sa, Collection<Card> options, boolean isOptional) {
|
public Card chooseSingleCard(final Player ai, SpellAbility sa, Collection<Card> options, boolean isOptional, Player targetedPlayer) {
|
||||||
Card choice = null;
|
Card choice = null;
|
||||||
// final String logic = sa.getParam("AILogic");
|
// final String logic = sa.getParam("AILogic");
|
||||||
// if (logic == null) {
|
// if (logic == null) {
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
package forge.card.ability.ai;
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
|
||||||
import forge.card.ability.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.card.spellability.SpellAbility;
|
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
|
import forge.game.spellability.SpellAbility;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODO: Write javadoc for this type.
|
* TODO: Write javadoc for this type.
|
||||||
@@ -1,19 +1,19 @@
|
|||||||
package forge.card.ability.ai;
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
import forge.ai.ComputerUtil;
|
||||||
|
import forge.ai.ComputerUtilCard;
|
||||||
|
import forge.ai.ComputerUtilCombat;
|
||||||
|
import forge.ai.SpellAbilityAi;
|
||||||
|
import forge.game.ability.AbilityUtils;
|
||||||
|
import forge.game.card.Card;
|
||||||
|
import forge.game.card.CardLists;
|
||||||
|
import forge.game.player.Player;
|
||||||
|
import forge.game.spellability.SpellAbility;
|
||||||
|
import forge.util.MyRandom;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
|
|
||||||
import forge.Card;
|
|
||||||
import forge.CardLists;
|
|
||||||
import forge.card.ability.AbilityUtils;
|
|
||||||
import forge.card.ability.SpellAbilityAi;
|
|
||||||
import forge.card.spellability.SpellAbility;
|
|
||||||
import forge.game.ai.ComputerUtil;
|
|
||||||
import forge.game.ai.ComputerUtilCard;
|
|
||||||
import forge.game.ai.ComputerUtilCombat;
|
|
||||||
import forge.game.player.Player;
|
|
||||||
import forge.util.MyRandom;
|
|
||||||
|
|
||||||
public class FightAi extends SpellAbilityAi {
|
public class FightAi extends SpellAbilityAi {
|
||||||
|
|
||||||
/* (non-Javadoc)
|
/* (non-Javadoc)
|
||||||
@@ -22,7 +22,7 @@ public class FightAi extends SpellAbilityAi {
|
|||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
final Card source = sa.getSourceCard();
|
final Card source = sa.getHostCard();
|
||||||
|
|
||||||
List<Card> aiCreatures = ai.getCreaturesInPlay();
|
List<Card> aiCreatures = ai.getCreaturesInPlay();
|
||||||
aiCreatures = CardLists.getTargetableCards(aiCreatures, sa);
|
aiCreatures = CardLists.getTargetableCards(aiCreatures, sa);
|
||||||
@@ -53,7 +53,7 @@ public class FightAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (sa.hasParam("TargetsFromDifferentZone")) {
|
if (sa.hasParam("TargetsFromDifferentZone")) {
|
||||||
if (humCreatures.isEmpty() && aiCreatures.isEmpty()) {
|
if (!(humCreatures.isEmpty() && aiCreatures.isEmpty())) {
|
||||||
for (Card humanCreature : humCreatures) {
|
for (Card humanCreature : humCreatures) {
|
||||||
for (Card aiCreature : aiCreatures) {
|
for (Card aiCreature : aiCreatures) {
|
||||||
if (ComputerUtilCombat.getDamageToKill(humanCreature) <= aiCreature.getNetAttack()
|
if (ComputerUtilCombat.getDamageToKill(humanCreature) <= aiCreature.getNetAttack()
|
||||||
@@ -107,7 +107,7 @@ public class FightAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//try to make a good trade or no trade
|
//try to make a good trade or no trade
|
||||||
final Card source = sa.getSourceCard();
|
final Card source = sa.getHostCard();
|
||||||
List<Card> humCreatures = ai.getOpponent().getCreaturesInPlay();
|
List<Card> humCreatures = ai.getOpponent().getCreaturesInPlay();
|
||||||
humCreatures = CardLists.getTargetableCards(humCreatures, sa);
|
humCreatures = CardLists.getTargetableCards(humCreatures, sa);
|
||||||
if (humCreatures.isEmpty()) {
|
if (humCreatures.isEmpty()) {
|
||||||
@@ -135,5 +135,30 @@ public class FightAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
public static boolean shouldFight(Card fighter, Card opponent, int pumpAttack, int pumpDefense) {
|
||||||
|
if (canKill(fighter, opponent, pumpAttack)) {
|
||||||
|
if (!canKill(opponent, fighter, -pumpDefense)) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
final Random r = MyRandom.getRandom();
|
||||||
|
if (r.nextInt(20)<(opponent.getCMC() - fighter.getCMC())) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
public static boolean canKill(Card fighter, Card opponent, int pumpAttack) {
|
||||||
|
if (opponent.getSVar("Targeting").equals("Dies")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (opponent.hasProtectionFrom(fighter) || !opponent.canBeDestroyed()
|
||||||
|
|| !opponent.getShield().isEmpty() || ComputerUtil.canRegenerate(opponent.getController(), opponent)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (fighter.hasKeyword("Deathtouch") || ComputerUtilCombat.getDamageToKill(opponent) <= fighter.getNetAttack() + pumpAttack) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
package forge.card.ability.ai;
|
package forge.ai.ability;
|
||||||
|
|
||||||
import forge.card.ability.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.card.spellability.SpellAbility;
|
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
|
import forge.game.spellability.SpellAbility;
|
||||||
|
|
||||||
public class FlipACoinAi extends SpellAbilityAi {
|
public class FlipACoinAi extends SpellAbilityAi {
|
||||||
|
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
package forge.card.ability.ai;
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
|
||||||
import forge.card.ability.SpellAbilityAi;
|
import forge.ai.ComputerUtilCombat;
|
||||||
import forge.card.spellability.SpellAbility;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.Game;
|
import forge.game.Game;
|
||||||
import forge.game.ai.ComputerUtilCombat;
|
|
||||||
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;
|
||||||
|
|
||||||
public class FogAi extends SpellAbilityAi {
|
public class FogAi extends SpellAbilityAi {
|
||||||
|
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
package forge.card.ability.ai;
|
package forge.ai.ability;
|
||||||
|
|
||||||
import forge.card.ability.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.card.spellability.SpellAbility;
|
|
||||||
import forge.card.spellability.TargetRestrictions;
|
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
|
import forge.game.spellability.SpellAbility;
|
||||||
|
import forge.game.spellability.TargetRestrictions;
|
||||||
|
|
||||||
public class GameLossAi extends SpellAbilityAi {
|
public class GameLossAi extends SpellAbilityAi {
|
||||||
@Override
|
@Override
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
package forge.card.ability.ai;
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
|
||||||
import forge.card.ability.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.card.spellability.SpellAbility;
|
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
|
import forge.game.spellability.SpellAbility;
|
||||||
|
|
||||||
public class GameWinAi extends SpellAbilityAi {
|
public class GameWinAi extends SpellAbilityAi {
|
||||||
/* (non-Javadoc)
|
/* (non-Javadoc)
|
||||||
30
forge-ai/src/main/java/forge/ai/ability/HauntAi.java
Normal file
30
forge-ai/src/main/java/forge/ai/ability/HauntAi.java
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
import forge.ai.ComputerUtilCard;
|
||||||
|
import forge.ai.SpellAbilityAi;
|
||||||
|
import forge.game.card.Card;
|
||||||
|
import forge.game.card.CardLists;
|
||||||
|
import forge.game.player.Player;
|
||||||
|
import forge.game.spellability.SpellAbility;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class HauntAi extends SpellAbilityAi {
|
||||||
|
|
||||||
|
/* (non-Javadoc)
|
||||||
|
* @see forge.card.ability.SpellAbilityAi#canPlayAI(forge.game.player.Player, forge.card.spellability.SpellAbility)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
||||||
|
return false; // should not get here
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Card chooseSingleCard(Player ai, SpellAbility sa, Collection<Card> creats, boolean isOptional, Player targetedPlayer) {
|
||||||
|
final List<Card> oppCreats = CardLists.filterControlledBy(creats, ai.getOpponents());
|
||||||
|
return ComputerUtilCard.getWorstCreatureAI(oppCreats.isEmpty() ? creats : oppCreats);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,13 +1,12 @@
|
|||||||
package forge.card.ability.ai;
|
package forge.ai.ability;
|
||||||
|
|
||||||
import java.util.Collection;
|
|
||||||
|
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.Card;
|
import forge.game.card.Card;
|
||||||
import forge.card.ability.SpellAbilityAi;
|
|
||||||
import forge.card.spellability.SpellAbility;
|
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
|
import forge.game.spellability.SpellAbility;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODO: Write javadoc for this type.
|
* TODO: Write javadoc for this type.
|
||||||
@@ -25,7 +24,7 @@ public class LegendaryRuleAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Card chooseSingleCard(Player ai, SpellAbility sa, Collection<Card> options, boolean isOptional) {
|
public Card chooseSingleCard(Player ai, SpellAbility sa, Collection<Card> options, boolean isOptional, Player targetedPlayer) {
|
||||||
// Choose a single legendary/planeswalker card to keep
|
// Choose a single legendary/planeswalker card to keep
|
||||||
Card firstOption = Iterables.getFirst(options, null);
|
Card firstOption = Iterables.getFirst(options, null);
|
||||||
boolean choosingFromPlanewalkers = firstOption.isPlaneswalker();
|
boolean choosingFromPlanewalkers = firstOption.isPlaneswalker();
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
package forge.card.ability.ai;
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
import forge.ai.SpellAbilityAi;
|
||||||
|
import forge.game.player.Player;
|
||||||
|
import forge.game.spellability.SpellAbility;
|
||||||
|
import forge.game.spellability.TargetRestrictions;
|
||||||
|
import forge.util.MyRandom;
|
||||||
|
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
|
|
||||||
import forge.card.ability.SpellAbilityAi;
|
|
||||||
import forge.card.spellability.SpellAbility;
|
|
||||||
import forge.card.spellability.TargetRestrictions;
|
|
||||||
import forge.game.player.Player;
|
|
||||||
import forge.util.MyRandom;
|
|
||||||
|
|
||||||
public class LifeExchangeAi extends SpellAbilityAi {
|
public class LifeExchangeAi extends SpellAbilityAi {
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -1,24 +1,15 @@
|
|||||||
package forge.card.ability.ai;
|
package forge.ai.ability;
|
||||||
|
|
||||||
import forge.Card;
|
import forge.ai.*;
|
||||||
import forge.card.ability.AbilityUtils;
|
|
||||||
import forge.card.ability.SpellAbilityAi;
|
|
||||||
import forge.card.cost.Cost;
|
|
||||||
import forge.card.spellability.AbilitySub;
|
|
||||||
import forge.card.spellability.SpellAbility;
|
|
||||||
import forge.card.spellability.TargetRestrictions;
|
|
||||||
import forge.game.Game;
|
import forge.game.Game;
|
||||||
import forge.game.ai.ComputerUtil;
|
import forge.game.ability.AbilityUtils;
|
||||||
import forge.game.ai.ComputerUtilCombat;
|
import forge.game.card.Card;
|
||||||
import forge.game.ai.ComputerUtilCost;
|
import forge.game.cost.Cost;
|
||||||
import forge.game.ai.ComputerUtilMana;
|
|
||||||
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;
|
||||||
* TODO: Write javadoc for this type.
|
import forge.game.spellability.TargetRestrictions;
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
public class LifeGainAi extends SpellAbilityAi {
|
public class LifeGainAi extends SpellAbilityAi {
|
||||||
|
|
||||||
@@ -28,27 +19,28 @@ public class LifeGainAi extends SpellAbilityAi {
|
|||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||||
final Cost abCost = sa.getPayCosts();
|
final Cost abCost = sa.getPayCosts();
|
||||||
final Card source = sa.getSourceCard();
|
final Card source = sa.getHostCard();
|
||||||
final Game game = source.getGame();
|
final Game game = source.getGame();
|
||||||
final int life = ai.getLife();
|
final int life = ai.getLife();
|
||||||
final String amountStr = sa.getParam("LifeAmount");
|
final String amountStr = sa.getParam("LifeAmount");
|
||||||
int lifeAmount = 0;
|
int lifeAmount = 0;
|
||||||
|
boolean activateForCost = ComputerUtil.activateForCost(sa, ai);
|
||||||
if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) {
|
if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) {
|
||||||
// Set PayX here to maximum value.
|
// Set PayX here to maximum value.
|
||||||
final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||||
source.setSVar("PayX", Integer.toString(xPay));
|
source.setSVar("PayX", Integer.toString(xPay));
|
||||||
lifeAmount = xPay;
|
lifeAmount = xPay;
|
||||||
} else {
|
} else {
|
||||||
lifeAmount = AbilityUtils.calculateAmount(sa.getSourceCard(), amountStr, sa);
|
lifeAmount = AbilityUtils.calculateAmount(sa.getHostCard(), amountStr, sa);
|
||||||
}
|
}
|
||||||
|
|
||||||
// don't use it if no life to gain
|
// don't use it if no life to gain
|
||||||
if (lifeAmount <= 0) {
|
if (!activateForCost && lifeAmount <= 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// don't play if the conditions aren't met, unless it would trigger a
|
// don't play if the conditions aren't met, unless it would trigger a
|
||||||
// beneficial sub-condition
|
// beneficial sub-condition
|
||||||
if (!sa.getConditions().areMet(sa)) {
|
if (!activateForCost && !sa.getConditions().areMet(sa)) {
|
||||||
final AbilitySub abSub = sa.getSubAbility();
|
final AbilitySub abSub = sa.getSubAbility();
|
||||||
if (abSub != null && !sa.isWrapper() && "True".equals(source.getSVar("AIPlayForSub"))) {
|
if (abSub != null && !sa.isWrapper() && "True".equals(source.getSVar("AIPlayForSub"))) {
|
||||||
if (!abSub.getConditions().areMet(abSub)) {
|
if (!abSub.getConditions().areMet(abSub)) {
|
||||||
@@ -80,7 +72,7 @@ public class LifeGainAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ai.canGainLife()) {
|
if (!activateForCost && !ai.canGainLife()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,10 +91,9 @@ public class LifeGainAi extends SpellAbilityAi {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!lifeCritical && (!game.getPhaseHandler().getNextTurn().equals(ai)
|
if (!lifeCritical && !activateForCost && (!game.getPhaseHandler().getNextTurn().equals(ai)
|
||||||
|| game.getPhaseHandler().getPhase().isBefore(PhaseType.END_OF_TURN))
|
|| game.getPhaseHandler().getPhase().isBefore(PhaseType.END_OF_TURN))
|
||||||
&& !sa.hasParam("PlayerTurn") && !SpellAbilityAi.isSorcerySpeed(sa)
|
&& !sa.hasParam("PlayerTurn") && !SpellAbilityAi.isSorcerySpeed(sa)) {
|
||||||
&& !ComputerUtil.activateForCost(sa, ai)) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -130,11 +121,11 @@ public class LifeGainAi extends SpellAbilityAi {
|
|||||||
* gainLifeDoTriggerAINoCost.
|
* gainLifeDoTriggerAINoCost.
|
||||||
* </p>
|
* </p>
|
||||||
* @param sa
|
* @param sa
|
||||||
* a {@link forge.card.spellability.SpellAbility} object.
|
* a {@link forge.game.spellability.SpellAbility} object.
|
||||||
* @param mandatory
|
* @param mandatory
|
||||||
* a boolean.
|
* a boolean.
|
||||||
* @param af
|
* @param af
|
||||||
* a {@link forge.card.ability.AbilityFactory} object.
|
* a {@link forge.game.ability.AbilityFactory} object.
|
||||||
*
|
*
|
||||||
* @return a boolean.
|
* @return a boolean.
|
||||||
*/
|
*/
|
||||||
@@ -157,7 +148,7 @@ public class LifeGainAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final Card source = sa.getSourceCard();
|
final Card source = sa.getHostCard();
|
||||||
final String amountStr = sa.getParam("LifeAmount");
|
final String amountStr = sa.getParam("LifeAmount");
|
||||||
if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) {
|
if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) {
|
||||||
// Set PayX here to maximum value.
|
// Set PayX here to maximum value.
|
||||||
@@ -1,17 +1,18 @@
|
|||||||
package forge.card.ability.ai;
|
package forge.ai.ability;
|
||||||
|
|
||||||
import java.util.List;
|
import forge.ai.ComputerUtil;
|
||||||
import forge.Card;
|
import forge.ai.ComputerUtilCost;
|
||||||
import forge.card.ability.AbilityUtils;
|
import forge.ai.ComputerUtilMana;
|
||||||
import forge.card.ability.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.card.cost.Cost;
|
import forge.game.ability.AbilityUtils;
|
||||||
import forge.card.spellability.SpellAbility;
|
import forge.game.card.Card;
|
||||||
import forge.card.spellability.TargetRestrictions;
|
import forge.game.cost.Cost;
|
||||||
import forge.game.ai.ComputerUtil;
|
|
||||||
import forge.game.ai.ComputerUtilCost;
|
|
||||||
import forge.game.ai.ComputerUtilMana;
|
|
||||||
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.TargetRestrictions;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
public class LifeLoseAi extends SpellAbilityAi {
|
public class LifeLoseAi extends SpellAbilityAi {
|
||||||
|
|
||||||
@@ -20,7 +21,7 @@ public class LifeLoseAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
List<Player> tgtPlayers = getTargetPlayers(sa);
|
List<Player> tgtPlayers = getTargetPlayers(sa);
|
||||||
|
|
||||||
final Card source = sa.getSourceCard();
|
final Card source = sa.getHostCard();
|
||||||
final String amountStr = sa.getParam("LifeAmount");
|
final String amountStr = sa.getParam("LifeAmount");
|
||||||
int amount = 0;
|
int amount = 0;
|
||||||
if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) {
|
if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) {
|
||||||
@@ -45,13 +46,13 @@ public class LifeLoseAi extends SpellAbilityAi {
|
|||||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||||
|
|
||||||
final Cost abCost = sa.getPayCosts();
|
final Cost abCost = sa.getPayCosts();
|
||||||
final Card source = sa.getSourceCard();
|
final Card source = sa.getHostCard();
|
||||||
|
|
||||||
final String amountStr = sa.getParam("LifeAmount");
|
final String amountStr = sa.getParam("LifeAmount");
|
||||||
|
|
||||||
// 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 = AbilityUtils.calculateAmount(sa.getSourceCard(), amountStr, sa);
|
int amount = AbilityUtils.calculateAmount(sa.getHostCard(), amountStr, sa);
|
||||||
|
|
||||||
if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) {
|
if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) {
|
||||||
// Set PayX here to maximum value.
|
// Set PayX here to maximum value.
|
||||||
@@ -145,7 +146,7 @@ public class LifeLoseAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final Card source = sa.getSourceCard();
|
final Card source = sa.getHostCard();
|
||||||
final String amountStr = sa.getParam("LifeAmount");
|
final String amountStr = sa.getParam("LifeAmount");
|
||||||
int amount = 0;
|
int amount = 0;
|
||||||
if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) {
|
if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) {
|
||||||
@@ -1,25 +1,25 @@
|
|||||||
package forge.card.ability.ai;
|
package forge.ai.ability;
|
||||||
|
|
||||||
import java.util.Random;
|
import forge.ai.ComputerUtilMana;
|
||||||
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.Card;
|
import forge.game.ability.AbilityUtils;
|
||||||
import forge.CounterType;
|
import forge.game.card.Card;
|
||||||
import forge.card.ability.AbilityUtils;
|
import forge.game.card.CounterType;
|
||||||
import forge.card.ability.SpellAbilityAi;
|
|
||||||
import forge.card.spellability.SpellAbility;
|
|
||||||
import forge.card.spellability.TargetRestrictions;
|
|
||||||
import forge.game.ai.ComputerUtilMana;
|
|
||||||
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.TargetRestrictions;
|
||||||
import forge.util.MyRandom;
|
import forge.util.MyRandom;
|
||||||
|
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
public class LifeSetAi extends SpellAbilityAi {
|
public class LifeSetAi extends SpellAbilityAi {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||||
final Random r = MyRandom.getRandom();
|
final Random r = MyRandom.getRandom();
|
||||||
// Ability_Cost abCost = sa.getPayCosts();
|
// Ability_Cost abCost = sa.getPayCosts();
|
||||||
final Card source = sa.getSourceCard();
|
final Card source = sa.getHostCard();
|
||||||
final int myLife = ai.getLife();
|
final int myLife = ai.getLife();
|
||||||
final Player opponent = ai.getOpponent();
|
final Player opponent = ai.getOpponent();
|
||||||
final int hlife = opponent.getLife();
|
final int hlife = opponent.getLife();
|
||||||
@@ -45,7 +45,7 @@ public class LifeSetAi extends SpellAbilityAi {
|
|||||||
source.setSVar("PayX", Integer.toString(xPay));
|
source.setSVar("PayX", Integer.toString(xPay));
|
||||||
amount = xPay;
|
amount = xPay;
|
||||||
} else {
|
} else {
|
||||||
amount = AbilityUtils.calculateAmount(sa.getSourceCard(), amountStr, sa);
|
amount = AbilityUtils.calculateAmount(sa.getHostCard(), amountStr, sa);
|
||||||
}
|
}
|
||||||
|
|
||||||
// prevent run-away activations - first time will always return true
|
// prevent run-away activations - first time will always return true
|
||||||
@@ -105,7 +105,7 @@ public class LifeSetAi extends SpellAbilityAi {
|
|||||||
final int myLife = ai.getLife();
|
final int myLife = ai.getLife();
|
||||||
final Player opponent = ai.getOpponent();
|
final Player opponent = ai.getOpponent();
|
||||||
final int hlife = opponent.getLife();
|
final int hlife = opponent.getLife();
|
||||||
final Card source = sa.getSourceCard();
|
final Card source = sa.getHostCard();
|
||||||
|
|
||||||
final String amountStr = sa.getParam("LifeAmount");
|
final String amountStr = sa.getParam("LifeAmount");
|
||||||
|
|
||||||
@@ -116,7 +116,7 @@ public class LifeSetAi extends SpellAbilityAi {
|
|||||||
source.setSVar("PayX", Integer.toString(xPay));
|
source.setSVar("PayX", Integer.toString(xPay));
|
||||||
amount = xPay;
|
amount = xPay;
|
||||||
} else {
|
} else {
|
||||||
amount = AbilityUtils.calculateAmount(sa.getSourceCard(), amountStr, sa);
|
amount = AbilityUtils.calculateAmount(sa.getHostCard(), amountStr, sa);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (source.getName().equals("Eternity Vessel")
|
if (source.getName().equals("Eternity Vessel")
|
||||||
40
forge-ai/src/main/java/forge/ai/ability/ManaEffectAi.java
Normal file
40
forge-ai/src/main/java/forge/ai/ability/ManaEffectAi.java
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
|
||||||
|
import forge.ai.ComputerUtil;
|
||||||
|
import forge.ai.SpellAbilityAi;
|
||||||
|
import forge.game.phase.PhaseType;
|
||||||
|
import forge.game.player.Player;
|
||||||
|
import forge.game.spellability.SpellAbility;
|
||||||
|
|
||||||
|
public class ManaEffectAi extends SpellAbilityAi {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||||
|
if (ai.getGame().getPhaseHandler().is(PhaseType.MAIN2) && ComputerUtil.activateForCost(sa, ai)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (ComputerUtil.playImmediately(ai, sa) && sa.getPayCosts() != null && sa.getPayCosts().hasNoManaCost()
|
||||||
|
&& sa.getPayCosts().isReusuableResource()
|
||||||
|
&& sa.getSubAbility() == null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param sa
|
||||||
|
* a {@link forge.game.spellability.SpellAbility} object.
|
||||||
|
* @param mandatory
|
||||||
|
* a boolean.
|
||||||
|
* @param af
|
||||||
|
* a {@link forge.game.ability.AbilityFactory} object.
|
||||||
|
*
|
||||||
|
* @return a boolean.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,26 +1,26 @@
|
|||||||
package forge.card.ability.ai;
|
package forge.ai.ability;
|
||||||
|
|
||||||
import java.util.List;
|
import forge.ai.ComputerUtil;
|
||||||
|
import forge.ai.ComputerUtilCost;
|
||||||
import forge.Card;
|
import forge.ai.ComputerUtilMana;
|
||||||
import forge.card.ability.AbilityUtils;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.card.ability.SpellAbilityAi;
|
import forge.game.ability.AbilityUtils;
|
||||||
import forge.card.cost.Cost;
|
import forge.game.card.Card;
|
||||||
import forge.card.spellability.SpellAbility;
|
import forge.game.cost.Cost;
|
||||||
import forge.card.spellability.TargetRestrictions;
|
|
||||||
import forge.game.ai.ComputerUtil;
|
|
||||||
import forge.game.ai.ComputerUtilCost;
|
|
||||||
import forge.game.ai.ComputerUtilMana;
|
|
||||||
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.spellability.SpellAbility;
|
||||||
|
import forge.game.spellability.TargetRestrictions;
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
public class MillAi extends SpellAbilityAi {
|
public class MillAi extends SpellAbilityAi {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||||
final Card source = sa.getSourceCard();
|
final Card source = sa.getHostCard();
|
||||||
final Cost abCost = sa.getPayCosts();
|
final Cost abCost = sa.getPayCosts();
|
||||||
|
|
||||||
if (abCost != null) {
|
if (abCost != null) {
|
||||||
@@ -94,7 +94,7 @@ public class MillAi extends SpellAbilityAi {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
final int numCards = AbilityUtils.calculateAmount(sa.getSourceCard(), sa.getParam("NumCards"), sa);
|
final int numCards = AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("NumCards"), sa);
|
||||||
|
|
||||||
final List<Card> pLibrary = opp.getCardsIn(ZoneType.Library);
|
final List<Card> pLibrary = opp.getCardsIn(ZoneType.Library);
|
||||||
|
|
||||||
@@ -135,7 +135,7 @@ public class MillAi extends SpellAbilityAi {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
final Card source = sa.getSourceCard();
|
final Card source = sa.getHostCard();
|
||||||
if (sa.getParam("NumCards").equals("X") && source.getSVar("X").equals("Count$xPaid")) {
|
if (sa.getParam("NumCards").equals("X") && source.getSVar("X").equals("Count$xPaid")) {
|
||||||
// Set PayX here to maximum value.
|
// Set PayX here to maximum value.
|
||||||
final int cardsToDiscard = Math.min(ComputerUtilMana.determineLeftoverMana(sa, aiPlayer), aiPlayer.getOpponent()
|
final int cardsToDiscard = Math.min(ComputerUtilMana.determineLeftoverMana(sa, aiPlayer), aiPlayer.getOpponent()
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
package forge.card.ability.ai;
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
|
||||||
import forge.card.ability.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.card.spellability.SpellAbility;
|
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
|
import forge.game.spellability.SpellAbility;
|
||||||
|
|
||||||
public class MustAttackAi extends SpellAbilityAi {
|
public class MustAttackAi extends SpellAbilityAi {
|
||||||
|
|
||||||
@@ -1,23 +1,22 @@
|
|||||||
package forge.card.ability.ai;
|
package forge.ai.ability;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import com.google.common.base.Predicate;
|
import com.google.common.base.Predicate;
|
||||||
|
import forge.ai.ComputerUtilCard;
|
||||||
import forge.Card;
|
import forge.ai.ComputerUtilCombat;
|
||||||
import forge.CardLists;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.CardPredicates;
|
import forge.game.ability.AbilityUtils;
|
||||||
import forge.card.ability.AbilityUtils;
|
import forge.game.card.Card;
|
||||||
import forge.card.ability.SpellAbilityAi;
|
import forge.game.card.CardLists;
|
||||||
import forge.card.spellability.SpellAbility;
|
import forge.game.card.CardPredicates;
|
||||||
import forge.card.spellability.TargetRestrictions;
|
|
||||||
import forge.game.ai.ComputerUtilCard;
|
|
||||||
import forge.game.ai.ComputerUtilCombat;
|
|
||||||
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;
|
||||||
|
import forge.game.spellability.SpellAbility;
|
||||||
|
import forge.game.spellability.TargetRestrictions;
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
public class MustBlockAi extends SpellAbilityAi {
|
public class MustBlockAi extends SpellAbilityAi {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -34,7 +33,7 @@ public class MustBlockAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(final Player ai, SpellAbility sa, boolean mandatory) {
|
protected boolean doTriggerAINoCost(final Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
final Card source = sa.getSourceCard();
|
final Card source = sa.getHostCard();
|
||||||
final TargetRestrictions abTgt = sa.getTargetRestrictions();
|
final TargetRestrictions abTgt = sa.getTargetRestrictions();
|
||||||
|
|
||||||
// only use on creatures that can attack
|
// only use on creatures that can attack
|
||||||
@@ -44,7 +43,7 @@ public class MustBlockAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
Card attacker = null;
|
Card attacker = null;
|
||||||
if (sa.hasParam("DefinedAttacker")) {
|
if (sa.hasParam("DefinedAttacker")) {
|
||||||
final List<Card> cards = AbilityUtils.getDefinedCards(sa.getSourceCard(), sa.getParam("DefinedAttacker"), sa);
|
final List<Card> cards = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("DefinedAttacker"), sa);
|
||||||
if (cards.isEmpty()) {
|
if (cards.isEmpty()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
45
forge-ai/src/main/java/forge/ai/ability/PeekAndRevealAi.java
Normal file
45
forge-ai/src/main/java/forge/ai/ability/PeekAndRevealAi.java
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
import forge.ai.SpellAbilityAi;
|
||||||
|
import forge.ai.SpellApiToAi;
|
||||||
|
import forge.game.phase.PhaseType;
|
||||||
|
import forge.game.player.Player;
|
||||||
|
import forge.game.player.PlayerActionConfirmMode;
|
||||||
|
import forge.game.spellability.AbilityStatic;
|
||||||
|
import forge.game.spellability.AbilitySub;
|
||||||
|
import forge.game.spellability.SpellAbility;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO: Write javadoc for this type.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class PeekAndRevealAi extends SpellAbilityAi {
|
||||||
|
|
||||||
|
/* (non-Javadoc)
|
||||||
|
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, forge.card.spellability.SpellAbility)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
||||||
|
if (sa instanceof AbilityStatic) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if ("Main2".equals(sa.getParam("AILogic"))) {
|
||||||
|
if (aiPlayer.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// So far this only appears on Triggers, but will expand
|
||||||
|
// once things get converted from Dig + NoMove
|
||||||
|
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) {
|
||||||
|
AbilitySub subAb = sa.getSubAbility();
|
||||||
|
return subAb != null && SpellApiToAi.Converter.get(subAb.getApi()).chkDrawbackWithSubs(player, subAb);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,20 +1,19 @@
|
|||||||
package forge.card.ability.ai;
|
package forge.ai.ability;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
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.ComputerUtil;
|
||||||
import forge.Card;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.CardPredicates;
|
|
||||||
import forge.card.ability.SpellAbilityAi;
|
|
||||||
import forge.card.spellability.SpellAbility;
|
|
||||||
import forge.game.Game;
|
import forge.game.Game;
|
||||||
import forge.game.ai.ComputerUtil;
|
import forge.game.card.Card;
|
||||||
|
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.spellability.SpellAbility;
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* AbilityFactory for Creature Spells.
|
* AbilityFactory for Creature Spells.
|
||||||
*
|
*
|
||||||
@@ -46,7 +45,7 @@ public class PermanentCreatureAi extends SpellAbilityAi {
|
|||||||
for(Card c : cards) {
|
for(Card c : cards) {
|
||||||
ArrayList<StaticAbility> statics = c.getStaticAbilities();
|
ArrayList<StaticAbility> statics = c.getStaticAbilities();
|
||||||
for(StaticAbility s : statics) {
|
for(StaticAbility s : statics) {
|
||||||
final Map<String, String> stabMap = s.getMapParams();
|
final Map<String, String> stabMap = s.parseParams();
|
||||||
|
|
||||||
if (!stabMap.get("Mode").equals("Continuous")) {
|
if (!stabMap.get("Mode").equals("Continuous")) {
|
||||||
continue;
|
continue;
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user