mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-13 09:17:59 +00:00
Compare commits
557 Commits
paperCardI
...
detainEffe
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c32b84b40c | ||
|
|
6962c2cf99 | ||
|
|
b7ec60863a | ||
|
|
239cf3ece7 | ||
|
|
06781fb6ff | ||
|
|
a85f00043b | ||
|
|
dde58be5a6 | ||
|
|
1ad4fda3e6 | ||
|
|
b78ef7b010 | ||
|
|
d1776ce37a | ||
|
|
415e247f6c | ||
|
|
ca4a934e0f | ||
|
|
f5e96bc756 | ||
|
|
7428b7420a | ||
|
|
761d7848ef | ||
|
|
e9fc7899cd | ||
|
|
df40fd5cb3 | ||
|
|
dce3b4b142 | ||
|
|
b20110edb4 | ||
|
|
4a05a66bc0 | ||
|
|
39acc78fad | ||
|
|
a0c58e2a1d | ||
|
|
c46ceca140 | ||
|
|
4db0b6a67f | ||
|
|
38f6b699c6 | ||
|
|
9d80f4e78f | ||
|
|
7dc95f8540 | ||
|
|
723ad5fe7c | ||
|
|
94aac494ea | ||
|
|
3e6b7af61f | ||
|
|
1574a88255 | ||
|
|
2e2fea9182 | ||
|
|
55549f1e70 | ||
|
|
71e97146ed | ||
|
|
35e4e580fa | ||
|
|
baab31d2e2 | ||
|
|
3f1295d9e4 | ||
|
|
d759cc4e94 | ||
|
|
4c941df647 | ||
|
|
1f39ad4e91 | ||
|
|
194e1d3356 | ||
|
|
0aa8c933d8 | ||
|
|
cf11802fbe | ||
|
|
2703cbc4c9 | ||
|
|
b87f3bd00b | ||
|
|
a1227167ea | ||
|
|
7ccb925d76 | ||
|
|
beedd17cfe | ||
|
|
332fa94dab | ||
|
|
ae4b5be29f | ||
|
|
cc4a417c0a | ||
|
|
8a6e03e172 | ||
|
|
3ff0c740c4 | ||
|
|
82f8a8f30b | ||
|
|
2f7c315822 | ||
|
|
89b9c050a8 | ||
|
|
eb70e21f3d | ||
|
|
ab2b06500b | ||
|
|
79845eff1d | ||
|
|
9d1b935643 | ||
|
|
ad0c81e984 | ||
|
|
e86f4e80cb | ||
|
|
af4a6de809 | ||
|
|
0d0fdd3c0b | ||
|
|
de4dcb19cf | ||
|
|
cfcbbb5c21 | ||
|
|
6d11361725 | ||
|
|
924d9f57a6 | ||
|
|
8c5dfb61e7 | ||
|
|
895c5c1e65 | ||
|
|
e64faa3cf2 | ||
|
|
11815a4cf2 | ||
|
|
d903e4d80d | ||
|
|
6530515e98 | ||
|
|
28711aa669 | ||
|
|
f3b2192dd2 | ||
|
|
d6451ae486 | ||
|
|
a78c648e77 | ||
|
|
b1afd28556 | ||
|
|
82f5e17705 | ||
|
|
15e955576f | ||
|
|
23b0fa09d9 | ||
|
|
34e31e7e29 | ||
|
|
bd6ea12cc6 | ||
|
|
10fa3f7f22 | ||
|
|
462b183548 | ||
|
|
4b5bf6fef4 | ||
|
|
1f28e46e06 | ||
|
|
990e515cb9 | ||
|
|
6ee86f9762 | ||
|
|
41dfb3488c | ||
|
|
f61e1cd435 | ||
|
|
1627503248 | ||
|
|
4c87c8a1ff | ||
|
|
8036d4a553 | ||
|
|
a9f18bbf48 | ||
|
|
64738f58d6 | ||
|
|
f426e9f236 | ||
|
|
234237b43a | ||
|
|
854d6640d1 | ||
|
|
c6514e0183 | ||
|
|
9164e76f44 | ||
|
|
5cf155fb94 | ||
|
|
11be569f35 | ||
|
|
00412f6418 | ||
|
|
c66e4a08e6 | ||
|
|
c22934f4af | ||
|
|
2792c57405 | ||
|
|
e3637586d6 | ||
|
|
5787dfbca5 | ||
|
|
9b80408bc6 | ||
|
|
073f32c1c9 | ||
|
|
6564e76aad | ||
|
|
35563fe834 | ||
|
|
9b46581289 | ||
|
|
dbde27bc88 | ||
|
|
b37c280bf8 | ||
|
|
643fc2106b | ||
|
|
48496cfbbc | ||
|
|
3022129fb5 | ||
|
|
5d0a7b5ef9 | ||
|
|
fa1557c775 | ||
|
|
cb47a9c4be | ||
|
|
9e2ab76be3 | ||
|
|
90c8f8e332 | ||
|
|
63397dee07 | ||
|
|
a602b1bdf2 | ||
|
|
8653a26d38 | ||
|
|
8ea5ac97a2 | ||
|
|
d72d36a8b9 | ||
|
|
664ce69993 | ||
|
|
93bca202bf | ||
|
|
72641e000b | ||
|
|
36696f9b1b | ||
|
|
83bfe2a524 | ||
|
|
827a02563b | ||
|
|
f06acf88f9 | ||
|
|
0f78751ce3 | ||
|
|
c9fbd579b6 | ||
|
|
4e12f73444 | ||
|
|
22f4a250e4 | ||
|
|
ed6966b570 | ||
|
|
14c6794ee3 | ||
|
|
9494321965 | ||
|
|
34d009a458 | ||
|
|
ac5128d133 | ||
|
|
12c487f7b8 | ||
|
|
285417275d | ||
|
|
f2db114cb6 | ||
|
|
87226d9bd9 | ||
|
|
a644a4c7bc | ||
|
|
462bdf19b9 | ||
|
|
3d9bb1f437 | ||
|
|
485d2dbaf8 | ||
|
|
8f8abdbab9 | ||
|
|
7ebe74e57c | ||
|
|
b45153bee8 | ||
|
|
55c4d4240f | ||
|
|
0d2d01060d | ||
|
|
f84237f25b | ||
|
|
07cff6f9a9 | ||
|
|
9f7852e76f | ||
|
|
943d6edabd | ||
|
|
800b88911a | ||
|
|
dccbd9912d | ||
|
|
b5f696756e | ||
|
|
9ff528029a | ||
|
|
44046b475b | ||
|
|
65959f9089 | ||
|
|
94d87039e3 | ||
|
|
d3a13d4515 | ||
|
|
b63bee045f | ||
|
|
977d46bca5 | ||
|
|
8293d4de63 | ||
|
|
94feb1b63b | ||
|
|
e37dac6cbe | ||
|
|
c7b98bc578 | ||
|
|
7d4369fdca | ||
|
|
28cb9f9d7d | ||
|
|
94e3c71809 | ||
|
|
5dd6a5b45a | ||
|
|
e486f846d8 | ||
|
|
e7793764a6 | ||
|
|
5aea2e5bbe | ||
|
|
f52410f2f8 | ||
|
|
53e2f199cc | ||
|
|
f86e9a56d1 | ||
|
|
31ebe48bf3 | ||
|
|
ff963b2cb9 | ||
|
|
9a6ebce85a | ||
|
|
eb5e37e3de | ||
|
|
92b96aea75 | ||
|
|
0f4c0488d5 | ||
|
|
6879089149 | ||
|
|
3e2eb86bd4 | ||
|
|
84ff75c9cf | ||
|
|
9d3605ee96 | ||
|
|
83cd6b7291 | ||
|
|
514d40aefd | ||
|
|
31cca02d8d | ||
|
|
5fbd2f9552 | ||
|
|
7ee0431a2f | ||
|
|
0786403fff | ||
|
|
4f7dbfbc80 | ||
|
|
6caf02fc77 | ||
|
|
9dedc9ecfa | ||
|
|
b62434616a | ||
|
|
ad8f3ea06f | ||
|
|
dbed505282 | ||
|
|
1468746045 | ||
|
|
6841b31849 | ||
|
|
a151dff3c5 | ||
|
|
90f6e2b17f | ||
|
|
7b1e96410f | ||
|
|
7ecf619b94 | ||
|
|
b1d8b6fd5c | ||
|
|
a15a990730 | ||
|
|
7402aab2a8 | ||
|
|
2a68427c82 | ||
|
|
14dd9721e3 | ||
|
|
6115eaf6da | ||
|
|
b4922ce353 | ||
|
|
0fb37bf2d4 | ||
|
|
8ce5272e95 | ||
|
|
08135545da | ||
|
|
3cdbb8f1dd | ||
|
|
4f662a5651 | ||
|
|
0e0811a1ac | ||
|
|
71882fe83c | ||
|
|
fce67b847c | ||
|
|
2ff0c6b92d | ||
|
|
b27b8b7ba5 | ||
|
|
f889abd57a | ||
|
|
1d2852fff6 | ||
|
|
48247f7d3b | ||
|
|
d3e6932a23 | ||
|
|
e4d3e061d1 | ||
|
|
e57708543a | ||
|
|
69e2f90c8d | ||
|
|
655d01114c | ||
|
|
0d40508e69 | ||
|
|
b785a84dca | ||
|
|
14af90cd27 | ||
|
|
0278918886 | ||
|
|
b44738cda3 | ||
|
|
ddbe2d6051 | ||
|
|
a15ebe86db | ||
|
|
16225d2894 | ||
|
|
b4c87a198a | ||
|
|
38da204607 | ||
|
|
dbe410f8af | ||
|
|
b05eb4eed3 | ||
|
|
02201c4527 | ||
|
|
6536d39969 | ||
|
|
c9b41c2b81 | ||
|
|
74e587a8a0 | ||
|
|
a751ad186e | ||
|
|
bae41478d9 | ||
|
|
10dbafbe8c | ||
|
|
504946f72a | ||
|
|
e45ae205ac | ||
|
|
4a5969157d | ||
|
|
bdda4d50f7 | ||
|
|
f565943d8d | ||
|
|
3158c0b1d9 | ||
|
|
4346d83582 | ||
|
|
7e016f4ca5 | ||
|
|
38242d0226 | ||
|
|
b565095136 | ||
|
|
93b5fd5f3e | ||
|
|
8ca03c1f50 | ||
|
|
f5204b89fb | ||
|
|
b9dc63b3df | ||
|
|
86910302dc | ||
|
|
a0cca65075 | ||
|
|
0cc2c3b275 | ||
|
|
2cb4856dc7 | ||
|
|
88a53226a2 | ||
|
|
1d42ab50c7 | ||
|
|
cf10851c78 | ||
|
|
c1727ffb3f | ||
|
|
43d79c024e | ||
|
|
3fcf200a86 | ||
|
|
8bc5588342 | ||
|
|
ab27b613e3 | ||
|
|
c6816dbe74 | ||
|
|
41e43a8441 | ||
|
|
ff91114f39 | ||
|
|
b3951555a1 | ||
|
|
f3a68e940e | ||
|
|
1d646753f2 | ||
|
|
b293700b13 | ||
|
|
ea2e0fa3e6 | ||
|
|
bad55a173d | ||
|
|
69538f248d | ||
|
|
c2f4736f08 | ||
|
|
9aa142ba5a | ||
|
|
f4aba94a18 | ||
|
|
910c06b4d2 | ||
|
|
490a9b1693 | ||
|
|
f6a8705849 | ||
|
|
df2f1105c1 | ||
|
|
acaf683166 | ||
|
|
1c6b256268 | ||
|
|
d59ec1527d | ||
|
|
4b6e46ab7d | ||
|
|
68b949bdbf | ||
|
|
a49cc1db97 | ||
|
|
701cc31e1c | ||
|
|
8832920a4d | ||
|
|
d044d0dc65 | ||
|
|
e7427bb180 | ||
|
|
989acaa7d4 | ||
|
|
6351ea2593 | ||
|
|
1a586f07d1 | ||
|
|
69ec802299 | ||
|
|
817b72f67f | ||
|
|
cf1398606b | ||
|
|
31d733aacb | ||
|
|
77544f855b | ||
|
|
1be4880776 | ||
|
|
ff39f82099 | ||
|
|
200e509cda | ||
|
|
6a70c6705d | ||
|
|
6749e5de7e | ||
|
|
9b83c7eeec | ||
|
|
1c8f7eccbe | ||
|
|
13c98c68e9 | ||
|
|
d492cff553 | ||
|
|
875bd54c21 | ||
|
|
9825bebb75 | ||
|
|
f2ab574a3b | ||
|
|
94babc2c53 | ||
|
|
4e42963033 | ||
|
|
6fec40522e | ||
|
|
eea9890f9d | ||
|
|
89d1cffc12 | ||
|
|
40f168bf1e | ||
|
|
891a098d2a | ||
|
|
b6c0d86945 | ||
|
|
2cb69c2d6e | ||
|
|
9a96760061 | ||
|
|
d4a484522e | ||
|
|
7a24b5cd0c | ||
|
|
c0e34799c8 | ||
|
|
964d500414 | ||
|
|
e2949b66fc | ||
|
|
06fcb531cd | ||
|
|
923b12caa0 | ||
|
|
69d75489e7 | ||
|
|
9e985ff471 | ||
|
|
47390abf3b | ||
|
|
59484f30b5 | ||
|
|
e36701a9f3 | ||
|
|
03b6567993 | ||
|
|
56ef1a1df5 | ||
|
|
6d4af69c19 | ||
|
|
b8bd9d92a7 | ||
|
|
6a4018f206 | ||
|
|
986b44af43 | ||
|
|
64e6dbad13 | ||
|
|
2e559effcb | ||
|
|
d02f755fa7 | ||
|
|
17ffda684f | ||
|
|
d6cd2f35ff | ||
|
|
81bba70548 | ||
|
|
831a2d23e4 | ||
|
|
a0f15cdcb1 | ||
|
|
e882bd8264 | ||
|
|
7101d638c6 | ||
|
|
865e3dd783 | ||
|
|
b1c992db26 | ||
|
|
58f79ae6c0 | ||
|
|
fd5af3c93e | ||
|
|
127d0fced6 | ||
|
|
6526d736bf | ||
|
|
cc4a43f0a2 | ||
|
|
286ade792f | ||
|
|
e681775f47 | ||
|
|
67a9a2c34a | ||
|
|
88c0cc00bc | ||
|
|
f16f153448 | ||
|
|
1f93dcf5db | ||
|
|
783fff300d | ||
|
|
935e4a00d5 | ||
|
|
6986b5a53d | ||
|
|
e6ed31e9d9 | ||
|
|
288c845116 | ||
|
|
597222dbf5 | ||
|
|
cea6eeabba | ||
|
|
8475456c0b | ||
|
|
651caccd71 | ||
|
|
8e9fb8570e | ||
|
|
5a183a6042 | ||
|
|
67e6a7aa1a | ||
|
|
b76c67f309 | ||
|
|
99c6b6d815 | ||
|
|
cadc39699d | ||
|
|
8c06aab7b3 | ||
|
|
25c59cd5dd | ||
|
|
9825239e43 | ||
|
|
fb69f245da | ||
|
|
7ef8dddc2a | ||
|
|
2abcae84b1 | ||
|
|
b4beb6c182 | ||
|
|
786d14663b | ||
|
|
f8269e69c4 | ||
|
|
29274d0acf | ||
|
|
d6b925f171 | ||
|
|
f1f18d1823 | ||
|
|
03ff2147db | ||
|
|
c66a72806d | ||
|
|
e89f2cbac0 | ||
|
|
d831530c50 | ||
|
|
9fe18c2af8 | ||
|
|
fe4ff7ac0f | ||
|
|
a8ca3d8188 | ||
|
|
3b9867c537 | ||
|
|
01d22c26f4 | ||
|
|
e8f7fe5a95 | ||
|
|
bd0b8fbc65 | ||
|
|
05e20b4e92 | ||
|
|
716d58ad4b | ||
|
|
ca12ad529a | ||
|
|
6eea82706d | ||
|
|
d69d005ce0 | ||
|
|
b98b322dfe | ||
|
|
e73e72d150 | ||
|
|
8cabc244f7 | ||
|
|
4f04e5cc13 | ||
|
|
bcf9d2585d | ||
|
|
e03b07f940 | ||
|
|
a45c6aa37e | ||
|
|
dd672945f2 | ||
|
|
e634be4273 | ||
|
|
9acf5bee41 | ||
|
|
1e3297ab64 | ||
|
|
58df290c8e | ||
|
|
e2075886b1 | ||
|
|
ad53abc75a | ||
|
|
a599c318dd | ||
|
|
14e2a0c5e2 | ||
|
|
2ae3efc12e | ||
|
|
fb18134c12 | ||
|
|
2215a101a3 | ||
|
|
4df2fe7ac5 | ||
|
|
c24369d1ec | ||
|
|
41eb86029a | ||
|
|
357026ce66 | ||
|
|
9b558cb069 | ||
|
|
303020ca75 | ||
|
|
b900abcc71 | ||
|
|
c4ba6df6e9 | ||
|
|
cc4a507799 | ||
|
|
d8de8ec696 | ||
|
|
e6563814e8 | ||
|
|
016d51669f | ||
|
|
f19828b2ec | ||
|
|
36fc87c2a1 | ||
|
|
4322bddabf | ||
|
|
c128a9d4ba | ||
|
|
2feeffc95c | ||
|
|
53de238a7e | ||
|
|
c207e74369 | ||
|
|
04ca02a77a | ||
|
|
6ff89c71b6 | ||
|
|
d7c6a8e53e | ||
|
|
150741a443 | ||
|
|
460f322c44 | ||
|
|
ef5d35fe38 | ||
|
|
16044556b5 | ||
|
|
81d5079468 | ||
|
|
edc4b22cbc | ||
|
|
c01dfd4740 | ||
|
|
53a4a46d1f | ||
|
|
d61e7e7102 | ||
|
|
17f4df9293 | ||
|
|
24071582bb | ||
|
|
5754466a0d | ||
|
|
a18eda92b7 | ||
|
|
11d0a8f2fe | ||
|
|
2b8b386882 | ||
|
|
45a5027451 | ||
|
|
2cb72f55f3 | ||
|
|
fa27fbab46 | ||
|
|
87821fe287 | ||
|
|
cee4bcd867 | ||
|
|
b9cc4c18a8 | ||
|
|
4577e61940 | ||
|
|
9ac6147a98 | ||
|
|
74d4ea8a78 | ||
|
|
e2f4c7f872 | ||
|
|
07db8e5a94 | ||
|
|
ed7fb03b9c | ||
|
|
f0ebc3e6a0 | ||
|
|
f2570b4cef | ||
|
|
97e1939021 | ||
|
|
bebb894f3e | ||
|
|
288c533c91 | ||
|
|
6891f9a5d5 | ||
|
|
37a62eed6b | ||
|
|
3ee6b0e58d | ||
|
|
7e6f772345 | ||
|
|
ef28173b65 | ||
|
|
a26614c6b6 | ||
|
|
ee00293e48 | ||
|
|
ce977387e6 | ||
|
|
8cc45dbbd8 | ||
|
|
851a4c104c | ||
| 45ddf3fded | |||
|
|
cb538f4026 | ||
|
|
6bd6db3c16 | ||
|
|
6af3940ffb | ||
|
|
66c864c8d7 | ||
|
|
15354dd8e8 | ||
|
|
215070fe3f | ||
|
|
ed73883e80 | ||
|
|
df85ebc0aa | ||
|
|
885a6af943 | ||
|
|
94820d4782 | ||
|
|
7f044e5ac3 | ||
|
|
1284f23620 | ||
|
|
bdcc8acdc4 | ||
|
|
73a9dfcf43 | ||
|
|
7a277ce283 | ||
|
|
f8b3f9dd30 | ||
|
|
840f3ea96c | ||
|
|
51929434be | ||
|
|
3ec480afa4 | ||
|
|
3d19e9e444 | ||
|
|
6d987791f7 | ||
|
|
7b7da00c22 | ||
|
|
34791bd892 | ||
|
|
e6bcd1be72 | ||
|
|
4cac354983 | ||
|
|
042f7f77a9 | ||
|
|
5fbb1dd0cd | ||
|
|
bb14dfc00e | ||
|
|
f83cc4ccfa | ||
|
|
d0d6835a5d | ||
|
|
104bc8fc55 | ||
|
|
057dd867a8 | ||
|
|
9eed8f5095 | ||
|
|
1f62be9773 | ||
|
|
7a46f92059 | ||
|
|
cfa79b9676 | ||
|
|
c0a63fa15b | ||
|
|
c61537ae16 | ||
|
|
3871095b92 | ||
|
|
dee846da49 | ||
|
|
2477553d13 | ||
|
|
c9df7d7f8e | ||
|
|
53cb093f9e | ||
|
|
28e86970dc | ||
|
|
f847fc1669 | ||
|
|
f3df55177a | ||
|
|
be6f345127 |
40
.github/workflows/maven-publish.yml
vendored
40
.github/workflows/maven-publish.yml
vendored
@@ -81,7 +81,7 @@ jobs:
|
|||||||
export _JAVA_OPTIONS="-Xmx2g"
|
export _JAVA_OPTIONS="-Xmx2g"
|
||||||
d=$(date +%m.%d)
|
d=$(date +%m.%d)
|
||||||
# build only desktop and only try to move desktop files
|
# build only desktop and only try to move desktop files
|
||||||
mvn -U -B clean -P windows-linux install -e -T 1C release:clean release:prepare release:perform -DskipTests
|
mvn -U -B clean -P windows-linux install -DskipTests -Dskip.flatten=true -e -T 1C release:clean release:prepare release:perform
|
||||||
mkdir izpack
|
mkdir izpack
|
||||||
# move bz2 and jar from work dir to izpack dir
|
# move bz2 and jar from work dir to izpack dir
|
||||||
mv /home/runner/work/forge/forge/forge-installer/*/*.{bz2,jar} izpack/
|
mv /home/runner/work/forge/forge/forge-installer/*/*.{bz2,jar} izpack/
|
||||||
@@ -128,6 +128,44 @@ jobs:
|
|||||||
removeArtifacts: true
|
removeArtifacts: true
|
||||||
makeLatest: true
|
makeLatest: true
|
||||||
|
|
||||||
|
- name: 🔧 Install XML tools
|
||||||
|
run: sudo apt-get install -y libxml2-utils
|
||||||
|
|
||||||
|
- name: 🔼 Bump versionCode in root POM
|
||||||
|
id: bump_version
|
||||||
|
run: |
|
||||||
|
cd /home/runner/work/forge/forge/
|
||||||
|
|
||||||
|
current_version=$(xmllint --xpath "//*[local-name()='versionCode']/text()" pom.xml)
|
||||||
|
echo "Current versionCode: $current_version"
|
||||||
|
|
||||||
|
IFS='.' read -r major minor patch <<< "${current_version}"
|
||||||
|
new_patch=$(printf "%02d" $((10#$patch + 1)))
|
||||||
|
new_version="${major}.${minor}.${new_patch}"
|
||||||
|
|
||||||
|
sed -i -E "s|<versionCode>.*</versionCode>|<versionCode>${new_version}</versionCode>|" pom.xml
|
||||||
|
|
||||||
|
echo "version_code=${new_version}" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: ♻️ Restore {revision} in child POMs
|
||||||
|
run: |
|
||||||
|
find . -name pom.xml ! -path "./pom.xml" | while read -r pom; do
|
||||||
|
sed -i -E 's|<version>2\.0+\.[0-9]+(-SNAPSHOT)?</version>|<version>${revision}</version>|' "$pom"
|
||||||
|
done
|
||||||
|
|
||||||
|
- name: 💾 Commit restored {revision}
|
||||||
|
run: |
|
||||||
|
# Add only pom.xml files
|
||||||
|
find . -name pom.xml -exec git add {} \;
|
||||||
|
|
||||||
|
# Commit if there are changes
|
||||||
|
if git diff --cached --quiet; then
|
||||||
|
echo "No pom.xml changes to commit."
|
||||||
|
else
|
||||||
|
git commit -m "Restore POM files for preparation of next release" || echo "No changes to commit"
|
||||||
|
git push
|
||||||
|
fi
|
||||||
|
|
||||||
- name: Send failure notification to Discord
|
- name: Send failure notification to Discord
|
||||||
if: failure() # This step runs only if the job fails
|
if: failure() # This step runs only if the job fails
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ Dev instructions here: [Getting Started](https://github.com/Card-Forge/forge/wik
|
|||||||
|
|
||||||
## Requirements / Tools
|
## Requirements / Tools
|
||||||
|
|
||||||
- you favourite Java IDE (IntelliJ, Eclipse, VSCodium, Emacs, Vi...)
|
- your favourite Java IDE (IntelliJ, Eclipse, VSCodium, Emacs, Vi...)
|
||||||
- Java JDK 17 or later
|
- Java JDK 17 or later
|
||||||
- Git
|
- Git
|
||||||
- Git client (optional)
|
- Git client (optional)
|
||||||
@@ -22,42 +22,41 @@ Dev instructions here: [Getting Started](https://github.com/Card-Forge/forge/wik
|
|||||||
|
|
||||||
- Clone your forked project to your local machine
|
- Clone your forked project to your local machine
|
||||||
|
|
||||||
- Go to the project location on your machine. Run Maven to download all dependencies and build a snapshot. Example for Windows & Linux: `mvn -U -B clean -P windows-linux install`
|
- Go to the project location on your machine. Run Maven to download all dependencies and build a snapshot. Example for Windows & Linux: `mvn -U -B clean -P windows-linux install`
|
||||||
|
|
||||||
## IntelliJ
|
## IntelliJ
|
||||||
|
|
||||||
IntelliJ is the recommended IDE for Forge development. Quick start guide for [setting up the Forge project within IntelliJ](https://github.com/Card-Forge/forge/wiki/IntelliJ-setup).
|
IntelliJ is the recommended IDE for Forge development. Quick start guide for [setting up the Forge project within IntelliJ](https://github.com/Card-Forge/forge/wiki/IntelliJ-setup).
|
||||||
|
|
||||||
|
|
||||||
## Eclipse
|
## Eclipse
|
||||||
|
|
||||||
Eclipse includes Maven integration so a separate install is not necessary. For other IDEs, your mileage may vary.
|
Eclipse includes Maven integration so a separate install is not necessary. For other IDEs, your mileage may vary.
|
||||||
At this time, Eclipse is not the recommended IDE for Forge development.
|
At this time, Eclipse is not the recommended IDE for Forge development.
|
||||||
|
|
||||||
### Project Setup
|
### Project Setup
|
||||||
|
|
||||||
- Follow the instructions for cloning from GitHub. You'll need to setup an account and your SSH key.
|
- Follow the instructions for cloning from GitHub. You'll need to setup an account and your SSH key.
|
||||||
|
|
||||||
If you are on a Windows machine you can use Putty with TortoiseGit for SSH keys. Run puttygen.exe to generate the key -- save the private key and export
|
If you are on a Windows machine you can use Putty with TortoiseGit for SSH keys. Run puttygen.exe to generate the key -- save the private key and export
|
||||||
the OpenSSH public key. If you just leave the dialog open, you can copy and paste the key from it to your GitHub profile under
|
the OpenSSH public key. If you just leave the dialog open, you can copy and paste the key from it to your GitHub profile under
|
||||||
"SSH keys". Run pageant.exe and add the private key generated earlier. TortoiseGit will use this for accessing GitHub.
|
"SSH keys". Run pageant.exe and add the private key generated earlier. TortoiseGit will use this for accessing GitHub.
|
||||||
|
|
||||||
- Fork the Forge git repo to your GitHub account.
|
- Fork the Forge git repo to your GitHub account.
|
||||||
|
|
||||||
- Clone your forked repo to your local machine.
|
- Clone your forked repo to your local machine.
|
||||||
|
|
||||||
- Make sure the Java SDK is installed -- not just the JRE. Java 17 or newer required. If you execute `java -version` at the shell or command prompt, it should report version 17 or later.
|
- Make sure the Java SDK is installed -- not just the JRE. Java 17 or newer required. If you execute `java -version` at the shell or command prompt, it should report version 17 or later.
|
||||||
|
|
||||||
- Install Eclipse 2021-12 or later for Java. Launch it.
|
- Install Eclipse 2021-12 or later for Java. Launch it.
|
||||||
|
|
||||||
- Create a workspace. Go to the workbench. Right-click inside of Package Explorer > Import... > Maven > Existing Maven Projects > Navigate to root path of the local forge repo and
|
- Create a workspace. Go to the workbench. Right-click inside of Package Explorer > Import... > Maven > Existing Maven Projects > Navigate to root path of the local forge repo and
|
||||||
ensure everything is checked > Finish.
|
ensure everything is checked > Finish.
|
||||||
|
|
||||||
- Let Eclipse run through building the project. You may be prompted for resolving any missing Maven plugins -- accept the ones offered. You may see errors appear in the "Problems" tab. These should
|
- Let Eclipse run through building the project. You may be prompted for resolving any missing Maven plugins -- accept the ones offered. You may see errors appear in the "Problems" tab. These should
|
||||||
be automatically resolved as plug-ins are installed and Eclipse continues the build process. If this is the first time for some plug-in installs, Eclipse may prompt you to restart. Do so. Be patient
|
be automatically resolved as plug-ins are installed and Eclipse continues the build process. If this is the first time for some plug-in installs, Eclipse may prompt you to restart. Do so. Be patient
|
||||||
for this first time through.
|
for this first time through.
|
||||||
|
|
||||||
- Once everything builds, all errors should disappear. You can now advance to Project launch.
|
- Once everything builds, all errors should disappear. You can now advance to Project launch.
|
||||||
|
|
||||||
### Project Launch
|
### Project Launch
|
||||||
|
|
||||||
@@ -67,15 +66,15 @@ This is the standard configuration used for releasing to Windows / Linux / MacOS
|
|||||||
|
|
||||||
- Right-click on forge-gui-desktop > Run As... > Java Application > "Main - forge.view" > Ok
|
- Right-click on forge-gui-desktop > Run As... > Java Application > "Main - forge.view" > Ok
|
||||||
|
|
||||||
- The familiar Forge splash screen, etc. should appear. Enjoy!
|
- The familiar Forge splash screen, etc. should appear. Enjoy!
|
||||||
|
|
||||||
#### Mobile (Desktop dev)
|
#### Mobile (Desktop dev)
|
||||||
|
|
||||||
This is the configuration used for doing mobile development using the Windows / Linux / MacOS front-end. Knowledge of libgdx is helpful here.
|
This is the configuration used for doing mobile development using the Windows / Linux / MacOS front-end. Knowledge of libgdx is helpful here.
|
||||||
|
|
||||||
- Right-click on forge-gui-mobile-dev > Run As... > Java Application > "Main - forge.app" > Ok.
|
- Right-click on forge-gui-mobile-dev > Run As... > Java Application > "Main - forge.app" > Ok.
|
||||||
|
|
||||||
- A view similar to a mobile phone should appear. Enjoy!
|
- A view similar to a mobile phone should appear. Enjoy!
|
||||||
|
|
||||||
### Eclipse / Android SDK Integration
|
### Eclipse / Android SDK Integration
|
||||||
|
|
||||||
@@ -99,7 +98,7 @@ TBD
|
|||||||
|
|
||||||
#### Android Platform
|
#### Android Platform
|
||||||
|
|
||||||
In Intellij, if the SDK Manager is not already running, go to Tools > Android > Android SDK Manager. Install the following options / versions:
|
In Intellij, if the SDK Manager is not already running, go to Tools > Android > Android SDK Manager. Install the following options / versions:
|
||||||
|
|
||||||
- Android SDK Build-tools 35.0.0
|
- Android SDK Build-tools 35.0.0
|
||||||
- Android 15 (API 35) SDK Platform
|
- Android 15 (API 35) SDK Platform
|
||||||
@@ -124,10 +123,11 @@ TBD
|
|||||||
|
|
||||||
SNAPSHOT builds can be built via the Maven integration in Eclipse.
|
SNAPSHOT builds can be built via the Maven integration in Eclipse.
|
||||||
|
|
||||||
1) Create a Maven build for the forge top-level project. Right-click on the forge project. Run as.. > Maven build...
|
1. Create a Maven build for the forge top-level project. Right-click on the forge project. Run as.. > Maven build...
|
||||||
|
|
||||||
- On the Main tab, set Goals: clean install, set Profiles: windows-linux
|
- On the Main tab, set Goals: clean install, set Profiles: windows-linux
|
||||||
|
|
||||||
2) Run forge Maven build. If everything built, you should see "BUILD SUCCESS" in the Console View.
|
2. Run forge Maven build. If everything built, you should see "BUILD SUCCESS" in the Console View.
|
||||||
|
|
||||||
The resulting snapshot will be found at: forge-gui-desktop/target/forge-gui-desktop-[version]-SNAPSHOT
|
The resulting snapshot will be found at: forge-gui-desktop/target/forge-gui-desktop-[version]-SNAPSHOT
|
||||||
|
|
||||||
@@ -141,7 +141,7 @@ Card scripting resources are found in the forge-gui/res/ path.
|
|||||||
|
|
||||||
### Project Hierarchy
|
### Project Hierarchy
|
||||||
|
|
||||||
Forge is divided into 4 primary projects with additional projects that target specific platform releases. The primary projects are:
|
Forge is divided into 4 primary projects with additional projects that target specific platform releases. The primary projects are:
|
||||||
|
|
||||||
- forge-ai
|
- forge-ai
|
||||||
- forge-core
|
- forge-core
|
||||||
@@ -158,32 +158,38 @@ The platform-specific projects are:
|
|||||||
|
|
||||||
#### forge-ai
|
#### forge-ai
|
||||||
|
|
||||||
|
The forge-ai project contains the computer opponent logic for gameplay. It includes decision-making algorithms for specific abilities, cards and turn phases.
|
||||||
|
|
||||||
#### forge-core
|
#### forge-core
|
||||||
|
|
||||||
|
The forge-core project contains the core game engine, card mechanics, rules engine, and fundamental game logic. It includes the implementation of Magic: The Gathering rules, card interactions, and the game state management system.
|
||||||
|
|
||||||
#### forge-game
|
#### forge-game
|
||||||
|
|
||||||
|
The forge-game project handles the game session management, player interactions, and game flow control. It includes implementations for multiplayer support, game modes, matchmaking, and game state persistence. This module bridges the core game engine with the user interface and networking components.
|
||||||
|
|
||||||
#### forge-gui
|
#### forge-gui
|
||||||
|
|
||||||
The forge-gui project includes the scripting resource definitions in the res/ path.
|
The forge-gui project contains the user interface components and rendering logic for the game. It includes the main game window, card displays, player interactions, and the scripting resource definitions in the res/ path.
|
||||||
|
|
||||||
#### forge-gui-android
|
#### forge-gui-android
|
||||||
|
|
||||||
Libgdx-based backend targeting Android. Requires Android SDK and relies on forge-gui-mobile for GUI logic.
|
Libgdx-based backend targeting Android. Requires Android SDK and relies on forge-gui-mobile for GUI logic.
|
||||||
|
|
||||||
#### forge-gui-desktop
|
#### forge-gui-desktop
|
||||||
|
|
||||||
Java Swing based GUI targeting desktop machines.
|
Java Swing based GUI targeting desktop machines.
|
||||||
|
|
||||||
Screen layout and game logic revolving around the GUI is found here. For example, the overlay arrows (when enabled) that indicate attackers and blockers, or the targets of the stack are defined and drawn by this.
|
Screen layout and game logic revolving around the GUI is found here. For example, the overlay arrows (when enabled) that indicate attackers and blockers, or the targets of the stack are defined and drawn by this.
|
||||||
|
|
||||||
#### forge-gui-ios
|
#### forge-gui-ios
|
||||||
|
|
||||||
Libgdx-based backend targeting iOS. Relies on forge-gui-mobile for GUI logic.
|
Libgdx-based backend targeting iOS. Relies on forge-gui-mobile for GUI logic.
|
||||||
|
|
||||||
#### forge-gui-mobile
|
#### forge-gui-mobile
|
||||||
|
|
||||||
Mobile GUI game logic utilizing [libgdx](https://libgdx.badlogicgames.com/) library. Screen layout and game logic revolving around the GUI for the mobile platforms is found here.
|
Mobile GUI game logic utilizing [libgdx](https://libgdx.badlogicgames.com/) library. Screen layout and game logic revolving around the GUI for the mobile platforms is found here.
|
||||||
|
|
||||||
#### forge-gui-mobile-dev
|
#### forge-gui-mobile-dev
|
||||||
|
|
||||||
Libgdx backend for desktop development for mobile backends. Utilizes LWJGL. Relies on forge-gui-mobile for GUI logic.
|
Libgdx backend for desktop development for mobile backends. Utilizes LWJGL. Relies on forge-gui-mobile for GUI logic.
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# ⚔️ Forge: The Magic: The Gathering Rules Engine
|
# ⚔️ Forge: The Magic: The Gathering Rules Engine
|
||||||
|
|
||||||
Join the **Forge community** on [Discord](https://discord.gg/HcPJNyD66a)!
|
Join the **Forge community** on [Discord](https://discord.gg/HcPJNyD66a)!
|
||||||
|
|
||||||
@@ -32,6 +32,7 @@ Join the **Forge community** on [Discord](https://discord.gg/HcPJNyD66a)!
|
|||||||
4. **Java Requirement:** Ensure you have **Java 17 or later** installed.
|
4. **Java Requirement:** Ensure you have **Java 17 or later** installed.
|
||||||
|
|
||||||
### 📱 Android Installation
|
### 📱 Android Installation
|
||||||
|
- _(Note: **Android 11** is the minimum requirements with at least **6GB RAM** to run smoothly. You need to enable **"Install unknown apps"** for Forge to initialize and update itself)_
|
||||||
- Download the **APK** from the [Snapshot Build](https://github.com/Card-Forge/forge/releases/tag/daily-snapshots). On the first launch, Forge will automatically download all necessary assets.
|
- Download the **APK** from the [Snapshot Build](https://github.com/Card-Forge/forge/releases/tag/daily-snapshots). On the first launch, Forge will automatically download all necessary assets.
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -46,11 +47,13 @@ Embark on a thrilling single-player journey where you can:
|
|||||||
- Challenge diverse AI opponents.
|
- Challenge diverse AI opponents.
|
||||||
- Collect cards and items to boost your abilities.
|
- Collect cards and items to boost your abilities.
|
||||||
|
|
||||||

|
<img width="1282" height="752" alt="Shandalar World" src="https://github.com/user-attachments/assets/9af31471-d688-442f-9418-9807d8635b72" />
|
||||||
|
|
||||||
### 🔍 Quest Modes
|
### 🔍 Quest Modes
|
||||||
Engage in focused gameplay without the overworld exploration—perfect for quick sessions!
|
Engage in focused gameplay without the overworld exploration—perfect for quick sessions!
|
||||||
|
|
||||||
|
<img width="1282" height="752" alt="Quest Duels" src="https://github.com/user-attachments/assets/b9613b1c-e8c3-4320-8044-6922c519aad4" />
|
||||||
|
|
||||||
### 🤖 AI Formats
|
### 🤖 AI Formats
|
||||||
Test your skills against AI in multiple formats:
|
Test your skills against AI in multiple formats:
|
||||||
- **Sealed**
|
- **Sealed**
|
||||||
@@ -60,6 +63,8 @@ Test your skills against AI in multiple formats:
|
|||||||
|
|
||||||
For comprehensive gameplay instructions, visit our [Gameplay Guide](https://github.com/Card-Forge/forge/wiki/Gameplay-Guide).
|
For comprehensive gameplay instructions, visit our [Gameplay Guide](https://github.com/Card-Forge/forge/wiki/Gameplay-Guide).
|
||||||
|
|
||||||
|
<img width="1282" height="752" alt="Sealed" src="https://github.com/user-attachments/assets/ae603dbd-4421-4753-a333-87cb0a28d772" />
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 💬 Support & Community
|
## 💬 Support & Community
|
||||||
|
|||||||
9
forge-ai/src/main/java/forge/ai/AiAbilityDecision.java
Normal file
9
forge-ai/src/main/java/forge/ai/AiAbilityDecision.java
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
package forge.ai;
|
||||||
|
|
||||||
|
public record AiAbilityDecision(int rating, AiPlayDecision decision) {
|
||||||
|
private static int MIN_RATING = 30;
|
||||||
|
|
||||||
|
public boolean willingToPlay() {
|
||||||
|
return rating > MIN_RATING && decision.willingToPlay();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -145,13 +145,15 @@ public class AiAttackController {
|
|||||||
sa.setActivatingPlayer(defender);
|
sa.setActivatingPlayer(defender);
|
||||||
if (sa.isCrew() && !ComputerUtilCost.checkTapTypeCost(defender, sa.getPayCosts(), c, sa, tappedDefenders)) {
|
if (sa.isCrew() && !ComputerUtilCost.checkTapTypeCost(defender, sa.getPayCosts(), c, sa, tappedDefenders)) {
|
||||||
continue;
|
continue;
|
||||||
} else if (!ComputerUtilCost.canPayCost(sa, defender, false) || !sa.getRestrictions().checkOtherRestrictions(c, sa, defender)) {
|
}
|
||||||
|
if (!ComputerUtilCost.canPayCost(sa, defender, false) || !sa.getRestrictions().checkOtherRestrictions(c, sa, defender)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
Card animatedCopy = AnimateAi.becomeAnimated(c, sa);
|
Card animatedCopy = AnimateAi.becomeAnimated(c, sa);
|
||||||
if (animatedCopy.isCreature()) {
|
if (animatedCopy.isCreature()) {
|
||||||
|
// TODO imprecise, only works 100% for colorless mana
|
||||||
int saCMC = sa.getPayCosts() != null && sa.getPayCosts().hasManaCost() ?
|
int saCMC = sa.getPayCosts() != null && sa.getPayCosts().hasManaCost() ?
|
||||||
sa.getPayCosts().getTotalMana().getCMC() : 0; // FIXME: imprecise, only works 100% for colorless mana
|
sa.getPayCosts().getTotalMana().getCMC() : 0;
|
||||||
if (totalMana - manaReserved >= saCMC) {
|
if (totalMana - manaReserved >= saCMC) {
|
||||||
manaReserved += saCMC;
|
manaReserved += saCMC;
|
||||||
defenders.add(animatedCopy);
|
defenders.add(animatedCopy);
|
||||||
|
|||||||
@@ -911,56 +911,14 @@ public class AiController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int oldCMC = -1;
|
// this is the "heaviest" check, which also sets up targets, defines X, etc.
|
||||||
boolean xCost = sa.costHasX() || host.hasKeyword(Keyword.STRIVE) || sa.getApi() == ApiType.Charm;
|
AiPlayDecision canPlay = canPlaySa(sa);
|
||||||
if (!xCost) {
|
|
||||||
if (!ComputerUtilCost.canPayCost(sa, player, sa.isTrigger())) {
|
|
||||||
// for most costs, it's OK to check if they can be paid early in order to avoid running a heavy API check
|
|
||||||
// when the AI won't even be able to play the spell in the first place (even if it could afford it)
|
|
||||||
return AiPlayDecision.CantAfford;
|
|
||||||
}
|
|
||||||
// TODO check for Reduce too, e.g. Battlefield Thaumaturge could make it castable
|
|
||||||
if (!sa.getAllTargetChoices().isEmpty()) {
|
|
||||||
oldCMC = CostAdjustment.adjust(sa.getPayCosts(), sa, false).getTotalMana().getCMC();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
AiPlayDecision canPlay = canPlaySa(sa); // this is the "heaviest" check, which also sets up targets, defines X, etc.
|
|
||||||
|
|
||||||
if (canPlay != AiPlayDecision.WillPlay) {
|
if (canPlay != AiPlayDecision.WillPlay) {
|
||||||
return canPlay;
|
return canPlay;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Account for possible Ward after the spell is fully targeted
|
if (!ComputerUtilCost.canPayCost(sa, player, sa.isTrigger())) {
|
||||||
// TODO: ideally, this should be done while targeting, so that a different target can be preferred if the best
|
|
||||||
// one is warded and can't be paid for. (currently it will be stuck with the target until it could pay)
|
|
||||||
if (!sa.isSpell() || sa.isCounterableBy(null)) {
|
|
||||||
for (TargetChoices tc : sa.getAllTargetChoices()) {
|
|
||||||
for (Card tgt : tc.getTargetCards()) {
|
|
||||||
// TODO some older cards don't use the keyword, so check for trigger instead
|
|
||||||
if (tgt.hasKeyword(Keyword.WARD) && tgt.isInPlay() && tgt.getController().isOpponentOf(host.getController())) {
|
|
||||||
Cost wardCost = ComputerUtilCard.getTotalWardCost(tgt);
|
|
||||||
if (wardCost.hasManaCost()) {
|
|
||||||
xCost |= wardCost.getTotalMana().getCMC() > 0;
|
|
||||||
}
|
|
||||||
SpellAbilityAi topAI = new SpellAbilityAi() {};
|
|
||||||
if (!topAI.willPayCosts(player, sa, wardCost, host)) {
|
|
||||||
return AiPlayDecision.CostNotAcceptable;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// check if some target raised cost
|
|
||||||
if (!xCost && oldCMC > -1) {
|
|
||||||
int finalCMC = CostAdjustment.adjust(sa.getPayCosts(), sa, false).getTotalMana().getCMC();
|
|
||||||
if (finalCMC > oldCMC) {
|
|
||||||
xCost = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (xCost && !ComputerUtilCost.canPayCost(sa, player, sa.isTrigger())) {
|
|
||||||
// for dependent costs with X, e.g. Repeal, which require a valid target to be specified before a decision can be made
|
// for dependent costs with X, e.g. Repeal, which require a valid target to be specified before a decision can be made
|
||||||
// on whether the cost can be paid, this can only be checked late after canPlaySa has been run (or the AI will misplay)
|
// on whether the cost can be paid, this can only be checked late after canPlaySa has been run (or the AI will misplay)
|
||||||
return AiPlayDecision.CantAfford;
|
return AiPlayDecision.CantAfford;
|
||||||
@@ -973,8 +931,6 @@ public class AiController {
|
|||||||
return AiPlayDecision.CantAfford;
|
return AiPlayDecision.CantAfford;
|
||||||
}
|
}
|
||||||
|
|
||||||
// if we got here, looks like we can play the final cost and we could properly set up and target the API and
|
|
||||||
// are willing to play the SA
|
|
||||||
return AiPlayDecision.WillPlay;
|
return AiPlayDecision.WillPlay;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1017,7 +973,7 @@ public class AiController {
|
|||||||
Sentry.setExtra("Card", card.getName());
|
Sentry.setExtra("Card", card.getName());
|
||||||
Sentry.setExtra("SA", sa.toString());
|
Sentry.setExtra("SA", sa.toString());
|
||||||
|
|
||||||
boolean canPlay = SpellApiToAi.Converter.get(sa).canPlayAIWithSubs(player, sa);
|
boolean canPlay = SpellApiToAi.Converter.get(sa).canPlayWithSubs(player, sa).willingToPlay();
|
||||||
|
|
||||||
// remove added extra
|
// remove added extra
|
||||||
Sentry.removeExtra("Card");
|
Sentry.removeExtra("Card");
|
||||||
@@ -1395,9 +1351,9 @@ public class AiController {
|
|||||||
if (spell instanceof SpellApiBased) {
|
if (spell instanceof SpellApiBased) {
|
||||||
boolean chance = false;
|
boolean chance = false;
|
||||||
if (withoutPayingManaCost) {
|
if (withoutPayingManaCost) {
|
||||||
chance = SpellApiToAi.Converter.get(spell).doTriggerNoCostWithSubs(player, spell, mandatory);
|
chance = SpellApiToAi.Converter.get(spell).doTriggerNoCostWithSubs(player, spell, mandatory).willingToPlay();
|
||||||
} else {
|
} else {
|
||||||
chance = SpellApiToAi.Converter.get(spell).doTriggerAI(player, spell, mandatory);
|
chance = SpellApiToAi.Converter.get(spell).doTrigger(player, spell, mandatory);
|
||||||
}
|
}
|
||||||
if (!chance) {
|
if (!chance) {
|
||||||
return AiPlayDecision.TargetingFailed;
|
return AiPlayDecision.TargetingFailed;
|
||||||
@@ -1804,9 +1760,9 @@ public class AiController {
|
|||||||
|
|
||||||
for (int i = 0; i < numToExile; i++) {
|
for (int i = 0; i < numToExile; i++) {
|
||||||
Card chosen = null;
|
Card chosen = null;
|
||||||
for (final Card c : grave) { // Exile noncreatures first in
|
for (final Card c : grave) {
|
||||||
// case we can revive. Might wanna do some additional
|
// Exile noncreatures first in case we can revive
|
||||||
// checking here for Flashback and the like.
|
// Might wanna do some additional checking here for Flashback and the like
|
||||||
if (!c.isCreature()) {
|
if (!c.isCreature()) {
|
||||||
chosen = c;
|
chosen = c;
|
||||||
break;
|
break;
|
||||||
@@ -1827,12 +1783,12 @@ public class AiController {
|
|||||||
return toExile;
|
return toExile;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean doTrigger(SpellAbility spell, boolean mandatory) {
|
public boolean doTrigger(SpellAbility sa, boolean mandatory) {
|
||||||
if (spell instanceof WrappedAbility)
|
if (sa instanceof WrappedAbility)
|
||||||
return doTrigger(((WrappedAbility) spell).getWrappedAbility(), mandatory);
|
return doTrigger(((WrappedAbility) sa).getWrappedAbility(), mandatory);
|
||||||
if (spell.getApi() != null)
|
if (sa.getApi() != null)
|
||||||
return SpellApiToAi.Converter.get(spell).doTriggerAI(player, spell, mandatory);
|
return SpellApiToAi.Converter.get(sa).doTrigger(player, sa, mandatory);
|
||||||
if (spell.getPayCosts() == Cost.Zero && !spell.usesTargeting()) {
|
if (sa.getPayCosts() == Cost.Zero && !sa.usesTargeting()) {
|
||||||
// For non-converted triggers (such as Cumulative Upkeep) that don't have costs or targets to worry about
|
// For non-converted triggers (such as Cumulative Upkeep) that don't have costs or targets to worry about
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -767,6 +767,12 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
|||||||
public PaymentDecision visit(CostRemoveCounter cost) {
|
public PaymentDecision visit(CostRemoveCounter cost) {
|
||||||
final String amount = cost.getAmount();
|
final String amount = cost.getAmount();
|
||||||
final String type = cost.getType();
|
final String type = cost.getType();
|
||||||
|
final GameEntityCounterTable counterTable = new GameEntityCounterTable();
|
||||||
|
|
||||||
|
// TODO Help AI filter card with most useless counters and put those counters in countertable for things like
|
||||||
|
// Moxite Refinery, similar to CostRemoveAnyCounter
|
||||||
|
// Probably a lot of that decision making can be re-used or pulled out for both PaymentDecisions to use
|
||||||
|
if (cost.counter == null) return null;
|
||||||
|
|
||||||
int c;
|
int c;
|
||||||
|
|
||||||
@@ -795,7 +801,8 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
|||||||
}
|
}
|
||||||
for (Card card : typeList) {
|
for (Card card : typeList) {
|
||||||
if (card.getCounters(cost.counter) >= c) {
|
if (card.getCounters(cost.counter) >= c) {
|
||||||
return PaymentDecision.card(card, c);
|
counterTable.put(null, card, cost.counter, c);
|
||||||
|
return PaymentDecision.counters(counterTable);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
@@ -806,7 +813,8 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return PaymentDecision.card(source, c);
|
counterTable.put(null, source, cost.counter, c);
|
||||||
|
return PaymentDecision.counters(counterTable);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -1,21 +1,52 @@
|
|||||||
package forge.ai;
|
package forge.ai;
|
||||||
|
|
||||||
public enum AiPlayDecision {
|
public enum AiPlayDecision {
|
||||||
|
// Play decision reasons
|
||||||
WillPlay,
|
WillPlay,
|
||||||
|
MandatoryPlay,
|
||||||
|
PlayToEmptyHand,
|
||||||
|
ImpactCombat,
|
||||||
|
ResponseToStackResolve,
|
||||||
|
AddBoardPresence,
|
||||||
|
Removal,
|
||||||
|
Tempo,
|
||||||
|
CardAdvantage,
|
||||||
|
|
||||||
|
// Play later decisions
|
||||||
|
WaitForCombat,
|
||||||
|
WaitForMain2,
|
||||||
|
WaitForEndOfTurn,
|
||||||
|
StackNotEmpty,
|
||||||
|
AnotherTime,
|
||||||
|
|
||||||
|
// Don't play decision reasons,
|
||||||
CantPlaySa,
|
CantPlaySa,
|
||||||
CantPlayAi,
|
CantPlayAi,
|
||||||
CantAfford,
|
CantAfford,
|
||||||
CantAffordX,
|
CantAffordX,
|
||||||
WaitForMain2,
|
DoesntImpactCombat,
|
||||||
AnotherTime,
|
DoesntImpactGame,
|
||||||
|
MissingLogic,
|
||||||
MissingNeededCards,
|
MissingNeededCards,
|
||||||
|
TimingRestrictions,
|
||||||
|
MissingPhaseRestrictions,
|
||||||
|
ConditionsNotMet,
|
||||||
NeedsToPlayCriteriaNotMet,
|
NeedsToPlayCriteriaNotMet,
|
||||||
|
StopRunawayActivations,
|
||||||
TargetingFailed,
|
TargetingFailed,
|
||||||
CostNotAcceptable,
|
CostNotAcceptable,
|
||||||
|
LifeInDanger,
|
||||||
WouldDestroyLegend,
|
WouldDestroyLegend,
|
||||||
WouldDestroyOtherPlaneswalker,
|
WouldDestroyOtherPlaneswalker,
|
||||||
WouldBecomeZeroToughnessCreature,
|
WouldBecomeZeroToughnessCreature,
|
||||||
WouldDestroyWorldEnchantment,
|
WouldDestroyWorldEnchantment,
|
||||||
BadEtbEffects,
|
BadEtbEffects,
|
||||||
CurseEffects
|
CurseEffects;
|
||||||
|
|
||||||
|
public boolean willingToPlay() {
|
||||||
|
return switch (this) {
|
||||||
|
case WillPlay, MandatoryPlay, PlayToEmptyHand, AddBoardPresence, ImpactCombat, ResponseToStackResolve, Removal, Tempo, CardAdvantage -> true;
|
||||||
|
default -> false;
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -864,7 +864,7 @@ public class ComputerUtil {
|
|||||||
|
|
||||||
// Run non-mandatory trigger.
|
// Run non-mandatory trigger.
|
||||||
// These checks only work if the Executing SpellAbility is an Ability_Sub.
|
// These checks only work if the Executing SpellAbility is an Ability_Sub.
|
||||||
if ((exSA instanceof AbilitySub) && !SpellApiToAi.Converter.get(exSA).doTriggerAI(ai, exSA, false)) {
|
if ((exSA instanceof AbilitySub) && !SpellApiToAi.Converter.get(exSA).doTrigger(ai, exSA, false)) {
|
||||||
// AI would not run this trigger if given the chance
|
// AI would not run this trigger if given the chance
|
||||||
return sacrificed;
|
return sacrificed;
|
||||||
}
|
}
|
||||||
@@ -1385,13 +1385,14 @@ public class ComputerUtil {
|
|||||||
|
|
||||||
// returns true if the AI should stop using the ability
|
// returns true if the AI should stop using the ability
|
||||||
public static boolean preventRunAwayActivations(final SpellAbility sa) {
|
public static boolean preventRunAwayActivations(final SpellAbility sa) {
|
||||||
int activations = sa.getActivationsThisTurn();
|
if (!sa.isActivatedAbility()) {
|
||||||
|
return false;
|
||||||
if (!sa.isIntrinsic()) {
|
|
||||||
return MyRandom.getRandom().nextFloat() >= .95; // Abilities created by static abilities have no memory
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (activations < 10) { //10 activations per turn should still be acceptable
|
int activations = sa.getActivationsThisTurn();
|
||||||
|
|
||||||
|
//10 activations should still be acceptable
|
||||||
|
if (activations < 10) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2736,7 +2737,7 @@ public class ComputerUtil {
|
|||||||
return safeCards;
|
return safeCards;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Card getKilledByTargeting(final SpellAbility sa, CardCollectionView validCards) {
|
public static Card getKilledByTargeting(final SpellAbility sa, Iterable<Card> validCards) {
|
||||||
CardCollection killables = CardLists.filter(validCards, c -> c.getController() != sa.getActivatingPlayer() && c.getSVar("Targeting").equals("Dies"));
|
CardCollection killables = CardLists.filter(validCards, c -> c.getController() != sa.getActivatingPlayer() && c.getSVar("Targeting").equals("Dies"));
|
||||||
return ComputerUtilCard.getBestCreatureAI(killables);
|
return ComputerUtilCard.getBestCreatureAI(killables);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -345,6 +345,10 @@ public class ComputerUtilAbility {
|
|||||||
if (source.hasSVar("AIPriorityModifier")) {
|
if (source.hasSVar("AIPriorityModifier")) {
|
||||||
p += Integer.parseInt(source.getSVar("AIPriorityModifier"));
|
p += Integer.parseInt(source.getSVar("AIPriorityModifier"));
|
||||||
}
|
}
|
||||||
|
// try to use it before it's gone
|
||||||
|
if (source.isInPlay() && source.hasSVar("EndOfTurnLeavePlay")) {
|
||||||
|
p += 1;
|
||||||
|
}
|
||||||
if (ComputerUtilCard.isCardRemAIDeck(sa.getOriginalHost() != null ? sa.getOriginalHost() : source)) {
|
if (ComputerUtilCard.isCardRemAIDeck(sa.getOriginalHost() != null ? sa.getOriginalHost() : source)) {
|
||||||
p -= 10;
|
p -= 10;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1819,18 +1819,18 @@ public class ComputerUtilCard {
|
|||||||
* @param sa Pump* or CounterPut*
|
* @param sa Pump* or CounterPut*
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
public static boolean canPumpAgainstRemoval(Player ai, SpellAbility sa) {
|
public static AiAbilityDecision canPumpAgainstRemoval(Player ai, SpellAbility sa) {
|
||||||
final List<GameObject> objects = ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), sa, true);
|
final List<GameObject> objects = ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), sa, true);
|
||||||
|
|
||||||
if (!sa.usesTargeting()) {
|
if (!sa.usesTargeting()) {
|
||||||
final List<Card> cards = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("Defined"), sa);
|
final List<Card> cards = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("Defined"), sa);
|
||||||
for (final Card card : cards) {
|
for (final Card card : cards) {
|
||||||
if (objects.contains(card)) {
|
if (objects.contains(card)) {
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.ResponseToStackResolve);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// For pumps without targeting restrictions, just return immediately until this is fleshed out.
|
// For pumps without targeting restrictions, just return immediately until this is fleshed out.
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
|
|
||||||
CardCollection threatenedTargets = CardLists.getTargetableCards(ai.getCardsIn(ZoneType.Battlefield), sa);
|
CardCollection threatenedTargets = CardLists.getTargetableCards(ai.getCardsIn(ZoneType.Battlefield), sa);
|
||||||
@@ -1849,11 +1849,11 @@ public class ComputerUtilCard {
|
|||||||
}
|
}
|
||||||
if (!sa.isTargetNumberValid()) {
|
if (!sa.isTargetNumberValid()) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.ResponseToStackResolve);
|
||||||
}
|
}
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isUselessCreature(Player ai, Card c) {
|
public static boolean isUselessCreature(Player ai, Card c) {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import java.util.List;
|
|||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
|
import forge.game.GameObject;
|
||||||
import org.apache.commons.lang3.ObjectUtils;
|
import org.apache.commons.lang3.ObjectUtils;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
@@ -139,11 +140,13 @@ public class ComputerUtilCost {
|
|||||||
if (type.equals("CARDNAME")) {
|
if (type.equals("CARDNAME")) {
|
||||||
if (source.getAbilityText().contains("Bloodrush")) {
|
if (source.getAbilityText().contains("Bloodrush")) {
|
||||||
continue;
|
continue;
|
||||||
} else if (ai.getGame().getPhaseHandler().is(PhaseType.END_OF_TURN, ai)
|
}
|
||||||
|
if (ai.getGame().getPhaseHandler().is(PhaseType.END_OF_TURN, ai)
|
||||||
&& !ai.isUnlimitedHandSize() && ai.getCardsIn(ZoneType.Hand).size() > ai.getMaxHandSize()) {
|
&& !ai.isUnlimitedHandSize() && ai.getCardsIn(ZoneType.Hand).size() > ai.getMaxHandSize()) {
|
||||||
// Better do something than just discard stuff
|
// Better do something than just discard stuff
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
typeList = CardLists.getValidCards(hand, type, source.getController(), source, sa);
|
typeList = CardLists.getValidCards(hand, type, source.getController(), source, sa);
|
||||||
if (typeList.size() > ai.getMaxHandSize()) {
|
if (typeList.size() > ai.getMaxHandSize()) {
|
||||||
@@ -248,11 +251,7 @@ public class ComputerUtilCost {
|
|||||||
// Does the AI want to use Sacrifice All?
|
// Does the AI want to use Sacrifice All?
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
Integer c = part.convertAmount();
|
int c = part.getAbilityAmount(sourceAbility);
|
||||||
|
|
||||||
if (c == null) {
|
|
||||||
c = part.getAbilityAmount(sourceAbility);
|
|
||||||
}
|
|
||||||
final AiController aic = ((PlayerControllerAi)ai.getController()).getAi();
|
final AiController aic = ((PlayerControllerAi)ai.getController()).getAi();
|
||||||
CardCollectionView choices = aic.chooseSacrificeType(part.getType(), sourceAbility, effect, c, exclude);
|
CardCollectionView choices = aic.chooseSacrificeType(part.getType(), sourceAbility, effect, c, exclude);
|
||||||
if (choices != null) {
|
if (choices != null) {
|
||||||
@@ -522,13 +521,12 @@ public class ComputerUtilCost {
|
|||||||
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.
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean cannotBeCountered = false;
|
|
||||||
|
|
||||||
// Check for stuff like Nether Void
|
// Check for stuff like Nether Void
|
||||||
int extraManaNeeded = 0;
|
int extraManaNeeded = 0;
|
||||||
if (!effect) {
|
if (!effect) {
|
||||||
|
boolean cannotBeCountered = !sa.isCounterableBy(null);
|
||||||
|
|
||||||
if (sa instanceof Spell) {
|
if (sa instanceof Spell) {
|
||||||
cannotBeCountered = !sa.isCounterableBy(null);
|
|
||||||
for (Card c : player.getGame().getCardsIn(ZoneType.Battlefield)) {
|
for (Card c : player.getGame().getCardsIn(ZoneType.Battlefield)) {
|
||||||
final String snem = c.getSVar("AI_SpellsNeedExtraMana");
|
final String snem = c.getSVar("AI_SpellsNeedExtraMana");
|
||||||
if (!StringUtils.isBlank(snem)) {
|
if (!StringUtils.isBlank(snem)) {
|
||||||
@@ -578,12 +576,24 @@ public class ComputerUtilCost {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ward - will be accounted for when rechecking a targeted ability
|
// Account for possible Ward after the spell is fully targeted
|
||||||
if (!sa.isTrigger() && (!sa.isSpell() || !cannotBeCountered)) {
|
// TODO: ideally, this should be done while targeting, so that a different target can be preferred if the best
|
||||||
|
// one is warded and can't be paid for. (currently it will be stuck with the target until it could pay)
|
||||||
|
if (!sa.isTrigger() && !cannotBeCountered) {
|
||||||
|
Set<GameObject> distinctObjects = Sets.newHashSet();
|
||||||
for (TargetChoices tc : sa.getAllTargetChoices()) {
|
for (TargetChoices tc : sa.getAllTargetChoices()) {
|
||||||
for (Card tgt : tc.getTargetCards()) {
|
for (Card tgt : tc.getTargetCards()) {
|
||||||
|
if (!distinctObjects.add(tgt)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// TODO some older cards don't use the keyword, so check for trigger instead
|
||||||
if (tgt.hasKeyword(Keyword.WARD) && tgt.isInPlay() && tgt.getController().isOpponentOf(sa.getHostCard().getController())) {
|
if (tgt.hasKeyword(Keyword.WARD) && tgt.isInPlay() && tgt.getController().isOpponentOf(sa.getHostCard().getController())) {
|
||||||
Cost wardCost = ComputerUtilCard.getTotalWardCost(tgt);
|
Cost wardCost = ComputerUtilCard.getTotalWardCost(tgt);
|
||||||
|
// don't use API converter since it might have special part logic not meant for Ward cost
|
||||||
|
SpellAbilityAi topAI = new SpellAbilityAi() {};
|
||||||
|
if (!topAI.willPayCosts(player, sa, wardCost, sa.getHostCard())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if (wardCost.hasManaCost()) {
|
if (wardCost.hasManaCost()) {
|
||||||
extraManaNeeded += wardCost.getTotalMana().getCMC();
|
extraManaNeeded += wardCost.getTotalMana().getCMC();
|
||||||
}
|
}
|
||||||
@@ -607,6 +617,7 @@ public class ComputerUtilCost {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO both of these call CostAdjustment.adjust, try to reuse instead
|
||||||
return ComputerUtilMana.canPayManaCost(cost, sa, player, extraManaNeeded, effect)
|
return ComputerUtilMana.canPayManaCost(cost, sa, player, extraManaNeeded, effect)
|
||||||
&& CostPayment.canPayAdditionalCosts(cost, sa, effect, player);
|
&& CostPayment.canPayAdditionalCosts(cost, sa, effect, player);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -226,7 +226,7 @@ public class ComputerUtilMana {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static SpellAbility chooseManaAbility(ManaCostBeingPaid cost, SpellAbility sa, Player ai, ManaCostShard toPay,
|
public static SpellAbility chooseManaAbility(ManaCostBeingPaid cost, SpellAbility sa, Player ai, ManaCostShard toPay,
|
||||||
Collection<SpellAbility> saList, boolean checkCosts) {
|
Collection<SpellAbility> maList, boolean checkCosts) {
|
||||||
Card saHost = sa.getHostCard();
|
Card saHost = sa.getHostCard();
|
||||||
|
|
||||||
// CastTotalManaSpent (AIPreference:ManaFrom$Type or AIManaPref$ Type)
|
// CastTotalManaSpent (AIPreference:ManaFrom$Type or AIManaPref$ Type)
|
||||||
@@ -240,12 +240,12 @@ public class ComputerUtilMana {
|
|||||||
manaSourceType = sa.getParam("AIManaPref");
|
manaSourceType = sa.getParam("AIManaPref");
|
||||||
}
|
}
|
||||||
if (manaSourceType != "") {
|
if (manaSourceType != "") {
|
||||||
List<SpellAbility> filteredList = Lists.newArrayList(saList);
|
List<SpellAbility> filteredList = Lists.newArrayList(maList);
|
||||||
switch (manaSourceType) {
|
switch (manaSourceType) {
|
||||||
case "Snow":
|
case "Snow":
|
||||||
filteredList.sort((ab1, ab2) -> ab1.getHostCard() != null && ab1.getHostCard().isSnow()
|
filteredList.sort((ab1, ab2) -> ab1.getHostCard() != null && ab1.getHostCard().isSnow()
|
||||||
&& ab2.getHostCard() != null && !ab2.getHostCard().isSnow() ? -1 : 1);
|
&& ab2.getHostCard() != null && !ab2.getHostCard().isSnow() ? -1 : 1);
|
||||||
saList = filteredList;
|
maList = filteredList;
|
||||||
break;
|
break;
|
||||||
case "Treasure":
|
case "Treasure":
|
||||||
// Try to spend only one Treasure if possible
|
// Try to spend only one Treasure if possible
|
||||||
@@ -253,22 +253,22 @@ public class ComputerUtilMana {
|
|||||||
&& ab2.getHostCard() != null && !ab2.getHostCard().getType().hasSubtype("Treasure") ? -1 : 1);
|
&& ab2.getHostCard() != null && !ab2.getHostCard().getType().hasSubtype("Treasure") ? -1 : 1);
|
||||||
SpellAbility first = filteredList.get(0);
|
SpellAbility first = filteredList.get(0);
|
||||||
if (first.getHostCard() != null && first.getHostCard().getType().hasSubtype("Treasure")) {
|
if (first.getHostCard() != null && first.getHostCard().getType().hasSubtype("Treasure")) {
|
||||||
saList.remove(first);
|
maList.remove(first);
|
||||||
List<SpellAbility> updatedList = Lists.newArrayList();
|
List<SpellAbility> updatedList = Lists.newArrayList();
|
||||||
updatedList.add(first);
|
updatedList.add(first);
|
||||||
updatedList.addAll(saList);
|
updatedList.addAll(maList);
|
||||||
saList = updatedList;
|
maList = updatedList;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "TreasureMax":
|
case "TreasureMax":
|
||||||
// Ok to spend as many Treasures as possible
|
// Ok to spend as many Treasures as possible
|
||||||
filteredList.sort((ab1, ab2) -> ab1.getHostCard() != null && ab1.getHostCard().getType().hasSubtype("Treasure")
|
filteredList.sort((ab1, ab2) -> ab1.getHostCard() != null && ab1.getHostCard().getType().hasSubtype("Treasure")
|
||||||
&& ab2.getHostCard() != null && !ab2.getHostCard().getType().hasSubtype("Treasure") ? -1 : 1);
|
&& ab2.getHostCard() != null && !ab2.getHostCard().getType().hasSubtype("Treasure") ? -1 : 1);
|
||||||
saList = filteredList;
|
maList = filteredList;
|
||||||
break;
|
break;
|
||||||
case "NotSameCard":
|
case "NotSameCard":
|
||||||
String hostName = sa.getHostCard().getName();
|
String hostName = sa.getHostCard().getName();
|
||||||
saList = filteredList.stream()
|
maList = filteredList.stream()
|
||||||
.filter(saPay -> !saPay.getHostCard().getName().equals(hostName))
|
.filter(saPay -> !saPay.getHostCard().getName().equals(hostName))
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
break;
|
break;
|
||||||
@@ -277,7 +277,7 @@ public class ComputerUtilMana {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (final SpellAbility ma : saList) {
|
for (final SpellAbility ma : maList) {
|
||||||
// this rarely seems like a good idea
|
// this rarely seems like a good idea
|
||||||
if (ma.getHostCard() == saHost) {
|
if (ma.getHostCard() == saHost) {
|
||||||
continue;
|
continue;
|
||||||
@@ -336,7 +336,7 @@ public class ComputerUtilMana {
|
|||||||
// Deprioritize Cavern of Souls, try to pay generic mana with it instead to use the NoCounter ability
|
// Deprioritize Cavern of Souls, try to pay generic mana with it instead to use the NoCounter ability
|
||||||
continue;
|
continue;
|
||||||
} else if (toPay == ManaCostShard.GENERIC || toPay == ManaCostShard.X) {
|
} else if (toPay == ManaCostShard.GENERIC || toPay == ManaCostShard.X) {
|
||||||
for (SpellAbility ab : saList) {
|
for (SpellAbility ab : maList) {
|
||||||
if (ab.isManaAbility() && ab.getManaPart().isAnyMana() && ab.hasParam("AddsNoCounter")) {
|
if (ab.isManaAbility() && ab.getManaPart().isAnyMana() && ab.hasParam("AddsNoCounter")) {
|
||||||
if (!ab.getHostCard().isTapped()) {
|
if (!ab.getHostCard().isTapped()) {
|
||||||
paymentChoice = ab;
|
paymentChoice = ab;
|
||||||
@@ -590,12 +590,12 @@ public class ComputerUtilMana {
|
|||||||
while (!cost.isPaid()) {
|
while (!cost.isPaid()) {
|
||||||
toPay = getNextShardToPay(cost, sourcesForShards);
|
toPay = getNextShardToPay(cost, sourcesForShards);
|
||||||
|
|
||||||
Collection<SpellAbility> saList = sourcesForShards.get(toPay);
|
Collection<SpellAbility> maList = sourcesForShards.get(toPay);
|
||||||
if (saList == null) {
|
if (maList == null) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
SpellAbility saPayment = chooseManaAbility(cost, sa, ai, toPay, saList, true);
|
SpellAbility saPayment = chooseManaAbility(cost, sa, ai, toPay, maList, true);
|
||||||
if (saPayment == null) {
|
if (saPayment == null) {
|
||||||
boolean lifeInsteadOfBlack = toPay.isBlack() && ai.hasKeyword("PayLifeInsteadOf:B");
|
boolean lifeInsteadOfBlack = toPay.isBlack() && ai.hasKeyword("PayLifeInsteadOf:B");
|
||||||
if ((!toPay.isPhyrexian() && !lifeInsteadOfBlack) || !ai.canPayLife(2, false, sa)) {
|
if ((!toPay.isPhyrexian() && !lifeInsteadOfBlack) || !ai.canPayLife(2, false, sa)) {
|
||||||
@@ -666,6 +666,7 @@ public class ComputerUtilMana {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int phyLifeToPay = 2;
|
||||||
boolean purePhyrexian = cost.containsOnlyPhyrexianMana();
|
boolean purePhyrexian = cost.containsOnlyPhyrexianMana();
|
||||||
boolean hasConverge = sa.getHostCard().hasConverge();
|
boolean hasConverge = sa.getHostCard().hasConverge();
|
||||||
ListMultimap<ManaCostShard, SpellAbility> sourcesForShards = getSourcesForShards(cost, sa, ai, test, checkPlayable, hasConverge);
|
ListMultimap<ManaCostShard, SpellAbility> sourcesForShards = getSourcesForShards(cost, sa, ai, test, checkPlayable, hasConverge);
|
||||||
@@ -693,13 +694,12 @@ public class ComputerUtilMana {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (sourcesForShards == null && !purePhyrexian) {
|
if (sourcesForShards == null && !purePhyrexian) {
|
||||||
break; // no mana abilities to use for paying
|
// no mana abilities to use for paying
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
toPay = getNextShardToPay(cost, sourcesForShards);
|
toPay = getNextShardToPay(cost, sourcesForShards);
|
||||||
|
|
||||||
boolean lifeInsteadOfBlack = toPay.isBlack() && ai.hasKeyword("PayLifeInsteadOf:B");
|
|
||||||
|
|
||||||
Collection<SpellAbility> saList = null;
|
Collection<SpellAbility> saList = null;
|
||||||
if (hasConverge &&
|
if (hasConverge &&
|
||||||
(toPay == ManaCostShard.GENERIC || toPay == ManaCostShard.X)) {
|
(toPay == ManaCostShard.GENERIC || toPay == ManaCostShard.X)) {
|
||||||
@@ -752,9 +752,14 @@ public class ComputerUtilMana {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (saPayment == null) {
|
if (saPayment == null) {
|
||||||
if ((!toPay.isPhyrexian() && !lifeInsteadOfBlack) || !ai.canPayLife(2, false, sa)
|
boolean lifeInsteadOfBlack = toPay.isBlack() && ai.hasKeyword("PayLifeInsteadOf:B");
|
||||||
|| (ai.getLife() <= 2 && !ai.cantLoseForZeroOrLessLife())) {
|
if ((!toPay.isPhyrexian() && !lifeInsteadOfBlack) || !ai.canPayLife(phyLifeToPay, false, sa)
|
||||||
break; // cannot pay
|
|| (ai.getLife() <= phyLifeToPay && !ai.cantLoseForZeroOrLessLife())) {
|
||||||
|
// cannot pay
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (test) {
|
||||||
|
phyLifeToPay += 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sa.hasParam("AIPhyrexianPayment")) {
|
if (sa.hasParam("AIPhyrexianPayment")) {
|
||||||
@@ -958,7 +963,6 @@ public class ComputerUtilMana {
|
|||||||
if (checkCosts) {
|
if (checkCosts) {
|
||||||
// Check if AI can still play this mana ability
|
// Check if AI can still play this mana ability
|
||||||
ma.setActivatingPlayer(ai);
|
ma.setActivatingPlayer(ai);
|
||||||
// if the AI can't pay the additional costs skip the mana ability
|
|
||||||
if (!CostPayment.canPayAdditionalCosts(ma.getPayCosts(), ma, false)) {
|
if (!CostPayment.canPayAdditionalCosts(ma.getPayCosts(), ma, false)) {
|
||||||
return false;
|
return false;
|
||||||
} else if (ma.getRestrictions() != null && ma.getRestrictions().isInstantSpeed()) {
|
} else if (ma.getRestrictions() != null && ma.getRestrictions().isInstantSpeed()) {
|
||||||
@@ -976,8 +980,9 @@ public class ComputerUtilMana {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ("Any".equals(s) || ai.getManaPool().canPayForShardWithColor(toPay, ManaAtom.fromName(s)))
|
if ("Any".equals(s) || ai.getManaPool().canPayForShardWithColor(toPay, ManaAtom.fromName(s))){
|
||||||
return true;
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -1503,7 +1508,7 @@ public class ComputerUtilMana {
|
|||||||
AbilitySub sub = m.getSubAbility();
|
AbilitySub sub = m.getSubAbility();
|
||||||
// We really shouldn't be hardcoding names here. ChkDrawback should just return true for them
|
// We really shouldn't be hardcoding names here. ChkDrawback should just return true for them
|
||||||
if (sub != null && !card.getName().equals("Pristine Talisman") && !card.getName().equals("Zhur-Taa Druid")) {
|
if (sub != null && !card.getName().equals("Pristine Talisman") && !card.getName().equals("Zhur-Taa Druid")) {
|
||||||
if (!SpellApiToAi.Converter.get(sub).chkDrawbackWithSubs(ai, sub)) {
|
if (!SpellApiToAi.Converter.get(sub).chkDrawbackWithSubs(ai, sub).willingToPlay()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
needsLimitedResources = true; // TODO: check for good drawbacks (gainLife)
|
needsLimitedResources = true; // TODO: check for good drawbacks (gainLife)
|
||||||
@@ -1583,7 +1588,7 @@ public class ComputerUtilMana {
|
|||||||
// don't use abilities with dangerous drawbacks
|
// don't use abilities with dangerous drawbacks
|
||||||
AbilitySub sub = m.getSubAbility();
|
AbilitySub sub = m.getSubAbility();
|
||||||
if (sub != null) {
|
if (sub != null) {
|
||||||
if (!SpellApiToAi.Converter.get(sub).chkDrawbackWithSubs(ai, sub)) {
|
if (!SpellApiToAi.Converter.get(sub).chkDrawbackWithSubs(ai, sub).willingToPlay()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1263,7 +1263,7 @@ public abstract class GameState {
|
|||||||
} else if (cardinfo[0].startsWith("T:")) {
|
} else if (cardinfo[0].startsWith("T:")) {
|
||||||
String tokenStr = cardinfo[0].substring(2);
|
String tokenStr = cardinfo[0].substring(2);
|
||||||
PaperToken token = StaticData.instance().getAllTokens().getToken(tokenStr,
|
PaperToken token = StaticData.instance().getAllTokens().getToken(tokenStr,
|
||||||
setCode != null ? setCode : CardEdition.UNKNOWN.getName());
|
setCode != null ? setCode : CardEdition.UNKNOWN_CODE);
|
||||||
if (token == null) {
|
if (token == null) {
|
||||||
System.err.println("ERROR: Tried to create a non-existent token named " + cardinfo[0] + " when loading game state!");
|
System.err.println("ERROR: Tried to create a non-existent token named " + cardinfo[0] + " when loading game state!");
|
||||||
continue;
|
continue;
|
||||||
|
|||||||
@@ -214,7 +214,7 @@ public class SpecialAiLogic {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// A logic for cards that say "Sacrifice a creature: put X +1/+1 counters on CARDNAME" (e.g. Falkenrath Aristocrat)
|
// A logic for cards that say "Sacrifice a creature: put X +1/+1 counters on CARDNAME" (e.g. Falkenrath Aristocrat)
|
||||||
public static boolean doAristocratWithCountersLogic(final Player ai, final SpellAbility sa) {
|
public static AiAbilityDecision doAristocratWithCountersLogic(final Player ai, final SpellAbility sa) {
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
final String logic = sa.getParam("AILogic"); // should not even get here unless there's an Aristocrats logic applied
|
final String logic = sa.getParam("AILogic"); // should not even get here unless there's an Aristocrats logic applied
|
||||||
final boolean isDeclareBlockers = ai.getGame().getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS);
|
final boolean isDeclareBlockers = ai.getGame().getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS);
|
||||||
@@ -222,14 +222,14 @@ public class SpecialAiLogic {
|
|||||||
final int numOtherCreats = Math.max(0, ai.getCreaturesInPlay().size() - 1);
|
final int numOtherCreats = Math.max(0, ai.getCreaturesInPlay().size() - 1);
|
||||||
if (numOtherCreats == 0) {
|
if (numOtherCreats == 0) {
|
||||||
// Cut short if there's nothing to sac at all
|
// Cut short if there's nothing to sac at all
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantAfford);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the standard Aristocrats logic applies first (if in the right conditions for it)
|
// Check if the standard Aristocrats logic applies first (if in the right conditions for it)
|
||||||
final boolean isThreatened = ComputerUtil.predictThreatenedObjects(ai, null, true).contains(source);
|
final boolean isThreatened = ComputerUtil.predictThreatenedObjects(ai, null, true).contains(source);
|
||||||
if (isDeclareBlockers || isThreatened) {
|
if (isDeclareBlockers || isThreatened) {
|
||||||
if (doAristocratLogic(ai, sa)) {
|
if (doAristocratLogic(ai, sa)) {
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -247,7 +247,7 @@ public class SpecialAiLogic {
|
|||||||
if (countersSa == null) {
|
if (countersSa == null) {
|
||||||
// Shouldn't get here if there is no PutCounter subability (wrong AI logic specified?)
|
// Shouldn't get here if there is no PutCounter subability (wrong AI logic specified?)
|
||||||
System.err.println("Warning: AILogic AristocratCounters was specified on " + source + ", but there was no PutCounter SA in chain!");
|
System.err.println("Warning: AILogic AristocratCounters was specified on " + source + ", but there was no PutCounter SA in chain!");
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlaySa);
|
||||||
}
|
}
|
||||||
|
|
||||||
final Game game = ai.getGame();
|
final Game game = ai.getGame();
|
||||||
@@ -263,7 +263,7 @@ public class SpecialAiLogic {
|
|||||||
relevantCreats.remove(source);
|
relevantCreats.remove(source);
|
||||||
if (relevantCreats.isEmpty()) {
|
if (relevantCreats.isEmpty()) {
|
||||||
// No relevant creatures to sac
|
// No relevant creatures to sac
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.MissingNeededCards);
|
||||||
}
|
}
|
||||||
|
|
||||||
int numCtrs = AbilityUtils.calculateAmount(source, countersSa.getParam("CounterNum"), countersSa);
|
int numCtrs = AbilityUtils.calculateAmount(source, countersSa.getParam("CounterNum"), countersSa);
|
||||||
@@ -287,16 +287,20 @@ public class SpecialAiLogic {
|
|||||||
|| (combat.isAttacking(card) && combat.isBlocked(card) && ComputerUtilCombat.combatantWouldBeDestroyed(ai, card, combat))
|
|| (combat.isAttacking(card) && combat.isBlocked(card) && ComputerUtilCombat.combatantWouldBeDestroyed(ai, card, combat))
|
||||||
);
|
);
|
||||||
if (!forcedSacTgts.isEmpty()) {
|
if (!forcedSacTgts.isEmpty()) {
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
final int numCreatsToSac = Math.max(0, (lethalDmg - source.getNetCombatDamage()) / numCtrs);
|
final int numCreatsToSac = Math.max(0, (lethalDmg - source.getNetCombatDamage()) / numCtrs);
|
||||||
|
|
||||||
if (defTappedOut || numCreatsToSac < relevantCreats.size() / 2) {
|
if (defTappedOut || numCreatsToSac < relevantCreats.size() / 2) {
|
||||||
return source.getNetCombatDamage() < lethalDmg
|
if (source.getNetCombatDamage() < lethalDmg
|
||||||
&& source.getNetCombatDamage() + relevantCreats.size() * numCtrs >= lethalDmg;
|
&& source.getNetCombatDamage() + relevantCreats.size() * numCtrs >= lethalDmg) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.ImpactCombat);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.DoesntImpactCombat);
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// We have already attacked. Thus, see if we have a creature to sac that is worse to lose
|
// We have already attacked. Thus, see if we have a creature to sac that is worse to lose
|
||||||
@@ -309,7 +313,7 @@ public class SpecialAiLogic {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (sacTgts.isEmpty()) {
|
if (sacTgts.isEmpty()) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
|
|
||||||
final boolean sourceCantDie = ComputerUtilCombat.combatantCantBeDestroyed(ai, source);
|
final boolean sourceCantDie = ComputerUtilCombat.combatantCantBeDestroyed(ai, source);
|
||||||
@@ -317,7 +321,10 @@ public class SpecialAiLogic {
|
|||||||
final int DefP = sourceCantDie ? 0 : Aggregates.sum(combat.getBlockers(source), Card::getNetPower);
|
final int DefP = sourceCantDie ? 0 : Aggregates.sum(combat.getBlockers(source), Card::getNetPower);
|
||||||
|
|
||||||
// Make sure we don't over-sacrifice, only sac until we can survive and kill a creature
|
// Make sure we don't over-sacrifice, only sac until we can survive and kill a creature
|
||||||
return source.getNetToughness() - source.getDamage() <= DefP || source.getNetCombatDamage() < minDefT;
|
if (source.getNetToughness() - source.getDamage() <= DefP || source.getNetCombatDamage() < minDefT) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
}
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// We can't deal lethal, check if there's any sac fodder than can be used for other circumstances
|
// We can't deal lethal, check if there's any sac fodder than can be used for other circumstances
|
||||||
@@ -329,7 +336,11 @@ public class SpecialAiLogic {
|
|||||||
|| ComputerUtil.predictThreatenedObjects(ai, null, true).contains(card)
|
|| ComputerUtil.predictThreatenedObjects(ai, null, true).contains(card)
|
||||||
);
|
);
|
||||||
|
|
||||||
return !sacFodder.isEmpty();
|
if (sacFodder.isEmpty()) {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.MissingNeededCards);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -360,10 +371,10 @@ public class SpecialAiLogic {
|
|||||||
// FIXME: We're emulating the UnlessCost on the SA to run the proper checks.
|
// FIXME: We're emulating the UnlessCost on the SA to run the proper checks.
|
||||||
// This is hacky, but it works. Perhaps a cleaner way exists?
|
// This is hacky, but it works. Perhaps a cleaner way exists?
|
||||||
sa.getMapParams().put("UnlessCost", falseSub.getParam("UnlessCost"));
|
sa.getMapParams().put("UnlessCost", falseSub.getParam("UnlessCost"));
|
||||||
willPlay = SpellApiToAi.Converter.get(ApiType.Counter).canPlayAIWithSubs(ai, sa);
|
willPlay = SpellApiToAi.Converter.get(ApiType.Counter).canPlayWithSubs(ai, sa).willingToPlay();
|
||||||
sa.getMapParams().remove("UnlessCost");
|
sa.getMapParams().remove("UnlessCost");
|
||||||
} else {
|
} else {
|
||||||
willPlay = SpellApiToAi.Converter.get(ApiType.Counter).canPlayAIWithSubs(ai, sa);
|
willPlay = SpellApiToAi.Converter.get(ApiType.Counter).canPlayWithSubs(ai, sa).willingToPlay();
|
||||||
}
|
}
|
||||||
return willPlay;
|
return willPlay;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -78,16 +78,17 @@ public class SpecialCardAi {
|
|||||||
|
|
||||||
// Arena and Magus of the Arena
|
// Arena and Magus of the Arena
|
||||||
public static class Arena {
|
public static class Arena {
|
||||||
public static boolean consider(final Player ai, final SpellAbility sa) {
|
public static AiAbilityDecision consider(final Player ai, final SpellAbility sa) {
|
||||||
final Game game = ai.getGame();
|
final Game game = ai.getGame();
|
||||||
|
|
||||||
|
// TODO This is basically removal, so we may want to play this at other times
|
||||||
if (!game.getPhaseHandler().is(PhaseType.END_OF_TURN) || game.getPhaseHandler().getNextTurn() != ai) {
|
if (!game.getPhaseHandler().is(PhaseType.END_OF_TURN) || game.getPhaseHandler().getNextTurn() != ai) {
|
||||||
return false; // at opponent's EOT only, to conserve mana
|
return new AiAbilityDecision(0, AiPlayDecision.WaitForEndOfTurn);
|
||||||
}
|
}
|
||||||
|
|
||||||
CardCollection aiCreatures = ai.getCreaturesInPlay();
|
CardCollection aiCreatures = ai.getCreaturesInPlay();
|
||||||
if (aiCreatures.isEmpty()) {
|
if (aiCreatures.isEmpty()) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.MissingNeededCards);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (Player opp : ai.getOpponents()) {
|
for (Player opp : ai.getOpponents()) {
|
||||||
@@ -111,11 +112,11 @@ public class SpecialCardAi {
|
|||||||
if (canKillAll) {
|
if (canKillAll) {
|
||||||
sa.getTargets().clear();
|
sa.getTargets().clear();
|
||||||
sa.getTargets().add(aiCreature);
|
sa.getTargets().add(aiCreature);
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.Removal);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return sa.isTargetNumberValid();
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -203,7 +204,7 @@ public class SpecialCardAi {
|
|||||||
|
|
||||||
// Chain of Acid
|
// Chain of Acid
|
||||||
public static class ChainOfAcid {
|
public static class ChainOfAcid {
|
||||||
public static boolean consider(final Player ai, final SpellAbility sa) {
|
public static AiAbilityDecision consider(final Player ai, final SpellAbility sa) {
|
||||||
List<Card> AiLandsOnly = CardLists.filter(ai.getCardsIn(ZoneType.Battlefield),
|
List<Card> AiLandsOnly = CardLists.filter(ai.getCardsIn(ZoneType.Battlefield),
|
||||||
CardPredicates.LANDS);
|
CardPredicates.LANDS);
|
||||||
List<Card> OppPerms = CardLists.filter(ai.getOpponents().getCardsIn(ZoneType.Battlefield),
|
List<Card> OppPerms = CardLists.filter(ai.getOpponents().getCardsIn(ZoneType.Battlefield),
|
||||||
@@ -213,13 +214,22 @@ public class SpecialCardAi {
|
|||||||
// which it can only distinguish by their CMC, considering >CMC higher value).
|
// which it can only distinguish by their CMC, considering >CMC higher value).
|
||||||
// Currently ensures that the AI will still have lands provided that the human player goes to
|
// Currently ensures that the AI will still have lands provided that the human player goes to
|
||||||
// destroy all the AI's lands in order (to avoid manalock).
|
// destroy all the AI's lands in order (to avoid manalock).
|
||||||
return !OppPerms.isEmpty() && AiLandsOnly.size() > OppPerms.size() + 2;
|
if (!OppPerms.isEmpty() && AiLandsOnly.size() > OppPerms.size() + 2) {
|
||||||
|
// If there are enough lands, target the worst non-creature permanent of the opponent
|
||||||
|
Card worstOppPerm = ComputerUtilCard.getWorstAI(OppPerms);
|
||||||
|
if (worstOppPerm != null) {
|
||||||
|
sa.resetTargets();
|
||||||
|
sa.getTargets().add(worstOppPerm);
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Chain of Smog
|
// Chain of Smog
|
||||||
public static class ChainOfSmog {
|
public static class ChainOfSmog {
|
||||||
public static boolean consider(final Player ai, final SpellAbility sa) {
|
public static AiAbilityDecision consider(final Player ai, final SpellAbility sa) {
|
||||||
if (ai.getCardsIn(ZoneType.Hand).isEmpty()) {
|
if (ai.getCardsIn(ZoneType.Hand).isEmpty()) {
|
||||||
// to avoid failure to add to stack, provide a legal target opponent first (choosing random at this point)
|
// to avoid failure to add to stack, provide a legal target opponent first (choosing random at this point)
|
||||||
// TODO: this makes the AI target opponents with 0 cards in hand, but bailing from here causes a
|
// TODO: this makes the AI target opponents with 0 cards in hand, but bailing from here causes a
|
||||||
@@ -235,10 +245,10 @@ public class SpecialCardAi {
|
|||||||
|
|
||||||
sa.getParent().resetTargets();
|
sa.getParent().resetTargets();
|
||||||
sa.getParent().getTargets().add(targOpp);
|
sa.getParent().getTargets().add(targOpp);
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -359,18 +369,28 @@ public class SpecialCardAi {
|
|||||||
private static final int demonSacThreshold = Integer.MAX_VALUE; // if we're in dire conditions, sac everything from worst to best hoping to find an answer
|
private static final int demonSacThreshold = Integer.MAX_VALUE; // if we're in dire conditions, sac everything from worst to best hoping to find an answer
|
||||||
|
|
||||||
public static boolean considerSacrificingCreature(final Player ai, final SpellAbility sa) {
|
public static boolean considerSacrificingCreature(final Player ai, final SpellAbility sa) {
|
||||||
|
Card c = sa.getHostCard();
|
||||||
|
|
||||||
|
// Only check for sacrifice if it's the owner's turn, and it can attack.
|
||||||
|
// TODO: Maybe check if sacrificing a creature allows AI to kill the opponent with the rest on their turn?
|
||||||
|
if (!CombatUtil.canAttack(c) ||
|
||||||
|
!ai.getGame().getPhaseHandler().isPlayerTurn(sa.getActivatingPlayer())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
CardCollection flyingCreatures = CardLists.filter(ai.getCardsIn(ZoneType.Battlefield),
|
CardCollection flyingCreatures = CardLists.filter(ai.getCardsIn(ZoneType.Battlefield),
|
||||||
CardPredicates.UNTAPPED.and(
|
CardPredicates.UNTAPPED.and(
|
||||||
CardPredicates.hasKeyword(Keyword.FLYING).or(CardPredicates.hasKeyword(Keyword.REACH))));
|
CardPredicates.hasKeyword(Keyword.FLYING).or(CardPredicates.hasKeyword(Keyword.REACH))));
|
||||||
boolean hasUsefulBlocker = false;
|
boolean hasUsefulBlocker = false;
|
||||||
|
|
||||||
for (Card c : flyingCreatures) {
|
for (Card fc : flyingCreatures) {
|
||||||
if (!ComputerUtilCard.isUselessCreature(ai, c)) {
|
if (!ComputerUtilCard.isUselessCreature(ai, fc)) {
|
||||||
hasUsefulBlocker = true;
|
hasUsefulBlocker = true;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return ai.getLife() <= sa.getHostCard().getNetPower() && !hasUsefulBlocker;
|
return ai.getLife() <= c.getNetPower() && !hasUsefulBlocker;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int getSacThreshold() {
|
public static int getSacThreshold() {
|
||||||
@@ -380,7 +400,7 @@ public class SpecialCardAi {
|
|||||||
|
|
||||||
// Donate
|
// Donate
|
||||||
public static class Donate {
|
public static class Donate {
|
||||||
public static boolean considerTargetingOpponent(final Player ai, final SpellAbility sa) {
|
public static AiAbilityDecision considerTargetingOpponent(final Player ai, final SpellAbility sa) {
|
||||||
final Card donateTarget = ComputerUtil.getCardPreference(ai, sa.getHostCard(), "DonateMe", CardLists.filter(
|
final Card donateTarget = ComputerUtil.getCardPreference(ai, sa.getHostCard(), "DonateMe", CardLists.filter(
|
||||||
ai.getCardsIn(ZoneType.Battlefield).threadSafeIterable(), CardPredicates.hasSVar("DonateMe")));
|
ai.getCardsIn(ZoneType.Battlefield).threadSafeIterable(), CardPredicates.hasSVar("DonateMe")));
|
||||||
if (donateTarget != null) {
|
if (donateTarget != null) {
|
||||||
@@ -390,7 +410,7 @@ public class SpecialCardAi {
|
|||||||
|
|
||||||
// All opponents have hexproof or something like that
|
// All opponents have hexproof or something like that
|
||||||
if (Iterables.isEmpty(oppList)) {
|
if (Iterables.isEmpty(oppList)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
|
|
||||||
// filter for player who does not have donate target already
|
// filter for player who does not have donate target already
|
||||||
@@ -408,31 +428,30 @@ public class SpecialCardAi {
|
|||||||
if (opp != null) {
|
if (opp != null) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
sa.getTargets().add(opp);
|
sa.getTargets().add(opp);
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
// No targets found to donate, so do nothing.
|
// No targets found to donate, so do nothing.
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean considerDonatingPermanent(final Player ai, final SpellAbility sa) {
|
public static AiAbilityDecision considerDonatingPermanent(final Player ai, final SpellAbility sa) {
|
||||||
Card donateTarget = ComputerUtil.getCardPreference(ai, sa.getHostCard(), "DonateMe", CardLists.filter(ai.getCardsIn(ZoneType.Battlefield).threadSafeIterable(), CardPredicates.hasSVar("DonateMe")));
|
Card donateTarget = ComputerUtil.getCardPreference(ai, sa.getHostCard(), "DonateMe", CardLists.filter(ai.getCardsIn(ZoneType.Battlefield).threadSafeIterable(), CardPredicates.hasSVar("DonateMe")));
|
||||||
if (donateTarget != null) {
|
if (donateTarget != null) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
sa.getTargets().add(donateTarget);
|
sa.getTargets().add(donateTarget);
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Should never get here because targetOpponent, called before targetPermanentToDonate, should already have made the AI bail
|
// Should never get here because targetOpponent, called before targetPermanentToDonate, should already have made the AI bail
|
||||||
System.err.println("Warning: Donate AI failed at SpecialCardAi.Donate#targetPermanentToDonate despite successfully targeting an opponent first.");
|
System.err.println("Warning: Donate AI failed at SpecialCardAi.Donate#targetPermanentToDonate despite successfully targeting an opponent first.");
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Electrostatic Pummeler
|
// Electrostatic Pummeler
|
||||||
public static class ElectrostaticPummeler {
|
public static class ElectrostaticPummeler {
|
||||||
public static boolean consider(final Player ai, final SpellAbility sa) {
|
public static AiAbilityDecision consider(final Player ai, final SpellAbility sa) {
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
Game game = ai.getGame();
|
Game game = ai.getGame();
|
||||||
Combat combat = game.getCombat();
|
Combat combat = game.getCombat();
|
||||||
@@ -445,13 +464,13 @@ public class SpecialCardAi {
|
|||||||
if (saTop.getApi() == ApiType.DealDamage || saTop.getApi() == ApiType.DamageAll) {
|
if (saTop.getApi() == ApiType.DealDamage || saTop.getApi() == ApiType.DamageAll) {
|
||||||
int dmg = AbilityUtils.calculateAmount(saTop.getHostCard(), saTop.getParam("NumDmg"), saTop);
|
int dmg = AbilityUtils.calculateAmount(saTop.getHostCard(), saTop.getParam("NumDmg"), saTop);
|
||||||
if (source.getNetToughness() - source.getDamage() <= dmg && predictedPT.getRight() - source.getDamage() > dmg)
|
if (source.getNetToughness() - source.getDamage() <= dmg && predictedPT.getRight() - source.getDamage() > dmg)
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do not activate if damage will be prevented
|
// Do not activate if damage will be prevented
|
||||||
if (source.staticDamagePrevention(predictedPT.getLeft(), 0, source, true) == 0) {
|
if (source.staticDamagePrevention(predictedPT.getLeft(), 0, source, true) == 0) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.DoesntImpactGame);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Activate Electrostatic Pummeler's pump only as a combat trick
|
// Activate Electrostatic Pummeler's pump only as a combat trick
|
||||||
@@ -460,14 +479,14 @@ public class SpecialCardAi {
|
|||||||
// We'll try to deal lethal trample/unblocked damage, so remember the card for attack
|
// We'll try to deal lethal trample/unblocked damage, so remember the card for attack
|
||||||
// and wait until declare blockers step.
|
// and wait until declare blockers step.
|
||||||
AiCardMemory.rememberCard(ai, source, AiCardMemory.MemorySet.MANDATORY_ATTACKERS);
|
AiCardMemory.rememberCard(ai, source, AiCardMemory.MemorySet.MANDATORY_ATTACKERS);
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
} else if (!game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
} else if (!game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.WaitForCombat);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (combat == null || !(combat.isAttacking(source) || combat.isBlocking(source))) {
|
if (combat == null || !(combat.isAttacking(source) || combat.isBlocking(source))) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean isBlocking = combat.isBlocking(source);
|
boolean isBlocking = combat.isBlocking(source);
|
||||||
@@ -492,11 +511,11 @@ public class SpecialCardAi {
|
|||||||
}
|
}
|
||||||
if (totalDamageToPW >= oppT + loyalty) {
|
if (totalDamageToPW >= oppT + loyalty) {
|
||||||
// Already enough damage to take care of the planeswalker
|
// Already enough damage to take care of the planeswalker
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.DoesntImpactCombat);
|
||||||
}
|
}
|
||||||
if ((unblocked || canTrample) && predictedPT.getLeft() >= oppT + loyalty) {
|
if ((unblocked || canTrample) && predictedPT.getLeft() >= oppT + loyalty) {
|
||||||
// Can pump to kill the planeswalker, go for it
|
// Can pump to kill the planeswalker, go for it
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.ImpactCombat);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -517,31 +536,31 @@ public class SpecialCardAi {
|
|||||||
// We can deal a lot of damage (either a lot of damage directly to the opponent,
|
// We can deal a lot of damage (either a lot of damage directly to the opponent,
|
||||||
// or kill the blocker(s) and damage the opponent at the same time, so go for it
|
// or kill the blocker(s) and damage the opponent at the same time, so go for it
|
||||||
AiCardMemory.rememberCard(ai, source, AiCardMemory.MemorySet.MANDATORY_ATTACKERS);
|
AiCardMemory.rememberCard(ai, source, AiCardMemory.MemorySet.MANDATORY_ATTACKERS);
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.ImpactCombat);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (predictedPT.getRight() - source.getDamage() <= oppP && oppHasFirstStrike && !cantDie) {
|
if (predictedPT.getRight() - source.getDamage() <= oppP && oppHasFirstStrike && !cantDie) {
|
||||||
// Can't survive first strike or double strike, don't pump
|
// Can't survive first strike or double strike, don't pump
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.DoesntImpactCombat);
|
||||||
}
|
}
|
||||||
if (predictedPT.getLeft() < oppT && (!cantDie || predictedPT.getRight() - source.getDamage() <= oppP)) {
|
if (predictedPT.getLeft() < oppT && (!cantDie || predictedPT.getRight() - source.getDamage() <= oppP)) {
|
||||||
// Can't pump enough to kill the blockers and survive, don't pump
|
// Can't pump enough to kill the blockers and survive, don't pump
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.DoesntImpactCombat);
|
||||||
}
|
}
|
||||||
if (source.getNetCombatDamage() > oppT && source.getNetToughness() > oppP) {
|
if (source.getNetCombatDamage() > oppT && source.getNetToughness() > oppP) {
|
||||||
// Already enough to kill the blockers and survive, don't overpump
|
// Already enough to kill the blockers and survive, don't overpump
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.DoesntImpactCombat);
|
||||||
}
|
}
|
||||||
if (oppCantDie && !source.hasKeyword(Keyword.TRAMPLE) && !source.isWitherDamage()
|
if (oppCantDie && !source.hasKeyword(Keyword.TRAMPLE) && !source.isWitherDamage()
|
||||||
&& predictedPT.getLeft() <= oppT) {
|
&& predictedPT.getLeft() <= oppT) {
|
||||||
// Can't kill or cripple anyone, as well as can't Trample over, so don't pump
|
// Can't kill or cripple anyone, as well as can't Trample over, so don't pump
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.DoesntImpactCombat);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we got here, it should be a favorable combat pump, resulting in at least one
|
// If we got here, it should be a favorable combat pump, resulting in at least one
|
||||||
// opposing creature dying, and hopefully with the Pummeler surviving combat.
|
// opposing creature dying, and hopefully with the Pummeler surviving combat.
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.ImpactCombat);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean predictOverwhelmingDamage(final Player ai, final SpellAbility sa) {
|
public static boolean predictOverwhelmingDamage(final Player ai, final SpellAbility sa) {
|
||||||
@@ -618,15 +637,15 @@ public class SpecialCardAi {
|
|||||||
|
|
||||||
// Fell the Mighty
|
// Fell the Mighty
|
||||||
public static class FellTheMighty {
|
public static class FellTheMighty {
|
||||||
public static boolean consider(final Player ai, final SpellAbility sa) {
|
public static AiAbilityDecision consider(final Player ai, final SpellAbility sa) {
|
||||||
CardCollection aiList = ai.getCreaturesInPlay();
|
CardCollection aiList = ai.getCreaturesInPlay();
|
||||||
if (aiList.isEmpty()) {
|
if (aiList.isEmpty()) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.MissingNeededCards);
|
||||||
}
|
}
|
||||||
CardLists.sortByPowerAsc(aiList);
|
CardLists.sortByPowerAsc(aiList);
|
||||||
Card lowest = aiList.get(0);
|
Card lowest = aiList.get(0);
|
||||||
if (!sa.canTarget(lowest)) {
|
if (!sa.canTarget(lowest)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
|
|
||||||
CardCollection oppList = CardLists.filter(ai.getGame().getCardsIn(ZoneType.Battlefield),
|
CardCollection oppList = CardLists.filter(ai.getGame().getCardsIn(ZoneType.Battlefield),
|
||||||
@@ -636,9 +655,9 @@ public class SpecialCardAi {
|
|||||||
if (ComputerUtilCard.evaluateCreatureList(oppList) > 200) {
|
if (ComputerUtilCard.evaluateCreatureList(oppList) > 200) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
sa.getTargets().add(lowest);
|
sa.getTargets().add(lowest);
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -673,25 +692,25 @@ public class SpecialCardAi {
|
|||||||
|
|
||||||
// Gideon Blackblade
|
// Gideon Blackblade
|
||||||
public static class GideonBlackblade {
|
public static class GideonBlackblade {
|
||||||
public static boolean consider(final Player ai, final SpellAbility sa) {
|
public static AiAbilityDecision consider(final Player ai, final SpellAbility sa) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
CardCollectionView otb = CardLists.filter(ai.getCardsIn(ZoneType.Battlefield), CardPredicates.isTargetableBy(sa));
|
CardCollectionView otb = CardLists.filter(ai.getCardsIn(ZoneType.Battlefield), CardPredicates.isTargetableBy(sa));
|
||||||
if (!otb.isEmpty()) {
|
if (!otb.isEmpty()) {
|
||||||
sa.getTargets().add(ComputerUtilCard.getBestAI(otb));
|
sa.getTargets().add(ComputerUtilCard.getBestAI(otb));
|
||||||
}
|
}
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Goblin Polka Band
|
// Goblin Polka Band
|
||||||
public static class GoblinPolkaBand {
|
public static class GoblinPolkaBand {
|
||||||
public static boolean consider(final Player ai, final SpellAbility sa) {
|
public static AiAbilityDecision consider(final Player ai, final SpellAbility sa) {
|
||||||
int maxPotentialTgts = ai.getOpponents().getCreaturesInPlay().filter(CardPredicates.UNTAPPED).size();
|
int maxPotentialTgts = ai.getOpponents().getCreaturesInPlay().filter(CardPredicates.UNTAPPED).size();
|
||||||
int maxPotentialPayment = ComputerUtilMana.determineLeftoverMana(sa, ai, "R", false);
|
int maxPotentialPayment = ComputerUtilMana.determineLeftoverMana(sa, ai, "R", false);
|
||||||
|
|
||||||
int numTgts = Math.min(maxPotentialPayment, maxPotentialTgts);
|
int numTgts = Math.min(maxPotentialPayment, maxPotentialTgts);
|
||||||
if (numTgts == 0) {
|
if (numTgts == 0) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set Announce
|
// Set Announce
|
||||||
@@ -701,7 +720,7 @@ public class SpecialCardAi {
|
|||||||
List<GameEntity> validTgts = sa.getTargetRestrictions().getAllCandidates(sa, true);
|
List<GameEntity> validTgts = sa.getTargetRestrictions().getAllCandidates(sa, true);
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
sa.getTargets().addAll(Aggregates.random(validTgts, numTgts));
|
sa.getTargets().addAll(Aggregates.random(validTgts, numTgts));
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -910,12 +929,12 @@ public class SpecialCardAi {
|
|||||||
|
|
||||||
// Living Death (and other similar cards using AILogic LivingDeath or AILogic ReanimateAll)
|
// Living Death (and other similar cards using AILogic LivingDeath or AILogic ReanimateAll)
|
||||||
public static class LivingDeath {
|
public static class LivingDeath {
|
||||||
public static boolean consider(final Player ai, final SpellAbility sa) {
|
public static AiAbilityDecision consider(final Player ai, final SpellAbility sa) {
|
||||||
// if there's another reanimator card currently suspended, don't cast a new one until the previous
|
// if there's another reanimator card currently suspended, don't cast a new one until the previous
|
||||||
// one resolves, otherwise the reanimation attempt will be ruined (e.g. Living End)
|
// one resolves, otherwise the reanimation attempt will be ruined (e.g. Living End)
|
||||||
for (Card ex : ai.getCardsIn(ZoneType.Exile)) {
|
for (Card ex : ai.getCardsIn(ZoneType.Exile)) {
|
||||||
if (ex.hasSVar("IsReanimatorCard") && ex.getCounters(CounterEnumType.TIME) > 0) {
|
if (ex.hasSVar("IsReanimatorCard") && ex.getCounters(CounterEnumType.TIME) > 0) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -926,7 +945,7 @@ public class SpecialCardAi {
|
|||||||
|
|
||||||
if (aiCreaturesInGY.isEmpty()) {
|
if (aiCreaturesInGY.isEmpty()) {
|
||||||
// nothing in graveyard, so cut short
|
// nothing in graveyard, so cut short
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (Card c : ai.getCreaturesInPlay()) {
|
for (Card c : ai.getCreaturesInPlay()) {
|
||||||
@@ -958,17 +977,30 @@ public class SpecialCardAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// if we get more value out of this than our opponent does (hopefully), go for it
|
// if we get more value out of this than our opponent does (hopefully), go for it
|
||||||
return (aiGraveyardPower - aiBattlefieldPower) > (oppGraveyardPower - oppBattlefieldPower + threshold);
|
if ((aiGraveyardPower - aiBattlefieldPower) > (oppGraveyardPower - oppBattlefieldPower + threshold)) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
} else {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Maze's End
|
// Maze's End
|
||||||
public static class MazesEnd {
|
public static class MazesEnd {
|
||||||
public static boolean consider(final Player ai, final SpellAbility sa) {
|
public static AiAbilityDecision consider(final Player ai, final SpellAbility sa) {
|
||||||
PhaseHandler ph = ai.getGame().getPhaseHandler();
|
PhaseHandler ph = ai.getGame().getPhaseHandler();
|
||||||
CardCollection availableGates = CardLists.filter(ai.getCardsIn(ZoneType.Library), CardPredicates.isType("Gate"));
|
CardCollection availableGates = CardLists.filter(ai.getCardsIn(ZoneType.Library), CardPredicates.isType("Gate"));
|
||||||
|
|
||||||
return ph.is(PhaseType.END_OF_TURN) && ph.getNextTurn() == ai && !availableGates.isEmpty();
|
if (ph.is(PhaseType.END_OF_TURN) && ph.getNextTurn() == ai && !availableGates.isEmpty()) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (availableGates.isEmpty()) {
|
||||||
|
// No gates available, so don't activate Maze's End
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.MissingNeededCards);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.AnotherTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Card considerCardToGet(final Player ai, final SpellAbility sa)
|
public static Card considerCardToGet(final Player ai, final SpellAbility sa)
|
||||||
@@ -1032,29 +1064,33 @@ public class SpecialCardAi {
|
|||||||
return exiledWith == null || (tgt != null && ComputerUtilCard.evaluateCreature(tgt) > ComputerUtilCard.evaluateCreature(exiledWith));
|
return exiledWith == null || (tgt != null && ComputerUtilCard.evaluateCreature(tgt) > ComputerUtilCard.evaluateCreature(exiledWith));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean considerCopy(final Player ai, final SpellAbility sa) {
|
public static AiAbilityDecision considerCopy(final Player ai, final SpellAbility sa) {
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
final Card exiledWith = source.getImprintedCards().isEmpty() ? null : source.getImprintedCards().getFirst();
|
final Card exiledWith = source.getImprintedCards().isEmpty() ? null : source.getImprintedCards().getFirst();
|
||||||
|
|
||||||
if (exiledWith == null) {
|
if (exiledWith == null) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.MissingNeededCards);
|
||||||
}
|
}
|
||||||
|
|
||||||
// We want to either be able to attack with the creature, or keep it until our opponent's end of turn as a
|
// We want to either be able to attack with the creature, or keep it until our opponent's end of turn as a
|
||||||
// potential blocker
|
// potential blocker
|
||||||
return ComputerUtilCard.doesSpecifiedCreatureAttackAI(ai, exiledWith)
|
if (ComputerUtilCard.doesSpecifiedCreatureAttackAI(ai, exiledWith)
|
||||||
|| (ai.getGame().getPhaseHandler().getPlayerTurn().isOpponentOf(ai) && ai.getGame().getCombat() != null
|
|| (ai.getGame().getPhaseHandler().getPlayerTurn().isOpponentOf(ai) && ai.getGame().getCombat() != null
|
||||||
&& !ai.getGame().getCombat().getAttackers().isEmpty());
|
&& !ai.getGame().getCombat().getAttackers().isEmpty())) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
} else {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Momir Vig, Simic Visionary Avatar
|
// Momir Vig, Simic Visionary Avatar
|
||||||
public static class MomirVigAvatar {
|
public static class MomirVigAvatar {
|
||||||
public static boolean consider(final Player ai, final SpellAbility sa) {
|
public static AiAbilityDecision consider(final Player ai, final SpellAbility sa) {
|
||||||
Card source = sa.getHostCard();
|
Card source = sa.getHostCard();
|
||||||
|
|
||||||
if (source.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.MAIN1)) {
|
if (source.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.MAIN1)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.AnotherTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
// In MoJhoSto, prefer Jhoira sorcery ability from time to time
|
// In MoJhoSto, prefer Jhoira sorcery ability from time to time
|
||||||
@@ -1065,7 +1101,7 @@ public class SpecialCardAi {
|
|||||||
int numLandsForJhoira = aic.getIntProperty(AiProps.MOJHOSTO_NUM_LANDS_TO_ACTIVATE_JHOIRA);
|
int numLandsForJhoira = aic.getIntProperty(AiProps.MOJHOSTO_NUM_LANDS_TO_ACTIVATE_JHOIRA);
|
||||||
|
|
||||||
if (ai.getLandsInPlay().size() >= numLandsForJhoira && MyRandom.percentTrue(chanceToPrefJhoira)) {
|
if (ai.getLandsInPlay().size() >= numLandsForJhoira && MyRandom.percentTrue(chanceToPrefJhoira)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.AnotherTime);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1074,7 +1110,7 @@ public class SpecialCardAi {
|
|||||||
|
|
||||||
// Some basic strategy for Momir
|
// Some basic strategy for Momir
|
||||||
if (tokenSize < 2) {
|
if (tokenSize < 2) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.AnotherTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tokenSize > 11) {
|
if (tokenSize > 11) {
|
||||||
@@ -1083,7 +1119,7 @@ public class SpecialCardAi {
|
|||||||
|
|
||||||
sa.setXManaCostPaid(tokenSize);
|
sa.setXManaCostPaid(tokenSize);
|
||||||
|
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1122,13 +1158,13 @@ public class SpecialCardAi {
|
|||||||
|
|
||||||
// Necropotence
|
// Necropotence
|
||||||
public static class Necropotence {
|
public static class Necropotence {
|
||||||
public static boolean consider(final Player ai, final SpellAbility sa) {
|
public static AiAbilityDecision consider(final Player ai, final SpellAbility sa) {
|
||||||
Game game = ai.getGame();
|
Game game = ai.getGame();
|
||||||
int computerHandSize = ai.getZone(ZoneType.Hand).size();
|
int computerHandSize = ai.getZone(ZoneType.Hand).size();
|
||||||
int maxHandSize = ai.getMaxHandSize();
|
int maxHandSize = ai.getMaxHandSize();
|
||||||
|
|
||||||
if (ai.getCardsIn(ZoneType.Library).isEmpty()) {
|
if (ai.getCardsIn(ZoneType.Library).isEmpty()) {
|
||||||
return false; // nothing to draw from the library
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ai.getCardsIn(ZoneType.Battlefield).anyMatch(CardPredicates.nameEquals("Yawgmoth's Bargain"))) {
|
if (ai.getCardsIn(ZoneType.Battlefield).anyMatch(CardPredicates.nameEquals("Yawgmoth's Bargain"))) {
|
||||||
@@ -1136,7 +1172,7 @@ public class SpecialCardAi {
|
|||||||
|
|
||||||
// TODO: in presence of bad effects which deal damage when a card is drawn, probably better to prefer Necropotence instead?
|
// TODO: in presence of bad effects which deal damage when a card is drawn, probably better to prefer Necropotence instead?
|
||||||
// (not sure how to detect the presence of such effects yet)
|
// (not sure how to detect the presence of such effects yet)
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
PhaseHandler ph = game.getPhaseHandler();
|
PhaseHandler ph = game.getPhaseHandler();
|
||||||
@@ -1158,23 +1194,33 @@ public class SpecialCardAi {
|
|||||||
// We're in a situation when we have nothing castable in hand, something needs to be done
|
// We're in a situation when we have nothing castable in hand, something needs to be done
|
||||||
if (!blackViseOTB) {
|
if (!blackViseOTB) {
|
||||||
// exile-loot +1 card when at max hand size, hoping to get a workable spell or land
|
// exile-loot +1 card when at max hand size, hoping to get a workable spell or land
|
||||||
return computerHandSize + exiledWithNecro - 1 == maxHandSize;
|
if (computerHandSize + exiledWithNecro - 1 == maxHandSize) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
} else {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// Loot to 7 in presence of Black Vise, hoping to find what to do
|
// Loot to 7 in presence of Black Vise, hoping to find what to do
|
||||||
// NOTE: can still currently get theoretically locked with 7 uncastable spells. Loot to 8 instead?
|
// NOTE: can still currently get theoretically locked with 7 uncastable spells. Loot to 8 instead?
|
||||||
return computerHandSize + exiledWithNecro <= maxHandSize;
|
if (computerHandSize + exiledWithNecro <= maxHandSize) {
|
||||||
|
// Loot to 7, hoping to find something playable
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
} else {
|
||||||
|
// Loot to 8, hoping to find something playable
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if (blackViseOTB && computerHandSize + exiledWithNecro - 1 >= 4) {
|
} else if (blackViseOTB && computerHandSize + exiledWithNecro - 1 >= 4) {
|
||||||
// try not to overdraw in presence of Black Vise
|
// try not to overdraw in presence of Black Vise
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
} else if (computerHandSize + exiledWithNecro - 1 >= maxHandSize) {
|
} else if (computerHandSize + exiledWithNecro - 1 >= maxHandSize) {
|
||||||
// Only draw until we reach max hand size
|
// Only draw until we reach max hand size
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
} else if (!ph.isPlayerTurn(ai) || !ph.is(PhaseType.MAIN2)) {
|
} else if (!ph.isPlayerTurn(ai) || !ph.is(PhaseType.MAIN2)) {
|
||||||
// Only activate in AI's own turn (sans the exception above)
|
// Only activate in AI's own turn (sans the exception above)
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.WaitForMain2);
|
||||||
}
|
}
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1294,7 +1340,7 @@ public class SpecialCardAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean considerSecondTarget(final Player ai, final SpellAbility sa) {
|
public static AiAbilityDecision considerSecondTarget(final Player ai, final SpellAbility sa) {
|
||||||
Card firstTgt = sa.getParent().getTargetCard();
|
Card firstTgt = sa.getParent().getTargetCard();
|
||||||
CardCollection candidates = ai.getOpponents().getCardsIn(ZoneType.Battlefield).filter(
|
CardCollection candidates = ai.getOpponents().getCardsIn(ZoneType.Battlefield).filter(
|
||||||
CardPredicates.sharesCardTypeWith(firstTgt).and(CardPredicates.isTargetableBy(sa)));
|
CardPredicates.sharesCardTypeWith(firstTgt).and(CardPredicates.isTargetableBy(sa)));
|
||||||
@@ -1302,89 +1348,105 @@ public class SpecialCardAi {
|
|||||||
if (secondTgt != null) {
|
if (secondTgt != null) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
sa.getTargets().add(secondTgt);
|
sa.getTargets().add(secondTgt);
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Price of Progress
|
// Price of Progress
|
||||||
public static class PriceOfProgress {
|
public static class PriceOfProgress {
|
||||||
public static boolean consider(final Player ai, final SpellAbility sa) {
|
public static AiAbilityDecision consider(final Player ai, final SpellAbility sa) {
|
||||||
// Don't play in early game - opponent likely still has lands to play
|
// Don't play in early game - opponent likely still has lands to play
|
||||||
if (ai.getGame().getPhaseHandler().getTurn() < 10) {
|
if (ai.getGame().getPhaseHandler().getTurn() < 10) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.AnotherTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
int aiLands = CardLists.filter(ai.getCardsIn(ZoneType.Battlefield), CardPredicates.NONBASIC_LANDS).size();
|
int aiLands = CardLists.filter(ai.getCardsIn(ZoneType.Battlefield), CardPredicates.NONBASIC_LANDS).size();
|
||||||
|
// TODO Better if we actually calculate the true damage
|
||||||
|
boolean willDieToPCasting = (ai.getLife() <= aiLands * 2);
|
||||||
|
if (!willDieToPCasting) {
|
||||||
|
boolean hasBridge = false;
|
||||||
|
for (Card c : ai.getCardsIn(ZoneType.Battlefield)) {
|
||||||
|
// Do we have a card in play that makes us want to empty out hand?
|
||||||
|
if (c.hasSVar("PreferredHandSize") && ai.getCardsIn(ZoneType.Hand).size() > Integer.parseInt(c.getSVar("PreferredHandSize"))) {
|
||||||
|
hasBridge = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
boolean hasBridge = false;
|
// Do if we need to lose cards to activate Ensnaring Bridge or Cursed Scroll
|
||||||
for (Card c : ai.getCardsIn(ZoneType.Battlefield)) {
|
// even if suboptimal play, but don't waste the card too early even then!
|
||||||
// Do we have a card in play that makes us want to empty out hand?
|
if (hasBridge) {
|
||||||
if (c.hasSVar("PreferredHandSize") && ai.getCardsIn(ZoneType.Hand).size() > Integer.parseInt(c.getSVar("PreferredHandSize"))) {
|
return new AiAbilityDecision(100, AiPlayDecision.PlayToEmptyHand);
|
||||||
hasBridge = true;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do if we need to lose cards to activate Ensnaring Bridge or Cursed Scroll
|
boolean willPlay = true;
|
||||||
// even if suboptimal play, but don't waste the card too early even then!
|
|
||||||
if ((hasBridge) && (ai.getGame().getPhaseHandler().getTurn() >= 10)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (Player opp : ai.getOpponents()) {
|
for (Player opp : ai.getOpponents()) {
|
||||||
int oppLands = CardLists.filter(opp.getCardsIn(ZoneType.Battlefield), CardPredicates.NONBASIC_LANDS).size();
|
int oppLands = CardLists.filter(opp.getCardsIn(ZoneType.Battlefield), CardPredicates.NONBASIC_LANDS).size();
|
||||||
|
// Don't if no enemy nonbasic lands
|
||||||
|
if (oppLands == 0) {
|
||||||
|
willPlay = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// Always if enemy would die and we don't!
|
// Always if enemy would die and we don't!
|
||||||
// TODO : predict actual damage instead of assuming it'll be 2*lands
|
// TODO : predict actual damage instead of assuming it'll be 2*lands
|
||||||
// Don't if we lose, unless we lose anyway to unblocked creatures next turn
|
// Don't if we lose, unless we lose anyway to unblocked creatures next turn
|
||||||
if ((ai.getLife() <= aiLands * 2) &&
|
if (willDieToPCasting &&
|
||||||
(!(ComputerUtil.aiLifeInDanger(ai, true, 0)) && ((ai.getOpponentsSmallestLifeTotal()) <= oppLands * 2))) {
|
(!(ComputerUtil.aiLifeInDanger(ai, true, 0)) && ((ai.getOpponentsSmallestLifeTotal()) <= oppLands * 2))) {
|
||||||
return false;
|
willPlay = false;
|
||||||
}
|
}
|
||||||
// Do if we can win
|
// Do if we can win
|
||||||
if ((ai.getOpponentsSmallestLifeTotal()) <= oppLands * 2) {
|
if (opp.getLife() <= oppLands * 2) {
|
||||||
return true;
|
return new AiAbilityDecision(1000, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
// Don't if we'd lose a larger percentage of our remaining life than enemy
|
// Don't if we'd lose a larger percentage of our remaining life than enemy
|
||||||
if ((aiLands / ((double) ai.getLife())) >
|
if ((aiLands / ((double) ai.getLife())) >
|
||||||
(oppLands / ((double) ai.getOpponentsSmallestLifeTotal()))) {
|
(oppLands / ((double) ai.getOpponentsSmallestLifeTotal()))) {
|
||||||
return false;
|
willPlay = false;
|
||||||
}
|
|
||||||
// Don't if no enemy nonbasic lands
|
|
||||||
if (oppLands == 0) {
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Don't if loss is equal in percentage but we lose more points
|
// Don't if loss is equal in percentage but we lose more points
|
||||||
if (((aiLands / ((double) ai.getLife())) == (oppLands / ((double) ai.getOpponentsSmallestLifeTotal())))
|
if (((aiLands / ((double) ai.getLife())) == (oppLands / ((double) ai.getOpponentsSmallestLifeTotal())))
|
||||||
&& (aiLands > oppLands)) {
|
&& (aiLands > oppLands)) {
|
||||||
return false;
|
willPlay = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
return true;
|
if (willPlay) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
} else {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sarkhan the Mad
|
// Sarkhan the Mad
|
||||||
public static class SarkhanTheMad {
|
public static class SarkhanTheMad {
|
||||||
public static boolean considerDig(final Player ai, final SpellAbility sa) {
|
public static AiAbilityDecision considerDig(final Player ai, final SpellAbility sa) {
|
||||||
return sa.getHostCard().getCounters(CounterEnumType.LOYALTY) == 1;
|
if (sa.getHostCard().getCounters(CounterEnumType.LOYALTY) == 1) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
} else {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean considerMakeDragon(final Player ai, final SpellAbility sa) {
|
public static AiAbilityDecision considerMakeDragon(final Player ai, final SpellAbility sa) {
|
||||||
// TODO: expand this logic to make the AI force the opponent to sacrifice a big threat bigger than a 5/5 flier?
|
// TODO: expand this logic to make the AI force the opponent to sacrifice a big threat bigger than a 5/5 flier?
|
||||||
CardCollection creatures = ai.getCreaturesInPlay();
|
CardCollection creatures = ai.getCreaturesInPlay();
|
||||||
boolean hasValidTgt = !CardLists.filter(creatures, t -> t.getNetPower() < 5 && t.getNetToughness() < 5).isEmpty();
|
boolean hasValidTgt = !CardLists.filter(creatures, t -> t.getNetPower() < 5 && t.getNetToughness() < 5).isEmpty();
|
||||||
if (hasValidTgt) {
|
if (hasValidTgt) {
|
||||||
Card worstCreature = ComputerUtilCard.getWorstCreatureAI(creatures);
|
Card worstCreature = ComputerUtilCard.getWorstCreatureAI(creatures);
|
||||||
sa.getTargets().add(worstCreature);
|
sa.getTargets().add(worstCreature);
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.AddBoardPresence);
|
||||||
}
|
}
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static boolean considerUltimate(final Player ai, final SpellAbility sa, final Player weakestOpp) {
|
public static boolean considerUltimate(final Player ai, final SpellAbility sa, final Player weakestOpp) {
|
||||||
int minLife = weakestOpp.getLife();
|
int minLife = weakestOpp.getLife();
|
||||||
|
|
||||||
@@ -1440,7 +1502,7 @@ public class SpecialCardAi {
|
|||||||
|
|
||||||
// Sorin, Vengeful Bloodlord
|
// Sorin, Vengeful Bloodlord
|
||||||
public static class SorinVengefulBloodlord {
|
public static class SorinVengefulBloodlord {
|
||||||
public static boolean consider(final Player ai, final SpellAbility sa) {
|
public static AiAbilityDecision consider(final Player ai, final SpellAbility sa) {
|
||||||
int loyalty = sa.getHostCard().getCounters(CounterEnumType.LOYALTY);
|
int loyalty = sa.getHostCard().getCounters(CounterEnumType.LOYALTY);
|
||||||
CardCollection creaturesToGet = CardLists.filter(ai.getCardsIn(ZoneType.Graveyard),
|
CardCollection creaturesToGet = CardLists.filter(ai.getCardsIn(ZoneType.Graveyard),
|
||||||
CardPredicates.CREATURES
|
CardPredicates.CREATURES
|
||||||
@@ -1454,7 +1516,7 @@ public class SpecialCardAi {
|
|||||||
CardLists.sortByCmcDesc(creaturesToGet);
|
CardLists.sortByCmcDesc(creaturesToGet);
|
||||||
|
|
||||||
if (creaturesToGet.isEmpty()) {
|
if (creaturesToGet.isEmpty()) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
// pick the best creature that will stay on the battlefield
|
// pick the best creature that will stay on the battlefield
|
||||||
@@ -1470,10 +1532,10 @@ public class SpecialCardAi {
|
|||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
sa.getTargets().add(best);
|
sa.getTargets().add(best);
|
||||||
sa.setXManaCostPaid(best.getCMC());
|
sa.setXManaCostPaid(best.getCMC());
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1587,23 +1649,27 @@ public class SpecialCardAi {
|
|||||||
|
|
||||||
// The One Ring
|
// The One Ring
|
||||||
public static class TheOneRing {
|
public static class TheOneRing {
|
||||||
public static boolean consider(final Player ai, final SpellAbility sa) {
|
public static AiAbilityDecision consider(final Player ai, final SpellAbility sa) {
|
||||||
if (!ai.canLoseLife() || ai.cantLoseForZeroOrLessLife()) {
|
if (!ai.canLoseLife() || ai.cantLoseForZeroOrLessLife()) {
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
AiController aic = ((PlayerControllerAi) ai.getController()).getAi();
|
AiController aic = ((PlayerControllerAi) ai.getController()).getAi();
|
||||||
int lifeInDanger = aic.getIntProperty(AiProps.AI_IN_DANGER_THRESHOLD);
|
int lifeInDanger = aic.getIntProperty(AiProps.AI_IN_DANGER_THRESHOLD);
|
||||||
int numCtrs = sa.getHostCard().getCounters(CounterEnumType.BURDEN);
|
int numCtrs = sa.getHostCard().getCounters(CounterEnumType.BURDEN);
|
||||||
|
|
||||||
return ai.getLife() > numCtrs + 1 && ai.getLife() > lifeInDanger
|
if (ai.getLife() > numCtrs + 1 && ai.getLife() > lifeInDanger
|
||||||
&& ai.getMaxHandSize() >= ai.getCardsIn(ZoneType.Hand).size() + numCtrs + 1;
|
&& ai.getMaxHandSize() >= ai.getCardsIn(ZoneType.Hand).size() + numCtrs + 1) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.LifeInDanger);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// The Scarab God
|
// The Scarab God
|
||||||
public static class TheScarabGod {
|
public static class TheScarabGod {
|
||||||
public static boolean consider(final Player ai, final SpellAbility sa) {
|
public static AiAbilityDecision consider(final Player ai, final SpellAbility sa) {
|
||||||
Card bestOppCreat = ComputerUtilCard.getBestAI(CardLists.filter(ai.getOpponents().getCardsIn(ZoneType.Graveyard), CardPredicates.CREATURES));
|
Card bestOppCreat = ComputerUtilCard.getBestAI(CardLists.filter(ai.getOpponents().getCardsIn(ZoneType.Graveyard), CardPredicates.CREATURES));
|
||||||
Card worstOwnCreat = ComputerUtilCard.getWorstAI(CardLists.filter(ai.getCardsIn(ZoneType.Graveyard), CardPredicates.CREATURES));
|
Card worstOwnCreat = ComputerUtilCard.getWorstAI(CardLists.filter(ai.getCardsIn(ZoneType.Graveyard), CardPredicates.CREATURES));
|
||||||
|
|
||||||
@@ -1614,13 +1680,19 @@ public class SpecialCardAi {
|
|||||||
sa.getTargets().add(worstOwnCreat);
|
sa.getTargets().add(worstOwnCreat);
|
||||||
}
|
}
|
||||||
|
|
||||||
return sa.getTargets().size() > 0;
|
if (!sa.getTargets().isEmpty()) {
|
||||||
|
// If we have a target, we can play this ability
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
} else {
|
||||||
|
// No valid targets, can't play this ability
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Timetwister
|
// Timetwister
|
||||||
public static class Timetwister {
|
public static class Timetwister {
|
||||||
public static boolean consider(final Player ai, final SpellAbility sa) {
|
public static AiAbilityDecision consider(final Player ai, final SpellAbility sa) {
|
||||||
final int aiHandSize = ai.getCardsIn(ZoneType.Hand).size();
|
final int aiHandSize = ai.getCardsIn(ZoneType.Hand).size();
|
||||||
int maxOppHandSize = 0;
|
int maxOppHandSize = 0;
|
||||||
|
|
||||||
@@ -1634,7 +1706,14 @@ public class SpecialCardAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// use in case we're getting low on cards or if we're significantly behind our opponent in cards in hand
|
// use in case we're getting low on cards or if we're significantly behind our opponent in cards in hand
|
||||||
return aiHandSize < HAND_SIZE_THRESHOLD || maxOppHandSize - aiHandSize > HAND_SIZE_THRESHOLD;
|
if (aiHandSize < HAND_SIZE_THRESHOLD || maxOppHandSize - aiHandSize > HAND_SIZE_THRESHOLD) {
|
||||||
|
// if the AI has less than 3 cards in hand or the opponent has more than 3 cards in hand than the AI
|
||||||
|
// then the AI is willing to play this ability
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
} else {
|
||||||
|
// otherwise, don't play this ability
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1695,12 +1774,12 @@ public class SpecialCardAi {
|
|||||||
|
|
||||||
// Volrath's Shapeshifter
|
// Volrath's Shapeshifter
|
||||||
public static class VolrathsShapeshifter {
|
public static class VolrathsShapeshifter {
|
||||||
public static boolean consider(final Player ai, final SpellAbility sa) {
|
public static AiAbilityDecision consider(final Player ai, final SpellAbility sa) {
|
||||||
PhaseHandler ph = ai.getGame().getPhaseHandler();
|
PhaseHandler ph = ai.getGame().getPhaseHandler();
|
||||||
if (ph.getPhase().isBefore(PhaseType.COMBAT_BEGIN)) {
|
if (ph.getPhase().isBefore(PhaseType.COMBAT_BEGIN)) {
|
||||||
// try not to do this too early to at least attempt to avoid situations where the AI
|
// try not to do this too early to at least attempt to avoid situations where the AI
|
||||||
// would cast a spell which would ruin the shapeshifting
|
// would cast a spell which would ruin the shapeshifting
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.WaitForMain2);
|
||||||
}
|
}
|
||||||
|
|
||||||
CardCollectionView aiGY = ai.getCardsIn(ZoneType.Graveyard);
|
CardCollectionView aiGY = ai.getCardsIn(ZoneType.Graveyard);
|
||||||
@@ -1716,11 +1795,15 @@ public class SpecialCardAi {
|
|||||||
if (topGY == null
|
if (topGY == null
|
||||||
|| !topGY.isCreature()
|
|| !topGY.isCreature()
|
||||||
|| ComputerUtilCard.evaluateCreature(creatHand) > ComputerUtilCard.evaluateCreature(topGY) + 80) {
|
|| ComputerUtilCard.evaluateCreature(creatHand) > ComputerUtilCard.evaluateCreature(topGY) + 80) {
|
||||||
return numCreatsInHand > 1 || !ComputerUtilMana.canPayManaCost(creatHand.getSpellPermanent(), ai, 0, false);
|
if ( numCreatsInHand > 1 || !ComputerUtilMana.canPayManaCost(creatHand.getSpellPermanent(), ai, 0, false)) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
} else {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static CardCollection targetBestCreature(final Player ai, final SpellAbility sa) {
|
public static CardCollection targetBestCreature(final Player ai, final SpellAbility sa) {
|
||||||
|
|||||||
@@ -39,70 +39,75 @@ import forge.util.collect.FCollectionView;
|
|||||||
*/
|
*/
|
||||||
public abstract class SpellAbilityAi {
|
public abstract class SpellAbilityAi {
|
||||||
|
|
||||||
public final boolean canPlayAIWithSubs(final Player aiPlayer, final SpellAbility sa) {
|
public final AiAbilityDecision canPlayWithSubs(final Player aiPlayer, final SpellAbility sa) {
|
||||||
if (!canPlayAI(aiPlayer, sa)) {
|
AiAbilityDecision decision = canPlay(aiPlayer, sa);
|
||||||
return false;
|
if (!decision.willingToPlay() && !"PlayForSub".equals(sa.getParam("AILogic"))) {
|
||||||
|
return decision;
|
||||||
}
|
}
|
||||||
final AbilitySub subAb = sa.getSubAbility();
|
final AbilitySub subAb = sa.getSubAbility();
|
||||||
return subAb == null || chkDrawbackWithSubs(aiPlayer, subAb);
|
if (subAb == null) {
|
||||||
|
return decision;
|
||||||
|
}
|
||||||
|
|
||||||
|
return chkDrawbackWithSubs(aiPlayer, subAb);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles the AI decision to play a "main" SpellAbility
|
* Handles the AI decision to play a "main" SpellAbility
|
||||||
*/
|
*/
|
||||||
protected boolean canPlayAI(final Player ai, final SpellAbility sa) {
|
protected AiAbilityDecision canPlay(final Player ai, final SpellAbility sa) {
|
||||||
final Card source = sa.getHostCard();
|
if (sa.getRestrictions() != null && !sa.getRestrictions().canPlay(sa.getHostCard(), sa)) {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlaySa);
|
||||||
if (sa.getRestrictions() != null && !sa.getRestrictions().canPlay(source, sa)) {
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return canPlayWithoutRestrict(ai, sa);
|
return canPlayWithoutRestrict(ai, sa);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected boolean canPlayWithoutRestrict(final Player ai, final SpellAbility sa) {
|
protected AiAbilityDecision canPlayWithoutRestrict(final Player ai, final SpellAbility sa) {
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
final Cost cost = sa.getPayCosts();
|
final Cost cost = sa.getPayCosts();
|
||||||
|
|
||||||
if (sa.hasParam("AICheckCanPlayWithDefinedX")) {
|
|
||||||
// FIXME: can this somehow be simplified without the need for an extra AI hint?
|
|
||||||
sa.setXManaCostPaid(ComputerUtilCost.getMaxXValue(sa, ai, false));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!checkConditions(ai, sa, sa.getConditions())) {
|
|
||||||
SpellAbility sub = sa.getSubAbility();
|
|
||||||
if (sub != null && !checkConditions(ai, sub, sub.getConditions())) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sa.hasParam("AILogic")) {
|
if (sa.hasParam("AILogic")) {
|
||||||
final String logic = sa.getParam("AILogic");
|
final String logic = sa.getParam("AILogic");
|
||||||
final boolean alwaysOnDiscard = "AlwaysOnDiscard".equals(logic) && ai.getGame().getPhaseHandler().is(PhaseType.END_OF_TURN, ai)
|
final boolean alwaysOnDiscard = "AlwaysOnDiscard".equals(logic) && ai.getGame().getPhaseHandler().is(PhaseType.END_OF_TURN, ai)
|
||||||
&& !ai.isUnlimitedHandSize() && ai.getCardsIn(ZoneType.Hand).size() > ai.getMaxHandSize();
|
&& !ai.isUnlimitedHandSize() && ai.getCardsIn(ZoneType.Hand).size() > ai.getMaxHandSize();
|
||||||
if (!checkAiLogic(ai, sa, logic)) {
|
if (!checkAiLogic(ai, sa, logic)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
if (!alwaysOnDiscard && !checkPhaseRestrictions(ai, sa, ai.getGame().getPhaseHandler(), logic)) {
|
if (!alwaysOnDiscard && !checkPhaseRestrictions(ai, sa, ai.getGame().getPhaseHandler(), logic)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.MissingPhaseRestrictions);
|
||||||
}
|
}
|
||||||
} else if (!checkPhaseRestrictions(ai, sa, ai.getGame().getPhaseHandler())) {
|
} else if (!checkPhaseRestrictions(ai, sa, ai.getGame().getPhaseHandler())) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.MissingPhaseRestrictions);
|
||||||
|
} else if (ComputerUtil.preventRunAwayActivations(sa)) {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.StopRunawayActivations);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!checkApiLogic(ai, sa)) {
|
AiAbilityDecision decision = checkApiLogic(ai, sa);
|
||||||
return false;
|
if (!decision.willingToPlay()) {
|
||||||
|
return decision;
|
||||||
}
|
}
|
||||||
// needs to be after API logic because needs to check possible X Cost?
|
|
||||||
|
// needs to be after API logic because needs to check possible X Cost
|
||||||
if (cost != null && !willPayCosts(ai, sa, cost, source)) {
|
if (cost != null && !willPayCosts(ai, sa, cost, source)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CostNotAcceptable);
|
||||||
}
|
}
|
||||||
return true;
|
|
||||||
|
// for cards like Figure of Destiny
|
||||||
|
// (it's unlikely many valid effect would work like this -
|
||||||
|
// and while in theory AI could turn some conditions true in response that's far too advanced as default)
|
||||||
|
if (!checkConditions(ai, sa)) {
|
||||||
|
SpellAbility sub = sa.getSubAbility();
|
||||||
|
if (sub == null || !checkConditions(ai, sub)) {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.NeedsToPlayCriteriaNotMet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return decision;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected boolean checkConditions(final Player ai, final SpellAbility sa, SpellAbilityCondition con) {
|
protected boolean checkConditions(final Player ai, final SpellAbility sa) {
|
||||||
// copy it to disable some checks that the AI need to check extra
|
// copy it to disable some checks that the AI need to check extra
|
||||||
con = (SpellAbilityCondition) con.copy();
|
SpellAbilityCondition con = (SpellAbilityCondition) sa.getConditions().copy();
|
||||||
|
|
||||||
// if manaspent, check if AI can pay the colored mana as cost
|
// if manaspent, check if AI can pay the colored mana as cost
|
||||||
if (!con.getManaSpent().isEmpty()) {
|
if (!con.getManaSpent().isEmpty()) {
|
||||||
@@ -116,40 +121,6 @@ public abstract class SpellAbilityAi {
|
|||||||
return con.areMet(sa);
|
return con.areMet(sa);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if the AI will play a SpellAbility with the specified AiLogic
|
|
||||||
*/
|
|
||||||
protected boolean checkAiLogic(final Player ai, final SpellAbility sa, final String aiLogic) {
|
|
||||||
if (aiLogic.equals("CheckCondition")) {
|
|
||||||
SpellAbility saCopy = sa.copy();
|
|
||||||
saCopy.setActivatingPlayer(ai);
|
|
||||||
return saCopy.metConditions();
|
|
||||||
}
|
|
||||||
|
|
||||||
return !("Never".equals(aiLogic));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if the AI is willing to pay for additional costs
|
|
||||||
* <p>
|
|
||||||
* Evaluated costs are: life, discard, sacrifice and counter-removal
|
|
||||||
*/
|
|
||||||
protected boolean willPayCosts(final Player ai, final SpellAbility sa, final Cost cost, final Card source) {
|
|
||||||
if (!ComputerUtilCost.checkLifeCost(ai, cost, source, 4, sa)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!ComputerUtilCost.checkDiscardCost(ai, cost, source, sa)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!ComputerUtilCost.checkSacrificeCost(ai, cost, source, sa)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!ComputerUtilCost.checkRemoveCounterCost(cost, source, sa)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if the AI will play a SpellAbility based on its phase restrictions
|
* Checks if the AI will play a SpellAbility based on its phase restrictions
|
||||||
*/
|
*/
|
||||||
@@ -159,19 +130,38 @@ public abstract class SpellAbilityAi {
|
|||||||
|
|
||||||
protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, final PhaseHandler ph,
|
protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, final PhaseHandler ph,
|
||||||
final String logic) {
|
final String logic) {
|
||||||
|
if (logic.equals("AtOppEOT")) {
|
||||||
|
return ph.getNextTurn() == ai && ph.is(PhaseType.END_OF_TURN);
|
||||||
|
}
|
||||||
return checkPhaseRestrictions(ai, sa, ph);
|
return checkPhaseRestrictions(ai, sa, ph);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the AI will play a SpellAbility with the specified AiLogic
|
||||||
|
*/
|
||||||
|
protected boolean checkAiLogic(final Player ai, final SpellAbility sa, final String aiLogic) {
|
||||||
|
if ("Never".equals(aiLogic)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!"Once".equals(aiLogic)) {
|
||||||
|
return !AiCardMemory.isRememberedCard(ai, sa.getHostCard(), AiCardMemory.MemorySet.ACTIVATED_THIS_TURN);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The rest of the logic not covered by the canPlayAI template is defined here
|
* The rest of the logic not covered by the canPlayAI template is defined here
|
||||||
*/
|
*/
|
||||||
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
|
protected AiAbilityDecision checkApiLogic(final Player ai, final SpellAbility sa) {
|
||||||
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
if (MyRandom.getRandom().nextFloat() < .8f) {
|
||||||
return false; // prevent infinite loop
|
// 80% chance to play the ability
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
return MyRandom.getRandom().nextFloat() < .8f; // random success
|
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
public final boolean doTriggerAI(final Player aiPlayer, final SpellAbility sa, final boolean mandatory) {
|
public final boolean doTrigger(final Player aiPlayer, final SpellAbility sa, final boolean mandatory) {
|
||||||
// this evaluation order is currently intentional as it does more stuff that helps avoiding some crashes
|
// this evaluation order is currently intentional as it does more stuff that helps avoiding some crashes
|
||||||
if (!ComputerUtilCost.canPayCost(sa, aiPlayer, true) && !mandatory) {
|
if (!ComputerUtilCost.canPayCost(sa, aiPlayer, true) && !mandatory) {
|
||||||
return false;
|
return false;
|
||||||
@@ -183,28 +173,48 @@ public abstract class SpellAbilityAi {
|
|||||||
return sa.isTargetNumberValid();
|
return sa.isTargetNumberValid();
|
||||||
}
|
}
|
||||||
|
|
||||||
return doTriggerNoCostWithSubs(aiPlayer, sa, mandatory);
|
return doTriggerNoCostWithSubs(aiPlayer, sa, mandatory).willingToPlay();
|
||||||
}
|
}
|
||||||
|
|
||||||
public final boolean doTriggerNoCostWithSubs(final Player aiPlayer, final SpellAbility sa, final boolean mandatory) {
|
public final AiAbilityDecision doTriggerNoCostWithSubs(final Player aiPlayer, final SpellAbility sa, final boolean mandatory) {
|
||||||
if (!doTriggerAINoCost(aiPlayer, sa, mandatory) && !"Always".equals(sa.getParam("AILogic"))) {
|
AiAbilityDecision decision = doTriggerNoCost(aiPlayer, sa, mandatory);
|
||||||
return false;
|
if (!decision.willingToPlay() && !"Always".equals(sa.getParam("AILogic"))) {
|
||||||
|
return decision;
|
||||||
}
|
}
|
||||||
final AbilitySub subAb = sa.getSubAbility();
|
final AbilitySub subAb = sa.getSubAbility();
|
||||||
return subAb == null || chkDrawbackWithSubs(aiPlayer, subAb) || mandatory;
|
if (subAb == null) {
|
||||||
}
|
if (decision.willingToPlay()) {
|
||||||
|
return decision;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
}
|
||||||
|
|
||||||
|
decision = chkDrawbackWithSubs(aiPlayer, subAb);
|
||||||
|
if (decision.willingToPlay()) {
|
||||||
|
return decision;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mandatory) {
|
||||||
|
return new AiAbilityDecision(50, AiPlayDecision.MandatoryPlay);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles the AI decision to play a triggered SpellAbility
|
* Handles the AI decision to play a triggered SpellAbility
|
||||||
*/
|
*/
|
||||||
protected boolean doTriggerAINoCost(final Player aiPlayer, final SpellAbility sa, final boolean mandatory) {
|
protected AiAbilityDecision doTriggerNoCost(final Player aiPlayer, final SpellAbility sa, final boolean mandatory) {
|
||||||
if (canPlayWithoutRestrict(aiPlayer, sa) && (!mandatory || sa.isTargetNumberValid())) {
|
AiAbilityDecision decision = canPlayWithoutRestrict(aiPlayer, sa);
|
||||||
return true;
|
if (decision.willingToPlay() && (!mandatory || sa.isTargetNumberValid())) {
|
||||||
|
// This is a weird check. Why do we care if its not mandatory if we WANT to do it?
|
||||||
|
return decision;
|
||||||
}
|
}
|
||||||
|
|
||||||
// not mandatory, short way out
|
// not mandatory, short way out
|
||||||
if (!mandatory) {
|
if (!mandatory) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
// invalid target might prevent it
|
// invalid target might prevent it
|
||||||
@@ -220,82 +230,13 @@ public abstract class SpellAbilityAi {
|
|||||||
if (sa.canTarget(p)) {
|
if (sa.canTarget(p)) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
sa.getTargets().add(p);
|
sa.getTargets().add(p);
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles the AI decision to play a sub-SpellAbility
|
|
||||||
*/
|
|
||||||
public boolean chkAIDrawback(final SpellAbility sa, final Player aiPlayer) {
|
|
||||||
// sub-SpellAbility might use targets too
|
|
||||||
if (sa.usesTargeting()) {
|
|
||||||
// no Candidates, no adding to Stack
|
|
||||||
if (!sa.getTargetRestrictions().hasCandidates(sa)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// but if it does, it should override this function
|
|
||||||
System.err.println("Warning: default (ie. inherited from base class) implementation of chkAIDrawback is used by " + sa.getHostCard().getName() + " for " + this.getClass().getName() + ". Consider declaring an overloaded method");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* <p>
|
|
||||||
* isSorcerySpeed.
|
|
||||||
* </p>
|
|
||||||
*
|
|
||||||
* @param sa
|
|
||||||
* a {@link forge.game.spellability.SpellAbility} object.
|
|
||||||
* @return a boolean.
|
|
||||||
*/
|
|
||||||
protected static boolean isSorcerySpeed(final SpellAbility sa, Player ai) {
|
|
||||||
return (sa.getRootAbility().isSpell() && sa.getHostCard().isSorcery())
|
|
||||||
|| (sa.getRootAbility().isActivatedAbility() && sa.getRootAbility().getRestrictions().isSorcerySpeed())
|
|
||||||
|| (sa.getRootAbility().isAdventure() && sa.getHostCard().getState(CardStateName.Secondary).getType().isSorcery())
|
|
||||||
|| (sa.isPwAbility() && !sa.withFlash(sa.getHostCard(), ai));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* <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) {
|
|
||||||
PhaseHandler phase = ai.getGame().getPhaseHandler();
|
|
||||||
|
|
||||||
// TODO probably also consider if winter orb or similar are out
|
|
||||||
|
|
||||||
if (sa instanceof AbilitySub) {
|
|
||||||
return true; // This is only true for Drawbacks and triggers
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!sa.getPayCosts().isReusuableResource()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ComputerUtil.playImmediately(ai, sa)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sa.isPwAbility() && phase.is(PhaseType.MAIN2)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (sa.isSpell() && !sa.isBuyback()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return phase.is(PhaseType.END_OF_TURN) && phase.getNextTurn().equals(ai);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -304,9 +245,35 @@ public abstract class SpellAbilityAi {
|
|||||||
* @param ab
|
* @param ab
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
public boolean chkDrawbackWithSubs(Player aiPlayer, AbilitySub ab) {
|
public AiAbilityDecision chkDrawbackWithSubs(Player aiPlayer, AbilitySub ab) {
|
||||||
final AbilitySub subAb = ab.getSubAbility();
|
final AbilitySub subAb = ab.getSubAbility();
|
||||||
return SpellApiToAi.Converter.get(ab).chkAIDrawback(ab, aiPlayer) && (subAb == null || chkDrawbackWithSubs(aiPlayer, subAb));
|
AiAbilityDecision decision = SpellApiToAi.Converter.get(ab).chkDrawback(ab, aiPlayer);
|
||||||
|
if (!decision.willingToPlay()) {
|
||||||
|
return decision;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (subAb == null) {
|
||||||
|
return decision;
|
||||||
|
}
|
||||||
|
|
||||||
|
return chkDrawbackWithSubs(aiPlayer, subAb);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the AI decision to play a sub-SpellAbility
|
||||||
|
*/
|
||||||
|
public AiAbilityDecision chkDrawback(final SpellAbility sa, final Player aiPlayer) {
|
||||||
|
// sub-SpellAbility might use targets too
|
||||||
|
if (sa.usesTargeting()) {
|
||||||
|
// no Candidates, no adding to Stack
|
||||||
|
if (!sa.getTargetRestrictions().hasCandidates(sa)) {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
|
}
|
||||||
|
// but if it does, it should override this function
|
||||||
|
System.err.println("Warning: default (ie. inherited from base class) implementation of chkAIDrawback is used by " + sa.getHostCard().getName() + " for " + this.getClass().getName() + ". Consider declaring an overloaded method");
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
|
}
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message, Map<String, Object> params) {
|
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message, Map<String, Object> params) {
|
||||||
@@ -314,25 +281,6 @@ public abstract class SpellAbilityAi {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean willPayUnlessCost(SpellAbility sa, Player payer, Cost cost, boolean alreadyPaid, FCollectionView<Player> payers) {
|
|
||||||
final Card source = sa.getHostCard();
|
|
||||||
final String aiLogic = sa.getParam("UnlessAI");
|
|
||||||
boolean payNever = "Never".equals(aiLogic);
|
|
||||||
boolean isMine = sa.getActivatingPlayer().equals(payer);
|
|
||||||
|
|
||||||
if (payNever) { return false; }
|
|
||||||
|
|
||||||
// AI will only pay when it's not already payed and only opponents abilities
|
|
||||||
if (alreadyPaid || (payers.size() > 1 && isMine)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return ComputerUtilCost.checkLifeCost(payer, cost, source, 4, sa)
|
|
||||||
&& ComputerUtilCost.checkDamageCost(payer, cost, source, 4, sa)
|
|
||||||
&& (isMine || ComputerUtilCost.checkSacrificeCost(payer, cost, source, sa))
|
|
||||||
&& (isMine || ComputerUtilCost.checkDiscardCost(payer, cost, source, sa));
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public <T extends GameEntity> T chooseSingleEntity(Player ai, SpellAbility sa, Collection<T> options, boolean isOptional, Player targetedPlayer, Map<String, Object> params) {
|
public <T extends GameEntity> T chooseSingleEntity(Player ai, SpellAbility sa, Collection<T> options, boolean isOptional, Player targetedPlayer, Map<String, Object> params) {
|
||||||
boolean hasPlayer = false;
|
boolean hasPlayer = false;
|
||||||
@@ -412,6 +360,46 @@ public abstract class SpellAbilityAi {
|
|||||||
return MyRandom.getRandom().nextBoolean();
|
return MyRandom.getRandom().nextBoolean();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the AI is willing to pay for additional costs
|
||||||
|
* <p>
|
||||||
|
* Evaluated costs are: life, discard, sacrifice and counter-removal
|
||||||
|
*/
|
||||||
|
protected boolean willPayCosts(final Player ai, final SpellAbility sa, final Cost cost, final Card source) {
|
||||||
|
if (!ComputerUtilCost.checkLifeCost(ai, cost, source, 4, sa)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!ComputerUtilCost.checkDiscardCost(ai, cost, source, sa)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!ComputerUtilCost.checkSacrificeCost(ai, cost, source, sa)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!ComputerUtilCost.checkRemoveCounterCost(cost, source, sa)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean willPayUnlessCost(SpellAbility sa, Player payer, Cost cost, boolean alreadyPaid, FCollectionView<Player> payers) {
|
||||||
|
final Card source = sa.getHostCard();
|
||||||
|
final String aiLogic = sa.getParam("UnlessAI");
|
||||||
|
boolean payNever = "Never".equals(aiLogic);
|
||||||
|
boolean isMine = sa.getActivatingPlayer().equals(payer);
|
||||||
|
|
||||||
|
if (payNever) { return false; }
|
||||||
|
|
||||||
|
// AI will only pay when it's not already payed and only opponents abilities
|
||||||
|
if (alreadyPaid || (payers.size() > 1 && isMine)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ComputerUtilCost.checkLifeCost(payer, cost, source, 4, sa)
|
||||||
|
&& ComputerUtilCost.checkDamageCost(payer, cost, source, 4, sa)
|
||||||
|
&& (isMine || ComputerUtilCost.checkSacrificeCost(payer, cost, source, sa))
|
||||||
|
&& (isMine || ComputerUtilCost.checkDiscardCost(payer, cost, source, sa));
|
||||||
|
}
|
||||||
|
|
||||||
public List<OptionalCostValue> chooseOptionalCosts(SpellAbility chosen, Player player, List<OptionalCostValue> optionalCostValues) {
|
public List<OptionalCostValue> chooseOptionalCosts(SpellAbility chosen, Player player, List<OptionalCostValue> optionalCostValues) {
|
||||||
List<OptionalCostValue> chosenOptCosts = Lists.newArrayList();
|
List<OptionalCostValue> chosenOptCosts = Lists.newArrayList();
|
||||||
Cost costSoFar = chosen.getPayCosts().copy();
|
Cost costSoFar = chosen.getPayCosts().copy();
|
||||||
@@ -421,14 +409,14 @@ public abstract class SpellAbilityAi {
|
|||||||
Cost fullCost = opt.getCost().copy().add(costSoFar);
|
Cost fullCost = opt.getCost().copy().add(costSoFar);
|
||||||
SpellAbility fullCostSa = chosen.copyWithDefinedCost(fullCost);
|
SpellAbility fullCostSa = chosen.copyWithDefinedCost(fullCost);
|
||||||
|
|
||||||
// Playability check for Kicker
|
|
||||||
if (opt.getType() == OptionalCost.Kicker1 || opt.getType() == OptionalCost.Kicker2) {
|
if (opt.getType() == OptionalCost.Kicker1 || opt.getType() == OptionalCost.Kicker2) {
|
||||||
SpellAbility kickedSaCopy = fullCostSa.copy();
|
SpellAbility kickedSaCopy = fullCostSa.copy();
|
||||||
kickedSaCopy.addOptionalCost(opt.getType());
|
kickedSaCopy.addOptionalCost(opt.getType());
|
||||||
Card copy = CardCopyService.getLKICopy(chosen.getHostCard());
|
Card copy = CardCopyService.getLKICopy(chosen.getHostCard());
|
||||||
copy.setCastSA(kickedSaCopy);
|
copy.setCastSA(kickedSaCopy);
|
||||||
if (ComputerUtilCard.checkNeedsToPlayReqs(copy, kickedSaCopy) != AiPlayDecision.WillPlay) {
|
if (ComputerUtilCard.checkNeedsToPlayReqs(copy, kickedSaCopy) != AiPlayDecision.WillPlay) {
|
||||||
continue; // don't choose kickers we don't want to play
|
// don't choose kickers we don't want to play
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -440,4 +428,56 @@ public abstract class SpellAbilityAi {
|
|||||||
|
|
||||||
return chosenOptCosts;
|
return chosenOptCosts;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* isSorcerySpeed.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param sa
|
||||||
|
* a {@link forge.game.spellability.SpellAbility} object.
|
||||||
|
* @return a boolean.
|
||||||
|
*/
|
||||||
|
protected static boolean isSorcerySpeed(final SpellAbility sa, Player ai) {
|
||||||
|
return (sa.getRootAbility().isSpell() && sa.getHostCard().isSorcery())
|
||||||
|
|| (sa.getRootAbility().isActivatedAbility() && sa.getRootAbility().getRestrictions().isSorcerySpeed())
|
||||||
|
|| (sa.getRootAbility().isAdventure() && sa.getHostCard().getState(CardStateName.Secondary).getType().isSorcery())
|
||||||
|
|| (sa.isPwAbility() && !sa.withFlash(sa.getHostCard(), ai));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <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) {
|
||||||
|
PhaseHandler phase = ai.getGame().getPhaseHandler();
|
||||||
|
|
||||||
|
// TODO probably also consider if winter orb or similar are out
|
||||||
|
|
||||||
|
if (sa instanceof AbilitySub) {
|
||||||
|
return true; // This is only true for Drawbacks and triggers
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!sa.getPayCosts().isReusuableResource()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ComputerUtil.playImmediately(ai, sa)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sa.isPwAbility() && phase.is(PhaseType.MAIN2)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (sa.isSpell() && !sa.isBuyback()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return phase.is(PhaseType.END_OF_TURN) && phase.getNextTurn().equals(ai);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -77,6 +77,7 @@ public enum SpellApiToAi {
|
|||||||
.put(ApiType.DelayedTrigger, DelayedTriggerAi.class)
|
.put(ApiType.DelayedTrigger, DelayedTriggerAi.class)
|
||||||
.put(ApiType.Destroy, DestroyAi.class)
|
.put(ApiType.Destroy, DestroyAi.class)
|
||||||
.put(ApiType.DestroyAll, DestroyAllAi.class)
|
.put(ApiType.DestroyAll, DestroyAllAi.class)
|
||||||
|
.put(ApiType.Detain, DetainAi.class)
|
||||||
.put(ApiType.Dig, DigAi.class)
|
.put(ApiType.Dig, DigAi.class)
|
||||||
.put(ApiType.DigMultiple, DigMultipleAi.class)
|
.put(ApiType.DigMultiple, DigMultipleAi.class)
|
||||||
.put(ApiType.DigUntil, DigUntilAi.class)
|
.put(ApiType.DigUntil, DigUntilAi.class)
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
import forge.ai.AiAbilityDecision;
|
||||||
|
import forge.ai.AiPlayDecision;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.ability.AbilityUtils;
|
import forge.game.ability.AbilityUtils;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
@@ -8,7 +10,6 @@ import forge.game.player.Player;
|
|||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.spellability.TargetRestrictions;
|
import forge.game.spellability.TargetRestrictions;
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
import forge.util.MyRandom;
|
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -16,78 +17,69 @@ import java.util.Map;
|
|||||||
public class ActivateAbilityAi extends SpellAbilityAi {
|
public class ActivateAbilityAi extends SpellAbilityAi {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
protected AiAbilityDecision checkApiLogic(Player ai, SpellAbility sa) {
|
||||||
// AI cannot use this properly until he can use SAs during Humans turn
|
|
||||||
|
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
final Player opp = ai.getStrongestOpponent();
|
final Player opp = ai.getStrongestOpponent();
|
||||||
|
|
||||||
List<Card> list = CardLists.getType(opp.getCardsIn(ZoneType.Battlefield), sa.getParamOrDefault("Type", "Card"));
|
List<Card> list = CardLists.getType(opp.getCardsIn(ZoneType.Battlefield), sa.getParamOrDefault("Type", "Card"));
|
||||||
if (list.isEmpty()) {
|
if (list.isEmpty()) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.MissingNeededCards);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!sa.usesTargeting()) {
|
if (!sa.usesTargeting()) {
|
||||||
final List<Player> defined = AbilityUtils.getDefinedPlayers(source, sa.getParam("Defined"), sa);
|
final List<Player> defined = AbilityUtils.getDefinedPlayers(source, sa.getParam("Defined"), sa);
|
||||||
|
|
||||||
if (!defined.contains(opp)) {
|
if (!defined.contains(opp)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.MissingNeededCards);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
if (sa.canTarget(opp)) {
|
if (sa.canTarget(opp)) {
|
||||||
sa.getTargets().add(opp);
|
sa.getTargets().add(opp);
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean randomReturn = MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
|
return super.checkApiLogic(ai, sa);
|
||||||
return randomReturn;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
protected AiAbilityDecision doTriggerNoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
final Player opp = ai.getStrongestOpponent();
|
final Player opp = ai.getStrongestOpponent();
|
||||||
|
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
|
|
||||||
if (null == tgt) {
|
if (null == tgt) {
|
||||||
if (mandatory) {
|
if (mandatory) {
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
} else {
|
} else {
|
||||||
final List<Player> defined = AbilityUtils.getDefinedPlayers(source, sa.getParam("Defined"), sa);
|
final List<Player> defined = AbilityUtils.getDefinedPlayers(source, sa.getParam("Defined"), sa);
|
||||||
|
if (defined.contains(opp)) {
|
||||||
return defined.contains(opp);
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
} else {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
sa.getTargets().add(opp);
|
sa.getTargets().add(opp);
|
||||||
}
|
}
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
public AiAbilityDecision chkDrawback(SpellAbility sa, Player ai) {
|
||||||
// AI cannot use this properly until he can use SAs during Humans turn
|
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
|
|
||||||
boolean randomReturn = true;
|
|
||||||
|
|
||||||
if (!sa.usesTargeting()) {
|
if (!sa.usesTargeting()) {
|
||||||
final List<Player> defined = AbilityUtils.getDefinedPlayers(source, sa.getParam("Defined"), sa);
|
final List<Player> defined = AbilityUtils.getDefinedPlayers(source, sa.getParam("Defined"), sa);
|
||||||
|
|
||||||
if (defined.contains(ai)) {
|
if (defined.contains(ai)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
sa.getTargets().add(ai.getWeakestOpponent());
|
sa.getTargets().add(ai.getWeakestOpponent());
|
||||||
}
|
}
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
return randomReturn;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
import forge.ai.AiAbilityDecision;
|
||||||
|
import forge.ai.AiPlayDecision;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
@@ -11,8 +13,8 @@ import forge.game.spellability.SpellAbility;
|
|||||||
public class AddPhaseAi extends SpellAbilityAi {
|
public class AddPhaseAi extends SpellAbilityAi {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
protected AiAbilityDecision canPlay(Player aiPlayer, SpellAbility sa) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,8 @@
|
|||||||
*/
|
*/
|
||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
import forge.ai.AiAbilityDecision;
|
||||||
|
import forge.ai.AiPlayDecision;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.ability.AbilityUtils;
|
import forge.game.ability.AbilityUtils;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
@@ -38,7 +40,7 @@ import java.util.List;
|
|||||||
public class AddTurnAi extends SpellAbilityAi {
|
public class AddTurnAi extends SpellAbilityAi {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
protected AiAbilityDecision doTriggerNoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
PlayerCollection targetableOpps = ai.getOpponents().filter(PlayerPredicates.isTargetableBy(sa));
|
PlayerCollection targetableOpps = ai.getOpponents().filter(PlayerPredicates.isTargetableBy(sa));
|
||||||
Player opp = targetableOpps.min(PlayerPredicates.compareByLife());
|
Player opp = targetableOpps.min(PlayerPredicates.compareByLife());
|
||||||
|
|
||||||
@@ -47,41 +49,41 @@ public class AddTurnAi extends SpellAbilityAi {
|
|||||||
if (sa.canTarget(ai) && (mandatory || !ai.getGame().getReplacementHandler().wouldExtraTurnBeSkipped(ai))) {
|
if (sa.canTarget(ai) && (mandatory || !ai.getGame().getReplacementHandler().wouldExtraTurnBeSkipped(ai))) {
|
||||||
sa.getTargets().add(ai);
|
sa.getTargets().add(ai);
|
||||||
} else if (mandatory) {
|
} else if (mandatory) {
|
||||||
for (final Player ally : ai.getAllies()) {
|
for (final Player ally : ai.getAllies()) {
|
||||||
if (sa.canTarget(ally)) {
|
if (sa.canTarget(ally)) {
|
||||||
sa.getTargets().add(ally);
|
sa.getTargets().add(ally);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!sa.getTargetRestrictions().isMinTargetsChosen(sa.getHostCard(), sa) && opp != null) {
|
if (!sa.getTargetRestrictions().isMinTargetsChosen(sa.getHostCard(), sa) && opp != null) {
|
||||||
sa.getTargets().add(opp);
|
sa.getTargets().add(opp);
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
final List<Player> tgtPlayers = AbilityUtils.getDefinedPlayers(sa.getHostCard(), 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 new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// TODO: improve ai for Sage of Hours
|
// TODO: improve ai for Sage of Hours
|
||||||
return StringUtils.isNumeric(sa.getParam("NumTurns"));
|
if (!StringUtils.isNumeric(sa.getParam("NumTurns"))) {
|
||||||
// not sure if the AI should be playing with cards that give the
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
// Human more turns.
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* (non-Javadoc)
|
/* (non-Javadoc)
|
||||||
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
|
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
protected AiAbilityDecision canPlay(Player aiPlayer, SpellAbility sa) {
|
||||||
return doTriggerAINoCost(aiPlayer, sa, false);
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,30 +1,23 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
import forge.ai.AiAbilityDecision;
|
||||||
|
import forge.ai.AiPlayDecision;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.card.CardLists;
|
import forge.game.card.CardLists;
|
||||||
import forge.game.card.CardPredicates;
|
import forge.game.card.CardPredicates;
|
||||||
import forge.game.phase.PhaseHandler;
|
|
||||||
import forge.game.phase.PhaseType;
|
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
|
|
||||||
public class AdvanceCrankAi extends SpellAbilityAi {
|
public class AdvanceCrankAi extends SpellAbilityAi {
|
||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
protected AiAbilityDecision canPlay(Player ai, SpellAbility sa) {
|
||||||
int nextSprocket = (ai.getCrankCounter() % 3) + 1;
|
int nextSprocket = (ai.getCrankCounter() % 3) + 1;
|
||||||
int crankCount = CardLists.count(ai.getCardsIn(ZoneType.Battlefield), CardPredicates.isContraptionOnSprocket(nextSprocket));
|
int crankCount = CardLists.count(ai.getCardsIn(ZoneType.Battlefield), CardPredicates.isContraptionOnSprocket(nextSprocket));
|
||||||
//Could evaluate whether we actually want to crank those, but this is probably fine for now.
|
if (crankCount < 2) {
|
||||||
if(crankCount < 2)
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
return false;
|
}
|
||||||
return super.canPlayAI(ai, sa);
|
return super.canPlay(ai, sa);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected boolean checkPhaseRestrictions(Player ai, SpellAbility sa, PhaseHandler ph, String logic) {
|
|
||||||
if(logic.equals("AtOppEOT"))
|
|
||||||
return ph.getNextTurn() == ai && ph.is(PhaseType.END_OF_TURN);
|
|
||||||
|
|
||||||
return super.checkPhaseRestrictions(ai, sa, ph, logic);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
import forge.ai.AiAbilityDecision;
|
||||||
|
import forge.ai.AiPlayDecision;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.ability.AbilityUtils;
|
import forge.game.ability.AbilityUtils;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
@@ -16,7 +18,7 @@ import java.util.Map;
|
|||||||
public class AlterAttributeAi extends SpellAbilityAi {
|
public class AlterAttributeAi extends SpellAbilityAi {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean checkApiLogic(Player aiPlayer, SpellAbility sa) {
|
protected AiAbilityDecision checkApiLogic(Player aiPlayer, SpellAbility sa) {
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
boolean activate = Boolean.parseBoolean(sa.getParamOrDefault("Activate", "true"));
|
boolean activate = Boolean.parseBoolean(sa.getParamOrDefault("Activate", "true"));
|
||||||
String[] attributes = sa.getParam("Attributes").split(",");
|
String[] attributes = sa.getParam("Attributes").split(",");
|
||||||
@@ -24,7 +26,7 @@ public class AlterAttributeAi extends SpellAbilityAi {
|
|||||||
if (sa.usesTargeting()) {
|
if (sa.usesTargeting()) {
|
||||||
// TODO add targeting logic
|
// TODO add targeting logic
|
||||||
// needed for Suspected
|
// needed for Suspected
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
|
|
||||||
final List<Card> defined = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa);
|
final List<Card> defined = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa);
|
||||||
@@ -36,7 +38,7 @@ public class AlterAttributeAi extends SpellAbilityAi {
|
|||||||
case "Solved":
|
case "Solved":
|
||||||
// there is currently no effect that would un-solve something
|
// there is currently no effect that would un-solve something
|
||||||
if (!c.isSolved() && activate) {
|
if (!c.isSolved() && activate) {
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "Suspect":
|
case "Suspect":
|
||||||
@@ -44,21 +46,21 @@ public class AlterAttributeAi extends SpellAbilityAi {
|
|||||||
// is Suspected good or bad?
|
// is Suspected good or bad?
|
||||||
// currently Suspected is better
|
// currently Suspected is better
|
||||||
if (!activate) {
|
if (!activate) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
|
||||||
case "Saddle":
|
case "Saddle":
|
||||||
case "Saddled":
|
case "Saddled":
|
||||||
// AI should not try to Saddle again?
|
// AI should not try to Saddle again?
|
||||||
if (c.isSaddled()) {
|
if (c.isSaddled()) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
import forge.ai.AiAbilityDecision;
|
||||||
|
import forge.ai.AiPlayDecision;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.player.PlayerActionConfirmMode;
|
import forge.game.player.PlayerActionConfirmMode;
|
||||||
@@ -13,8 +14,8 @@ public class AlwaysPlayAi extends SpellAbilityAi {
|
|||||||
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
|
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
protected AiAbilityDecision canPlay(Player aiPlayer, SpellAbility sa) {
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ package forge.ai.ability;
|
|||||||
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 com.google.common.collect.Sets;
|
import com.google.common.collect.Sets;
|
||||||
|
import forge.ai.AiAbilityDecision;
|
||||||
|
import forge.ai.AiPlayDecision;
|
||||||
import forge.ai.ComputerUtilCard;
|
import forge.ai.ComputerUtilCard;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.Game;
|
import forge.game.Game;
|
||||||
@@ -19,24 +21,28 @@ import java.util.Map;
|
|||||||
|
|
||||||
public class AmassAi extends SpellAbilityAi {
|
public class AmassAi extends SpellAbilityAi {
|
||||||
@Override
|
@Override
|
||||||
protected boolean checkApiLogic(Player ai, final SpellAbility sa) {
|
protected AiAbilityDecision checkApiLogic(Player ai, final SpellAbility sa) {
|
||||||
CardCollection aiArmies = CardLists.getType(ai.getCardsIn(ZoneType.Battlefield), "Army");
|
CardCollection aiArmies = CardLists.getType(ai.getCardsIn(ZoneType.Battlefield), "Army");
|
||||||
Card host = sa.getHostCard();
|
Card host = sa.getHostCard();
|
||||||
final Game game = ai.getGame();
|
final Game game = ai.getGame();
|
||||||
|
|
||||||
if (!aiArmies.isEmpty()) {
|
if (!aiArmies.isEmpty()) {
|
||||||
return aiArmies.anyMatch(CardPredicates.canReceiveCounters(CounterEnumType.P1P1));
|
if (aiArmies.anyMatch(CardPredicates.canReceiveCounters(CounterEnumType.P1P1))) {
|
||||||
|
// If AI has an Army that can receive counters, play the ability
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
} else {
|
||||||
|
// AI has Armies but none can receive counters, so don't play
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.DoesntImpactGame);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
final String type = sa.getParam("Type");
|
final String type = sa.getParam("Type");
|
||||||
StringBuilder sb = new StringBuilder("b_0_0_");
|
final String tokenScript = "b_0_0_" + sa.getOriginalParam("Type").toLowerCase() + "_army";
|
||||||
sb.append(sa.getOriginalParam("Type").toLowerCase()).append("_army");
|
|
||||||
final String tokenScript = sb.toString();
|
|
||||||
final int amount = AbilityUtils.calculateAmount(host, sa.getParamOrDefault("Num", "1"), sa);
|
final int amount = AbilityUtils.calculateAmount(host, sa.getParamOrDefault("Num", "1"), sa);
|
||||||
|
|
||||||
Card token = TokenInfo.getProtoType(tokenScript, sa, ai, false);
|
Card token = TokenInfo.getProtoType(tokenScript, sa, ai, false);
|
||||||
|
|
||||||
if (token == null) {
|
if (token == null) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
token.setController(ai, 0);
|
token.setController(ai, 0);
|
||||||
@@ -63,7 +69,11 @@ public class AmassAi extends SpellAbilityAi {
|
|||||||
//reset static abilities
|
//reset static abilities
|
||||||
game.getAction().checkStaticAbilities(false);
|
game.getAction().checkStaticAbilities(false);
|
||||||
|
|
||||||
return result;
|
if (result) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
} else {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -82,8 +92,12 @@ public class AmassAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
protected AiAbilityDecision doTriggerNoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
return mandatory || checkApiLogic(ai, sa);
|
if (mandatory) {
|
||||||
|
return new AiAbilityDecision(50, AiPlayDecision.MandatoryPlay);
|
||||||
|
}
|
||||||
|
|
||||||
|
return checkApiLogic(ai, sa);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -142,21 +142,20 @@ public class AnimateAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean checkApiLogic(Player aiPlayer, SpellAbility sa) {
|
protected AiAbilityDecision checkApiLogic(Player aiPlayer, SpellAbility sa) {
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
final Game game = aiPlayer.getGame();
|
final Game game = aiPlayer.getGame();
|
||||||
final PhaseHandler ph = game.getPhaseHandler();
|
final PhaseHandler ph = game.getPhaseHandler();
|
||||||
if (!sa.metConditions() && sa.getSubAbility() == null) {
|
|
||||||
return false; // what is this for?
|
|
||||||
}
|
|
||||||
if (!game.getStack().isEmpty() && game.getStack().peekAbility().getApi() == ApiType.Sacrifice) {
|
if (!game.getStack().isEmpty() && game.getStack().peekAbility().getApi() == ApiType.Sacrifice) {
|
||||||
|
// Should I animate a card before i have to sacrifice something better?
|
||||||
if (!isAnimatedThisTurn(aiPlayer, source)) {
|
if (!isAnimatedThisTurn(aiPlayer, source)) {
|
||||||
rememberAnimatedThisTurn(aiPlayer, source);
|
rememberAnimatedThisTurn(aiPlayer, source);
|
||||||
return true; // interrupt sacrifice
|
return new AiAbilityDecision(100, AiPlayDecision.ResponseToStackResolve);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!ComputerUtilCost.checkTapTypeCost(aiPlayer, sa.getPayCosts(), source, sa, new CardCollection())) {
|
if (!ComputerUtilCost.checkTapTypeCost(aiPlayer, sa.getPayCosts(), source, sa, new CardCollection())) {
|
||||||
return false; // prevent crewing with equal or better creatures
|
return new AiAbilityDecision(0, AiPlayDecision.CostNotAcceptable);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sa.costHasManaX() && sa.getSVar("X").equals("Count$xPaid")) {
|
if (sa.costHasManaX() && sa.getSVar("X").equals("Count$xPaid")) {
|
||||||
@@ -202,16 +201,16 @@ public class AnimateAi extends SpellAbilityAi {
|
|||||||
if (!isSorcerySpeed(sa, aiPlayer) && !"Permanent".equals(sa.getParam("Duration"))) {
|
if (!isSorcerySpeed(sa, aiPlayer) && !"Permanent".equals(sa.getParam("Duration"))) {
|
||||||
if (sa.isCrew() && c.isCreature()) {
|
if (sa.isCrew() && c.isCreature()) {
|
||||||
// Do not try to crew a vehicle which is already a creature
|
// Do not try to crew a vehicle which is already a creature
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
Card animatedCopy = becomeAnimated(c, sa);
|
Card animatedCopy = becomeAnimated(c, sa);
|
||||||
if (ph.isPlayerTurn(aiPlayer)
|
if (ph.isPlayerTurn(aiPlayer)
|
||||||
&& !ComputerUtilCard.doesSpecifiedCreatureAttackAI(aiPlayer, animatedCopy)) {
|
&& !ComputerUtilCard.doesSpecifiedCreatureAttackAI(aiPlayer, animatedCopy)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.DoesntImpactCombat);
|
||||||
}
|
}
|
||||||
if (ph.getPlayerTurn().isOpponentOf(aiPlayer)
|
if (ph.getPlayerTurn().isOpponentOf(aiPlayer)
|
||||||
&& !ComputerUtilCard.doesSpecifiedCreatureBlock(aiPlayer, animatedCopy)) {
|
&& !ComputerUtilCard.doesSpecifiedCreatureBlock(aiPlayer, animatedCopy)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.DoesntImpactCombat);
|
||||||
}
|
}
|
||||||
// also check if maybe there are static effects applied to the animated copy that would matter
|
// also check if maybe there are static effects applied to the animated copy that would matter
|
||||||
// (e.g. Myth Realized)
|
// (e.g. Myth Realized)
|
||||||
@@ -227,8 +226,9 @@ public class AnimateAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
if (bFlag) {
|
if (bFlag) {
|
||||||
rememberAnimatedThisTurn(aiPlayer, sa.getHostCard());
|
rememberAnimatedThisTurn(aiPlayer, sa.getHostCard());
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
return bFlag; // All of the defined stuff is animated, not very useful
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
} else {
|
} else {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
return animateTgtAI(sa);
|
return animateTgtAI(sa);
|
||||||
@@ -237,35 +237,38 @@ public class AnimateAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
|
public AiAbilityDecision chkDrawback(SpellAbility sa, Player aiPlayer) {
|
||||||
if (sa.usesTargeting()) {
|
if (sa.usesTargeting()) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
return animateTgtAI(sa);
|
return animateTgtAI(sa);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
protected AiAbilityDecision doTriggerNoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
||||||
|
AiAbilityDecision decision;
|
||||||
if (sa.usesTargeting()) {
|
if (sa.usesTargeting()) {
|
||||||
if(animateTgtAI(sa))
|
decision = animateTgtAI(sa);
|
||||||
return true;
|
if (decision.willingToPlay()) {
|
||||||
else if (!mandatory)
|
return decision;
|
||||||
return false;
|
} else if (!mandatory) {
|
||||||
|
return decision;
|
||||||
|
}
|
||||||
else {
|
else {
|
||||||
// fallback if animate is mandatory
|
// fallback if animate is mandatory
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
List<Card> list = CardUtil.getValidCardsToTarget(sa);
|
List<Card> list = CardUtil.getValidCardsToTarget(sa);
|
||||||
if (list.isEmpty()) {
|
if (list.isEmpty()) {
|
||||||
return false;
|
return decision;
|
||||||
}
|
}
|
||||||
Card toAnimate = ComputerUtilCard.getWorstAI(list);
|
Card toAnimate = ComputerUtilCard.getWorstAI(list);
|
||||||
rememberAnimatedThisTurn(aiPlayer, toAnimate);
|
rememberAnimatedThisTurn(aiPlayer, toAnimate);
|
||||||
sa.getTargets().add(toAnimate);
|
sa.getTargets().add(toAnimate);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -273,7 +276,7 @@ public class AnimateAi extends SpellAbilityAi {
|
|||||||
return player.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2);
|
return player.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean animateTgtAI(final SpellAbility sa) {
|
private AiAbilityDecision animateTgtAI(final SpellAbility sa) {
|
||||||
final Player ai = sa.getActivatingPlayer();
|
final Player ai = sa.getActivatingPlayer();
|
||||||
final PhaseHandler ph = ai.getGame().getPhaseHandler();
|
final PhaseHandler ph = ai.getGame().getPhaseHandler();
|
||||||
final String logic = sa.getParamOrDefault("AILogic", "");
|
final String logic = sa.getParamOrDefault("AILogic", "");
|
||||||
@@ -295,7 +298,7 @@ public class AnimateAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
// list is empty, no possible targets
|
// list is empty, no possible targets
|
||||||
if (list.isEmpty() && !alwaysActivatePWAbility) {
|
if (list.isEmpty() && !alwaysActivatePWAbility) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
// something is used for animate into creature
|
// something is used for animate into creature
|
||||||
@@ -362,7 +365,7 @@ public class AnimateAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
// data is empty, no good targets
|
// data is empty, no good targets
|
||||||
if (data.isEmpty() && !alwaysActivatePWAbility) {
|
if (data.isEmpty() && !alwaysActivatePWAbility) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
|
|
||||||
// get the best creature to be animated
|
// get the best creature to be animated
|
||||||
@@ -385,13 +388,13 @@ public class AnimateAi extends SpellAbilityAi {
|
|||||||
holdAnimatedTillMain2(ai, worst);
|
holdAnimatedTillMain2(ai, worst);
|
||||||
if (!ComputerUtilMana.canPayManaCost(sa, ai, 0, sa.isTrigger())) {
|
if (!ComputerUtilMana.canPayManaCost(sa, ai, 0, sa.isTrigger())) {
|
||||||
releaseHeldTillMain2(ai, worst);
|
releaseHeldTillMain2(ai, worst);
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantAfford);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
rememberAnimatedThisTurn(ai, worst);
|
rememberAnimatedThisTurn(ai, worst);
|
||||||
sa.getTargets().add(worst);
|
sa.getTargets().add(worst);
|
||||||
}
|
}
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (logic.equals("SetPT")) {
|
if (logic.equals("SetPT")) {
|
||||||
@@ -403,7 +406,7 @@ public class AnimateAi extends SpellAbilityAi {
|
|||||||
&& (buffed.getNetPower() - worst.getNetPower() >= 3 || !ComputerUtilCard.doesCreatureAttackAI(ai, worst))) {
|
&& (buffed.getNetPower() - worst.getNetPower() >= 3 || !ComputerUtilCard.doesCreatureAttackAI(ai, worst))) {
|
||||||
sa.getTargets().add(worst);
|
sa.getTargets().add(worst);
|
||||||
rememberAnimatedThisTurn(ai, worst);
|
rememberAnimatedThisTurn(ai, worst);
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -415,7 +418,7 @@ public class AnimateAi extends SpellAbilityAi {
|
|||||||
boolean isValuableAttacker = ph.is(PhaseType.MAIN1, ai) && ComputerUtilCard.doesSpecifiedCreatureAttackAI(ai, animated);
|
boolean isValuableAttacker = ph.is(PhaseType.MAIN1, ai) && ComputerUtilCard.doesSpecifiedCreatureAttackAI(ai, animated);
|
||||||
boolean isValuableBlocker = combat != null && combat.getDefendingPlayers().contains(ai) && ComputerUtilCard.doesSpecifiedCreatureBlock(ai, animated);
|
boolean isValuableBlocker = combat != null && combat.getDefendingPlayers().contains(ai) && ComputerUtilCard.doesSpecifiedCreatureBlock(ai, animated);
|
||||||
if (isValuableAttacker || isValuableBlocker)
|
if (isValuableAttacker || isValuableBlocker)
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -425,7 +428,7 @@ public class AnimateAi extends SpellAbilityAi {
|
|||||||
if(worst != null) {
|
if(worst != null) {
|
||||||
sa.getTargets().add(worst);
|
sa.getTargets().add(worst);
|
||||||
rememberAnimatedThisTurn(ai, worst);
|
rememberAnimatedThisTurn(ai, worst);
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -435,7 +438,7 @@ public class AnimateAi extends SpellAbilityAi {
|
|||||||
if(best != null) {
|
if(best != null) {
|
||||||
sa.getTargets().add(best);
|
sa.getTargets().add(best);
|
||||||
rememberAnimatedThisTurn(ai, best);
|
rememberAnimatedThisTurn(ai, best);
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -443,7 +446,7 @@ public class AnimateAi extends SpellAbilityAi {
|
|||||||
// two are the only things
|
// two are the only things
|
||||||
// that animate a target. Those can just use AI:RemoveDeck:All until
|
// that animate a target. Those can just use AI:RemoveDeck:All until
|
||||||
// this can do a reasonably good job of picking a good target
|
// this can do a reasonably good job of picking a good target
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Card becomeAnimated(final Card card, final SpellAbility sa) {
|
public static Card becomeAnimated(final Card card, final SpellAbility sa) {
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
import forge.ai.AiAbilityDecision;
|
||||||
|
import forge.ai.AiPlayDecision;
|
||||||
import forge.ai.ComputerUtilCard;
|
import forge.ai.ComputerUtilCard;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
@@ -9,24 +11,30 @@ import forge.game.spellability.SpellAbility;
|
|||||||
public class AnimateAllAi extends SpellAbilityAi {
|
public class AnimateAllAi extends SpellAbilityAi {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
protected AiAbilityDecision canPlay(Player aiPlayer, SpellAbility sa) {
|
||||||
String logic = sa.getParamOrDefault("AILogic", "");
|
String logic = sa.getParamOrDefault("AILogic", "");
|
||||||
|
|
||||||
if ("CreatureAdvantage".equals(logic) && !aiPlayer.getCreaturesInPlay().isEmpty()) {
|
if ("CreatureAdvantage".equals(logic) && !aiPlayer.getCreaturesInPlay().isEmpty()) {
|
||||||
// TODO: improve this or implement a better logic for abilities like Oko, the Trickster ultimate
|
// TODO: improve this or implement a better logic for abilities like Oko, the Trickster ultimate
|
||||||
for (Card c : aiPlayer.getCreaturesInPlay()) {
|
for (Card c : aiPlayer.getCreaturesInPlay()) {
|
||||||
if (ComputerUtilCard.doesCreatureAttackAI(aiPlayer, c)) {
|
if (ComputerUtilCard.doesCreatureAttackAI(aiPlayer, c)) {
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return "Always".equals(logic);
|
if ("Always".equals(logic)) {
|
||||||
} // end animateAllCanPlayAI()
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
}
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
protected AiAbilityDecision doTriggerNoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
||||||
return mandatory || canPlayAI(aiPlayer, sa);
|
if (mandatory) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
}
|
||||||
|
return canPlay(aiPlayer, sa);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
import forge.ai.AiAbilityDecision;
|
||||||
|
import forge.ai.AiPlayDecision;
|
||||||
import forge.ai.ComputerUtilCost;
|
import forge.ai.ComputerUtilCost;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.GameEntity;
|
import forge.game.GameEntity;
|
||||||
import forge.game.ability.ApiType;
|
import forge.game.ability.ApiType;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
import forge.game.card.CardCollectionView;
|
import forge.game.card.CardCollectionView;
|
||||||
import forge.game.phase.PhaseHandler;
|
|
||||||
import forge.game.phase.PhaseType;
|
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
@@ -16,30 +16,32 @@ import java.util.List;
|
|||||||
|
|
||||||
public class AssembleContraptionAi extends SpellAbilityAi {
|
public class AssembleContraptionAi extends SpellAbilityAi {
|
||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
protected AiAbilityDecision canPlay(Player ai, SpellAbility sa) {
|
||||||
//Pulls double duty as the OpenAttraction API. Same logic; usually good to do as long as we have the appropriate cards.
|
|
||||||
CardCollectionView deck = getDeck(ai, sa);
|
CardCollectionView deck = getDeck(ai, sa);
|
||||||
|
|
||||||
if(deck.isEmpty())
|
if(deck.isEmpty())
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
|
|
||||||
if(!super.canPlayAI(ai, sa))
|
AiAbilityDecision superDecision = super.canPlay(ai, sa);
|
||||||
return false;
|
if (!superDecision.willingToPlay())
|
||||||
|
return superDecision;
|
||||||
|
|
||||||
if ("X".equals(sa.getParam("Amount")) && sa.getSVar("X").equals("Count$xPaid")) {
|
if ("X".equals(sa.getParam("Amount")) && sa.getSVar("X").equals("Count$xPaid")) {
|
||||||
int xPay = ComputerUtilCost.getMaxXValue(sa, ai, sa.isTrigger());
|
int xPay = ComputerUtilCost.getMaxXValue(sa, ai, sa.isTrigger());
|
||||||
xPay = Math.max(xPay, deck.size());
|
xPay = Math.max(xPay, deck.size());
|
||||||
if (xPay == 0) {
|
if (xPay == 0) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantAffordX);
|
||||||
}
|
}
|
||||||
sa.getRootAbility().setXManaCostPaid(xPay);
|
sa.getRootAbility().setXManaCostPaid(xPay);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(sa.hasParam("DefinedContraption") && sa.usesTargeting()) {
|
if(sa.hasParam("DefinedContraption") && sa.usesTargeting()) {
|
||||||
return getGoodReassembleTarget(ai, sa) != null;
|
if (getGoodReassembleTarget(ai, sa) == null) {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static CardCollectionView getDeck(Player ai, SpellAbility sa) {
|
private static CardCollectionView getDeck(Player ai, SpellAbility sa) {
|
||||||
@@ -48,11 +50,11 @@ public class AssembleContraptionAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean checkApiLogic(Player ai, SpellAbility sa) {
|
protected AiAbilityDecision checkApiLogic(Player ai, SpellAbility sa) {
|
||||||
if ("X".equals(sa.getParam("Amount")) && sa.getSVar("X").equals("Count$xPaid")) {
|
if ("X".equals(sa.getParam("Amount")) && sa.getSVar("X").equals("Count$xPaid")) {
|
||||||
int xPay = ComputerUtilCost.getMaxXValue(sa, ai, sa.isTrigger());
|
int xPay = ComputerUtilCost.getMaxXValue(sa, ai, sa.isTrigger());
|
||||||
if (xPay == 0) {
|
if (xPay == 0) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
sa.getRootAbility().setXManaCostPaid(xPay);
|
sa.getRootAbility().setXManaCostPaid(xPay);
|
||||||
}
|
}
|
||||||
@@ -62,7 +64,7 @@ public class AssembleContraptionAi extends SpellAbilityAi {
|
|||||||
if(target != null)
|
if(target != null)
|
||||||
sa.getTargets().add(target);
|
sa.getTargets().add(target);
|
||||||
else
|
else
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
return super.checkApiLogic(ai, sa);
|
return super.checkApiLogic(ai, sa);
|
||||||
@@ -84,26 +86,16 @@ public class AssembleContraptionAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean checkPhaseRestrictions(Player ai, SpellAbility sa, PhaseHandler ph, String logic) {
|
protected AiAbilityDecision doTriggerNoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
||||||
if(logic.equals("AtOppEOT"))
|
|
||||||
return ph.getNextTurn() == ai && ph.is(PhaseType.END_OF_TURN);
|
|
||||||
|
|
||||||
return super.checkPhaseRestrictions(ai, sa, ph);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
|
|
||||||
if(getDeck(aiPlayer, sa).isEmpty())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return super.chkAIDrawback(sa, aiPlayer);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
|
||||||
if(!mandatory && getDeck(aiPlayer, sa).isEmpty())
|
if(!mandatory && getDeck(aiPlayer, sa).isEmpty())
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
|
return super.doTriggerNoCost(aiPlayer, sa, mandatory);
|
||||||
|
}
|
||||||
|
|
||||||
return super.doTriggerAINoCost(aiPlayer, sa, mandatory);
|
@Override
|
||||||
|
public AiAbilityDecision chkDrawback(SpellAbility sa, Player aiPlayer) {
|
||||||
|
if(getDeck(aiPlayer, sa).isEmpty())
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
|
return super.chkDrawback(sa, aiPlayer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
|
import forge.ai.AiAbilityDecision;
|
||||||
|
import forge.ai.AiPlayDecision;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
@@ -10,11 +12,11 @@ import java.util.Map;
|
|||||||
|
|
||||||
public class AssignGroupAi extends SpellAbilityAi {
|
public class AssignGroupAi extends SpellAbilityAi {
|
||||||
|
|
||||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
@Override
|
||||||
|
protected AiAbilityDecision canPlay(Player ai, SpellAbility sa) {
|
||||||
// TODO: Currently this AI relies on the card-specific limiting hints (NeedsToPlay / NeedsToPlayVar),
|
// TODO: Currently this AI relies on the card-specific limiting hints (NeedsToPlay / NeedsToPlayVar),
|
||||||
// otherwise the AI considers the card playable.
|
// otherwise the AI considers the card playable.
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public SpellAbility chooseSingleSpellAbility(Player player, SpellAbility sa, List<SpellAbility> spells, Map<String, Object> params) {
|
public SpellAbility chooseSingleSpellAbility(Player player, SpellAbility sa, List<SpellAbility> spells, Map<String, Object> params) {
|
||||||
|
|||||||
@@ -45,24 +45,14 @@ public class AttachAi extends SpellAbilityAi {
|
|||||||
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
|
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
protected AiAbilityDecision checkApiLogic(Player ai, SpellAbility sa) {
|
||||||
final Cost abCost = sa.getPayCosts();
|
final Cost abCost = sa.getPayCosts();
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
|
|
||||||
// TODO: improve this so that the AI can use a flash aura buff as a means of killing opposing creatures
|
// TODO: improve this so that the AI can use a flash aura buff as a means of killing opposing creatures
|
||||||
// and gaining card advantage
|
// and gaining card advantage
|
||||||
if (source.hasKeyword("MayFlashSac") && !ai.canCastSorcery()) {
|
if (source.hasKeyword("MayFlashSac") && !ai.canCastSorcery()) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TimingRestrictions);
|
||||||
}
|
|
||||||
|
|
||||||
if (abCost != null) {
|
|
||||||
// AI currently disabled for these costs
|
|
||||||
if (!ComputerUtilCost.checkSacrificeCost(ai, abCost, source, sa)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, sa)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (source.isAura() && sa.isSpell() && !source.ignoreLegendRule() && ai.isCardInPlay(source.getName())) {
|
if (source.isAura() && sa.isSpell() && !source.ignoreLegendRule() && ai.isCardInPlay(source.getName())) {
|
||||||
@@ -70,20 +60,16 @@ public class AttachAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
// TODO: Add some extra checks for where the AI may want to cast a replacement aura
|
// TODO: Add some extra checks for where the AI may want to cast a replacement aura
|
||||||
// on another creature and keep it when the original enchanted creature is useless
|
// on another creature and keep it when the original enchanted creature is useless
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.WouldDestroyLegend);
|
||||||
}
|
|
||||||
|
|
||||||
// prevent run-away activations - first time will always return true
|
|
||||||
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Attach spells always have a target
|
// Attach spells always have a target
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
if (tgt != null) {
|
if (tgt != null) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
if (!attachPreference(sa, tgt, false)) {
|
AiAbilityDecision attachDecision = attachPreference(sa, tgt, false);
|
||||||
return false;
|
if (!attachDecision.willingToPlay()) {
|
||||||
|
return attachDecision;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,7 +80,7 @@ public class AttachAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
if ((source.hasKeyword(Keyword.FLASH) || (!ai.canCastSorcery() && sa.canCastTiming(ai)))
|
if ((source.hasKeyword(Keyword.FLASH) || (!ai.canCastSorcery() && sa.canCastTiming(ai)))
|
||||||
&& source.isAura() && advancedFlash && !doAdvancedFlashAuraLogic(ai, sa, sa.getTargetCard())) {
|
&& source.isAura() && advancedFlash && !doAdvancedFlashAuraLogic(ai, sa, sa.getTargetCard())) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (abCost.getTotalMana().countX() > 0 && sa.getSVar("X").equals("Count$xPaid")) {
|
if (abCost.getTotalMana().countX() > 0 && sa.getSVar("X").equals("Count$xPaid")) {
|
||||||
@@ -102,7 +88,7 @@ public class AttachAi extends SpellAbilityAi {
|
|||||||
final int xPay = ComputerUtilCost.getMaxXValue(sa, ai, sa.isTrigger());
|
final int xPay = ComputerUtilCost.getMaxXValue(sa, ai, sa.isTrigger());
|
||||||
|
|
||||||
if (xPay == 0) {
|
if (xPay == 0) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantAffordX);
|
||||||
}
|
}
|
||||||
|
|
||||||
sa.setXManaCostPaid(xPay);
|
sa.setXManaCostPaid(xPay);
|
||||||
@@ -112,10 +98,10 @@ public class AttachAi extends SpellAbilityAi {
|
|||||||
final SpellAbility effectExile = AbilityFactory.getAbility(source.getSVar("TrigExile"), source);
|
final SpellAbility effectExile = AbilityFactory.getAbility(source.getSVar("TrigExile"), source);
|
||||||
effectExile.setActivatingPlayer(ai);
|
effectExile.setActivatingPlayer(ai);
|
||||||
final List<Card> targets = CardUtil.getValidCardsToTarget(effectExile);
|
final List<Card> targets = CardUtil.getValidCardsToTarget(effectExile);
|
||||||
return !targets.isEmpty();
|
return !targets.isEmpty() ? new AiAbilityDecision(100, AiPlayDecision.WillPlay) : new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean doAdvancedFlashAuraLogic(Player ai, SpellAbility sa, Card attachTarget) {
|
private boolean doAdvancedFlashAuraLogic(Player ai, SpellAbility sa, Card attachTarget) {
|
||||||
@@ -955,9 +941,8 @@ public class AttachAi extends SpellAbilityAi {
|
|||||||
* @return true, if successful
|
* @return true, if successful
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(final Player ai, final SpellAbility sa, final boolean mandatory) {
|
protected AiAbilityDecision doTriggerNoCost(final Player ai, final SpellAbility sa, final boolean mandatory) {
|
||||||
final Card card = sa.getHostCard();
|
final Card card = sa.getHostCard();
|
||||||
// Check if there are any valid targets
|
|
||||||
List<GameObject> targets = new ArrayList<>();
|
List<GameObject> targets = new ArrayList<>();
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
if (tgt == null) {
|
if (tgt == null) {
|
||||||
@@ -969,23 +954,43 @@ public class AttachAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
if (!mandatory && card.isEquipment() && !targets.isEmpty()) {
|
if (!mandatory && card.isEquipment() && !targets.isEmpty()) {
|
||||||
Card newTarget = (Card) targets.get(0);
|
Card newTarget = (Card) targets.get(0);
|
||||||
//don't equip human creatures
|
|
||||||
if (newTarget.getController().isOpponentOf(ai)) {
|
if (newTarget.getController().isOpponentOf(ai)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
|
|
||||||
//don't equip a worse creature
|
|
||||||
if (card.isEquipping()) {
|
if (card.isEquipping()) {
|
||||||
Card oldTarget = card.getEquipping();
|
Card oldTarget = card.getEquipping();
|
||||||
if (ComputerUtilCard.evaluateCreature(oldTarget) > ComputerUtilCard.evaluateCreature(newTarget)) {
|
if (ComputerUtilCard.evaluateCreature(oldTarget) > ComputerUtilCard.evaluateCreature(newTarget)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
|
}
|
||||||
|
boolean stacking = !card.hasSVar("NonStackingAttachEffect") || !newTarget.isEquippedBy(card.getName());
|
||||||
|
if (!stacking) {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
// don't equip creatures that don't gain anything
|
|
||||||
return !card.hasSVar("NonStackingAttachEffect") || !newTarget.isEquippedBy(card.getName());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
@Override
|
||||||
|
public AiAbilityDecision chkDrawback(final SpellAbility sa, final Player ai) {
|
||||||
|
if (sa.isTrigger() && sa.usesTargeting()) {
|
||||||
|
CardCollection targetables = CardLists.getTargetableCards(ai.getCardsIn(ZoneType.Battlefield), sa);
|
||||||
|
CardCollection source = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("Object"), sa);
|
||||||
|
Card tgt = attachGeneralAI(ai, sa, targetables, !sa.getRootAbility().isOptionalTrigger(), source.getFirst(), null);
|
||||||
|
if (tgt != null) {
|
||||||
|
sa.resetTargets();
|
||||||
|
sa.getTargets().add(tgt);
|
||||||
|
}
|
||||||
|
if (sa.isTargetNumberValid()) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
} else {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
|
}
|
||||||
|
} else if ("Remembered".equals(sa.getParam("Defined")) && sa.getParent() != null
|
||||||
|
&& sa.getParent().getApi() == ApiType.Token && sa.getParent().hasParam("RememberTokens")) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
}
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean isAuraSpell(final SpellAbility sa) {
|
private static boolean isAuraSpell(final SpellAbility sa) {
|
||||||
@@ -1005,13 +1010,13 @@ public class AttachAi extends SpellAbilityAi {
|
|||||||
* the mandatory
|
* the mandatory
|
||||||
* @return true, if successful
|
* @return true, if successful
|
||||||
*/
|
*/
|
||||||
private static boolean attachPreference(final SpellAbility sa, final TargetRestrictions tgt, final boolean mandatory) {
|
private static AiAbilityDecision attachPreference(final SpellAbility sa, final TargetRestrictions tgt, final boolean mandatory) {
|
||||||
GameObject o;
|
GameObject o;
|
||||||
boolean spellCanTargetPlayer = false;
|
boolean spellCanTargetPlayer = false;
|
||||||
if (isAuraSpell(sa)) {
|
if (isAuraSpell(sa)) {
|
||||||
Card source = sa.getHostCard();
|
Card source = sa.getHostCard();
|
||||||
if (!source.hasKeyword(Keyword.ENCHANT)) {
|
if (!source.hasKeyword(Keyword.ENCHANT)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
for (KeywordInterface ki : source.getKeywords(Keyword.ENCHANT)) {
|
for (KeywordInterface ki : source.getKeywords(Keyword.ENCHANT)) {
|
||||||
String ko = ki.getOriginal();
|
String ko = ki.getOriginal();
|
||||||
@@ -1036,11 +1041,11 @@ public class AttachAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (o == null) {
|
if (o == null) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
|
|
||||||
sa.getTargets().add(o);
|
sa.getTargets().add(o);
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1692,25 +1697,6 @@ public class AttachAi extends SpellAbilityAi {
|
|||||||
return chosen;
|
return chosen;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean chkAIDrawback(final SpellAbility sa, final Player ai) {
|
|
||||||
// TODO for targeting optional Halvar trigger, needs to be coordinated with PumpAi to make it playable
|
|
||||||
if (sa.isTrigger() && sa.usesTargeting()) {
|
|
||||||
CardCollection targetables = CardLists.getTargetableCards(ai.getCardsIn(ZoneType.Battlefield), sa);
|
|
||||||
CardCollection source = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("Object"), sa);
|
|
||||||
Card tgt = attachGeneralAI(ai, sa, targetables, !sa.getRootAbility().isOptionalTrigger(), source.getFirst(), null);
|
|
||||||
if (tgt != null) {
|
|
||||||
sa.resetTargets();
|
|
||||||
sa.getTargets().add(tgt);
|
|
||||||
}
|
|
||||||
return sa.isTargetNumberValid();
|
|
||||||
} else if ("Remembered".equals(sa.getParam("Defined")) && sa.getParent() != null
|
|
||||||
&& sa.getParent().getApi() == ApiType.Token && sa.getParent().hasParam("RememberTokens")) {
|
|
||||||
// Living Weapon or similar
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message, Map<String, Object> params) {
|
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message, Map<String, Object> params) {
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
import forge.ai.AiAbilityDecision;
|
||||||
|
import forge.ai.AiPlayDecision;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.card.CardCollectionView;
|
import forge.game.card.CardCollectionView;
|
||||||
import forge.game.card.CardLists;
|
import forge.game.card.CardLists;
|
||||||
@@ -11,7 +13,7 @@ import forge.util.MyRandom;
|
|||||||
|
|
||||||
public class BalanceAi extends SpellAbilityAi {
|
public class BalanceAi extends SpellAbilityAi {
|
||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
protected AiAbilityDecision canPlay(Player aiPlayer, SpellAbility sa) {
|
||||||
String logic = sa.getParam("AILogic");
|
String logic = sa.getParam("AILogic");
|
||||||
int diff = 0;
|
int diff = 0;
|
||||||
Player opp = aiPlayer.getWeakestOpponent();
|
Player opp = aiPlayer.getWeakestOpponent();
|
||||||
@@ -37,7 +39,7 @@ public class BalanceAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
if (diff < 0) {
|
if (diff < 0) {
|
||||||
// Don't sacrifice permanents even if opponent has a ton of cards in hand
|
// Don't sacrifice permanents even if opponent has a ton of cards in hand
|
||||||
return false;
|
return new AiAbilityDecision(0, forge.ai.AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
final CardCollectionView humHand = opp.getCardsIn(ZoneType.Hand);
|
final CardCollectionView humHand = opp.getCardsIn(ZoneType.Hand);
|
||||||
@@ -45,6 +47,7 @@ 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 && MyRandom.getRandom().nextInt(100) < diff*10;
|
boolean willPlay = diff > 2 && MyRandom.getRandom().nextInt(100) < diff*10;
|
||||||
|
return new AiAbilityDecision(willPlay ? 100 : 0, willPlay ? forge.ai.AiPlayDecision.WillPlay : AiPlayDecision.StopRunawayActivations);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
import forge.ai.AiAbilityDecision;
|
||||||
|
import forge.ai.AiPlayDecision;
|
||||||
import forge.ai.ComputerUtilCard;
|
import forge.ai.ComputerUtilCard;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.Game;
|
import forge.game.Game;
|
||||||
@@ -16,55 +17,51 @@ import forge.game.zone.ZoneType;
|
|||||||
|
|
||||||
public class BecomesBlockedAi extends SpellAbilityAi {
|
public class BecomesBlockedAi extends SpellAbilityAi {
|
||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
protected AiAbilityDecision canPlay(Player aiPlayer, SpellAbility sa) {
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
final Game game = aiPlayer.getGame();
|
final Game game = aiPlayer.getGame();
|
||||||
|
|
||||||
if (!game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)
|
if (!game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)
|
||||||
|| !game.getPhaseHandler().getPlayerTurn().isOpponentOf(aiPlayer)) {
|
|| !game.getPhaseHandler().getPlayerTurn().isOpponentOf(aiPlayer)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tgt != null) {
|
if (tgt != null) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
CardCollection list = CardLists.filterControlledBy(game.getCardsIn(ZoneType.Battlefield), aiPlayer.getOpponents());
|
CardCollection list = CardLists.filterControlledBy(game.getCardsIn(ZoneType.Battlefield), aiPlayer.getOpponents());
|
||||||
list = CardLists.getTargetableCards(list, sa);
|
list = CardLists.getTargetableCards(list, sa);
|
||||||
list = CardLists.getNotKeyword(list, Keyword.TRAMPLE);
|
list = CardLists.getNotKeyword(list, Keyword.TRAMPLE);
|
||||||
|
|
||||||
while (sa.canAddMoreTarget()) {
|
while (sa.canAddMoreTarget()) {
|
||||||
Card choice = null;
|
Card choice = null;
|
||||||
|
|
||||||
if (list.isEmpty()) {
|
if (list.isEmpty()) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
choice = ComputerUtilCard.getBestCreatureAI(list);
|
choice = ComputerUtilCard.getBestCreatureAI(list);
|
||||||
|
|
||||||
if (choice == null) { // can't find anything left
|
if (choice == null) { // can't find anything left
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
list.remove(choice);
|
list.remove(choice);
|
||||||
sa.getTargets().add(choice);
|
sa.getTargets().add(choice);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
|
public AiAbilityDecision chkDrawback(SpellAbility sa, Player aiPlayer) {
|
||||||
// TODO - implement AI
|
// TODO - implement AI
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
protected AiAbilityDecision doTriggerNoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
||||||
boolean chance;
|
|
||||||
|
|
||||||
// TODO - implement AI
|
// TODO - implement AI
|
||||||
chance = false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
|
|
||||||
return chance;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
import forge.ai.AiAbilityDecision;
|
||||||
import forge.ai.AiAttackController;
|
import forge.ai.AiAttackController;
|
||||||
|
import forge.ai.AiPlayDecision;
|
||||||
import forge.ai.ComputerUtilCard;
|
import forge.ai.ComputerUtilCard;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.Game;
|
import forge.game.Game;
|
||||||
@@ -10,14 +12,13 @@ import forge.game.player.Player;
|
|||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.spellability.TargetRestrictions;
|
import forge.game.spellability.TargetRestrictions;
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
import forge.util.MyRandom;
|
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class BidLifeAi extends SpellAbilityAi {
|
public class BidLifeAi extends SpellAbilityAi {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
protected AiAbilityDecision checkApiLogic(Player aiPlayer, SpellAbility sa) {
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
final Game game = source.getGame();
|
final Game game = source.getGame();
|
||||||
TargetRestrictions tgt = sa.getTargetRestrictions();
|
TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
@@ -26,31 +27,31 @@ public class BidLifeAi extends SpellAbilityAi {
|
|||||||
if (tgt.canTgtCreature()) {
|
if (tgt.canTgtCreature()) {
|
||||||
List<Card> list = CardLists.getTargetableCards(AiAttackController.choosePreferredDefenderPlayer(aiPlayer).getCardsIn(ZoneType.Battlefield), sa);
|
List<Card> list = CardLists.getTargetableCards(AiAttackController.choosePreferredDefenderPlayer(aiPlayer).getCardsIn(ZoneType.Battlefield), sa);
|
||||||
if (list.isEmpty()) {
|
if (list.isEmpty()) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
Card c = ComputerUtilCard.getBestCreatureAI(list);
|
Card c = ComputerUtilCard.getBestCreatureAI(list);
|
||||||
if (sa.canTarget(c)) {
|
if (sa.canTarget(c)) {
|
||||||
sa.getTargets().add(c);
|
sa.getTargets().add(c);
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
} else if (tgt.getZone().contains(ZoneType.Stack)) {
|
} else if (tgt.getZone().contains(ZoneType.Stack)) {
|
||||||
if (game.getStack().isEmpty()) {
|
if (game.getStack().isEmpty()) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
final SpellAbility topSA = game.getStack().peekAbility();
|
final SpellAbility topSA = game.getStack().peekAbility();
|
||||||
if (!topSA.isCounterableBy(sa) || aiPlayer.equals(topSA.getActivatingPlayer())) {
|
if (!topSA.isCounterableBy(sa) || aiPlayer.equals(topSA.getActivatingPlayer())) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
if (sa.canTargetSpellAbility(topSA)) {
|
if (sa.canTargetSpellAbility(topSA)) {
|
||||||
sa.getTargets().add(topSA);
|
sa.getTargets().add(topSA);
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
boolean chance = MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
|
|
||||||
return chance;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,8 @@
|
|||||||
*/
|
*/
|
||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
import forge.ai.AiAbilityDecision;
|
||||||
|
import forge.ai.AiPlayDecision;
|
||||||
import forge.ai.ComputerUtilCard;
|
import forge.ai.ComputerUtilCard;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
@@ -46,9 +48,9 @@ public final class BondAi extends SpellAbilityAi {
|
|||||||
* @return a boolean.
|
* @return a boolean.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
protected AiAbilityDecision canPlay(Player aiPlayer, SpellAbility sa) {
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
} // end bondCanPlayAI()
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer, Map<String, Object> params) {
|
protected Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer, Map<String, Object> params) {
|
||||||
@@ -56,7 +58,7 @@ public final class BondAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(final Player aiPlayer, final SpellAbility sa, final boolean mandatory) {
|
protected AiAbilityDecision doTriggerNoCost(final Player aiPlayer, final SpellAbility sa, final boolean mandatory) {
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
|
||||||
|
import forge.ai.AiAbilityDecision;
|
||||||
|
import forge.ai.AiPlayDecision;
|
||||||
import forge.ai.ComputerUtilCard;
|
import forge.ai.ComputerUtilCard;
|
||||||
import forge.ai.SpecialAiLogic;
|
import forge.ai.SpecialAiLogic;
|
||||||
import forge.ai.SpecialCardAi;
|
import forge.ai.SpecialCardAi;
|
||||||
@@ -21,16 +23,18 @@ public class BranchAi extends SpellAbilityAi {
|
|||||||
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
|
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
protected AiAbilityDecision canPlay(Player aiPlayer, SpellAbility sa) {
|
||||||
final String aiLogic = sa.getParamOrDefault("AILogic", "");
|
final String aiLogic = sa.getParamOrDefault("AILogic", "");
|
||||||
if ("GrislySigil".equals(aiLogic)) {
|
if ("GrislySigil".equals(aiLogic)) {
|
||||||
return SpecialCardAi.GrislySigil.consider(aiPlayer, sa);
|
boolean result = SpecialCardAi.GrislySigil.consider(aiPlayer, sa);
|
||||||
|
return new AiAbilityDecision(result ? 100 : 0, result ? AiPlayDecision.WillPlay : AiPlayDecision.CantPlayAi);
|
||||||
} else if ("BranchCounter".equals(aiLogic)) {
|
} else if ("BranchCounter".equals(aiLogic)) {
|
||||||
return SpecialAiLogic.doBranchCounterspellLogic(aiPlayer, sa); // Bring the Ending, Anticognition (hacky implementation)
|
boolean result = SpecialAiLogic.doBranchCounterspellLogic(aiPlayer, sa);
|
||||||
|
return new AiAbilityDecision(result ? 100 : 0, result ? AiPlayDecision.WillPlay : AiPlayDecision.CantPlayAi);
|
||||||
} else if ("TgtAttacker".equals(aiLogic)) {
|
} else if ("TgtAttacker".equals(aiLogic)) {
|
||||||
final Combat combat = aiPlayer.getGame().getCombat();
|
final Combat combat = aiPlayer.getGame().getCombat();
|
||||||
if (combat == null || combat.getAttackingPlayer() != aiPlayer) {
|
if (combat == null || combat.getAttackingPlayer() != aiPlayer) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
final CardCollection attackers = combat.getAttackers();
|
final CardCollection attackers = combat.getAttackers();
|
||||||
@@ -45,16 +49,20 @@ public class BranchAi extends SpellAbilityAi {
|
|||||||
sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(attackers));
|
sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(attackers));
|
||||||
}
|
}
|
||||||
|
|
||||||
return sa.isTargetNumberValid();
|
return new AiAbilityDecision(sa.isTargetNumberValid() ? 100 : 0, sa.isTargetNumberValid() ? AiPlayDecision.WillPlay : AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: expand for other cases where the AI is needed to make a decision on a branch
|
// TODO: expand for other cases where the AI is needed to make a decision on a branch
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
protected AiAbilityDecision doTriggerNoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
||||||
return canPlayAI(aiPlayer, sa) || mandatory;
|
AiAbilityDecision decision = canPlay(aiPlayer, sa);
|
||||||
|
if (decision.willingToPlay() || mandatory) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
}
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
|
||||||
|
import forge.ai.AiAbilityDecision;
|
||||||
|
import forge.ai.AiPlayDecision;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
@@ -10,15 +12,15 @@ public class CannotPlayAi extends SpellAbilityAi {
|
|||||||
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
|
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
protected AiAbilityDecision canPlay(Player aiPlayer, SpellAbility sa) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* (non-Javadoc)
|
/* (non-Javadoc)
|
||||||
* @see forge.card.abilityfactory.SpellAiLogic#chkAIDrawback(java.util.Map, forge.card.spellability.SpellAbility, forge.game.player.Player)
|
* @see forge.card.abilityfactory.SpellAiLogic#chkAIDrawback(java.util.Map, forge.card.spellability.SpellAbility, forge.game.player.Player)
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
|
public AiAbilityDecision chkDrawback(SpellAbility sa, Player aiPlayer) {
|
||||||
return canPlayAI(aiPlayer, sa);
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
import forge.ai.AiAbilityDecision;
|
||||||
|
import forge.ai.AiPlayDecision;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.GameEntity;
|
import forge.game.GameEntity;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
@@ -15,34 +17,36 @@ public class ChangeCombatantsAi extends SpellAbilityAi {
|
|||||||
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
|
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
protected AiAbilityDecision canPlay(Player aiPlayer, SpellAbility sa) {
|
||||||
// TODO: Extend this if possible for cards that have this as an activated ability
|
// TODO: Extend this if possible for cards that have this as an activated ability
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
protected AiAbilityDecision doTriggerNoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
||||||
return mandatory || canPlayAI(aiPlayer, sa);
|
if (mandatory) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
}
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* (non-Javadoc)
|
/* (non-Javadoc)
|
||||||
* @see forge.card.abilityfactory.SpellAiLogic#chkAIDrawback(java.util.Map, forge.card.spellability.SpellAbility, forge.game.player.Player)
|
* @see forge.card.abilityfactory.SpellAiLogic#chkAIDrawback(java.util.Map, forge.card.spellability.SpellAbility, forge.game.player.Player)
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
|
public AiAbilityDecision chkDrawback(SpellAbility sa, Player aiPlayer) {
|
||||||
final String logic = sa.getParamOrDefault("AILogic", "");
|
final String logic = sa.getParamOrDefault("AILogic", "");
|
||||||
|
|
||||||
if (logic.equals("WeakestOppExceptCtrl")) {
|
if (logic.equals("WeakestOppExceptCtrl")) {
|
||||||
PlayerCollection targetableOpps = aiPlayer.getOpponents();
|
PlayerCollection targetableOpps = aiPlayer.getOpponents();
|
||||||
targetableOpps.remove(sa.getHostCard().getController());
|
targetableOpps.remove(sa.getHostCard().getController());
|
||||||
if (targetableOpps.isEmpty()) {
|
if (targetableOpps.isEmpty()) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -63,4 +67,3 @@ public class ChangeCombatantsAi extends SpellAbilityAi {
|
|||||||
return (T)weakestTargetableOpp;
|
return (T)weakestTargetableOpp;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
import forge.ai.ComputerUtil;
|
import forge.ai.*;
|
||||||
import forge.ai.ComputerUtilAbility;
|
|
||||||
import forge.ai.ComputerUtilMana;
|
|
||||||
import forge.ai.SpellAbilityAi;
|
|
||||||
import forge.card.mana.ManaCost;
|
import forge.card.mana.ManaCost;
|
||||||
import forge.game.Game;
|
import forge.game.Game;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
@@ -21,7 +18,7 @@ public class ChangeTargetsAi extends SpellAbilityAi {
|
|||||||
* forge.game.spellability.SpellAbility)
|
* forge.game.spellability.SpellAbility)
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected boolean checkApiLogic(Player ai, SpellAbility sa) {
|
protected AiAbilityDecision checkApiLogic(Player ai, SpellAbility sa) {
|
||||||
final Game game = sa.getHostCard().getGame();
|
final Game game = sa.getHostCard().getGame();
|
||||||
final SpellAbility topSa = game.getStack().isEmpty() ? null
|
final SpellAbility topSa = game.getStack().isEmpty() ? null
|
||||||
: ComputerUtilAbility.getTopSpellAbilityOnStack(game, sa);
|
: ComputerUtilAbility.getTopSpellAbilityOnStack(game, sa);
|
||||||
@@ -32,47 +29,50 @@ public class ChangeTargetsAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
// The AI can't otherwise play this ability, but should at least not
|
// The AI can't otherwise play this ability, but should at least not
|
||||||
// miss mandatory activations (e.g. triggers).
|
// miss mandatory activations (e.g. triggers).
|
||||||
return sa.isMandatory();
|
if (sa.isMandatory()) {
|
||||||
|
return new AiAbilityDecision(50, AiPlayDecision.MandatoryPlay);
|
||||||
|
}
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean doSpellMagnet(SpellAbility sa, SpellAbility topSa, Player aiPlayer) {
|
private AiAbilityDecision doSpellMagnet(SpellAbility sa, SpellAbility topSa, Player aiPlayer) {
|
||||||
// For cards like Spellskite that retarget spells to itself
|
// For cards like Spellskite that retarget spells to itself
|
||||||
if (topSa == null) {
|
if (topSa == null) {
|
||||||
// nothing on stack, so nothing to target
|
// nothing on stack, so nothing to target
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
final TargetChoices topTargets = topSa.getTargets();
|
final TargetChoices topTargets = topSa.getTargets();
|
||||||
final Card topHost = topSa.getHostCard();
|
final Card topHost = topSa.getHostCard();
|
||||||
|
|
||||||
if (sa.getTargets().size() != 0 && sa.isTrigger()) {
|
if (!sa.getTargets().isEmpty() && sa.isTrigger()) {
|
||||||
// something was already chosen before (e.g. in response to a trigger - Mizzium Meddler), so just proceed
|
// something was already chosen before (e.g. in response to a trigger - Mizzium Meddler), so just proceed
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!topSa.usesTargeting() || topTargets.getTargetCards().contains(sa.getHostCard())) {
|
if (!topSa.usesTargeting() || topTargets.getTargetCards().contains(sa.getHostCard())) {
|
||||||
// if this does not target at all or already targets host, no need to redirect it again
|
// if this does not target at all or already targets host, no need to redirect it again
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (Card tgt : topTargets.getTargetCards()) {
|
for (Card tgt : topTargets.getTargetCards()) {
|
||||||
if (ComputerUtilAbility.getAbilitySourceName(sa).equals(tgt.getName()) && tgt.getController().equals(aiPlayer)) {
|
if (ComputerUtilAbility.getAbilitySourceName(sa).equals(tgt.getName()) && tgt.getController().equals(aiPlayer)) {
|
||||||
// We are already targeting at least one card with the same name (e.g. in presence of 2+ Spellskites),
|
// We are already targeting at least one card with the same name (e.g. in presence of 2+ Spellskites),
|
||||||
// no need to retarget again to another one
|
// no need to retarget again to another one
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (topHost != null && !topHost.getController().isOpponentOf(aiPlayer)) {
|
if (topHost != null && !topHost.getController().isOpponentOf(aiPlayer)) {
|
||||||
// make sure not to redirect our own abilities
|
// make sure not to redirect our own abilities
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
if (!topSa.canTarget(sa.getHostCard())) {
|
if (!topSa.canTarget(sa.getHostCard())) {
|
||||||
// don't try targeting it if we can't legally target the host card with it in the first place
|
// don't try targeting it if we can't legally target the host card with it in the first place
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
if (!sa.canTarget(topSa)) {
|
if (!sa.canTarget(topSa)) {
|
||||||
// don't try retargeting a spell that the current card can't legally retarget (e.g. Muck Drubb + Lightning Bolt to the face)
|
// don't try retargeting a spell that the current card can't legally retarget (e.g. Muck Drubb + Lightning Bolt to the face)
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sa.getPayCosts().getCostMana() != null && sa.getPayCosts().getCostMana().getMana().hasPhyrexian()) {
|
if (sa.getPayCosts().getCostMana() != null && sa.getPayCosts().getCostMana().getMana().hasPhyrexian()) {
|
||||||
@@ -85,22 +85,22 @@ public class ChangeTargetsAi extends SpellAbilityAi {
|
|||||||
if (potentialDmg != -1 && potentialDmg <= payDamage && !canPay
|
if (potentialDmg != -1 && potentialDmg <= payDamage && !canPay
|
||||||
&& topTargets.contains(aiPlayer)) {
|
&& topTargets.contains(aiPlayer)) {
|
||||||
// do not pay Phyrexian mana if the spell is a damaging one but it deals less damage or the same damage as we'll pay life
|
// do not pay Phyrexian mana if the spell is a damaging one but it deals less damage or the same damage as we'll pay life
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Card firstCard = topTargets.getFirstTargetedCard();
|
Card firstCard = topTargets.getFirstTargetedCard();
|
||||||
// if we're not the target don't intervene unless we can steal a buff
|
// if we're not the target don't intervene unless we can steal a buff
|
||||||
if (firstCard != null && !aiPlayer.equals(firstCard.getController()) && !topHost.getController().equals(firstCard.getController()) && !topHost.getController().getAllies().contains(firstCard.getController())) {
|
if (firstCard != null && !aiPlayer.equals(firstCard.getController()) && !topHost.getController().equals(firstCard.getController()) && !topHost.getController().getAllies().contains(firstCard.getController())) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
Player firstPlayer = topTargets.getFirstTargetedPlayer();
|
Player firstPlayer = topTargets.getFirstTargetedPlayer();
|
||||||
if (firstPlayer != null && !aiPlayer.equals(firstPlayer)) {
|
if (firstPlayer != null && !aiPlayer.equals(firstPlayer)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
sa.getTargets().add(topSa);
|
sa.getTargets().add(topSa);
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,6 +46,30 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
private static CardCollection multipleCardsToChoose = new CardCollection();
|
private static CardCollection multipleCardsToChoose = new CardCollection();
|
||||||
|
|
||||||
protected boolean willPayCosts(Player ai, SpellAbility sa, Cost cost, Card source) {
|
protected boolean willPayCosts(Player ai, SpellAbility sa, Cost cost, Card source) {
|
||||||
|
if (sa.isHidden()) {
|
||||||
|
if (!ComputerUtilCost.checkSacrificeCost(ai, cost, source, sa)
|
||||||
|
&& !"Battlefield".equals(sa.getParam("Destination")) && !source.isLand()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ComputerUtilCost.checkLifeCost(ai, cost, source, 4, sa)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ComputerUtilCost.checkDiscardCost(ai, cost, source, sa)) {
|
||||||
|
for (final CostPart part : cost.getCostParts()) {
|
||||||
|
if (part instanceof CostDiscard) {
|
||||||
|
CostDiscard cd = (CostDiscard) part;
|
||||||
|
// this is mainly for typecycling
|
||||||
|
if (!cd.payCostFromSource() || !ComputerUtil.isWorseThanDraw(ai, source)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
if (sa.isCraft()) {
|
if (sa.isCraft()) {
|
||||||
CardCollection payingCards = new CardCollection();
|
CardCollection payingCards = new CardCollection();
|
||||||
int needed = 0;
|
int needed = 0;
|
||||||
@@ -129,14 +153,12 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean checkApiLogic(Player aiPlayer, SpellAbility sa) {
|
protected AiAbilityDecision checkApiLogic(Player aiPlayer, SpellAbility sa) {
|
||||||
// Checks for "return true" unlike checkAiLogic()
|
|
||||||
|
|
||||||
multipleCardsToChoose.clear();
|
multipleCardsToChoose.clear();
|
||||||
String aiLogic = sa.getParam("AILogic");
|
String aiLogic = sa.getParam("AILogic");
|
||||||
if (aiLogic != null) {
|
if (aiLogic != null) {
|
||||||
if (aiLogic.equals("Always")) {
|
if (aiLogic.equals("Always")) {
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
} else if (aiLogic.startsWith("SacAndUpgrade")) { // Birthing Pod, Natural Order, etc.
|
} else if (aiLogic.startsWith("SacAndUpgrade")) { // Birthing Pod, Natural Order, etc.
|
||||||
return doSacAndUpgradeLogic(aiPlayer, sa);
|
return doSacAndUpgradeLogic(aiPlayer, sa);
|
||||||
} else if (aiLogic.startsWith("SacAndRetFromGrave")) { // Recurring Nightmare, etc.
|
} else if (aiLogic.startsWith("SacAndRetFromGrave")) { // Recurring Nightmare, etc.
|
||||||
@@ -156,10 +178,18 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
} else if (aiLogic.equals("MazesEnd")) {
|
} else if (aiLogic.equals("MazesEnd")) {
|
||||||
return SpecialCardAi.MazesEnd.consider(aiPlayer, sa);
|
return SpecialCardAi.MazesEnd.consider(aiPlayer, sa);
|
||||||
} else if (aiLogic.equals("Pongify")) {
|
} else if (aiLogic.equals("Pongify")) {
|
||||||
return sa.isTargetNumberValid(); // Pre-targeted in checkAiLogic
|
if (sa.isTargetNumberValid()) {
|
||||||
|
// Pre-targeted in checkAiLogic
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
} else {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
|
}
|
||||||
} else if (aiLogic.equals("ReturnCastable")) {
|
} else if (aiLogic.equals("ReturnCastable")) {
|
||||||
return !sa.getHostCard().getExiledCards().isEmpty()
|
if (!sa.getHostCard().getExiledCards().isEmpty()
|
||||||
&& ComputerUtilMana.canPayManaCost(sa.getHostCard().getExiledCards().getFirst().getFirstSpellAbility(), aiPlayer, 0, false);
|
&& ComputerUtilMana.canPayManaCost(sa.getHostCard().getExiledCards().getFirst().getFirstSpellAbility(), aiPlayer, 0, false)) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
}
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (sa.isHidden()) {
|
if (sa.isHidden()) {
|
||||||
@@ -178,7 +208,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
* @return a boolean.
|
* @return a boolean.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
|
public AiAbilityDecision chkDrawback(SpellAbility sa, Player aiPlayer) {
|
||||||
if (sa.isHidden()) {
|
if (sa.isHidden()) {
|
||||||
return hiddenOriginPlayDrawbackAI(aiPlayer, sa);
|
return hiddenOriginPlayDrawbackAI(aiPlayer, sa);
|
||||||
}
|
}
|
||||||
@@ -197,7 +227,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
* @return a boolean.
|
* @return a boolean.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
protected AiAbilityDecision doTriggerNoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
||||||
String aiLogic = sa.getParamOrDefault("AILogic", "");
|
String aiLogic = sa.getParamOrDefault("AILogic", "");
|
||||||
|
|
||||||
if (sa.isReplacementAbility() && "Command".equals(sa.getParam("Destination")) && "ReplacedCard".equals(sa.getParam("Defined"))) {
|
if (sa.isReplacementAbility() && "Command".equals(sa.getParam("Destination")) && "ReplacedCard".equals(sa.getParam("Defined"))) {
|
||||||
@@ -206,10 +236,10 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ("Always".equals(aiLogic)) {
|
if ("Always".equals(aiLogic)) {
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
} else if ("IfNotBuffed".equals(aiLogic)) {
|
} else if ("IfNotBuffed".equals(aiLogic)) {
|
||||||
if (ComputerUtilCard.isUselessCreature(aiPlayer, sa.getHostCard())) {
|
if (ComputerUtilCard.isUselessCreature(aiPlayer, sa.getHostCard())) {
|
||||||
return true; // debuffed by opponent's auras to the level that it becomes useless
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
int delta = 0;
|
int delta = 0;
|
||||||
for (Card enc : sa.getHostCard().getEnchantedBy()) {
|
for (Card enc : sa.getHostCard().getEnchantedBy()) {
|
||||||
@@ -219,9 +249,17 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
delta++;
|
delta++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return delta <= 0;
|
if (delta <= 0) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
} else {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
|
}
|
||||||
} else if ("SaviorOfOllenbock".equals(aiLogic)) {
|
} else if ("SaviorOfOllenbock".equals(aiLogic)) {
|
||||||
return SpecialCardAi.SaviorOfOllenbock.consider(aiPlayer, sa);
|
if (SpecialCardAi.SaviorOfOllenbock.consider(aiPlayer, sa)) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
} else {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sa.isHidden()) {
|
if (sa.isHidden()) {
|
||||||
@@ -250,7 +288,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
* a {@link forge.game.spellability.SpellAbility} object.
|
* a {@link forge.game.spellability.SpellAbility} object.
|
||||||
* @return a boolean.
|
* @return a boolean.
|
||||||
*/
|
*/
|
||||||
private static boolean hiddenOriginCanPlayAI(final Player ai, final SpellAbility sa) {
|
private static AiAbilityDecision hiddenOriginCanPlayAI(final Player ai, final SpellAbility sa) {
|
||||||
// Fetching should occur fairly often as it helps cast more spells, and
|
// Fetching should occur fairly often as it helps cast more spells, and
|
||||||
// have access to more mana
|
// have access to more mana
|
||||||
final Cost abCost = sa.getPayCosts();
|
final Cost abCost = sa.getPayCosts();
|
||||||
@@ -267,75 +305,35 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
} catch (IllegalArgumentException ex) {
|
} catch (IllegalArgumentException ex) {
|
||||||
// This happens when Origin is something like
|
// This happens when Origin is something like
|
||||||
// "Graveyard,Library" (Doomsday)
|
// "Graveyard,Library" (Doomsday)
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
final String destination = sa.getParam("Destination");
|
final String destination = sa.getParam("Destination");
|
||||||
|
|
||||||
if (abCost != null) {
|
if (sa.isNinjutsu()) {
|
||||||
// AI currently disabled for these costs
|
if (!source.ignoreLegendRule() && ai.isCardInPlay(source.getName())) {
|
||||||
if (!ComputerUtilCost.checkSacrificeCost(ai, abCost, source, sa)
|
return new AiAbilityDecision(0, AiPlayDecision.WouldDestroyLegend);
|
||||||
&& !(destination.equals("Battlefield") && !source.isLand())) {
|
}
|
||||||
return false;
|
if (ai.getGame().getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DAMAGE)) {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.WaitForCombat);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, sa)) {
|
if (ai.getGame().getCombat() == null) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.WaitForCombat);
|
||||||
}
|
}
|
||||||
|
List<Card> attackers = ai.getGame().getCombat().getUnblockedAttackers();
|
||||||
if (!ComputerUtilCost.checkDiscardCost(ai, abCost, source, sa)) {
|
boolean lowerCMC = false;
|
||||||
for (final CostPart part : abCost.getCostParts()) {
|
for (Card attacker : attackers) {
|
||||||
if (part instanceof CostDiscard) {
|
if (attacker.getCMC() < source.getCMC()) {
|
||||||
CostDiscard cd = (CostDiscard) part;
|
lowerCMC = true;
|
||||||
// this is mainly for typecycling
|
break;
|
||||||
if (!cd.payCostFromSource() || !ComputerUtil.isWorseThanDraw(ai, source)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (!lowerCMC) {
|
||||||
if (sa.isNinjutsu()) {
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
if (!source.ignoreLegendRule() && ai.isCardInPlay(source.getName())) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (ai.getGame().getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DAMAGE)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ai.getGame().getCombat() == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
List<Card> attackers = ai.getGame().getCombat().getUnblockedAttackers();
|
|
||||||
boolean lowerCMC = false;
|
|
||||||
for (Card attacker : attackers) {
|
|
||||||
if (attacker.getCMC() < source.getCMC()) {
|
|
||||||
lowerCMC = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!lowerCMC) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// don't play if the conditions aren't met, unless it would trigger a beneficial sub-condition
|
|
||||||
if (!activateForCost && !sa.metConditions()) {
|
|
||||||
final AbilitySub abSub = sa.getSubAbility();
|
|
||||||
if (abSub != null && !sa.isWrapper() && "True".equals(source.getSVar("AIPlayForSub"))) {
|
|
||||||
if (!abSub.metConditions()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// prevent run-away activations - first time will always return true
|
|
||||||
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
Iterable<Player> pDefined = Lists.newArrayList(source.getController());
|
Iterable<Player> pDefined = Lists.newArrayList(source.getController());
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
if (tgt != null && tgt.canTgtPlayer()) {
|
if (tgt != null && tgt.canTgtPlayer()) {
|
||||||
@@ -347,7 +345,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
sa.getTargets().add(ai);
|
sa.getTargets().add(ai);
|
||||||
}
|
}
|
||||||
if (!sa.isTargetNumberValid()) {
|
if (!sa.isTargetNumberValid()) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
pDefined = sa.getTargets().getTargetPlayers();
|
pDefined = sa.getTargets().getTargetPlayers();
|
||||||
} else {
|
} else {
|
||||||
@@ -391,12 +389,12 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!activateForCost && list.isEmpty()) {
|
if (!activateForCost && list.isEmpty()) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
if ("Atarka's Command".equals(sourceName)
|
if ("Atarka's Command".equals(sourceName)
|
||||||
&& (list.size() < 2 || ai.getLandsPlayedThisTurn() < 1)) {
|
&& (list.size() < 2 || ai.getLandsPlayedThisTurn() < 1)) {
|
||||||
// be strict on playing lands off charms
|
// be strict on playing lands off charms
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
String num = sa.getParamOrDefault("ChangeNum", "1");
|
String num = sa.getParamOrDefault("ChangeNum", "1");
|
||||||
@@ -404,55 +402,65 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
if (sa.getSVar("X").equals("Count$xPaid")) {
|
if (sa.getSVar("X").equals("Count$xPaid")) {
|
||||||
// Set PayX here to maximum value.
|
// Set PayX here to maximum value.
|
||||||
int xPay = ComputerUtilCost.getMaxXValue(sa, ai, sa.isTrigger());
|
int xPay = ComputerUtilCost.getMaxXValue(sa, ai, sa.isTrigger());
|
||||||
if (xPay == 0) return false;
|
if (xPay == 0) {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantAffordX);
|
||||||
|
}
|
||||||
xPay = Math.min(xPay, list.size());
|
xPay = Math.min(xPay, list.size());
|
||||||
sa.setXManaCostPaid(xPay);
|
sa.setXManaCostPaid(xPay);
|
||||||
} else {
|
} else {
|
||||||
// Figure out the X amount, bail if it's zero (nothing will change zone).
|
// Figure out the X amount, bail if it's zero (nothing will change zone).
|
||||||
int xValue = AbilityUtils.calculateAmount(source, "X", sa);
|
int xValue = AbilityUtils.calculateAmount(source, "X", sa);
|
||||||
if (xValue == 0) {
|
if (xValue == 0) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantAffordX);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sourceName.equals("Temur Sabertooth")) {
|
if (sourceName.equals("Temur Sabertooth")) {
|
||||||
// activated bounce + pump
|
// activated bounce + pump
|
||||||
if (ComputerUtilCard.shouldPumpCard(ai, sa.getSubAbility(), source, 0, 0, Arrays.asList("Indestructible")) ||
|
boolean pumpDecision = ComputerUtilCard.shouldPumpCard(ai, sa.getSubAbility(), source, 0, 0, Arrays.asList("Indestructible"));
|
||||||
ComputerUtilCard.canPumpAgainstRemoval(ai, sa.getSubAbility())) {
|
AiAbilityDecision saveDecision = ComputerUtilCard.canPumpAgainstRemoval(ai, sa.getSubAbility());
|
||||||
|
if (pumpDecision || saveDecision.willingToPlay()) {
|
||||||
for (Card c : list) {
|
for (Card c : list) {
|
||||||
if (ComputerUtilCard.evaluateCreature(c) < ComputerUtilCard.evaluateCreature(source)) {
|
if (ComputerUtilCard.evaluateCreature(c) < ComputerUtilCard.evaluateCreature(source)) {
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.ResponseToStackResolve);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return canBouncePermanent(ai, sa, list) != null;
|
if (canBouncePermanent(ai, sa, list) != null) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
}
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ComputerUtil.playImmediately(ai, sa)) {
|
if (ComputerUtil.playImmediately(ai, sa)) {
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
// don't use fetching to top of library/graveyard before main2
|
// don't use fetching to top of library/graveyard before main2
|
||||||
if (ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2)
|
if (ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2)
|
||||||
&& !sa.hasParam("ActivationPhases")) {
|
&& !sa.hasParam("ActivationPhases")) {
|
||||||
if (!destination.equals("Battlefield") && !destination.equals("Hand")) {
|
if (!destination.equals("Battlefield") && !destination.equals("Hand")) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
// Only tutor something in main1 if hand is almost empty
|
// Only tutor something in main1 if hand is almost empty
|
||||||
if (ai.getCardsIn(ZoneType.Hand).size() > 1 && destination.equals("Hand")
|
if (ai.getCardsIn(ZoneType.Hand).size() > 1 && destination.equals("Hand")
|
||||||
&& !aiLogic.equals("AnyMainPhase")) {
|
&& !aiLogic.equals("AnyMainPhase")) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ComputerUtil.waitForBlocking(sa)) {
|
if (ComputerUtil.waitForBlocking(sa)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.WaitForCombat);
|
||||||
}
|
}
|
||||||
|
|
||||||
final AbilitySub subAb = sa.getSubAbility();
|
final AbilitySub subAb = sa.getSubAbility();
|
||||||
return subAb == null || SpellApiToAi.Converter.get(subAb).chkDrawbackWithSubs(ai, subAb);
|
if (subAb == null) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
}
|
||||||
|
|
||||||
|
return SpellApiToAi.Converter.get(subAb).chkDrawbackWithSubs(ai, subAb);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -464,7 +472,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
* a {@link forge.game.spellability.SpellAbility} object.
|
* a {@link forge.game.spellability.SpellAbility} object.
|
||||||
* @return a boolean.
|
* @return a boolean.
|
||||||
*/
|
*/
|
||||||
private static boolean hiddenOriginPlayDrawbackAI(final Player aiPlayer, final SpellAbility sa) {
|
private static AiAbilityDecision hiddenOriginPlayDrawbackAI(final Player aiPlayer, final SpellAbility sa) {
|
||||||
// if putting cards from hand to library and parent is drawing cards
|
// if putting cards from hand to library and parent is drawing cards
|
||||||
// make sure this will actually do something:
|
// make sure this will actually do something:
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
@@ -476,11 +484,11 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
} else if (!isCurse && sa.canTarget(aiPlayer)) {
|
} else if (!isCurse && sa.canTarget(aiPlayer)) {
|
||||||
sa.getTargets().add(aiPlayer);
|
sa.getTargets().add(aiPlayer);
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -494,7 +502,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
* a boolean.
|
* a boolean.
|
||||||
* @return a boolean.
|
* @return a boolean.
|
||||||
*/
|
*/
|
||||||
private static boolean hiddenTriggerAI(final Player ai, final SpellAbility sa, final boolean mandatory) {
|
private static AiAbilityDecision hiddenTriggerAI(final Player ai, final SpellAbility sa, final boolean mandatory) {
|
||||||
// Fetching should occur fairly often as it helps cast more spells, and
|
// Fetching should occur fairly often as it helps cast more spells, and
|
||||||
// have access to more mana
|
// have access to more mana
|
||||||
|
|
||||||
@@ -507,7 +515,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
* to make sub-optimal choices (waste bounce) than to make obvious mistakes
|
* to make sub-optimal choices (waste bounce) than to make obvious mistakes
|
||||||
* (bounce useful permanent).
|
* (bounce useful permanent).
|
||||||
*/
|
*/
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -545,15 +553,15 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
pDefined = sa.getTargets().getTargetPlayers();
|
pDefined = sa.getTargets().getTargetPlayers();
|
||||||
|
|
||||||
if (Iterables.isEmpty(pDefined)) {
|
if (Iterables.isEmpty(pDefined)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mandatory) {
|
if (mandatory) {
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (mandatory) {
|
if (mandatory) {
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
pDefined = AbilityUtils.getDefinedPlayers(sa.getHostCard(), sa.getParam("Defined"), sa);
|
pDefined = AbilityUtils.getDefinedPlayers(sa.getHostCard(), sa.getParam("Defined"), sa);
|
||||||
}
|
}
|
||||||
@@ -567,10 +575,10 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (list.isEmpty()) {
|
if (list.isEmpty()) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.MissingNeededCards);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
// *********** Utility functions for Hidden ********************
|
// *********** Utility functions for Hidden ********************
|
||||||
@@ -673,7 +681,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
* a {@link forge.game.spellability.SpellAbility} object.
|
* a {@link forge.game.spellability.SpellAbility} object.
|
||||||
* @return a boolean.
|
* @return a boolean.
|
||||||
*/
|
*/
|
||||||
private static boolean knownOriginCanPlayAI(final Player ai, final SpellAbility sa) {
|
private static AiAbilityDecision knownOriginCanPlayAI(final Player ai, final SpellAbility sa) {
|
||||||
// Retrieve either this card, or target Cards in Graveyard
|
// Retrieve either this card, or target Cards in Graveyard
|
||||||
|
|
||||||
final List<ZoneType> origin = Lists.newArrayList();
|
final List<ZoneType> origin = Lists.newArrayList();
|
||||||
@@ -685,20 +693,16 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
final ZoneType destination = ZoneType.smartValueOf(sa.getParam("Destination"));
|
final ZoneType destination = ZoneType.smartValueOf(sa.getParam("Destination"));
|
||||||
|
|
||||||
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sa.usesTargeting()) {
|
if (sa.usesTargeting()) {
|
||||||
if (!isPreferredTarget(ai, sa, false, false)) {
|
if (!isPreferredTarget(ai, sa, false, false)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// non-targeted retrieval
|
// non-targeted retrieval
|
||||||
final List<Card> retrieval = sa.knownDetermineDefined(sa.getParam("Defined"));
|
final List<Card> retrieval = sa.knownDetermineDefined(sa.getParam("Defined"));
|
||||||
|
|
||||||
if (retrieval == null || retrieval.isEmpty()) {
|
if (retrieval == null || retrieval.isEmpty()) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
// return this card from graveyard: cards like Hammer of Bogardan
|
// return this card from graveyard: cards like Hammer of Bogardan
|
||||||
@@ -709,7 +713,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
// (dying or losing control of)
|
// (dying or losing control of)
|
||||||
if (origin.contains(ZoneType.Battlefield)) {
|
if (origin.contains(ZoneType.Battlefield)) {
|
||||||
if (ai.getGame().getStack().isEmpty()) {
|
if (ai.getGame().getStack().isEmpty()) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
final AbilitySub abSub = sa.getSubAbility();
|
final AbilitySub abSub = sa.getSubAbility();
|
||||||
@@ -722,7 +726,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
if (!(destination.equals(ZoneType.Exile)
|
if (!(destination.equals(ZoneType.Exile)
|
||||||
&& (subApi == ApiType.DelayedTrigger || subApi == ApiType.ChangeZone || "DelayedBlink".equals(sa.getParam("AILogic"))))
|
&& (subApi == ApiType.DelayedTrigger || subApi == ApiType.ChangeZone || "DelayedBlink".equals(sa.getParam("AILogic"))))
|
||||||
&& !destination.equals(ZoneType.Hand)) {
|
&& !destination.equals(ZoneType.Hand)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
final List<GameObject> objects = ComputerUtil.predictThreatenedObjects(ai, sa);
|
final List<GameObject> objects = ComputerUtil.predictThreatenedObjects(ai, sa);
|
||||||
@@ -734,13 +738,13 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!contains) {
|
if (!contains) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (destination == ZoneType.Battlefield) {
|
if (destination == ZoneType.Battlefield) {
|
||||||
if (ComputerUtil.isETBprevented(retrieval.get(0))) {
|
if (ComputerUtil.isETBprevented(retrieval.get(0))) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
// predict whether something may put a ETBing creature below zero toughness
|
// predict whether something may put a ETBing creature below zero toughness
|
||||||
@@ -750,7 +754,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
final Card copy = CardCopyService.getLKICopy(c);
|
final Card copy = CardCopyService.getLKICopy(c);
|
||||||
ComputerUtilCard.applyStaticContPT(c.getGame(), copy, null);
|
ComputerUtilCard.applyStaticContPT(c.getGame(), copy, null);
|
||||||
if (copy.getNetToughness() <= 0) {
|
if (copy.getNetToughness() <= 0) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -764,13 +768,17 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (nothingWillReturn) {
|
if (nothingWillReturn) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final AbilitySub subAb = sa.getSubAbility();
|
final AbilitySub subAb = sa.getSubAbility();
|
||||||
return subAb == null || SpellApiToAi.Converter.get(subAb).chkDrawbackWithSubs(ai, subAb);
|
if (subAb == null) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
}
|
||||||
|
|
||||||
|
return SpellApiToAi.Converter.get(subAb).chkDrawbackWithSubs(ai, subAb);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -784,7 +792,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
protected boolean checkPhaseRestrictions(Player ai, SpellAbility sa, PhaseHandler ph) {
|
protected boolean checkPhaseRestrictions(Player ai, SpellAbility sa, PhaseHandler ph) {
|
||||||
String aiLogic = sa.getParamOrDefault("AILogic", "");
|
String aiLogic = sa.getParamOrDefault("AILogic", "");
|
||||||
|
|
||||||
if (aiLogic.equals("SurvivalOfTheFittest") || aiLogic.equals("AtOppEOT")) {
|
if (aiLogic.equals("SurvivalOfTheFittest")) {
|
||||||
return ph.getNextTurn().equals(ai) && ph.is(PhaseType.END_OF_TURN);
|
return ph.getNextTurn().equals(ai) && ph.is(PhaseType.END_OF_TURN);
|
||||||
} else if (aiLogic.equals("Main1") && ph.is(PhaseType.MAIN1, ai)) {
|
} else if (aiLogic.equals("Main1") && ph.is(PhaseType.MAIN1, ai)) {
|
||||||
return true;
|
return true;
|
||||||
@@ -843,16 +851,26 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
* a {@link forge.game.spellability.SpellAbility} object.
|
* a {@link forge.game.spellability.SpellAbility} object.
|
||||||
* @return a boolean.
|
* @return a boolean.
|
||||||
*/
|
*/
|
||||||
private static boolean knownOriginPlayDrawbackAI(final Player aiPlayer, final SpellAbility sa) {
|
private static AiAbilityDecision knownOriginPlayDrawbackAI(final Player aiPlayer, final SpellAbility sa) {
|
||||||
if ("MimicVat".equals(sa.getParam("AILogic"))) {
|
if ("MimicVat".equals(sa.getParam("AILogic"))) {
|
||||||
return SpecialCardAi.MimicVat.considerExile(aiPlayer, sa);
|
if (SpecialCardAi.MimicVat.considerExile(aiPlayer, sa)) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
} else {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!sa.usesTargeting()) {
|
if (!sa.usesTargeting()) {
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
return isPreferredTarget(aiPlayer, sa, false, true);
|
if (!isPreferredTarget(aiPlayer, sa, false, true)) {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
|
} else {
|
||||||
|
// if we are here, we have a target
|
||||||
|
// so we can play the ability
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1493,7 +1511,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (choice == null) { // can't find anything left
|
if (choice == null) { // can't find anything left
|
||||||
if (sa.getTargets().size() == 0 || sa.getTargets().size() < tgt.getMinTargets(sa.getHostCard(), sa)) {
|
if (sa.getTargets().isEmpty() || sa.getTargets().size() < tgt.getMinTargets(sa.getHostCard(), sa)) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -1521,13 +1539,21 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
* a boolean.
|
* a boolean.
|
||||||
* @return a boolean.
|
* @return a boolean.
|
||||||
*/
|
*/
|
||||||
private static boolean knownOriginTriggerAI(final Player ai, final SpellAbility sa, final boolean mandatory) {
|
private static AiAbilityDecision knownOriginTriggerAI(final Player ai, final SpellAbility sa, final boolean mandatory) {
|
||||||
final String logic = sa.getParamOrDefault("AILogic", "");
|
final String logic = sa.getParamOrDefault("AILogic", "");
|
||||||
|
|
||||||
if ("DeathgorgeScavenger".equals(logic)) {
|
if ("DeathgorgeScavenger".equals(logic)) {
|
||||||
return SpecialCardAi.DeathgorgeScavenger.consider(ai, sa);
|
if (SpecialCardAi.DeathgorgeScavenger.consider(ai, sa)) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
} else {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
|
}
|
||||||
} else if ("ExtraplanarLens".equals(logic)) {
|
} else if ("ExtraplanarLens".equals(logic)) {
|
||||||
return SpecialCardAi.ExtraplanarLens.consider(ai, sa);
|
if (SpecialCardAi.ExtraplanarLens.consider(ai, sa)) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
} else {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
|
}
|
||||||
} else if ("ExileCombatThreat".equals(logic)) {
|
} else if ("ExileCombatThreat".equals(logic)) {
|
||||||
return doExileCombatThreatLogic(ai, sa);
|
return doExileCombatThreatLogic(ai, sa);
|
||||||
}
|
}
|
||||||
@@ -1539,14 +1565,27 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
if (!list.isEmpty()) {
|
if (!list.isEmpty()) {
|
||||||
final Card attachedTo = list.get(0);
|
final Card attachedTo = list.get(0);
|
||||||
// This code is for the Dragon auras
|
// This code is for the Dragon auras
|
||||||
return !attachedTo.getController().isOpponentOf(ai);
|
if (!attachedTo.getController().isOpponentOf(ai)) {
|
||||||
|
// If the AI is not the controller of the attachedTo card, then it is not a valid target.
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
} else {
|
||||||
|
// If the AI is the controller of the attachedTo card, then it is a valid target.
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (isPreferredTarget(ai, sa, mandatory, true)) {
|
} else if (isPreferredTarget(ai, sa, mandatory, true)) {
|
||||||
// do nothing
|
// do nothing
|
||||||
} else return isUnpreferredTarget(ai, sa, mandatory);
|
} else {
|
||||||
|
if (isUnpreferredTarget(ai, sa, mandatory)) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
} else {
|
||||||
|
// If the AI is not the controller of the attachedTo card, then it is not a valid target.
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Card chooseCardToHiddenOriginChangeZone(ZoneType destination, List<ZoneType> origin, SpellAbility sa, CardCollection fetchList, Player player, final Player decider) {
|
public static Card chooseCardToHiddenOriginChangeZone(ZoneType destination, List<ZoneType> origin, SpellAbility sa, CardCollection fetchList, Player player, final Player decider) {
|
||||||
@@ -1777,7 +1816,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
return super.chooseSingleAttackableEntity(ai, sa, options, params);
|
return super.chooseSingleAttackableEntity(ai, sa, options, params);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean doSacAndReturnFromGraveLogic(final Player ai, final SpellAbility sa) {
|
private AiAbilityDecision doSacAndReturnFromGraveLogic(final Player ai, final SpellAbility sa) {
|
||||||
Card source = sa.getHostCard();
|
Card source = sa.getHostCard();
|
||||||
String definedSac = StringUtils.split(source.getSVar("AIPreference"), "$")[1];
|
String definedSac = StringUtils.split(source.getSVar("AIPreference"), "$")[1];
|
||||||
|
|
||||||
@@ -1796,14 +1835,14 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
sa.getTargets().add(bestRet);
|
sa.getTargets().add(bestRet);
|
||||||
source.setSVar("AIPreferenceOverride", "Creature.cmcEQ" + worstSac.getCMC());
|
source.setSVar("AIPreferenceOverride", "Creature.cmcEQ" + worstSac.getCMC());
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean doSacAndUpgradeLogic(final Player ai, final SpellAbility sa) {
|
private AiAbilityDecision doSacAndUpgradeLogic(final Player ai, final SpellAbility sa) {
|
||||||
Card source = sa.getHostCard();
|
Card source = sa.getHostCard();
|
||||||
PhaseHandler ph = ai.getGame().getPhaseHandler();
|
PhaseHandler ph = ai.getGame().getPhaseHandler();
|
||||||
String logic = sa.getParam("AILogic");
|
String logic = sa.getParam("AILogic");
|
||||||
@@ -1811,7 +1850,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
if (!ph.is(PhaseType.MAIN2)) {
|
if (!ph.is(PhaseType.MAIN2)) {
|
||||||
// Should be given a chance to cast other spells as well as to use a previously upgraded creature
|
// Should be given a chance to cast other spells as well as to use a previously upgraded creature
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.WaitForMain2);
|
||||||
}
|
}
|
||||||
|
|
||||||
String definedSac = StringUtils.split(source.getSVar("AIPreference"), "$")[1];
|
String definedSac = StringUtils.split(source.getSVar("AIPreference"), "$")[1];
|
||||||
@@ -1850,15 +1889,14 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
if (!listGoal.isEmpty()) {
|
if (!listGoal.isEmpty()) {
|
||||||
// make sure we're upgrading sacCMC->goalCMC
|
// make sure we're upgrading sacCMC->goalCMC
|
||||||
source.setSVar("AIPreferenceOverride", "Creature.cmcEQ" + sacCMC);
|
source.setSVar("AIPreferenceOverride", "Creature.cmcEQ" + sacCMC);
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// no candidates to upgrade
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean doReturnCommanderLogic(SpellAbility sa, Player aiPlayer) {
|
public AiAbilityDecision doReturnCommanderLogic(SpellAbility sa, Player aiPlayer) {
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
Map<AbilityKey, Object> originalParams = (Map<AbilityKey, Object>)sa.getReplacingObject(AbilityKey.OriginalParams);
|
Map<AbilityKey, Object> originalParams = (Map<AbilityKey, Object>)sa.getReplacingObject(AbilityKey.OriginalParams);
|
||||||
SpellAbility causeSa = (SpellAbility)originalParams.get(AbilityKey.Cause);
|
SpellAbility causeSa = (SpellAbility)originalParams.get(AbilityKey.Cause);
|
||||||
@@ -1867,13 +1905,13 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
if (Objects.equals(ZoneType.Hand, destination)) {
|
if (Objects.equals(ZoneType.Hand, destination)) {
|
||||||
// If the commander is being moved to your hand, don't replace since its easier to cast it again
|
// If the commander is being moved to your hand, don't replace since its easier to cast it again
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Squee, the Immortal: easier to recast it (the call below has to be "contains" since SA is an intrinsic effect)
|
// Squee, the Immortal: easier to recast it (the call below has to be "contains" since SA is an intrinsic effect)
|
||||||
if (sa.getHostCard().getName().contains("Squee, the Immortal") &&
|
if (sa.getHostCard().getName().contains("Squee, the Immortal") &&
|
||||||
(destination == ZoneType.Graveyard || destination == ZoneType.Exile)) {
|
(destination == ZoneType.Graveyard || destination == ZoneType.Exile)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (causeSa != null && (causeSub = causeSa.getSubAbility()) != null) {
|
if (causeSa != null && (causeSub = causeSa.getSubAbility()) != null) {
|
||||||
@@ -1882,28 +1920,38 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
if (subApi == ApiType.ChangeZone && "Exile".equals(causeSub.getParam("Origin"))
|
if (subApi == ApiType.ChangeZone && "Exile".equals(causeSub.getParam("Origin"))
|
||||||
&& "Battlefield".equals(causeSub.getParam("Destination"))) {
|
&& "Battlefield".equals(causeSub.getParam("Destination"))) {
|
||||||
// A blink effect implemented using ChangeZone API
|
// A blink effect implemented using ChangeZone API
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
} else // This is an intrinsic effect that blinks the card (e.g. Obzedat, Ghost Council), no need to
|
} else // This is an intrinsic effect that blinks the card (e.g. Obzedat, Ghost Council), no need to
|
||||||
// return the commander to the Command zone.
|
// return the commander to the Command zone.
|
||||||
if (subApi == ApiType.DelayedTrigger) {
|
if (subApi == ApiType.DelayedTrigger) {
|
||||||
SpellAbility exec = causeSub.getAdditionalAbility("Execute");
|
SpellAbility exec = causeSub.getAdditionalAbility("Execute");
|
||||||
if (exec != null && exec.getApi() == ApiType.ChangeZone) {
|
if (exec != null && exec.getApi() == ApiType.ChangeZone) {
|
||||||
// A blink effect implemented using a delayed trigger
|
// A blink effect implemented using a delayed trigger
|
||||||
return !"Exile".equals(exec.getParam("Origin")) || !"Battlefield".equals(exec.getParam("Destination"));
|
if (!"Exile".equals(exec.getParam("Origin")) || !"Battlefield".equals(exec.getParam("Destination"))) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
} else {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (causeSa.getHostCard() == null || !causeSa.getHostCard().equals(sa.getReplacingObject(AbilityKey.Card))
|
||||||
|
|| !causeSa.getActivatingPlayer().equals(aiPlayer)) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
} else {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else return causeSa.getHostCard() == null || !causeSa.getHostCard().equals(sa.getReplacingObject(AbilityKey.Card))
|
|
||||||
|| !causeSa.getActivatingPlayer().equals(aiPlayer);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Normally we want the commander back in Command zone to recast him later
|
// Normally we want the commander back in Command zone to recast it later
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean doExileCombatThreatLogic(final Player aiPlayer, final SpellAbility sa) {
|
public static AiAbilityDecision doExileCombatThreatLogic(final Player aiPlayer, final SpellAbility sa) {
|
||||||
final Combat combat = aiPlayer.getGame().getCombat();
|
final Combat combat = aiPlayer.getGame().getCombat();
|
||||||
|
|
||||||
if (combat == null) {
|
if (combat == null) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.AnotherTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
Card choice = null;
|
Card choice = null;
|
||||||
@@ -1938,9 +1986,9 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
if (choice != null) {
|
if (choice != null) {
|
||||||
sa.getTargets().add(choice);
|
sa.getTargets().add(choice);
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Card doExilePreferenceLogic(final Player aiPlayer, final SpellAbility sa, CardCollection fetchList) {
|
public static Card doExilePreferenceLogic(final Player aiPlayer, final SpellAbility sa, CardCollection fetchList) {
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
import forge.ai.AiAbilityDecision;
|
||||||
|
import forge.ai.AiPlayDecision;
|
||||||
import forge.ai.*;
|
import forge.ai.*;
|
||||||
import forge.game.Game;
|
import forge.game.Game;
|
||||||
import forge.game.ability.AbilityUtils;
|
import forge.game.ability.AbilityUtils;
|
||||||
@@ -19,7 +21,7 @@ import java.util.Map;
|
|||||||
|
|
||||||
public class ChangeZoneAllAi extends SpellAbilityAi {
|
public class ChangeZoneAllAi extends SpellAbilityAi {
|
||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
protected AiAbilityDecision canPlay(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.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
@@ -32,14 +34,14 @@ public class ChangeZoneAllAi extends SpellAbilityAi {
|
|||||||
if (abCost != null) {
|
if (abCost != null) {
|
||||||
// AI currently disabled for these costs
|
// AI currently disabled for these costs
|
||||||
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, sa)) {
|
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, sa)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CostNotAcceptable);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ComputerUtilCost.checkDiscardCost(ai, abCost, source, sa)) {
|
if (!ComputerUtilCost.checkDiscardCost(ai, abCost, source, sa)) {
|
||||||
boolean aiLogicAllowsDiscard = aiLogic.startsWith("DiscardAll");
|
boolean aiLogicAllowsDiscard = aiLogic.startsWith("DiscardAll");
|
||||||
|
|
||||||
if (!aiLogicAllowsDiscard) {
|
if (!aiLogicAllowsDiscard) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CostNotAcceptable);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -59,31 +61,29 @@ public class ChangeZoneAllAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
// Ugin AI: always try to sweep before considering +1
|
// Ugin AI: always try to sweep before considering +1
|
||||||
if (sourceName.equals("Ugin, the Spirit Dragon")) {
|
if (sourceName.equals("Ugin, the Spirit Dragon")) {
|
||||||
return SpecialCardAi.UginTheSpiritDragon.considerPWAbilityPriority(ai, sa, origin, oppType, computerType);
|
boolean result = SpecialCardAi.UginTheSpiritDragon.considerPWAbilityPriority(ai, sa, origin, oppType, computerType);
|
||||||
|
return result ? new AiAbilityDecision(100, AiPlayDecision.WillPlay) : new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
oppType = AbilityUtils.filterListByType(oppType, sa.getParam("ChangeType"), sa);
|
oppType = AbilityUtils.filterListByType(oppType, sa.getParam("ChangeType"), sa);
|
||||||
computerType = AbilityUtils.filterListByType(computerType, sa.getParam("ChangeType"), sa);
|
computerType = AbilityUtils.filterListByType(computerType, sa.getParam("ChangeType"), sa);
|
||||||
|
|
||||||
if ("LivingDeath".equals(aiLogic)) {
|
if ("LivingDeath".equals(aiLogic)) {
|
||||||
// Living Death AI
|
|
||||||
return SpecialCardAi.LivingDeath.consider(ai, sa);
|
return SpecialCardAi.LivingDeath.consider(ai, sa);
|
||||||
} else if ("Timetwister".equals(aiLogic)) {
|
} else if ("Timetwister".equals(aiLogic)) {
|
||||||
// Timetwister AI
|
|
||||||
return SpecialCardAi.Timetwister.consider(ai, sa);
|
return SpecialCardAi.Timetwister.consider(ai, sa);
|
||||||
} else if ("RetDiscardedThisTurn".equals(aiLogic)) {
|
} else if ("RetDiscardedThisTurn".equals(aiLogic)) {
|
||||||
// e.g. Shadow of the Grave
|
boolean result = !ai.getDiscardedThisTurn().isEmpty() && ai.getGame().getPhaseHandler().is(PhaseType.END_OF_TURN);
|
||||||
return ai.getDiscardedThisTurn().size() > 0 && ai.getGame().getPhaseHandler().is(PhaseType.END_OF_TURN);
|
return result ? new AiAbilityDecision(100, AiPlayDecision.WillPlay) : new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
} else if ("ExileGraveyards".equals(aiLogic)) {
|
} else if ("ExileGraveyards".equals(aiLogic)) {
|
||||||
for (Player opp : ai.getOpponents()) {
|
for (Player opp : ai.getOpponents()) {
|
||||||
CardCollectionView cardsGY = opp.getCardsIn(ZoneType.Graveyard);
|
CardCollectionView cardsGY = opp.getCardsIn(ZoneType.Graveyard);
|
||||||
CardCollection creats = CardLists.filter(cardsGY, CardPredicates.CREATURES);
|
CardCollection creats = CardLists.filter(cardsGY, CardPredicates.CREATURES);
|
||||||
|
|
||||||
if (opp.hasDelirium() || opp.hasThreshold() || creats.size() >= 5) {
|
if (opp.hasDelirium() || opp.hasThreshold() || creats.size() >= 5) {
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
} else if ("ManifestCreatsFromGraveyard".equals(aiLogic)) {
|
} else if ("ManifestCreatsFromGraveyard".equals(aiLogic)) {
|
||||||
PlayerCollection players = ai.getOpponents();
|
PlayerCollection players = ai.getOpponents();
|
||||||
players.add(ai);
|
players.add(ai);
|
||||||
@@ -98,68 +98,48 @@ public class ChangeZoneAllAi extends SpellAbilityAi {
|
|||||||
bestTgt = player;
|
bestTgt = player;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bestTgt != null) {
|
if (bestTgt != null) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
sa.getTargets().add(bestTgt);
|
sa.getTargets().add(bestTgt);
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO improve restrictions on when the AI would want to use this
|
// TODO improve restrictions on when the AI would want to use this
|
||||||
// spBounceAll has some AI we can compare to.
|
// spBounceAll has some AI we can compare to.
|
||||||
if (origin.equals(ZoneType.Hand) || origin.equals(ZoneType.Library)) {
|
if (origin.equals(ZoneType.Hand) || origin.equals(ZoneType.Library)) {
|
||||||
if (!sa.usesTargeting()) {
|
if (!sa.usesTargeting()) {
|
||||||
// TODO: improve logic for non-targeted SAs of this type (most are currently AI:RemoveDeck:All, e.g. Memory Jar)
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
return true;
|
|
||||||
} else {
|
} else {
|
||||||
// search targetable Opponents
|
|
||||||
final PlayerCollection oppList = ai.getOpponents().filter(PlayerPredicates.isTargetableBy(sa));
|
final PlayerCollection oppList = ai.getOpponents().filter(PlayerPredicates.isTargetableBy(sa));
|
||||||
|
|
||||||
if (oppList.isEmpty()) {
|
if (oppList.isEmpty()) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlaySa);
|
||||||
}
|
}
|
||||||
|
|
||||||
// get the one with the most handsize
|
|
||||||
Player oppTarget = oppList.max(PlayerPredicates.compareByZoneSize(origin));
|
Player oppTarget = oppList.max(PlayerPredicates.compareByZoneSize(origin));
|
||||||
|
|
||||||
// set the target
|
|
||||||
if (!oppTarget.getCardsIn(ZoneType.Hand).isEmpty()) {
|
if (!oppTarget.getCardsIn(ZoneType.Hand).isEmpty()) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
sa.getTargets().add(oppTarget);
|
sa.getTargets().add(oppTarget);
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlaySa);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (origin.equals(ZoneType.Battlefield)) {
|
} else if (origin.equals(ZoneType.Battlefield)) {
|
||||||
// this statement is assuming the AI is trying to use this spell offensively
|
|
||||||
// if the AI is using it defensively, then something else needs to occur
|
|
||||||
// if only creatures are affected evaluate both lists and pass only
|
|
||||||
// if human creatures are more valuable
|
|
||||||
if (sa.usesTargeting()) {
|
if (sa.usesTargeting()) {
|
||||||
// search targetable Opponents
|
|
||||||
final PlayerCollection oppList = ai.getOpponents().filter(PlayerPredicates.isTargetableBy(sa));
|
final PlayerCollection oppList = ai.getOpponents().filter(PlayerPredicates.isTargetableBy(sa));
|
||||||
|
|
||||||
if (oppList.isEmpty()) {
|
if (oppList.isEmpty()) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlaySa);
|
||||||
}
|
}
|
||||||
|
|
||||||
// get the one with the most in graveyard
|
|
||||||
// zone is visible so evaluate which would be hurt the most
|
|
||||||
Player oppTarget = oppList.max(PlayerPredicates.compareByZoneSize(origin));
|
Player oppTarget = oppList.max(PlayerPredicates.compareByZoneSize(origin));
|
||||||
|
|
||||||
// set the target
|
|
||||||
if (oppTarget.getCardsIn(ZoneType.Graveyard).isEmpty()) {
|
if (oppTarget.getCardsIn(ZoneType.Graveyard).isEmpty()) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
sa.getTargets().add(oppTarget);
|
sa.getTargets().add(oppTarget);
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlaySa);
|
||||||
}
|
}
|
||||||
computerType = new CardCollection();
|
computerType = new CardCollection();
|
||||||
}
|
}
|
||||||
|
|
||||||
int creatureEvalThreshold = 200; // value difference (in evaluateCreatureList units)
|
int creatureEvalThreshold = 200; // value difference (in evaluateCreatureList units)
|
||||||
int nonCreatureEvalThreshold = 3; // CMC difference
|
int nonCreatureEvalThreshold = 3; // CMC difference
|
||||||
if (ai.getController().isAI()) {
|
if (ai.getController().isAI()) {
|
||||||
@@ -181,103 +161,80 @@ public class ChangeZoneAllAi extends SpellAbilityAi {
|
|||||||
&& game.getPhaseHandler().getPlayerTurn().isOpponentOf(ai)) {
|
&& game.getPhaseHandler().getPlayerTurn().isOpponentOf(ai)) {
|
||||||
// Life is in serious danger, return all creatures from the battlefield to wherever
|
// Life is in serious danger, return all creatures from the battlefield to wherever
|
||||||
// so they don't deal lethal damage
|
// so they don't deal lethal damage
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ((ComputerUtilCard.evaluateCreatureList(computerType) + creatureEvalThreshold) >= ComputerUtilCard
|
if ((ComputerUtilCard.evaluateCreatureList(computerType) + creatureEvalThreshold) >= ComputerUtilCard
|
||||||
.evaluateCreatureList(oppType)) {
|
.evaluateCreatureList(oppType)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
} // mass zone change for non-creatures: evaluate both lists by CMC and pass only if human
|
} else if ((ComputerUtilCard.evaluatePermanentList(computerType) + nonCreatureEvalThreshold) >= ComputerUtilCard
|
||||||
// permanents are more valuable
|
|
||||||
else if ((ComputerUtilCard.evaluatePermanentList(computerType) + nonCreatureEvalThreshold) >= ComputerUtilCard
|
|
||||||
.evaluatePermanentList(oppType)) {
|
.evaluatePermanentList(oppType)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Don't cast during main1?
|
|
||||||
if (game.getPhaseHandler().is(PhaseType.MAIN1, ai) && !aiLogic.equals("Main1")) {
|
if (game.getPhaseHandler().is(PhaseType.MAIN1, ai) && !aiLogic.equals("Main1")) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TimingRestrictions);
|
||||||
}
|
}
|
||||||
} else if (origin.equals(ZoneType.Graveyard)) {
|
} else if (origin.equals(ZoneType.Graveyard)) {
|
||||||
if (sa.usesTargeting()) {
|
if (sa.usesTargeting()) {
|
||||||
// search targetable Opponents
|
|
||||||
final PlayerCollection oppList = ai.getOpponents().filter(PlayerPredicates.isTargetableBy(sa));
|
final PlayerCollection oppList = ai.getOpponents().filter(PlayerPredicates.isTargetableBy(sa));
|
||||||
|
|
||||||
if (oppList.isEmpty()) {
|
if (oppList.isEmpty()) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlaySa);
|
||||||
}
|
}
|
||||||
|
|
||||||
// get the one with the most in graveyard
|
|
||||||
// zone is visible so evaluate which would be hurt the most
|
|
||||||
Player oppTarget = Collections.max(oppList, AiPlayerPredicates.compareByZoneValue(sa.getParam("ChangeType"), origin, sa));
|
Player oppTarget = Collections.max(oppList, AiPlayerPredicates.compareByZoneValue(sa.getParam("ChangeType"), origin, sa));
|
||||||
|
|
||||||
// set the target
|
|
||||||
if (!oppTarget.getCardsIn(ZoneType.Graveyard).isEmpty()) {
|
if (!oppTarget.getCardsIn(ZoneType.Graveyard).isEmpty()) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
sa.getTargets().add(oppTarget);
|
sa.getTargets().add(oppTarget);
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlaySa);
|
||||||
}
|
}
|
||||||
} else if (destination.equals(ZoneType.Library) && "Card.YouOwn".equals(sa.getParam("ChangeType"))) {
|
} else if (destination.equals(ZoneType.Library) && "Card.YouOwn".equals(sa.getParam("ChangeType"))) {
|
||||||
return (ai.getCardsIn(ZoneType.Graveyard).size() > ai.getCardsIn(ZoneType.Library).size())
|
boolean result = (ai.getCardsIn(ZoneType.Graveyard).size() > ai.getCardsIn(ZoneType.Library).size())
|
||||||
&& !ComputerUtil.isPlayingReanimator(ai);
|
&& !ComputerUtil.isPlayingReanimator(ai);
|
||||||
|
return result ? new AiAbilityDecision(100, AiPlayDecision.WillPlay) : new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
} else if (origin.equals(ZoneType.Exile)) {
|
} else if (origin.equals(ZoneType.Exile)) {
|
||||||
if (aiLogic.startsWith("DiscardAllAndRetExiled")) {
|
if (aiLogic.startsWith("DiscardAllAndRetExiled")) {
|
||||||
int numExiledWithSrc = CardLists.filter(ai.getCardsIn(ZoneType.Exile), CardPredicates.isExiledWith(source)).size();
|
int numExiledWithSrc = CardLists.filter(ai.getCardsIn(ZoneType.Exile), CardPredicates.isExiledWith(source)).size();
|
||||||
int curHandSize = ai.getCardsIn(ZoneType.Hand).size();
|
int curHandSize = ai.getCardsIn(ZoneType.Hand).size();
|
||||||
|
|
||||||
// minimum card advantage unless the hand will be fully reloaded
|
|
||||||
int minAdv = aiLogic.contains(".minAdv") ? Integer.parseInt(aiLogic.substring(aiLogic.indexOf(".minAdv") + 7)) : 0;
|
int minAdv = aiLogic.contains(".minAdv") ? Integer.parseInt(aiLogic.substring(aiLogic.indexOf(".minAdv") + 7)) : 0;
|
||||||
boolean noDiscard = aiLogic.contains(".noDiscard");
|
boolean noDiscard = aiLogic.contains(".noDiscard");
|
||||||
|
|
||||||
if (numExiledWithSrc > curHandSize || (noDiscard && numExiledWithSrc > 0)) {
|
if (numExiledWithSrc > curHandSize || (noDiscard && numExiledWithSrc > 0)) {
|
||||||
if (ComputerUtil.predictThreatenedObjects(ai, sa, true).contains(source)) {
|
if (ComputerUtil.predictThreatenedObjects(ai, sa, true).contains(source)) {
|
||||||
// Try to gain some card advantage if the card will die anyway
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
// TODO: ideally, should evaluate the hand value and not discard good hands to it
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
boolean result = (curHandSize + minAdv - 1 < numExiledWithSrc) || (!noDiscard && numExiledWithSrc >= ai.getMaxHandSize());
|
||||||
return (curHandSize + minAdv - 1 < numExiledWithSrc) || (!noDiscard && numExiledWithSrc >= ai.getMaxHandSize());
|
return result ? new AiAbilityDecision(100, AiPlayDecision.WillPlay) : new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
} else if (origin.equals(ZoneType.Stack)) {
|
} else if (origin.equals(ZoneType.Stack)) {
|
||||||
// TODO
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (destination.equals(ZoneType.Battlefield)) {
|
if (destination.equals(ZoneType.Battlefield)) {
|
||||||
if (sa.hasParam("GainControl")) {
|
if (sa.hasParam("GainControl")) {
|
||||||
// Check if the cards are valuable enough
|
|
||||||
if (CardLists.getNotType(oppType, "Creature").isEmpty() && CardLists.getNotType(computerType, "Creature").isEmpty()) {
|
if (CardLists.getNotType(oppType, "Creature").isEmpty() && CardLists.getNotType(computerType, "Creature").isEmpty()) {
|
||||||
if ((ComputerUtilCard.evaluateCreatureList(computerType) + ComputerUtilCard
|
if ((ComputerUtilCard.evaluateCreatureList(computerType) + ComputerUtilCard
|
||||||
.evaluateCreatureList(oppType)) < 400) {
|
.evaluateCreatureList(oppType)) < 400) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
} // otherwise evaluate both lists by CMC and pass only if human
|
} else if ((ComputerUtilCard.evaluatePermanentList(computerType) + ComputerUtilCard
|
||||||
// permanents are less valuable
|
|
||||||
else if ((ComputerUtilCard.evaluatePermanentList(computerType) + ComputerUtilCard
|
|
||||||
.evaluatePermanentList(oppType)) < 6) {
|
.evaluatePermanentList(oppType)) < 6) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// don't activate if human gets more back than AI does
|
|
||||||
if (CardLists.getNotType(oppType, "Creature").isEmpty() && CardLists.getNotType(computerType, "Creature").isEmpty()) {
|
if (CardLists.getNotType(oppType, "Creature").isEmpty() && CardLists.getNotType(computerType, "Creature").isEmpty()) {
|
||||||
if (ComputerUtilCard.evaluateCreatureList(computerType) <= (ComputerUtilCard
|
if (ComputerUtilCard.evaluateCreatureList(computerType) <= (ComputerUtilCard
|
||||||
.evaluateCreatureList(oppType) + 100)) {
|
.evaluateCreatureList(oppType) + 100)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
} // otherwise evaluate both lists by CMC and pass only if human
|
} else if (ComputerUtilCard.evaluatePermanentList(computerType) <= (ComputerUtilCard
|
||||||
// permanents are less valuable
|
|
||||||
else if (ComputerUtilCard.evaluatePermanentList(computerType) <= (ComputerUtilCard
|
|
||||||
.evaluatePermanentList(oppType) + 2)) {
|
.evaluatePermanentList(oppType) + 2)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
boolean result = ((MyRandom.getRandom().nextFloat() < .8) || sa.isTrigger()) && chance;
|
||||||
return (((MyRandom.getRandom().nextFloat() < .8) || sa.isTrigger()) && chance);
|
return result ? new AiAbilityDecision(100, AiPlayDecision.WillPlay) : new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -292,11 +249,11 @@ public class ChangeZoneAllAi extends SpellAbilityAi {
|
|||||||
* @return a boolean.
|
* @return a boolean.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
|
public AiAbilityDecision chkDrawback(SpellAbility sa, Player aiPlayer) {
|
||||||
// if putting cards from hand to library and parent is drawing cards
|
// if putting cards from hand to library and parent is drawing cards
|
||||||
// make sure this will actually do something:
|
// make sure this will actually do something:
|
||||||
|
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* (non-Javadoc)
|
/* (non-Javadoc)
|
||||||
@@ -328,127 +285,92 @@ public class ChangeZoneAllAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player ai, final SpellAbility sa, boolean mandatory) {
|
protected AiAbilityDecision doTriggerNoCost(Player ai, final SpellAbility sa, boolean mandatory) {
|
||||||
// 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 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);
|
||||||
|
|
||||||
if (ComputerUtilAbility.getAbilitySourceName(sa).equals("Profaner of the Dead")) {
|
if (ComputerUtilAbility.getAbilitySourceName(sa).equals("Profaner of the Dead")) {
|
||||||
// TODO: this is a stub to prevent the AI from crashing the game when, for instance, playing the opponent's
|
boolean result = ai.getOpponents().getCardsIn(origin).anyMatch(CardPredicates.CREATURES);
|
||||||
// Profaner from exile without paying its mana cost. Otherwise the card is marked AI:RemoveDeck:All and
|
return result ? new AiAbilityDecision(100, AiPlayDecision.WillPlay) : new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
// there is no specific AI to support playing it in a smarter way. Feel free to expand.
|
|
||||||
return ai.getOpponents().getCardsIn(origin).anyMatch(CardPredicates.CREATURES);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
CardCollectionView humanType = ai.getOpponents().getCardsIn(origin);
|
CardCollectionView humanType = ai.getOpponents().getCardsIn(origin);
|
||||||
humanType = AbilityUtils.filterListByType(humanType, sa.getParam("ChangeType"), sa);
|
humanType = AbilityUtils.filterListByType(humanType, sa.getParam("ChangeType"), sa);
|
||||||
|
|
||||||
CardCollectionView computerType = ai.getCardsIn(origin);
|
CardCollectionView computerType = ai.getCardsIn(origin);
|
||||||
computerType = AbilityUtils.filterListByType(computerType, sa.getParam("ChangeType"), sa);
|
computerType = AbilityUtils.filterListByType(computerType, sa.getParam("ChangeType"), sa);
|
||||||
|
|
||||||
// TODO improve restrictions on when the AI would want to use this
|
|
||||||
// spBounceAll has some AI we can compare to.
|
|
||||||
if (origin.equals(ZoneType.Hand) || origin.equals(ZoneType.Library)) {
|
if (origin.equals(ZoneType.Hand) || origin.equals(ZoneType.Library)) {
|
||||||
if (sa.usesTargeting()) {
|
if (sa.usesTargeting()) {
|
||||||
// search targetable Opponents
|
|
||||||
final PlayerCollection oppList = ai.getOpponents().filter(PlayerPredicates.isTargetableBy(sa));
|
final PlayerCollection oppList = ai.getOpponents().filter(PlayerPredicates.isTargetableBy(sa));
|
||||||
|
|
||||||
if (oppList.isEmpty()) {
|
if (oppList.isEmpty()) {
|
||||||
if (mandatory && !sa.isTargetNumberValid() && sa.canTarget(ai)) {
|
if (mandatory && !sa.isTargetNumberValid() && sa.canTarget(ai)) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
sa.getTargets().add(ai);
|
sa.getTargets().add(ai);
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlaySa);
|
||||||
}
|
}
|
||||||
|
|
||||||
// get the one with the most handsize
|
|
||||||
Player oppTarget = oppList.max(PlayerPredicates.compareByZoneSize(origin));
|
Player oppTarget = oppList.max(PlayerPredicates.compareByZoneSize(origin));
|
||||||
|
|
||||||
// set the target
|
|
||||||
if (!oppTarget.getCardsIn(ZoneType.Hand).isEmpty() || mandatory) {
|
if (!oppTarget.getCardsIn(ZoneType.Hand).isEmpty() || mandatory) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
sa.getTargets().add(oppTarget);
|
sa.getTargets().add(oppTarget);
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlaySa);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (origin.equals(ZoneType.Battlefield)) {
|
} else if (origin.equals(ZoneType.Battlefield)) {
|
||||||
// if mandatory, no need to evaluate
|
|
||||||
if (mandatory) {
|
if (mandatory) {
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
// this statement is assuming the AI is trying to use this spell offensively
|
|
||||||
// if the AI is using it defensively, then something else needs to occur
|
|
||||||
// if only creatures are affected evaluate both lists and pass only
|
|
||||||
// if human creatures are more valuable
|
|
||||||
if (CardLists.getNotType(humanType, "Creature").isEmpty() && CardLists.getNotType(computerType, "Creature").isEmpty()) {
|
if (CardLists.getNotType(humanType, "Creature").isEmpty() && CardLists.getNotType(computerType, "Creature").isEmpty()) {
|
||||||
if (ComputerUtilCard.evaluateCreatureList(computerType) >= ComputerUtilCard.evaluateCreatureList(humanType)) {
|
if (ComputerUtilCard.evaluateCreatureList(computerType) >= ComputerUtilCard.evaluateCreatureList(humanType)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
} // otherwise evaluate both lists by CMC and pass only if human
|
} else if (ComputerUtilCard.evaluatePermanentList(computerType) >= ComputerUtilCard.evaluatePermanentList(humanType)) {
|
||||||
// permanents are more valuable
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
else if (ComputerUtilCard.evaluatePermanentList(computerType) >= ComputerUtilCard.evaluatePermanentList(humanType)) {
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
} else if (origin.equals(ZoneType.Graveyard)) {
|
} else if (origin.equals(ZoneType.Graveyard)) {
|
||||||
if (sa.usesTargeting()) {
|
if (sa.usesTargeting()) {
|
||||||
// search targetable Opponents
|
|
||||||
final PlayerCollection oppList = ai.getOpponents().filter(PlayerPredicates.isTargetableBy(sa));
|
final PlayerCollection oppList = ai.getOpponents().filter(PlayerPredicates.isTargetableBy(sa));
|
||||||
|
|
||||||
if (oppList.isEmpty()) {
|
if (oppList.isEmpty()) {
|
||||||
if (mandatory && !sa.isTargetNumberValid() && sa.canTarget(ai)) {
|
if (mandatory && !sa.isTargetNumberValid() && sa.canTarget(ai)) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
sa.getTargets().add(ai);
|
sa.getTargets().add(ai);
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
return sa.isTargetNumberValid();
|
return sa.isTargetNumberValid() ? new AiAbilityDecision(100, AiPlayDecision.WillPlay) : new AiAbilityDecision(0, AiPlayDecision.CantPlaySa);
|
||||||
}
|
}
|
||||||
|
|
||||||
// get the one with the most in graveyard
|
|
||||||
// zone is visible so evaluate which would be hurt the most
|
|
||||||
Player oppTarget = oppList.max(
|
Player oppTarget = oppList.max(
|
||||||
AiPlayerPredicates.compareByZoneValue(sa.getParam("ChangeType"), origin, sa));
|
AiPlayerPredicates.compareByZoneValue(sa.getParam("ChangeType"), origin, sa));
|
||||||
|
|
||||||
// set the target
|
|
||||||
if (!oppTarget.getCardsIn(ZoneType.Graveyard).isEmpty() || mandatory) {
|
if (!oppTarget.getCardsIn(ZoneType.Graveyard).isEmpty() || mandatory) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
sa.getTargets().add(oppTarget);
|
sa.getTargets().add(oppTarget);
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlaySa);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (origin.equals(ZoneType.Exile)) {
|
|
||||||
|
|
||||||
} else if (origin.equals(ZoneType.Stack)) {
|
|
||||||
// currently only exists indirectly (e.g. Summary Dismissal via PlayAi)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (destination.equals(ZoneType.Battlefield)) {
|
if (destination.equals(ZoneType.Battlefield)) {
|
||||||
// if mandatory, no need to evaluate
|
|
||||||
if (mandatory) {
|
if (mandatory) {
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
if (sa.hasParam("GainControl")) {
|
if (sa.hasParam("GainControl")) {
|
||||||
// Check if the cards are valuable enough
|
|
||||||
if (CardLists.getNotType(humanType, "Creature").isEmpty() && CardLists.getNotType(computerType, "Creature").isEmpty()) {
|
if (CardLists.getNotType(humanType, "Creature").isEmpty() && CardLists.getNotType(computerType, "Creature").isEmpty()) {
|
||||||
return (ComputerUtilCard.evaluateCreatureList(computerType) + ComputerUtilCard.evaluateCreatureList(humanType)) >= 1;
|
boolean result = (ComputerUtilCard.evaluateCreatureList(computerType) + ComputerUtilCard.evaluateCreatureList(humanType)) >= 1;
|
||||||
} // otherwise evaluate both lists by CMC and pass only if human
|
return result ? new AiAbilityDecision(100, AiPlayDecision.WillPlay) : new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
// permanents are less valuable
|
}
|
||||||
return (ComputerUtilCard.evaluatePermanentList(computerType) + ComputerUtilCard
|
boolean result = (ComputerUtilCard.evaluatePermanentList(computerType) + ComputerUtilCard
|
||||||
.evaluatePermanentList(humanType)) >= 1;
|
.evaluatePermanentList(humanType)) >= 1;
|
||||||
|
return result ? new AiAbilityDecision(100, AiPlayDecision.WillPlay) : new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
// don't activate if human gets more back than AI does
|
|
||||||
if (CardLists.getNotType(humanType, "Creature").isEmpty() && CardLists.getNotType(computerType, "Creature").isEmpty()) {
|
if (CardLists.getNotType(humanType, "Creature").isEmpty() && CardLists.getNotType(computerType, "Creature").isEmpty()) {
|
||||||
return ComputerUtilCard.evaluateCreatureList(computerType) > ComputerUtilCard.evaluateCreatureList(humanType);
|
boolean result = ComputerUtilCard.evaluateCreatureList(computerType) > ComputerUtilCard.evaluateCreatureList(humanType);
|
||||||
} // otherwise evaluate both lists by CMC and pass only if human
|
return result ? new AiAbilityDecision(100, AiPlayDecision.WillPlay) : new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
// permanents are less valuable
|
}
|
||||||
return ComputerUtilCard.evaluatePermanentList(computerType) > ComputerUtilCard.evaluatePermanentList(humanType);
|
boolean result = ComputerUtilCard.evaluatePermanentList(computerType) > ComputerUtilCard.evaluatePermanentList(humanType);
|
||||||
|
return result ? new AiAbilityDecision(100, AiPlayDecision.WillPlay) : new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import java.util.Map;
|
|||||||
|
|
||||||
public class CharmAi extends SpellAbilityAi {
|
public class CharmAi extends SpellAbilityAi {
|
||||||
@Override
|
@Override
|
||||||
protected boolean checkApiLogic(Player ai, SpellAbility sa) {
|
protected AiAbilityDecision checkApiLogic(Player ai, SpellAbility sa) {
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
List<AbilitySub> choices = CharmEffect.makePossibleOptions(sa);
|
List<AbilitySub> choices = CharmEffect.makePossibleOptions(sa);
|
||||||
|
|
||||||
@@ -70,10 +70,10 @@ public class CharmAi extends SpellAbilityAi {
|
|||||||
// Set minimum choices for triggers where chooseMultipleOptionsAi() returns null
|
// Set minimum choices for triggers where chooseMultipleOptionsAi() returns null
|
||||||
chosenList = chooseOptionsAi(sa, choices, ai, true, num, min);
|
chosenList = chooseOptionsAi(sa, choices, ai, true, num, min);
|
||||||
if (chosenList.isEmpty() && min != 0) {
|
if (chosenList.isEmpty() && min != 0) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,7 +81,7 @@ public class CharmAi extends SpellAbilityAi {
|
|||||||
sa.setChosenList(chosenList);
|
sa.setChosenList(chosenList);
|
||||||
|
|
||||||
if (choiceForOpp) {
|
if (choiceForOpp) {
|
||||||
return true;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sa.isSpell()) {
|
if (sa.isSpell()) {
|
||||||
@@ -90,7 +90,11 @@ public class CharmAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// prevent run-away activations - first time will always return true
|
// prevent run-away activations - first time will always return true
|
||||||
return MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
|
if (MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn())) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<AbilitySub> chooseOptionsAi(SpellAbility sa, List<AbilitySub> choices, final Player ai, boolean isTrigger, int num, int min) {
|
private List<AbilitySub> chooseOptionsAi(SpellAbility sa, List<AbilitySub> choices, final Player ai, boolean isTrigger, int num, int min) {
|
||||||
@@ -276,10 +280,10 @@ public class CharmAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean chkDrawbackWithSubs(Player aiPlayer, AbilitySub ab) {
|
public AiAbilityDecision chkDrawbackWithSubs(Player aiPlayer, AbilitySub ab) {
|
||||||
// choices were already targeted
|
// choices were already targeted
|
||||||
if (ab.getRootAbility().getChosenList() != null) {
|
if (ab.getRootAbility().getChosenList() != null) {
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
return super.chkDrawbackWithSubs(aiPlayer, ab);
|
return super.chkDrawbackWithSubs(aiPlayer, ab);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import forge.game.Game;
|
|||||||
import forge.game.card.*;
|
import forge.game.card.*;
|
||||||
import forge.game.combat.Combat;
|
import forge.game.combat.Combat;
|
||||||
import forge.game.keyword.Keyword;
|
import forge.game.keyword.Keyword;
|
||||||
import forge.game.phase.PhaseHandler;
|
|
||||||
import forge.game.phase.PhaseType;
|
import forge.game.phase.PhaseType;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.player.PlayerPredicates;
|
import forge.game.player.PlayerPredicates;
|
||||||
@@ -26,19 +25,19 @@ public class ChooseCardAi extends SpellAbilityAi {
|
|||||||
* The rest of the logic not covered by the canPlayAI template is defined here
|
* The rest of the logic not covered by the canPlayAI template is defined here
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
|
protected AiAbilityDecision checkApiLogic(final Player ai, final SpellAbility sa) {
|
||||||
if (sa.usesTargeting()) {
|
if (sa.usesTargeting()) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
// search targetable Opponents
|
// search targetable Opponents
|
||||||
final List<Player> oppList = ai.getOpponents().filter(PlayerPredicates.isTargetableBy(sa));
|
final List<Player> oppList = ai.getOpponents().filter(PlayerPredicates.isTargetableBy(sa));
|
||||||
|
|
||||||
if (oppList.isEmpty()) {
|
if (oppList.isEmpty()) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
sa.getTargets().add(Iterables.getFirst(oppList, null));
|
sa.getTargets().add(Iterables.getFirst(oppList, null));
|
||||||
}
|
}
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -135,23 +134,14 @@ public class ChooseCardAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
public AiAbilityDecision chkDrawback(SpellAbility sa, Player ai) {
|
||||||
if (sa.hasParam("AILogic") && !checkAiLogic(ai, sa, sa.getParam("AILogic"))) {
|
if (sa.hasParam("AILogic") && !checkAiLogic(ai, sa, sa.getParam("AILogic"))) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
return checkApiLogic(ai, sa);
|
return checkApiLogic(ai, sa);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected boolean checkPhaseRestrictions(Player ai, SpellAbility sa, PhaseHandler ph) {
|
|
||||||
String aiLogic = sa.getParamOrDefault("AILogic", "");
|
|
||||||
|
|
||||||
if (aiLogic.equals("AtOppEOT")) {
|
|
||||||
return ph.getNextTurn().equals(ai) && ph.is(PhaseType.END_OF_TURN);
|
|
||||||
}
|
|
||||||
|
|
||||||
return super.checkPhaseRestrictions(ai, sa, ph);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc)
|
/* (non-Javadoc)
|
||||||
* @see forge.card.ability.SpellAbilityAi#chooseSingleCard(forge.card.spellability.SpellAbility, java.util.List, boolean)
|
* @see forge.card.ability.SpellAbilityAi#chooseSingleCard(forge.card.spellability.SpellAbility, java.util.List, boolean)
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -22,16 +22,20 @@ import java.util.Map;
|
|||||||
public class ChooseCardNameAi extends SpellAbilityAi {
|
public class ChooseCardNameAi extends SpellAbilityAi {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
protected AiAbilityDecision canPlay(Player ai, SpellAbility sa) {
|
||||||
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)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.WaitForCombat);
|
||||||
}
|
}
|
||||||
|
|
||||||
String logic = sa.getParam("AILogic");
|
String logic = sa.getParam("AILogic");
|
||||||
if (logic.equals("CursedScroll")) {
|
if (logic.equals("CursedScroll")) {
|
||||||
return SpecialCardAi.CursedScroll.consider(ai, sa);
|
if (SpecialCardAi.CursedScroll.consider(ai, sa)) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
} else {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
@@ -43,13 +47,13 @@ public class ChooseCardNameAi extends SpellAbilityAi {
|
|||||||
sa.getTargets().add(ai);
|
sa.getTargets().add(ai);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
protected AiAbilityDecision doTriggerNoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
String aiLogic = sa.getParamOrDefault("AILogic", "");
|
String aiLogic = sa.getParamOrDefault("AILogic", "");
|
||||||
if ("PithingNeedle".equals(aiLogic)) {
|
if ("PithingNeedle".equals(aiLogic)) {
|
||||||
// Make sure theres something in play worth Needlings.
|
// Make sure theres something in play worth Needlings.
|
||||||
@@ -57,18 +61,29 @@ public class ChooseCardNameAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
CardCollection oppPerms = CardLists.getValidCards(ai.getOpponents().getCardsIn(ZoneType.Battlefield), "Card.OppCtrl+hasNonManaActivatedAbility", ai, sa.getHostCard(), sa);
|
CardCollection oppPerms = CardLists.getValidCards(ai.getOpponents().getCardsIn(ZoneType.Battlefield), "Card.OppCtrl+hasNonManaActivatedAbility", ai, sa.getHostCard(), sa);
|
||||||
if (oppPerms.isEmpty()) {
|
if (oppPerms.isEmpty()) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.MissingNeededCards);
|
||||||
}
|
}
|
||||||
|
|
||||||
Card card = ComputerUtilCard.getBestPlaneswalkerAI(oppPerms);
|
Card card = ComputerUtilCard.getBestPlaneswalkerAI(oppPerms);
|
||||||
if (card != null) {
|
if (card != null) {
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5 percent chance to cast per opposing card with a non mana ability
|
// 5 percent chance to cast per opposing card with a non mana ability
|
||||||
return MyRandom.getRandom().nextFloat() <= .05 * oppPerms.size();
|
if (MyRandom.getRandom().nextFloat() <= .05 * oppPerms.size()) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
} else {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mandatory) {
|
||||||
|
// If mandatory, then we will play it.
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
} else {
|
||||||
|
// If not mandatory, then we won't play it.
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
return mandatory;
|
|
||||||
}
|
}
|
||||||
/* (non-Javadoc)
|
/* (non-Javadoc)
|
||||||
* @see forge.card.ability.SpellAbilityAi#chooseSingleCard(forge.card.spellability.SpellAbility, java.util.List, boolean)
|
* @see forge.card.ability.SpellAbilityAi#chooseSingleCard(forge.card.spellability.SpellAbility, java.util.List, boolean)
|
||||||
|
|||||||
@@ -11,40 +11,45 @@ import forge.game.phase.PhaseType;
|
|||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
import forge.util.MyRandom;
|
|
||||||
|
|
||||||
public class ChooseColorAi extends SpellAbilityAi {
|
public class ChooseColorAi extends SpellAbilityAi {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
protected AiAbilityDecision checkApiLogic(Player ai, SpellAbility sa) {
|
||||||
final Game game = ai.getGame();
|
final Game game = ai.getGame();
|
||||||
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
|
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
|
||||||
final PhaseHandler ph = game.getPhaseHandler();
|
final PhaseHandler ph = game.getPhaseHandler();
|
||||||
|
|
||||||
if (!sa.hasParam("AILogic")) {
|
if (!sa.hasParam("AILogic")) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.MissingLogic);
|
||||||
}
|
}
|
||||||
final String logic = sa.getParam("AILogic");
|
final String logic = sa.getParam("AILogic");
|
||||||
|
|
||||||
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ("Nykthos, Shrine to Nyx".equals(sourceName)) {
|
if ("Nykthos, Shrine to Nyx".equals(sourceName)) {
|
||||||
return SpecialCardAi.NykthosShrineToNyx.consider(ai, sa);
|
if (SpecialCardAi.NykthosShrineToNyx.consider(ai, sa)) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
} else {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ("Oona, Queen of the Fae".equals(sourceName)) {
|
if ("Oona, Queen of the Fae".equals(sourceName)) {
|
||||||
if (ph.isPlayerTurn(ai) || ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)) {
|
if (ph.isPlayerTurn(ai) || ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.AnotherTime);
|
||||||
}
|
}
|
||||||
// Set PayX here to maximum value.
|
// Set PayX here to maximum value.
|
||||||
sa.setXManaCostPaid(ComputerUtilCost.getMaxXValue(sa, ai, false));
|
sa.setXManaCostPaid(ComputerUtilCost.getMaxXValue(sa, ai, false));
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ("Addle".equals(sourceName)) {
|
if ("Addle".equals(sourceName)) {
|
||||||
return !ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS) && !ai.getWeakestOpponent().getCardsIn(ZoneType.Hand).isEmpty();
|
// TODO Why is this not in the AI logic?
|
||||||
|
// Why are we specifying the weakest opponent?
|
||||||
|
if (!ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS) && !ai.getWeakestOpponent().getCardsIn(ZoneType.Hand).isEmpty()) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
} else {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.AnotherTime);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (logic.equals("MostExcessOpponentControls")) {
|
if (logic.equals("MostExcessOpponentControls")) {
|
||||||
@@ -54,10 +59,10 @@ public class ChooseColorAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
int excess = ComputerUtilCard.evaluatePermanentList(opplist) - ComputerUtilCard.evaluatePermanentList(ailist);
|
int excess = ComputerUtilCard.evaluatePermanentList(opplist) - ComputerUtilCard.evaluatePermanentList(ailist);
|
||||||
if (excess > 4) {
|
if (excess > 4) {
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
} else if (logic.equals("MostProminentInComputerDeck")) {
|
} else if (logic.equals("MostProminentInComputerDeck")) {
|
||||||
if ("Astral Cornucopia".equals(sourceName)) {
|
if ("Astral Cornucopia".equals(sourceName)) {
|
||||||
// activate in Main 2 hoping that the extra mana surplus will make a difference
|
// activate in Main 2 hoping that the extra mana surplus will make a difference
|
||||||
@@ -65,22 +70,28 @@ public class ChooseColorAi extends SpellAbilityAi {
|
|||||||
CardCollectionView permanents = CardLists.filter(ai.getCardsIn(ZoneType.Hand),
|
CardCollectionView permanents = CardLists.filter(ai.getCardsIn(ZoneType.Hand),
|
||||||
CardPredicates.NONLAND_PERMANENTS);
|
CardPredicates.NONLAND_PERMANENTS);
|
||||||
|
|
||||||
return permanents.size() > 0 && ph.is(PhaseType.MAIN2, ai);
|
if (!permanents.isEmpty() && ph.is(PhaseType.MAIN2, ai)) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
} else {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.WaitForMain2);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if (logic.equals("HighestDevotionToColor")) {
|
} else if (logic.equals("HighestDevotionToColor")) {
|
||||||
// currently only works more or less reliably in Main2 to cast own spells
|
// currently only works more or less reliably in Main2 to cast own spells
|
||||||
if (!ph.is(PhaseType.MAIN2, ai)) {
|
if (!ph.is(PhaseType.MAIN2, ai)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.WaitForMain2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean chance = MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
return chance;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
protected AiAbilityDecision doTriggerNoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
return mandatory || canPlayAI(ai, sa);
|
if (mandatory) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
}
|
||||||
|
return canPlay(ai, sa);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
import forge.ai.AiAbilityDecision;
|
||||||
|
import forge.ai.AiPlayDecision;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.Direction;
|
import forge.game.Direction;
|
||||||
import forge.game.Game;
|
import forge.game.Game;
|
||||||
@@ -18,11 +20,11 @@ public class ChooseDirectionAi extends SpellAbilityAi {
|
|||||||
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
|
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
protected AiAbilityDecision canPlay(Player ai, SpellAbility sa) {
|
||||||
final String logic = sa.getParam("AILogic");
|
final String logic = sa.getParam("AILogic");
|
||||||
final Game game = sa.getActivatingPlayer().getGame();
|
final Game game = sa.getActivatingPlayer().getGame();
|
||||||
if (logic == null) {
|
if (logic == null) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.MissingLogic);
|
||||||
} else {
|
} else {
|
||||||
if ("Aminatou".equals(logic)) {
|
if ("Aminatou".equals(logic)) {
|
||||||
CardCollection all = CardLists.filter(game.getCardsIn(ZoneType.Battlefield), CardPredicates.NONLAND_PERMANENTS);
|
CardCollection all = CardLists.filter(game.getCardsIn(ZoneType.Battlefield), CardPredicates.NONLAND_PERMANENTS);
|
||||||
@@ -33,19 +35,24 @@ public class ChooseDirectionAi extends SpellAbilityAi {
|
|||||||
CardCollection right = CardLists.filterControlledBy(all, game.getNextPlayerAfter(ai, Direction.Right));
|
CardCollection right = CardLists.filterControlledBy(all, game.getNextPlayerAfter(ai, Direction.Right));
|
||||||
int leftValue = Aggregates.sum(left, Card::getCMC);
|
int leftValue = Aggregates.sum(left, Card::getCMC);
|
||||||
int rightValue = Aggregates.sum(right, Card::getCMC);
|
int rightValue = Aggregates.sum(right, Card::getCMC);
|
||||||
return aiValue <= leftValue && aiValue <= rightValue;
|
if (aiValue <= leftValue && aiValue <= rightValue) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
public AiAbilityDecision chkDrawback(SpellAbility sa, Player ai) {
|
||||||
return canPlayAI(ai, sa);
|
return canPlay(ai, sa);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
protected AiAbilityDecision doTriggerNoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
return mandatory || canPlayAI(ai, sa);
|
if (mandatory) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
}
|
||||||
|
return canPlay(ai, sa);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,18 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
import forge.ai.AiAbilityDecision;
|
||||||
import forge.ai.AiAttackController;
|
import forge.ai.AiAttackController;
|
||||||
|
import forge.ai.AiPlayDecision;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.util.MyRandom;
|
|
||||||
|
|
||||||
public class ChooseEvenOddAi extends SpellAbilityAi {
|
public class ChooseEvenOddAi extends SpellAbilityAi {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
protected AiAbilityDecision checkApiLogic(Player aiPlayer, SpellAbility sa) {
|
||||||
if (!sa.hasParam("AILogic")) {
|
if (!sa.hasParam("AILogic")) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.MissingLogic);
|
||||||
}
|
}
|
||||||
if (sa.usesTargeting()) {
|
if (sa.usesTargeting()) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
@@ -19,16 +20,17 @@ public class ChooseEvenOddAi extends SpellAbilityAi {
|
|||||||
if (sa.canTarget(opp)) {
|
if (sa.canTarget(opp)) {
|
||||||
sa.getTargets().add(opp);
|
sa.getTargets().add(opp);
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
boolean chance = MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
return chance;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
protected AiAbilityDecision doTriggerNoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
return mandatory || canPlayAI(ai, sa);
|
if (mandatory) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
}
|
||||||
|
return canPlay(ai, sa);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import forge.game.Game;
|
|||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
import forge.game.card.CardCollectionView;
|
import forge.game.card.CardCollectionView;
|
||||||
import forge.game.cost.Cost;
|
import forge.game.cost.Cost;
|
||||||
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.AbilitySub;
|
import forge.game.spellability.AbilitySub;
|
||||||
@@ -20,7 +19,6 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
|
||||||
public class ChooseGenericAi extends SpellAbilityAi {
|
public class ChooseGenericAi extends SpellAbilityAi {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -29,13 +27,10 @@ public class ChooseGenericAi extends SpellAbilityAi {
|
|||||||
return true;
|
return true;
|
||||||
} else if ("Pump".equals(aiLogic) || "BestOption".equals(aiLogic)) {
|
} else if ("Pump".equals(aiLogic) || "BestOption".equals(aiLogic)) {
|
||||||
for (AbilitySub sb : sa.getAdditionalAbilityList("Choices")) {
|
for (AbilitySub sb : sa.getAdditionalAbilityList("Choices")) {
|
||||||
if (SpellApiToAi.Converter.get(sb).canPlayAIWithSubs(ai, sb)) {
|
if (SpellApiToAi.Converter.get(sb).canPlayWithSubs(ai, sb).willingToPlay()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if ("AtOppEOT".equals(aiLogic)) {
|
|
||||||
PhaseHandler ph = ai.getGame().getPhaseHandler();
|
|
||||||
return ph.is(PhaseType.END_OF_TURN) && ph.getNextTurn() == ai;
|
|
||||||
} else if ("Always".equals(aiLogic)) {
|
} else if ("Always".equals(aiLogic)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -43,35 +38,46 @@ public class ChooseGenericAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
|
protected AiAbilityDecision checkApiLogic(final Player ai, final SpellAbility sa) {
|
||||||
return sa.hasParam("AILogic");
|
if (sa.hasParam("AILogic")) {
|
||||||
|
// This is equivilant to what was here before but feels bad
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* (non-Javadoc)
|
/* (non-Javadoc)
|
||||||
* @see forge.card.abilityfactory.SpellAiLogic#chkAIDrawback(java.util.Map, forge.card.spellability.SpellAbility, forge.game.player.Player)
|
* @see forge.card.abilityfactory.SpellAiLogic#chkAIDrawback(java.util.Map, forge.card.spellability.SpellAbility, forge.game.player.Player)
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
|
public AiAbilityDecision chkDrawback(SpellAbility sa, Player aiPlayer) {
|
||||||
return sa.isTrigger() ? doTriggerAINoCost(aiPlayer, sa, sa.isMandatory()) : checkApiLogic(aiPlayer, sa);
|
AiAbilityDecision decision;
|
||||||
|
if (sa.isTrigger()) {
|
||||||
|
decision = doTriggerNoCost(aiPlayer, sa, sa.isMandatory());
|
||||||
|
} else {
|
||||||
|
decision = checkApiLogic(aiPlayer, sa);
|
||||||
|
}
|
||||||
|
|
||||||
|
return decision;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(final Player aiPlayer, final SpellAbility sa, final boolean mandatory) {
|
protected AiAbilityDecision doTriggerNoCost(final Player aiPlayer, final SpellAbility sa, final boolean mandatory) {
|
||||||
if ("CombustibleGearhulk".equals(sa.getParam("AILogic")) || "SoulEcho".equals(sa.getParam("AILogic"))) {
|
if ("CombustibleGearhulk".equals(sa.getParam("AILogic")) || "SoulEcho".equals(sa.getParam("AILogic"))) {
|
||||||
for (final Player p : aiPlayer.getOpponents()) {
|
for (final Player p : aiPlayer.getOpponents()) {
|
||||||
if (p.canBeTargetedBy(sa)) {
|
if (p.canBeTargetedBy(sa)) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
sa.getTargets().add(p);
|
sa.getTargets().add(p);
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true; // perhaps the opponent(s) had Sigarda, Heron's Grace or another effect giving hexproof in play, still play the creature as 6/6
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay); // perhaps the opponent(s) had Sigarda, Heron's Grace or another effect giving hexproof in play, still play the creature as 6/6
|
||||||
}
|
}
|
||||||
if (ComputerUtilAbility.getAbilitySourceName(sa).equals("Deathmist Raptor")) {
|
if (ComputerUtilAbility.getAbilitySourceName(sa).equals("Deathmist Raptor")) {
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
return super.doTriggerNoCost(aiPlayer, sa, mandatory);
|
||||||
return super.doTriggerAINoCost(aiPlayer, sa, mandatory);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -262,7 +268,7 @@ public class ChooseGenericAi extends SpellAbilityAi {
|
|||||||
List<SpellAbility> filtered = Lists.newArrayList();
|
List<SpellAbility> filtered = Lists.newArrayList();
|
||||||
// filter first for the spells which can be done
|
// filter first for the spells which can be done
|
||||||
for (SpellAbility sp : spells) {
|
for (SpellAbility sp : spells) {
|
||||||
if (SpellApiToAi.Converter.get(sp).canPlayAIWithSubs(player, sp)) {
|
if (SpellApiToAi.Converter.get(sp).canPlayWithSubs(player, sp).willingToPlay()) {
|
||||||
filtered.add(sp);
|
filtered.add(sp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,21 +1,18 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
import forge.ai.AiAttackController;
|
import forge.ai.*;
|
||||||
import forge.ai.ComputerUtilCard;
|
|
||||||
import forge.ai.SpellAbilityAi;
|
|
||||||
import forge.game.ability.AbilityUtils;
|
import forge.game.ability.AbilityUtils;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.util.MyRandom;
|
|
||||||
|
|
||||||
public class ChooseNumberAi extends SpellAbilityAi {
|
public class ChooseNumberAi extends SpellAbilityAi {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
protected AiAbilityDecision checkApiLogic(Player aiPlayer, SpellAbility sa) {
|
||||||
String aiLogic = sa.getParamOrDefault("AILogic", "");
|
String aiLogic = sa.getParamOrDefault("AILogic", "");
|
||||||
|
|
||||||
if (aiLogic.isEmpty()) {
|
if (aiLogic.isEmpty()) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.MissingLogic);
|
||||||
} else if (aiLogic.equals("SweepCreatures")) {
|
} else if (aiLogic.equals("SweepCreatures")) {
|
||||||
int maxChoiceLimit = AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("Max"), sa);
|
int maxChoiceLimit = AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("Max"), sa);
|
||||||
int ownCreatureCount = aiPlayer.getCreaturesInPlay().size();
|
int ownCreatureCount = aiPlayer.getCreaturesInPlay().size();
|
||||||
@@ -30,17 +27,24 @@ public class ChooseNumberAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (refOpp == null) {
|
if (refOpp == null) {
|
||||||
return false; // no opponent has any creatures
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
int evalAI = ComputerUtilCard.evaluateCreatureList(aiPlayer.getCreaturesInPlay());
|
int evalAI = ComputerUtilCard.evaluateCreatureList(aiPlayer.getCreaturesInPlay());
|
||||||
int evalOpp = ComputerUtilCard.evaluateCreatureList(refOpp.getCreaturesInPlay());
|
int evalOpp = ComputerUtilCard.evaluateCreatureList(refOpp.getCreaturesInPlay());
|
||||||
|
|
||||||
if (aiPlayer.getLifeLostLastTurn() + aiPlayer.getLifeLostThisTurn() == 0 && evalAI > evalOpp) {
|
if (aiPlayer.getLifeLostLastTurn() + aiPlayer.getLifeLostThisTurn() == 0 && evalAI > evalOpp) {
|
||||||
return false; // we're not pressured and our stuff seems better, don't do it yet
|
// we're not pressured and our stuff seems better, don't do it yet
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.AnotherTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
return ownCreatureCount > oppMaxCreatureCount + 2 || ownCreatureCount < Math.min(oppMaxCreatureCount, maxChoiceLimit);
|
if (ownCreatureCount > oppMaxCreatureCount + 2 || ownCreatureCount < Math.min(oppMaxCreatureCount, maxChoiceLimit)) {
|
||||||
|
// we have more creatures than the opponent, or we have less than the opponent but more than the max choice limit
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
} else {
|
||||||
|
// we have less creatures than the opponent and less than the max choice limit
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sa.usesTargeting()) {
|
if (sa.usesTargeting()) {
|
||||||
@@ -49,16 +53,18 @@ public class ChooseNumberAi extends SpellAbilityAi {
|
|||||||
if (sa.canTarget(opp)) {
|
if (sa.canTarget(opp)) {
|
||||||
sa.getTargets().add(opp);
|
sa.getTargets().add(opp);
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
boolean chance = MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
return chance;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
protected AiAbilityDecision doTriggerNoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
return mandatory || canPlayAI(ai, sa);
|
if (mandatory) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
} {
|
||||||
|
return canPlay(ai, sa);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ package forge.ai.ability;
|
|||||||
|
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
|
import forge.ai.AiAbilityDecision;
|
||||||
|
import forge.ai.AiPlayDecision;
|
||||||
import forge.ai.ComputerUtil;
|
import forge.ai.ComputerUtil;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
@@ -15,18 +17,18 @@ import java.util.Map;
|
|||||||
|
|
||||||
public class ChoosePlayerAi extends SpellAbilityAi {
|
public class ChoosePlayerAi extends SpellAbilityAi {
|
||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
protected AiAbilityDecision canPlay(Player ai, SpellAbility sa) {
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
public AiAbilityDecision chkDrawback(SpellAbility sa, Player ai) {
|
||||||
return canPlayAI(ai, sa);
|
return canPlay(ai, sa);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
protected AiAbilityDecision doTriggerNoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
return canPlayAI(ai, sa);
|
return canPlay(ai, sa);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -1,10 +1,7 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
import forge.ai.AiAttackController;
|
import forge.ai.*;
|
||||||
import forge.ai.ComputerUtilCard;
|
|
||||||
import forge.ai.ComputerUtilCombat;
|
|
||||||
import forge.ai.SpellAbilityAi;
|
|
||||||
import forge.game.Game;
|
import forge.game.Game;
|
||||||
import forge.game.GameObject;
|
import forge.game.GameObject;
|
||||||
import forge.game.ability.AbilityUtils;
|
import forge.game.ability.AbilityUtils;
|
||||||
@@ -14,7 +11,6 @@ import forge.game.card.CardCollectionView;
|
|||||||
import forge.game.card.CardLists;
|
import forge.game.card.CardLists;
|
||||||
import forge.game.card.CardPredicates;
|
import forge.game.card.CardPredicates;
|
||||||
import forge.game.combat.Combat;
|
import forge.game.combat.Combat;
|
||||||
import forge.game.cost.Cost;
|
|
||||||
import forge.game.phase.PhaseType;
|
import forge.game.phase.PhaseType;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
@@ -32,21 +28,13 @@ public class ChooseSourceAi extends SpellAbilityAi {
|
|||||||
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
|
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(final Player ai, SpellAbility sa) {
|
protected AiAbilityDecision checkApiLogic(final Player ai, SpellAbility sa) {
|
||||||
// TODO: AI Support! Currently this is copied from AF ChooseCard.
|
// 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
|
// 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 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
|
// to the player because a CoP was pre-activated on it - unless, of course, there's another
|
||||||
// possible reason to attack with that creature).
|
// possible reason to attack with that creature).
|
||||||
final Card host = sa.getHostCard();
|
final Card host = sa.getHostCard();
|
||||||
final Cost abCost = sa.getPayCosts();
|
|
||||||
final Card source = sa.getHostCard();
|
|
||||||
|
|
||||||
if (abCost != null) {
|
|
||||||
if (!willPayCosts(ai, sa, abCost, source)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sa.usesTargeting()) {
|
if (sa.usesTargeting()) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
@@ -54,7 +42,7 @@ public class ChooseSourceAi extends SpellAbilityAi {
|
|||||||
if (sa.canTarget(opp)) {
|
if (sa.canTarget(opp)) {
|
||||||
sa.getTargets().add(opp);
|
sa.getTargets().add(opp);
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (sa.hasParam("AILogic")) {
|
if (sa.hasParam("AILogic")) {
|
||||||
@@ -63,11 +51,11 @@ public class ChooseSourceAi extends SpellAbilityAi {
|
|||||||
if (!game.getStack().isEmpty()) {
|
if (!game.getStack().isEmpty()) {
|
||||||
final SpellAbility topStack = game.getStack().peekAbility();
|
final SpellAbility topStack = game.getStack().peekAbility();
|
||||||
if (sa.hasParam("Choices") && !topStack.matchesValid(topStack.getHostCard(), sa.getParam("Choices").split(","))) {
|
if (sa.hasParam("Choices") && !topStack.matchesValid(topStack.getHostCard(), sa.getParam("Choices").split(","))) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
final ApiType threatApi = topStack.getApi();
|
final ApiType threatApi = topStack.getApi();
|
||||||
if (threatApi != ApiType.DealDamage && threatApi != ApiType.DamageAll) {
|
if (threatApi != ApiType.DealDamage && threatApi != ApiType.DamageAll) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
|
|
||||||
final Card threatSource = topStack.getHostCard();
|
final Card threatSource = topStack.getHostCard();
|
||||||
@@ -79,13 +67,17 @@ public class ChooseSourceAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!objects.contains(ai) || topStack.hasParam("NoPrevention")) {
|
if (!objects.contains(ai) || topStack.hasParam("NoPrevention")) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
int dmg = AbilityUtils.calculateAmount(threatSource, topStack.getParam("NumDmg"), topStack);
|
int dmg = AbilityUtils.calculateAmount(threatSource, topStack.getParam("NumDmg"), topStack);
|
||||||
return ComputerUtilCombat.predictDamageTo(ai, dmg, threatSource, false) > 0;
|
if (ComputerUtilCombat.predictDamageTo(ai, dmg, threatSource, false) > 0) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
} else {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (game.getPhaseHandler().getPhase() != PhaseType.COMBAT_DECLARE_BLOCKERS) {
|
if (game.getPhaseHandler().getPhase() != PhaseType.COMBAT_DECLARE_BLOCKERS) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.AnotherTime);
|
||||||
}
|
}
|
||||||
CardCollectionView choices = game.getCardsIn(ZoneType.Battlefield);
|
CardCollectionView choices = game.getCardsIn(ZoneType.Battlefield);
|
||||||
if (sa.hasParam("Choices")) {
|
if (sa.hasParam("Choices")) {
|
||||||
@@ -98,11 +90,13 @@ public class ChooseSourceAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
return ComputerUtilCombat.damageIfUnblocked(c, ai, combat, true) > 0;
|
return ComputerUtilCombat.damageIfUnblocked(c, ai, combat, true) > 0;
|
||||||
});
|
});
|
||||||
return !choices.isEmpty();
|
if (choices.isEmpty()) {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -21,23 +21,37 @@ import java.util.Set;
|
|||||||
|
|
||||||
public class ChooseTypeAi extends SpellAbilityAi {
|
public class ChooseTypeAi extends SpellAbilityAi {
|
||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
protected AiAbilityDecision canPlay(Player aiPlayer, SpellAbility sa) {
|
||||||
String aiLogic = sa.getParamOrDefault("AILogic", "");
|
String aiLogic = sa.getParamOrDefault("AILogic", "");
|
||||||
|
|
||||||
if (aiLogic.isEmpty()) {
|
if (aiLogic.isEmpty()) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.MissingLogic);
|
||||||
} else if ("MostProminentComputerControls".equals(aiLogic)) {
|
} else if ("MostProminentComputerControls".equals(aiLogic)) {
|
||||||
if (ComputerUtilAbility.getAbilitySourceName(sa).equals("Mirror Entity Avatar")) {
|
if (ComputerUtilAbility.getAbilitySourceName(sa).equals("Mirror Entity Avatar")) {
|
||||||
return doMirrorEntityLogic(aiPlayer, sa);
|
if (doMirrorEntityLogic(aiPlayer, sa)) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
} else {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (!chooseType(sa, aiPlayer.getCardsIn(ZoneType.Battlefield)).isEmpty()) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
} else {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
return !chooseType(sa, aiPlayer.getCardsIn(ZoneType.Battlefield)).isEmpty();
|
|
||||||
} else if ("MostProminentComputerControlsOrOwns".equals(aiLogic)) {
|
} else if ("MostProminentComputerControlsOrOwns".equals(aiLogic)) {
|
||||||
return !chooseType(sa, aiPlayer.getCardsIn(Arrays.asList(ZoneType.Hand, ZoneType.Battlefield))).isEmpty();
|
return !chooseType(sa, aiPlayer.getCardsIn(Arrays.asList(ZoneType.Hand, ZoneType.Battlefield))).isEmpty()
|
||||||
|
? new AiAbilityDecision(100, AiPlayDecision.WillPlay)
|
||||||
|
: new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
} else if ("MostProminentOppControls".equals(aiLogic)) {
|
} else if ("MostProminentOppControls".equals(aiLogic)) {
|
||||||
return !chooseType(sa, aiPlayer.getOpponents().getCardsIn(ZoneType.Battlefield)).isEmpty();
|
return !chooseType(sa, aiPlayer.getOpponents().getCardsIn(ZoneType.Battlefield)).isEmpty()
|
||||||
|
? new AiAbilityDecision(100, AiPlayDecision.WillPlay)
|
||||||
|
: new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
return doTriggerAINoCost(aiPlayer, sa, false);
|
return doTriggerNoCost(aiPlayer, sa, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean doMirrorEntityLogic(Player aiPlayer, SpellAbility sa) {
|
private boolean doMirrorEntityLogic(Player aiPlayer, SpellAbility sa) {
|
||||||
@@ -101,7 +115,7 @@ public class ChooseTypeAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
protected AiAbilityDecision doTriggerNoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
boolean isCurse = sa.isCurse();
|
boolean isCurse = sa.isCurse();
|
||||||
|
|
||||||
if (sa.usesTargeting()) {
|
if (sa.usesTargeting()) {
|
||||||
@@ -133,16 +147,16 @@ public class ChooseTypeAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!sa.isTargetNumberValid()) {
|
if (!sa.isTargetNumberValid()) {
|
||||||
return false; // nothing to target?
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (final Player p : AbilityUtils.getDefinedPlayers(sa.getHostCard(), sa.getParam("Defined"), sa)) {
|
for (final Player p : AbilityUtils.getDefinedPlayers(sa.getHostCard(), sa.getParam("Defined"), sa)) {
|
||||||
if (p.isOpponentOf(ai) && !mandatory && !isCurse) {
|
if (p.isOpponentOf(ai) && !mandatory && !isCurse) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String chooseType(SpellAbility sa, CardCollectionView cards) {
|
private String chooseType(SpellAbility sa, CardCollectionView cards) {
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ package forge.ai.ability;
|
|||||||
|
|
||||||
|
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
|
import forge.ai.AiAbilityDecision;
|
||||||
|
import forge.ai.AiPlayDecision;
|
||||||
import forge.ai.ComputerUtilCard;
|
import forge.ai.ComputerUtilCard;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
@@ -22,14 +24,15 @@ public class ClashAi extends SpellAbilityAi {
|
|||||||
* @see forge.card.abilityfactory.SpellAiLogic#doTriggerAINoCost(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility, boolean)
|
* @see forge.card.abilityfactory.SpellAiLogic#doTriggerAINoCost(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility, boolean)
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
protected AiAbilityDecision doTriggerNoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
||||||
boolean legalAction = true;
|
boolean legalAction = true;
|
||||||
|
|
||||||
if (sa.usesTargeting()) {
|
if (sa.usesTargeting()) {
|
||||||
legalAction = selectTarget(aiPlayer, sa);
|
legalAction = selectTarget(aiPlayer, sa);
|
||||||
}
|
}
|
||||||
|
|
||||||
return legalAction;
|
return legalAction ? new AiAbilityDecision(100, AiPlayDecision.WillPlay)
|
||||||
|
: new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -39,14 +42,17 @@ public class ClashAi extends SpellAbilityAi {
|
|||||||
* forge.game.spellability.SpellAbility)
|
* forge.game.spellability.SpellAbility)
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected boolean checkApiLogic(Player ai, SpellAbility sa) {
|
protected AiAbilityDecision checkApiLogic(Player ai, SpellAbility sa) {
|
||||||
boolean legalAction = true;
|
boolean legalAction = true;
|
||||||
|
|
||||||
if (sa.usesTargeting()) {
|
if (sa.usesTargeting()) {
|
||||||
legalAction = selectTarget(ai, sa);
|
legalAction = selectTarget(ai, sa);
|
||||||
|
if (!legalAction) {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return legalAction;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -104,7 +110,6 @@ public class ClashAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return sa.getTargets().size() > 0;
|
return !sa.getTargets().isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
import forge.ai.AiAbilityDecision;
|
||||||
|
import forge.ai.AiPlayDecision;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.ai.SpellApiToAi;
|
import forge.ai.SpellApiToAi;
|
||||||
import forge.game.ability.AbilityUtils;
|
import forge.game.ability.AbilityUtils;
|
||||||
@@ -12,7 +14,8 @@ import forge.game.trigger.TriggerType;
|
|||||||
|
|
||||||
public class ClassLevelUpAi extends SpellAbilityAi {
|
public class ClassLevelUpAi extends SpellAbilityAi {
|
||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
protected AiAbilityDecision canPlay(Player aiPlayer, SpellAbility sa) {
|
||||||
|
// TODO does leveling up affect combat? Otherwise wait for Main2
|
||||||
Card host = sa.getHostCard();
|
Card host = sa.getHostCard();
|
||||||
final int level = host.getClassLevel() + 1;
|
final int level = host.getClassLevel() + 1;
|
||||||
for (StaticAbility stAb : host.getStaticAbilities()) {
|
for (StaticAbility stAb : host.getStaticAbilities()) {
|
||||||
@@ -25,12 +28,12 @@ public class ClassLevelUpAi extends SpellAbilityAi {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
SpellAbility effect = t.ensureAbility();
|
SpellAbility effect = t.ensureAbility();
|
||||||
if (!SpellApiToAi.Converter.get(effect).doTriggerAI(aiPlayer, effect, false)) {
|
if (!SpellApiToAi.Converter.get(effect).doTrigger(aiPlayer, effect, false)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
import forge.ai.AiAbilityDecision;
|
||||||
|
import forge.ai.AiPlayDecision;
|
||||||
import forge.ai.ComputerUtilCard;
|
import forge.ai.ComputerUtilCard;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.Game;
|
import forge.game.Game;
|
||||||
@@ -18,7 +20,7 @@ import java.util.Map;
|
|||||||
public class CloneAi extends SpellAbilityAi {
|
public class CloneAi extends SpellAbilityAi {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
protected AiAbilityDecision canPlay(Player ai, SpellAbility sa) {
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
final Game game = source.getGame();
|
final Game game = source.getGame();
|
||||||
|
|
||||||
@@ -37,7 +39,7 @@ public class CloneAi extends SpellAbilityAi {
|
|||||||
// "Can I use this to block something?"
|
// "Can I use this to block something?"
|
||||||
|
|
||||||
if (!checkPhaseRestrictions(ai, sa, game.getPhaseHandler())) {
|
if (!checkPhaseRestrictions(ai, sa, game.getPhaseHandler())) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.MissingPhaseRestrictions);
|
||||||
}
|
}
|
||||||
|
|
||||||
PhaseHandler phase = game.getPhaseHandler();
|
PhaseHandler phase = game.getPhaseHandler();
|
||||||
@@ -66,18 +68,19 @@ public class CloneAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!bFlag) { // All of the defined stuff is cloned, not very useful
|
if (!bFlag) { // All of the defined stuff is cloned, not very useful
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.MissingNeededCards);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
useAbility &= cloneTgtAI(sa);
|
useAbility &= cloneTgtAI(sa);
|
||||||
}
|
}
|
||||||
|
|
||||||
return useAbility;
|
return useAbility ? new AiAbilityDecision(100, AiPlayDecision.WillPlay)
|
||||||
|
: new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
} // end cloneCanPlayAI()
|
} // end cloneCanPlayAI()
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
|
public AiAbilityDecision chkDrawback(SpellAbility sa, Player aiPlayer) {
|
||||||
// AI should only activate this during Human's turn
|
// AI should only activate this during Human's turn
|
||||||
boolean chance = true;
|
boolean chance = true;
|
||||||
|
|
||||||
@@ -85,11 +88,12 @@ public class CloneAi extends SpellAbilityAi {
|
|||||||
chance = cloneTgtAI(sa);
|
chance = cloneTgtAI(sa);
|
||||||
}
|
}
|
||||||
|
|
||||||
return chance;
|
return chance ? new AiAbilityDecision(100, AiPlayDecision.WillPlay)
|
||||||
|
: new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
protected AiAbilityDecision doTriggerNoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
||||||
Card host = sa.getHostCard();
|
Card host = sa.getHostCard();
|
||||||
boolean chance = true;
|
boolean chance = true;
|
||||||
|
|
||||||
@@ -111,7 +115,11 @@ public class CloneAi extends SpellAbilityAi {
|
|||||||
// Eventually, we can call the trigger of ETB abilities with
|
// Eventually, we can call the trigger of ETB abilities with
|
||||||
// not mandatory as part of the checks to cast something
|
// not mandatory as part of the checks to cast something
|
||||||
|
|
||||||
return chance || mandatory;
|
if (mandatory || chance) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
import forge.ai.ComputerUtil;
|
import forge.ai.*;
|
||||||
import forge.ai.ComputerUtilCard;
|
|
||||||
import forge.ai.ComputerUtilMana;
|
|
||||||
import forge.ai.SpellAbilityAi;
|
|
||||||
import forge.game.ability.AbilityUtils;
|
import forge.game.ability.AbilityUtils;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
import forge.game.card.CardCollection;
|
import forge.game.card.CardCollection;
|
||||||
@@ -14,16 +11,16 @@ import forge.game.zone.ZoneType;
|
|||||||
|
|
||||||
public class ConniveAi extends SpellAbilityAi {
|
public class ConniveAi extends SpellAbilityAi {
|
||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
protected AiAbilityDecision canPlay(Player ai, SpellAbility sa) {
|
||||||
if (!ai.canDraw()) {
|
if (!ai.canDraw()) {
|
||||||
return false; // can't draw anything
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
Card host = sa.getHostCard();
|
Card host = sa.getHostCard();
|
||||||
|
|
||||||
final int num = AbilityUtils.calculateAmount(host, sa.getParamOrDefault("ConniveNum", "1"), sa);
|
final int num = AbilityUtils.calculateAmount(host, sa.getParamOrDefault("ConniveNum", "1"), sa);
|
||||||
if (num == 0) {
|
if (num == 0) {
|
||||||
return false; // Won't do anything
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
CardCollection list = CardLists.getTargetableCards(ai.getCardsIn(ZoneType.Battlefield), sa);
|
CardCollection list = CardLists.getTargetableCards(ai.getCardsIn(ZoneType.Battlefield), sa);
|
||||||
@@ -41,7 +38,7 @@ public class ConniveAi extends SpellAbilityAi {
|
|||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
while (sa.canAddMoreTarget()) {
|
while (sa.canAddMoreTarget()) {
|
||||||
if ((list.isEmpty() && sa.isTargetNumberValid() && !sa.getTargets().isEmpty())) {
|
if ((list.isEmpty() && sa.isTargetNumberValid() && !sa.getTargets().isEmpty())) {
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (list.isEmpty()) {
|
if (list.isEmpty()) {
|
||||||
@@ -53,7 +50,7 @@ public class ConniveAi extends SpellAbilityAi {
|
|||||||
if (list.isEmpty()) {
|
if (list.isEmpty()) {
|
||||||
// Not mandatory, or the the list was regenerated and is still empty,
|
// Not mandatory, or the the list was regenerated and is still empty,
|
||||||
// so return whether or not we found enough targets
|
// so return whether or not we found enough targets
|
||||||
return sa.isTargetNumberValid();
|
return new AiAbilityDecision(sa.isTargetNumberValid() ? 100 : 0, sa.isTargetNumberValid() ? AiPlayDecision.WillPlay : AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
Card choice = ComputerUtilCard.getBestCreatureAI(list);
|
Card choice = ComputerUtilCard.getBestCreatureAI(list);
|
||||||
@@ -66,13 +63,17 @@ public class ConniveAi extends SpellAbilityAi {
|
|||||||
list.clear();
|
list.clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return !sa.getTargets().isEmpty() && sa.isTargetNumberValid();
|
if (!sa.getTargets().isEmpty() && sa.isTargetNumberValid()) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
} else {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
protected AiAbilityDecision doTriggerNoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
if (!ai.canDraw() && !mandatory) {
|
if (!ai.canDraw() && !mandatory) {
|
||||||
return false; // can't draw anything
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean preferred = true;
|
boolean preferred = true;
|
||||||
@@ -85,7 +86,7 @@ public class ConniveAi extends SpellAbilityAi {
|
|||||||
while (sa.canAddMoreTarget()) {
|
while (sa.canAddMoreTarget()) {
|
||||||
if (mandatory) {
|
if (mandatory) {
|
||||||
if ((list.isEmpty() || !preferred) && sa.isTargetNumberValid()) {
|
if ((list.isEmpty() || !preferred) && sa.isTargetNumberValid()) {
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (list.isEmpty() && preferred) {
|
if (list.isEmpty() && preferred) {
|
||||||
@@ -98,14 +99,13 @@ public class ConniveAi extends SpellAbilityAi {
|
|||||||
// Still an empty list, but we have to choose something (mandatory); expand targeting to
|
// Still an empty list, but we have to choose something (mandatory); expand targeting to
|
||||||
// include AI's own cards to see if there's anything targetable (e.g. Plague Belcher).
|
// include AI's own cards to see if there's anything targetable (e.g. Plague Belcher).
|
||||||
list = CardLists.getTargetableCards(ai.getCardsIn(ZoneType.Battlefield), sa);
|
list = CardLists.getTargetableCards(ai.getCardsIn(ZoneType.Battlefield), sa);
|
||||||
preferred = false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (list.isEmpty()) {
|
if (list.isEmpty()) {
|
||||||
// Not mandatory, or the the list was regenerated and is still empty,
|
// Not mandatory, or the the list was regenerated and is still empty,
|
||||||
// so return whether or not we found enough targets
|
// so return whether or not we found enough targets
|
||||||
return sa.isTargetNumberValid();
|
return new AiAbilityDecision(sa.isTargetNumberValid() ? 100 : 0, sa.isTargetNumberValid() ? AiPlayDecision.WillPlay : AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
Card choice = ComputerUtilCard.getBestCreatureAI(list);
|
Card choice = ComputerUtilCard.getBestCreatureAI(list);
|
||||||
@@ -118,7 +118,10 @@ public class ConniveAi extends SpellAbilityAi {
|
|||||||
list.clear();
|
list.clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return new AiAbilityDecision(
|
||||||
|
sa.isTargetNumberValid() && !sa.getTargets().isEmpty() ? 100 : 0,
|
||||||
|
sa.isTargetNumberValid() ? AiPlayDecision.WillPlay : AiPlayDecision.TargetingFailed
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
import forge.ai.ComputerUtilCard;
|
import forge.ai.*;
|
||||||
import forge.ai.SpecialCardAi;
|
|
||||||
import forge.ai.SpellAbilityAi;
|
|
||||||
import forge.game.ability.AbilityUtils;
|
import forge.game.ability.AbilityUtils;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
import forge.game.card.CardCollection;
|
import forge.game.card.CardCollection;
|
||||||
@@ -13,7 +11,6 @@ import forge.game.player.Player;
|
|||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.spellability.TargetRestrictions;
|
import forge.game.spellability.TargetRestrictions;
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
import forge.util.MyRandom;
|
|
||||||
|
|
||||||
public class ControlExchangeAi extends SpellAbilityAi {
|
public class ControlExchangeAi extends SpellAbilityAi {
|
||||||
|
|
||||||
@@ -21,7 +18,7 @@ public class ControlExchangeAi extends SpellAbilityAi {
|
|||||||
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
|
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(Player ai, final SpellAbility sa) {
|
protected AiAbilityDecision checkApiLogic(Player ai, final SpellAbility sa) {
|
||||||
Card object1 = null;
|
Card object1 = null;
|
||||||
Card object2 = null;
|
Card object2 = null;
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
@@ -41,35 +38,40 @@ public class ControlExchangeAi extends SpellAbilityAi {
|
|||||||
sa.getTargets().add(object2);
|
sa.getTargets().add(object2);
|
||||||
}
|
}
|
||||||
if (object1 == null || object2 == null) {
|
if (object1 == null || object2 == null) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
if (ComputerUtilCard.evaluateCreature(object1) > ComputerUtilCard.evaluateCreature(object2) + 40) {
|
if (ComputerUtilCard.evaluateCreature(object1) > ComputerUtilCard.evaluateCreature(object2) + 40) {
|
||||||
sa.getTargets().add(object1);
|
sa.getTargets().add(object1);
|
||||||
return MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
protected AiAbilityDecision doTriggerNoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
||||||
if (!sa.usesTargeting()) {
|
if (!sa.usesTargeting()) {
|
||||||
if (mandatory) {
|
if (mandatory) {
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (mandatory) {
|
if (mandatory) {
|
||||||
return chkAIDrawback(sa, aiPlayer) || sa.isTargetNumberValid();
|
AiAbilityDecision decision = chkDrawback(sa, aiPlayer);
|
||||||
|
if (sa.isTargetNumberValid()) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
}
|
||||||
|
|
||||||
|
return decision;
|
||||||
} else {
|
} else {
|
||||||
return canPlayAI(aiPlayer, sa);
|
return canPlay(aiPlayer, sa);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
|
public AiAbilityDecision chkDrawback(SpellAbility sa, Player aiPlayer) {
|
||||||
if (!sa.usesTargeting()) {
|
if (!sa.usesTargeting()) {
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
@@ -90,7 +92,7 @@ public class ControlExchangeAi extends SpellAbilityAi {
|
|||||||
list = CardLists.getTargetableCards(list, sa);
|
list = CardLists.getTargetableCards(list, sa);
|
||||||
|
|
||||||
if (list.isEmpty())
|
if (list.isEmpty())
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
|
|
||||||
Card best = ComputerUtilCard.getBestAI(list);
|
Card best = ComputerUtilCard.getBestAI(list);
|
||||||
|
|
||||||
@@ -106,7 +108,7 @@ public class ControlExchangeAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
// Defined card is better than this one, try to avoid trade
|
// Defined card is better than this one, try to avoid trade
|
||||||
if (!best.equals(realBest)) {
|
if (!best.equals(realBest)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,10 +117,10 @@ public class ControlExchangeAi extends SpellAbilityAi {
|
|||||||
return doTrigTwoTargetsLogic(aiPlayer, sa, best);
|
return doTrigTwoTargetsLogic(aiPlayer, sa, best);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean doTrigTwoTargetsLogic(Player ai, SpellAbility sa, Card bestFirstTgt) {
|
private AiAbilityDecision doTrigTwoTargetsLogic(Player ai, SpellAbility sa, Card bestFirstTgt) {
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
final int creatureThreshold = 100; // TODO: make this configurable from the AI profile
|
final int creatureThreshold = 100; // TODO: make this configurable from the AI profile
|
||||||
final int nonCreatureThreshold = 2;
|
final int nonCreatureThreshold = 2;
|
||||||
@@ -130,30 +132,30 @@ public class ControlExchangeAi extends SpellAbilityAi {
|
|||||||
list = CardLists.getTargetableCards(list, sa);
|
list = CardLists.getTargetableCards(list, sa);
|
||||||
|
|
||||||
if (list.isEmpty()) {
|
if (list.isEmpty()) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
|
|
||||||
Card aiWorst = ComputerUtilCard.getWorstAI(list);
|
Card aiWorst = ComputerUtilCard.getWorstAI(list);
|
||||||
if (aiWorst == null) {
|
if (aiWorst == null) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (aiWorst != bestFirstTgt) {
|
if (aiWorst != bestFirstTgt) {
|
||||||
if (bestFirstTgt.isCreature() && aiWorst.isCreature()) {
|
if (bestFirstTgt.isCreature() && aiWorst.isCreature()) {
|
||||||
if ((ComputerUtilCard.evaluateCreature(bestFirstTgt) > ComputerUtilCard.evaluateCreature(aiWorst) + creatureThreshold) || sa.isMandatory()) {
|
if ((ComputerUtilCard.evaluateCreature(bestFirstTgt) > ComputerUtilCard.evaluateCreature(aiWorst) + creatureThreshold) || sa.isMandatory()) {
|
||||||
sa.getTargets().add(aiWorst);
|
sa.getTargets().add(aiWorst);
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// TODO: compare non-creatures by CMC - can be improved, at least shouldn't give control of things like the Power Nine
|
// TODO: compare non-creatures by CMC - can be improved, at least shouldn't give control of things like the Power Nine
|
||||||
if ((bestFirstTgt.getCMC() > aiWorst.getCMC() + nonCreatureThreshold) || sa.isMandatory()) {
|
if ((bestFirstTgt.getCMC() > aiWorst.getCMC() + nonCreatureThreshold) || sa.isMandatory()) {
|
||||||
sa.getTargets().add(aiWorst);
|
sa.getTargets().add(aiWorst);
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sa.clearTargets();
|
sa.clearTargets();
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ import java.util.Map;
|
|||||||
*/
|
*/
|
||||||
public class ControlGainAi extends SpellAbilityAi {
|
public class ControlGainAi extends SpellAbilityAi {
|
||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(final Player ai, final SpellAbility sa) {
|
protected AiAbilityDecision canPlay(final Player ai, final SpellAbility sa) {
|
||||||
final List<String> lose = Lists.newArrayList();
|
final List<String> lose = Lists.newArrayList();
|
||||||
|
|
||||||
if (sa.hasParam("LoseControl")) {
|
if (sa.hasParam("LoseControl")) {
|
||||||
@@ -81,22 +81,30 @@ public class ControlGainAi extends SpellAbilityAi {
|
|||||||
if (sa.hasParam("AllValid")) {
|
if (sa.hasParam("AllValid")) {
|
||||||
CardCollectionView tgtCards = opponents.getCardsIn(ZoneType.Battlefield);
|
CardCollectionView tgtCards = opponents.getCardsIn(ZoneType.Battlefield);
|
||||||
tgtCards = AbilityUtils.filterListByType(tgtCards, sa.getParam("AllValid"), sa);
|
tgtCards = AbilityUtils.filterListByType(tgtCards, sa.getParam("AllValid"), sa);
|
||||||
return !tgtCards.isEmpty();
|
|
||||||
|
if (tgtCards.isEmpty()) {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.MissingNeededCards);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
} else {
|
} else {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
if (sa.hasParam("TargetingPlayer")) {
|
if (sa.hasParam("TargetingPlayer")) {
|
||||||
Player targetingPlayer = AbilityUtils.getDefinedPlayers(sa.getHostCard(), sa.getParam("TargetingPlayer"), sa).get(0);
|
Player targetingPlayer = AbilityUtils.getDefinedPlayers(sa.getHostCard(), sa.getParam("TargetingPlayer"), sa).get(0);
|
||||||
sa.setTargetingPlayer(targetingPlayer);
|
sa.setTargetingPlayer(targetingPlayer);
|
||||||
return targetingPlayer.getController().chooseTargetsFor(sa);
|
if (targetingPlayer.getController().chooseTargetsFor(sa)) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
} else {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tgt.canOnlyTgtOpponent()) {
|
if (tgt.canOnlyTgtOpponent()) {
|
||||||
List<Player> oppList = opponents.filter(PlayerPredicates.isTargetableBy(sa));
|
List<Player> oppList = opponents.filter(PlayerPredicates.isTargetableBy(sa));
|
||||||
|
|
||||||
if (oppList.isEmpty()) {
|
if (oppList.isEmpty()) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tgt.isRandomTarget()) {
|
if (tgt.isRandomTarget()) {
|
||||||
@@ -111,12 +119,12 @@ public class ControlGainAi extends SpellAbilityAi {
|
|||||||
if (lose.contains("EOT")
|
if (lose.contains("EOT")
|
||||||
&& game.getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)
|
&& game.getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)
|
||||||
&& !sa.isTrigger()) {
|
&& !sa.isTrigger()) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sa.hasParam("Defined")) {
|
if (sa.hasParam("Defined")) {
|
||||||
// no need to target, we'll pick up the target from Defined
|
// no need to target, we'll pick up the target from Defined
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
CardCollection list = opponents.getCardsIn(ZoneType.Battlefield);
|
CardCollection list = opponents.getCardsIn(ZoneType.Battlefield);
|
||||||
@@ -165,7 +173,7 @@ public class ControlGainAi extends SpellAbilityAi {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (list.isEmpty()) {
|
if (list.isEmpty()) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
|
|
||||||
int creatures = 0, artifacts = 0, planeswalkers = 0, lands = 0, enchantments = 0;
|
int creatures = 0, artifacts = 0, planeswalkers = 0, lands = 0, enchantments = 0;
|
||||||
@@ -194,7 +202,7 @@ public class ControlGainAi extends SpellAbilityAi {
|
|||||||
if (list.isEmpty()) {
|
if (list.isEmpty()) {
|
||||||
if ((sa.getTargets().size() < tgt.getMinTargets(sa.getHostCard(), sa)) || (sa.getTargets().size() == 0)) {
|
if ((sa.getTargets().size() < tgt.getMinTargets(sa.getHostCard(), sa)) || (sa.getTargets().size() == 0)) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
} else {
|
} else {
|
||||||
// TODO is this good enough? for up to amounts?
|
// TODO is this good enough? for up to amounts?
|
||||||
break;
|
break;
|
||||||
@@ -257,39 +265,41 @@ public class ControlGainAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return new AiAbilityDecision(
|
||||||
|
sa.isTargetNumberValid() ? 100 : 0,
|
||||||
|
sa.isTargetNumberValid() ? AiPlayDecision.WillPlay : AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
protected AiAbilityDecision doTriggerNoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
if (!sa.usesTargeting()) {
|
if (!sa.usesTargeting()) {
|
||||||
if (mandatory) {
|
if (mandatory) {
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (sa.hasParam("TargetingPlayer") || (!this.canPlayAI(ai, sa) && mandatory)) {
|
if (sa.hasParam("TargetingPlayer") || (mandatory && !this.canPlay(ai, sa).willingToPlay())) {
|
||||||
if (sa.getTargetRestrictions().canOnlyTgtOpponent()) {
|
if (sa.getTargetRestrictions().canOnlyTgtOpponent()) {
|
||||||
List<Player> oppList = ai.getOpponents().filter(PlayerPredicates.isTargetableBy(sa));
|
List<Player> oppList = ai.getOpponents().filter(PlayerPredicates.isTargetableBy(sa));
|
||||||
if (oppList.isEmpty()) {
|
if (oppList.isEmpty()) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
sa.getTargets().add(Aggregates.random(oppList));
|
sa.getTargets().add(Aggregates.random(oppList));
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
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 new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
sa.getTargets().add(ComputerUtilCard.getWorstAI(list));
|
sa.getTargets().add(ComputerUtilCard.getWorstAI(list));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean chkAIDrawback(SpellAbility sa, final Player ai) {
|
public AiAbilityDecision chkDrawback(SpellAbility sa, final Player ai) {
|
||||||
final Game game = ai.getGame();
|
final Game game = ai.getGame();
|
||||||
|
|
||||||
// Special card logic that is processed elsewhere
|
// Special card logic that is processed elsewhere
|
||||||
@@ -305,7 +315,7 @@ public class ControlGainAi extends SpellAbilityAi {
|
|||||||
CardCollectionView tgtCards = ai.getOpponents().getCardsIn(ZoneType.Battlefield);
|
CardCollectionView tgtCards = ai.getOpponents().getCardsIn(ZoneType.Battlefield);
|
||||||
tgtCards = AbilityUtils.filterListByType(tgtCards, sa.getParam("AllValid"), sa);
|
tgtCards = AbilityUtils.filterListByType(tgtCards, sa.getParam("AllValid"), sa);
|
||||||
if (tgtCards.isEmpty()) {
|
if (tgtCards.isEmpty()) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.MissingNeededCards);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
final List<String> lose = Lists.newArrayList();
|
final List<String> lose = Lists.newArrayList();
|
||||||
@@ -314,10 +324,14 @@ public class ControlGainAi extends SpellAbilityAi {
|
|||||||
lose.addAll(Lists.newArrayList(sa.getParam("LoseControl").split(",")));
|
lose.addAll(Lists.newArrayList(sa.getParam("LoseControl").split(",")));
|
||||||
}
|
}
|
||||||
|
|
||||||
return !lose.contains("EOT")
|
if (lose.contains("EOT")
|
||||||
|| !game.getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS);
|
&& game.getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)) {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.AnotherTime);
|
||||||
|
} else {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return this.canPlayAI(ai, sa);
|
return this.canPlay(ai, sa);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,8 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
|
import forge.ai.AiAbilityDecision;
|
||||||
|
import forge.ai.AiPlayDecision;
|
||||||
import forge.ai.ComputerUtilCard;
|
import forge.ai.ComputerUtilCard;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
@@ -41,24 +43,22 @@ import java.util.Map;
|
|||||||
*/
|
*/
|
||||||
public class ControlGainVariantAi extends SpellAbilityAi {
|
public class ControlGainVariantAi extends SpellAbilityAi {
|
||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(final Player ai, final SpellAbility sa) {
|
protected AiAbilityDecision canPlay(final Player ai, final SpellAbility sa) {
|
||||||
|
|
||||||
String logic = sa.getParam("AILogic");
|
String logic = sa.getParam("AILogic");
|
||||||
|
|
||||||
if ("GainControlOwns".equals(logic)) {
|
if ("GainControlOwns".equals(logic)) {
|
||||||
List<Card> list = CardLists.filter(ai.getGame().getCardsIn(ZoneType.Battlefield), crd -> crd.isCreature() && !crd.getController().equals(crd.getOwner()));
|
List<Card> list = CardLists.filter(ai.getGame().getCardsIn(ZoneType.Battlefield), crd -> crd.isCreature() && !crd.getController().equals(crd.getOwner()));
|
||||||
if (list.isEmpty()) {
|
if (list.isEmpty()) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
for (final Card c : list) {
|
for (final Card c : list) {
|
||||||
if (ai.equals(c.getController())) {
|
if (ai.equals(c.getController())) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.MissingNeededCards);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -22,45 +22,52 @@ import java.util.function.Predicate;
|
|||||||
|
|
||||||
public class CopyPermanentAi extends SpellAbilityAi {
|
public class CopyPermanentAi extends SpellAbilityAi {
|
||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
protected AiAbilityDecision checkApiLogic(Player aiPlayer, SpellAbility sa) {
|
||||||
Card source = sa.getHostCard();
|
Card source = sa.getHostCard();
|
||||||
PhaseHandler ph = aiPlayer.getGame().getPhaseHandler();
|
PhaseHandler ph = aiPlayer.getGame().getPhaseHandler();
|
||||||
String aiLogic = sa.getParamOrDefault("AILogic", "");
|
String aiLogic = sa.getParamOrDefault("AILogic", "");
|
||||||
|
|
||||||
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ("MomirAvatar".equals(aiLogic)) {
|
if ("MomirAvatar".equals(aiLogic)) {
|
||||||
return SpecialCardAi.MomirVigAvatar.consider(aiPlayer, sa);
|
return SpecialCardAi.MomirVigAvatar.consider(aiPlayer, sa);
|
||||||
} else if ("MimicVat".equals(aiLogic)) {
|
} else if ("MimicVat".equals(aiLogic)) {
|
||||||
return SpecialCardAi.MimicVat.considerCopy(aiPlayer, sa);
|
return SpecialCardAi.MimicVat.considerCopy(aiPlayer, sa);
|
||||||
} else if ("AtEOT".equals(aiLogic)) {
|
} else if ("AtEOT".equals(aiLogic)) {
|
||||||
return ph.is(PhaseType.END_OF_TURN);
|
if (ph.is(PhaseType.END_OF_TURN)) {
|
||||||
} else if ("AtOppEOT".equals(aiLogic)) {
|
if (ph.getPlayerTurn() == aiPlayer) {
|
||||||
return ph.is(PhaseType.END_OF_TURN) && ph.getPlayerTurn() != aiPlayer;
|
// If it's the AI's turn, it can activate at EOT
|
||||||
} else if ("DuplicatePerms".equals(aiLogic)) {
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
} else {
|
||||||
|
// If it's not the AI's turn, it can't activate at EOT
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Not at EOT phase
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.WaitForEndOfTurn);
|
||||||
|
}
|
||||||
|
} if ("DuplicatePerms".equals(aiLogic)) {
|
||||||
final List<Card> valid = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa);
|
final List<Card> valid = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa);
|
||||||
if (valid.size() < 2) {
|
if (valid.size() < 2) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.MissingNeededCards);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sa.hasParam("AtEOT") && !ph.is(PhaseType.MAIN1)) {
|
if (sa.hasParam("AtEOT") && !ph.is(PhaseType.MAIN1)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.AnotherTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sa.hasParam("Defined")) {
|
if (sa.hasParam("Defined")) {
|
||||||
// If there needs to be an imprinted card, don't activate the ability if nothing was imprinted yet (e.g. Mimic Vat)
|
// If there needs to be an imprinted card, don't activate the ability if nothing was imprinted yet (e.g. Mimic Vat)
|
||||||
if (sa.getParam("Defined").equals("Imprinted.ExiledWithSource") && source.getImprintedCards().isEmpty()) {
|
if (sa.getParam("Defined").equals("Imprinted.ExiledWithSource") && source.getImprintedCards().isEmpty()) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.MissingNeededCards);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sa.isEmbalm() || sa.isEternalize()) {
|
if (sa.isEmbalm() || sa.isEternalize()) {
|
||||||
// E.g. Vizier of Many Faces: check to make sure it makes sense to make the token now
|
// E.g. Vizier of Many Faces: check to make sure it makes sense to make the token now
|
||||||
if (ComputerUtilCard.checkNeedsToPlayReqs(sa.getHostCard(), sa) != AiPlayDecision.WillPlay) {
|
AiPlayDecision decision = ComputerUtilCard.checkNeedsToPlayReqs(sa.getHostCard(), sa);
|
||||||
return false;
|
|
||||||
|
if (decision != AiPlayDecision.WillPlay) {
|
||||||
|
return new AiAbilityDecision(0, decision);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,37 +82,45 @@ public class CopyPermanentAi extends SpellAbilityAi {
|
|||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
Player targetingPlayer = AbilityUtils.getDefinedPlayers(source, sa.getParam("TargetingPlayer"), sa).get(0);
|
Player targetingPlayer = AbilityUtils.getDefinedPlayers(source, sa.getParam("TargetingPlayer"), sa).get(0);
|
||||||
sa.setTargetingPlayer(targetingPlayer);
|
sa.setTargetingPlayer(targetingPlayer);
|
||||||
return targetingPlayer.getController().chooseTargetsFor(sa);
|
if (targetingPlayer.getController().chooseTargetsFor(sa)) {
|
||||||
|
if (sa.isTargetNumberValid()) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
} else {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
|
}
|
||||||
} else if (sa.usesTargeting() && sa.getTargetRestrictions().canTgtPlayer()) {
|
} else if (sa.usesTargeting() && sa.getTargetRestrictions().canTgtPlayer()) {
|
||||||
if (!sa.isCurse()) {
|
if (!sa.isCurse()) {
|
||||||
if (sa.canTarget(aiPlayer)) {
|
if (sa.canTarget(aiPlayer)) {
|
||||||
sa.getTargets().add(aiPlayer);
|
sa.getTargets().add(aiPlayer);
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
} else {
|
} else {
|
||||||
for (Player p : aiPlayer.getYourTeam()) {
|
for (Player p : aiPlayer.getYourTeam()) {
|
||||||
if (sa.canTarget(p)) {
|
if (sa.canTarget(p)) {
|
||||||
sa.getTargets().add(p);
|
sa.getTargets().add(p);
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (Player p : aiPlayer.getOpponents()) {
|
for (Player p : aiPlayer.getOpponents()) {
|
||||||
if (sa.canTarget(p)) {
|
if (sa.canTarget(p)) {
|
||||||
sa.getTargets().add(p);
|
sa.getTargets().add(p);
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return doTriggerAINoCost(aiPlayer, sa, false);
|
return doTriggerNoCost(aiPlayer, sa, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(final Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
protected AiAbilityDecision doTriggerNoCost(final Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
||||||
final Card host = sa.getHostCard();
|
final Card host = sa.getHostCard();
|
||||||
final Player activator = sa.getActivatingPlayer();
|
final Player activator = sa.getActivatingPlayer();
|
||||||
final Game game = host.getGame();
|
final Game game = host.getGame();
|
||||||
@@ -128,13 +143,13 @@ public class CopyPermanentAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
//Nothing to target
|
//Nothing to target
|
||||||
if (list.isEmpty()) {
|
if (list.isEmpty()) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
|
|
||||||
CardCollection betterList = CardLists.filter(list, CardPredicates.isRemAIDeck().negate());
|
CardCollection betterList = CardLists.filter(list, CardPredicates.isRemAIDeck().negate());
|
||||||
if (betterList.isEmpty()) {
|
if (betterList.isEmpty()) {
|
||||||
if (!mandatory) {
|
if (!mandatory) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
list = betterList;
|
list = betterList;
|
||||||
@@ -146,7 +161,7 @@ public class CopyPermanentAi extends SpellAbilityAi {
|
|||||||
if (felidarGuardian.size() > 0) {
|
if (felidarGuardian.size() > 0) {
|
||||||
// can copy a Felidar Guardian and combo off, so let's do it
|
// can copy a Felidar Guardian and combo off, so let's do it
|
||||||
sa.getTargets().add(felidarGuardian.get(0));
|
sa.getTargets().add(felidarGuardian.get(0));
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,9 +170,9 @@ public class CopyPermanentAi extends SpellAbilityAi {
|
|||||||
list = CardLists.canSubsequentlyTarget(list, sa);
|
list = CardLists.canSubsequentlyTarget(list, sa);
|
||||||
|
|
||||||
if (list.isEmpty()) {
|
if (list.isEmpty()) {
|
||||||
if (!sa.isTargetNumberValid() || sa.getTargets().size() == 0) {
|
if (!sa.isTargetNumberValid() || sa.getTargets().isEmpty()) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
} else {
|
} else {
|
||||||
// TODO is this good enough? for up to amounts?
|
// TODO is this good enough? for up to amounts?
|
||||||
break;
|
break;
|
||||||
@@ -177,9 +192,9 @@ public class CopyPermanentAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (choice == null) { // can't find anything left
|
if (choice == null) { // can't find anything left
|
||||||
if (!sa.isTargetNumberValid() || sa.getTargets().size() == 0) {
|
if (!sa.isTargetNumberValid() || sa.getTargets().isEmpty()) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
} else {
|
} else {
|
||||||
// TODO is this good enough? for up to amounts?
|
// TODO is this good enough? for up to amounts?
|
||||||
break;
|
break;
|
||||||
@@ -194,20 +209,22 @@ public class CopyPermanentAi extends SpellAbilityAi {
|
|||||||
choices = CardLists.getValidCards(choices, sa.getParam("Choices"), activator, host, sa);
|
choices = CardLists.getValidCards(choices, sa.getParam("Choices"), activator, host, sa);
|
||||||
Collection<Card> betterChoices = getBetterOptions(aiPlayer, sa, choices, !mandatory);
|
Collection<Card> betterChoices = getBetterOptions(aiPlayer, sa, choices, !mandatory);
|
||||||
if (betterChoices.isEmpty()) {
|
if (betterChoices.isEmpty()) {
|
||||||
return mandatory;
|
if (mandatory) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
} else {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
// if no targeting, it should always be ok
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ("TriggeredCardController".equals(sa.getParam("Controller"))) {
|
if ("TriggeredCardController".equals(sa.getParam("Controller"))) {
|
||||||
Card trigCard = (Card)sa.getTriggeringObject(AbilityKey.Card);
|
Card trigCard = (Card)sa.getTriggeringObject(AbilityKey.Card);
|
||||||
if (!mandatory && trigCard != null && trigCard.getController().isOpponentOf(aiPlayer)) {
|
if (!mandatory && trigCard != null && trigCard.getController().isOpponentOf(aiPlayer)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* (non-Javadoc)
|
/* (non-Javadoc)
|
||||||
|
|||||||
@@ -17,14 +17,15 @@ import java.util.Map;
|
|||||||
public class CopySpellAbilityAi extends SpellAbilityAi {
|
public class CopySpellAbilityAi extends SpellAbilityAi {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
protected AiAbilityDecision checkApiLogic(Player aiPlayer, SpellAbility sa) {
|
||||||
Game game = aiPlayer.getGame();
|
Game game = aiPlayer.getGame();
|
||||||
int chance = ((PlayerControllerAi)aiPlayer.getController()).getAi().getIntProperty(AiProps.CHANCE_TO_COPY_OWN_SPELL_WHILE_ON_STACK);
|
int chance = ((PlayerControllerAi)aiPlayer.getController()).getAi().getIntProperty(AiProps.CHANCE_TO_COPY_OWN_SPELL_WHILE_ON_STACK);
|
||||||
int diff = ((PlayerControllerAi)aiPlayer.getController()).getAi().getIntProperty(AiProps.ALWAYS_COPY_SPELL_IF_CMC_DIFF);
|
int diff = ((PlayerControllerAi)aiPlayer.getController()).getAi().getIntProperty(AiProps.ALWAYS_COPY_SPELL_IF_CMC_DIFF);
|
||||||
String logic = sa.getParamOrDefault("AILogic", "");
|
String logic = sa.getParamOrDefault("AILogic", "");
|
||||||
|
|
||||||
if (game.getStack().isEmpty()) {
|
if (game.getStack().isEmpty()) {
|
||||||
return sa.isMandatory() || "Always".equals(logic);
|
boolean result = sa.isMandatory() || "Always".equals(logic);
|
||||||
|
return result ? new AiAbilityDecision(100, AiPlayDecision.WillPlay) : new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
final SpellAbility top = game.getStack().peekAbility();
|
final SpellAbility top = game.getStack().peekAbility();
|
||||||
@@ -42,46 +43,39 @@ public class CopySpellAbilityAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
if (!MyRandom.percentTrue(chance)
|
if (!MyRandom.percentTrue(chance)
|
||||||
&& !"AlwaysIfViable".equals(logic)
|
&& !"AlwaysIfViable".equals(logic)
|
||||||
&& !"OnceIfViable".equals(logic)
|
|
||||||
&& !"AlwaysCopyActivatedAbilities".equals(logic)) {
|
&& !"AlwaysCopyActivatedAbilities".equals(logic)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
|
||||||
|
|
||||||
if ("OnceIfViable".equals(logic)) {
|
|
||||||
if (AiCardMemory.isRememberedCard(aiPlayer, sa.getHostCard(), AiCardMemory.MemorySet.ACTIVATED_THIS_TURN)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sa.usesTargeting()) {
|
if (sa.usesTargeting()) {
|
||||||
// Filter AI-specific targets if provided
|
// Filter AI-specific targets if provided
|
||||||
if ("OnlyOwned".equals(sa.getParam("AITgts"))) {
|
if ("OnlyOwned".equals(sa.getParam("AITgts"))) {
|
||||||
if (!top.getActivatingPlayer().equals(aiPlayer)) {
|
if (!top.getActivatingPlayer().equals(aiPlayer)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (top.isWrapper() || top.isActivatedAbility()) {
|
if (top.isWrapper() || top.isActivatedAbility()) {
|
||||||
// Shouldn't even try with triggered or wrapped abilities at this time, will crash
|
// Shouldn't even try with triggered or wrapped abilities at this time, will crash
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
} else if (top.getApi() == ApiType.CopySpellAbility) {
|
} else if (top.getApi() == ApiType.CopySpellAbility) {
|
||||||
// Don't try to copy a copy ability, too complex for the AI to handle
|
// Don't try to copy a copy ability, too complex for the AI to handle
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
} else if (top.getApi() == ApiType.Mana) {
|
} else if (top.getApi() == ApiType.Mana) {
|
||||||
// would lead to Stack Overflow by trying to play this again
|
// would lead to Stack Overflow by trying to play this again
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
} else if (top.getApi() == ApiType.DestroyAll || top.getApi() == ApiType.SacrificeAll || top.getApi() == ApiType.ChangeZoneAll || top.getApi() == ApiType.TapAll || top.getApi() == ApiType.UnattachAll) {
|
} else if (top.getApi() == ApiType.DestroyAll || top.getApi() == ApiType.SacrificeAll || top.getApi() == ApiType.ChangeZoneAll || top.getApi() == ApiType.TapAll || top.getApi() == ApiType.UnattachAll) {
|
||||||
if (!top.usesTargeting() || top.getActivatingPlayer().equals(aiPlayer)) {
|
if (!top.usesTargeting() || top.getActivatingPlayer().equals(aiPlayer)) {
|
||||||
// If we activated a mass removal / mass tap / mass bounce / etc. spell, or if the opponent activated it but
|
// If we activated a mass removal / mass tap / mass bounce / etc. spell, or if the opponent activated it but
|
||||||
// it can't be retargeted, no reason to copy this spell since it'll probably do the same thing and is useless as a copy
|
// it can't be retargeted, no reason to copy this spell since it'll probably do the same thing and is useless as a copy
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
} else if (top.hasParam("ConditionManaSpent") || top.getHostCard().hasSVar("AINoCopy")) {
|
} else if (top.hasParam("ConditionManaSpent") || top.getHostCard().hasSVar("AINoCopy")) {
|
||||||
// Mana spent is not copied, so these spells generally do nothing when copied.
|
// Mana spent is not copied, so these spells generally do nothing when copied.
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
} else if (ComputerUtilCard.isCardRemAIDeck(top.getHostCard())) {
|
} else if (ComputerUtilCard.isCardRemAIDeck(top.getHostCard())) {
|
||||||
// Don't try to copy anything you can't understand how to handle
|
// Don't try to copy anything you can't understand how to handle
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
// A copy is necessary to properly test the SA before targeting the copied spell, otherwise the copy SA will fizzle.
|
// A copy is necessary to properly test the SA before targeting the copied spell, otherwise the copy SA will fizzle.
|
||||||
@@ -100,31 +94,49 @@ public class CopySpellAbilityAi extends SpellAbilityAi {
|
|||||||
if (decision == AiPlayDecision.WillPlay) {
|
if (decision == AiPlayDecision.WillPlay) {
|
||||||
sa.getTargets().add(top);
|
sa.getTargets().add(top);
|
||||||
AiCardMemory.rememberCard(aiPlayer, sa.getHostCard(), AiCardMemory.MemorySet.ACTIVATED_THIS_TURN);
|
AiCardMemory.rememberCard(aiPlayer, sa.getHostCard(), AiCardMemory.MemorySet.ACTIVATED_THIS_TURN);
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
return new AiAbilityDecision(0, decision);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// the AI should not miss mandatory activations
|
// the AI should not miss mandatory activations
|
||||||
return sa.isMandatory() || "Always".equals(logic);
|
boolean result = sa.isMandatory() || "Always".equals(logic);
|
||||||
|
return result ? new AiAbilityDecision(100, AiPlayDecision.WillPlay) : new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
protected AiAbilityDecision doTriggerNoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
||||||
// the AI should not miss mandatory activations (e.g. Precursor Golem trigger)
|
// the AI should not miss mandatory activations (e.g. Precursor Golem trigger)
|
||||||
String logic = sa.getParamOrDefault("AILogic", "");
|
String logic = sa.getParamOrDefault("AILogic", "");
|
||||||
return mandatory || logic.contains("Always"); // this includes logic like AlwaysIfViable
|
|
||||||
|
if (mandatory) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (logic.contains("Always")) {
|
||||||
|
// If the logic is "Always" or "AlwaysIfViable", we will always play this ability
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean chkAIDrawback(final SpellAbility sa, final Player aiPlayer) {
|
public AiAbilityDecision chkDrawback(final SpellAbility sa, final Player aiPlayer) {
|
||||||
if ("ChainOfSmog".equals(sa.getParam("AILogic"))) {
|
if ("ChainOfSmog".equals(sa.getParam("AILogic"))) {
|
||||||
return SpecialCardAi.ChainOfSmog.consider(aiPlayer, sa);
|
return SpecialCardAi.ChainOfSmog.consider(aiPlayer, sa);
|
||||||
} else if ("ChainOfAcid".equals(sa.getParam("AILogic"))) {
|
} else if ("ChainOfAcid".equals(sa.getParam("AILogic"))) {
|
||||||
return SpecialCardAi.ChainOfAcid.consider(aiPlayer, sa);
|
return SpecialCardAi.ChainOfAcid.consider(aiPlayer, sa);
|
||||||
}
|
|
||||||
|
|
||||||
return canPlayAI(aiPlayer, sa) || (sa.isMandatory() && super.chkAIDrawback(sa, aiPlayer));
|
}
|
||||||
|
AiAbilityDecision decision = canPlay(aiPlayer, sa);
|
||||||
|
if (!decision.willingToPlay()) {
|
||||||
|
if (sa.isMandatory()) {
|
||||||
|
return super.chkDrawback(sa, aiPlayer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return decision;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -138,7 +150,7 @@ public class CopySpellAbilityAi extends SpellAbilityAi {
|
|||||||
// Chain of Acid requires special attention here since otherwise the AI will confirm the copy and then
|
// Chain of Acid requires special attention here since otherwise the AI will confirm the copy and then
|
||||||
// run into the necessity of confirming a mandatory Destroy, thus destroying all of its own permanents.
|
// run into the necessity of confirming a mandatory Destroy, thus destroying all of its own permanents.
|
||||||
if ("ChainOfAcid".equals(sa.getParam("AILogic"))) {
|
if ("ChainOfAcid".equals(sa.getParam("AILogic"))) {
|
||||||
return SpecialCardAi.ChainOfAcid.consider(player, sa);
|
return SpecialCardAi.ChainOfAcid.consider(player, sa).willingToPlay();
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -26,13 +26,11 @@ import forge.game.zone.ZoneType;
|
|||||||
import forge.util.MyRandom;
|
import forge.util.MyRandom;
|
||||||
import forge.util.collect.FCollectionView;
|
import forge.util.collect.FCollectionView;
|
||||||
|
|
||||||
|
|
||||||
public class CounterAi extends SpellAbilityAi {
|
public class CounterAi extends SpellAbilityAi {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
protected AiAbilityDecision checkApiLogic(Player ai, SpellAbility sa) {
|
||||||
boolean toReturn = true;
|
boolean toReturn = true;
|
||||||
final Cost abCost = sa.getPayCosts();
|
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
|
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
|
||||||
final Game game = ai.getGame();
|
final Game game = ai.getGame();
|
||||||
@@ -40,22 +38,12 @@ public class CounterAi extends SpellAbilityAi {
|
|||||||
SpellAbility tgtSA = null;
|
SpellAbility tgtSA = null;
|
||||||
|
|
||||||
if (game.getStack().isEmpty()) {
|
if (game.getStack().isEmpty()) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
|
||||||
|
|
||||||
if (abCost != null) {
|
|
||||||
// AI currently disabled for these costs
|
|
||||||
if (!ComputerUtilCost.checkSacrificeCost(ai, abCost, source, sa)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, sa)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ("Force of Will".equals(sourceName)) {
|
if ("Force of Will".equals(sourceName)) {
|
||||||
if (!SpecialCardAi.ForceOfWill.consider(ai, sa)) {
|
if (!SpecialCardAi.ForceOfWill.consider(ai, sa)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,19 +51,19 @@ public class CounterAi extends SpellAbilityAi {
|
|||||||
final SpellAbility topSA = ComputerUtilAbility.getTopSpellAbilityOnStack(game, sa);
|
final SpellAbility topSA = ComputerUtilAbility.getTopSpellAbilityOnStack(game, sa);
|
||||||
if ((topSA.isSpell() && !topSA.isCounterableBy(sa)) || ai.getYourTeam().contains(topSA.getActivatingPlayer())) {
|
if ((topSA.isSpell() && !topSA.isCounterableBy(sa)) || ai.getYourTeam().contains(topSA.getActivatingPlayer())) {
|
||||||
// might as well check for player's friendliness
|
// might as well check for player's friendliness
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
} else if (sa.hasParam("ConditionWouldDestroy") && !CounterEffect.checkForConditionWouldDestroy(sa, topSA)) {
|
} else if (sa.hasParam("ConditionWouldDestroy") && !CounterEffect.checkForConditionWouldDestroy(sa, topSA)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlaySa);
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if the top ability on the stack corresponds to the AI-specific targeting declaration, if provided
|
// check if the top ability on the stack corresponds to the AI-specific targeting declaration, if provided
|
||||||
if (sa.hasParam("AITgts") && (topSA.getHostCard() == null
|
if (sa.hasParam("AITgts") && (topSA.getHostCard() == null
|
||||||
|| !topSA.getHostCard().isValid(sa.getParam("AITgts"), sa.getActivatingPlayer(), source, sa))) {
|
|| !topSA.getHostCard().isValid(sa.getParam("AITgts"), sa.getActivatingPlayer(), source, sa))) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sa.hasParam("CounterNoManaSpell") && topSA.getTotalManaSpent() > 0) {
|
if (sa.hasParam("CounterNoManaSpell") && topSA.getTotalManaSpent() > 0) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sa.hasParam("UnlessCost") && "TargetedController".equals(sa.getParamOrDefault("UnlessPayer", "TargetedController"))) {
|
if (sa.hasParam("UnlessCost") && "TargetedController".equals(sa.getParamOrDefault("UnlessPayer", "TargetedController"))) {
|
||||||
@@ -84,7 +72,7 @@ public class CounterAi extends SpellAbilityAi {
|
|||||||
CostDiscard discardCost = unlessCost.getCostPartByType(CostDiscard.class);
|
CostDiscard discardCost = unlessCost.getCostPartByType(CostDiscard.class);
|
||||||
if ("Hand".equals(discardCost.getType())) {
|
if ("Hand".equals(discardCost.getType())) {
|
||||||
if (topSA.getActivatingPlayer().getCardsIn(ZoneType.Hand).size() < 2) {
|
if (topSA.getActivatingPlayer().getCardsIn(ZoneType.Hand).size() < 2) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -100,10 +88,11 @@ public class CounterAi extends SpellAbilityAi {
|
|||||||
tgtCMC += topSA.getPayCosts().getTotalMana().countX() > 0 ? 3 : 0; // TODO: somehow determine the value of X paid and account for it?
|
tgtCMC += topSA.getPayCosts().getTotalMana().countX() > 0 ? 3 : 0; // TODO: somehow determine the value of X paid and account for it?
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return false;
|
// This spell doesn't target. Must be a "Coutner All" or "Counter trigger" type of ability.
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
String unlessCost = sa.hasParam("UnlessCost") ? sa.getParam("UnlessCost").trim() : null;
|
String unlessCost = sa.hasParam("UnlessCost") ? sa.getParam("UnlessCost").trim() : null;
|
||||||
@@ -122,13 +111,13 @@ public class CounterAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (toPay == 0) {
|
if (toPay == 0) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantAffordX);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (toPay <= usableManaSources) {
|
if (toPay <= usableManaSources) {
|
||||||
// If this is a reusable Resource, feel free to play it most of the time
|
// If this is a reusable Resource, feel free to play it most of the time
|
||||||
if (!playReusable(ai, sa)) {
|
if (!playReusable(ai, sa)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantAfford);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -147,15 +136,15 @@ public class CounterAi extends SpellAbilityAi {
|
|||||||
if (sa.hasParam("AILogic")) {
|
if (sa.hasParam("AILogic")) {
|
||||||
String logic = sa.getParam("AILogic");
|
String logic = sa.getParam("AILogic");
|
||||||
if ("Never".equals(logic)) {
|
if ("Never".equals(logic)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
} else if (logic.startsWith("MinCMC.")) { // TODO fix Daze and fold into AITgts
|
} else if (logic.startsWith("MinCMC.")) { // TODO fix Daze and fold into AITgts
|
||||||
int minCMC = Integer.parseInt(logic.substring(7));
|
int minCMC = Integer.parseInt(logic.substring(7));
|
||||||
if (tgtCMC < minCMC) {
|
if (tgtCMC < minCMC) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
} else if ("NullBrooch".equals(logic)) {
|
} else if ("NullBrooch".equals(logic)) {
|
||||||
if (!SpecialCardAi.NullBrooch.consider(ai, sa)) {
|
if (!SpecialCardAi.NullBrooch.consider(ai, sa)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -234,40 +223,40 @@ public class CounterAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (dontCounter) {
|
if (dontCounter) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
return toReturn;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
|
public AiAbilityDecision chkDrawback(SpellAbility sa, Player aiPlayer) {
|
||||||
return doTriggerAINoCost(aiPlayer, sa, true);
|
return doTriggerNoCost(aiPlayer, sa, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
protected AiAbilityDecision doTriggerNoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
final Game game = ai.getGame();
|
final Game game = ai.getGame();
|
||||||
|
|
||||||
if (sa.usesTargeting()) {
|
if (sa.usesTargeting()) {
|
||||||
if (game.getStack().isEmpty()) {
|
if (game.getStack().isEmpty()) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
|
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
if (mandatory && !sa.canAddMoreTarget()) {
|
if (mandatory && !sa.canAddMoreTarget()) {
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
Pair<SpellAbility, Boolean> pair = chooseTargetSpellAbility(game, sa, ai, mandatory);
|
Pair<SpellAbility, Boolean> pair = chooseTargetSpellAbility(game, sa, ai, mandatory);
|
||||||
SpellAbility tgtSA = pair.getLeft();
|
SpellAbility tgtSA = pair.getLeft();
|
||||||
|
|
||||||
if (tgtSA == null) {
|
if (tgtSA == null) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
sa.getTargets().add(tgtSA);
|
sa.getTargets().add(tgtSA);
|
||||||
if (!mandatory && !pair.getRight()) {
|
if (!mandatory && !pair.getRight()) {
|
||||||
// If not mandatory and not preferred, bail out after setting target
|
// If not mandatory and not preferred, bail out after setting target
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
String unlessCost = sa.hasParam("UnlessCost") ? sa.getParam("UnlessCost").trim() : null;
|
String unlessCost = sa.hasParam("UnlessCost") ? sa.getParam("UnlessCost").trim() : null;
|
||||||
@@ -288,14 +277,13 @@ public class CounterAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
if (!mandatory) {
|
if (!mandatory) {
|
||||||
if (toPay == 0) {
|
if (toPay == 0) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantAffordX);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (toPay <= usableManaSources) {
|
if (toPay <= usableManaSources) {
|
||||||
// If this is a reusable Resource, feel free to play it most
|
// If this is a reusable Resource, feel free to play it most of the time
|
||||||
// of the time
|
|
||||||
if (!playReusable(ai,sa) || (MyRandom.getRandom().nextFloat() < .4)) {
|
if (!playReusable(ai,sa) || (MyRandom.getRandom().nextFloat() < .4)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantAfford);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -312,7 +300,7 @@ public class CounterAi extends SpellAbilityAi {
|
|||||||
// force the Human into making decisions)
|
// force the Human into making decisions)
|
||||||
|
|
||||||
// But really it should be more picky about how it counters things
|
// But really it should be more picky about how it counters things
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Pair<SpellAbility, Boolean> chooseTargetSpellAbility(Game game, SpellAbility sa, Player ai, boolean mandatory) {
|
public Pair<SpellAbility, Boolean> chooseTargetSpellAbility(Game game, SpellAbility sa, Player ai, boolean mandatory) {
|
||||||
@@ -362,11 +350,11 @@ public class CounterAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean willPayUnlessCost(SpellAbility sa, Player payer, Cost cost, boolean alreadyPaid, FCollectionView<Player> payers) {
|
public boolean willPayUnlessCost(SpellAbility sa, Player payer, Cost cost, boolean alreadyPaid, FCollectionView<Player> payers) {
|
||||||
// ward or human misplay
|
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
final Game game = source.getGame();
|
final Game game = source.getGame();
|
||||||
List<SpellAbility> spells = AbilityUtils.getDefinedSpellAbilities(source, sa.getParamOrDefault("Defined", "Targeted"), sa);
|
List<SpellAbility> spells = AbilityUtils.getDefinedSpellAbilities(source, sa.getParamOrDefault("Defined", "Targeted"), sa);
|
||||||
for (SpellAbility toBeCountered : spells) {
|
for (SpellAbility toBeCountered : spells) {
|
||||||
|
// ward or human misplay
|
||||||
if (!toBeCountered.isCounterableBy(sa)) {
|
if (!toBeCountered.isCounterableBy(sa)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -381,7 +369,7 @@ public class CounterAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// no reason to pay if we don't plan to confirm
|
// no reason to pay if we don't plan to confirm
|
||||||
if (toBeCountered.isOptionalTrigger() && !SpellApiToAi.Converter.get(toBeCountered).doTriggerNoCostWithSubs(payer, toBeCountered, false)) {
|
if (toBeCountered.isOptionalTrigger() && !SpellApiToAi.Converter.get(toBeCountered).doTriggerNoCostWithSubs(payer, toBeCountered, false).willingToPlay()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// TODO check hasFizzled
|
// TODO check hasFizzled
|
||||||
|
|||||||
@@ -45,13 +45,13 @@ public abstract class CountersAi extends SpellAbilityAi {
|
|||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param list
|
* @param list
|
||||||
* a {@link forge.CardList} object.
|
* a {@link CardCollectionView} object.
|
||||||
* @param type
|
* @param type
|
||||||
* a {@link java.lang.String} object.
|
* a {@link String} object.
|
||||||
* @param amount
|
* @param amount
|
||||||
* a int.
|
* a int.
|
||||||
* @param newParam TODO
|
* @param ai a {@link Player} object.
|
||||||
* @return a {@link forge.game.card.Card} object.
|
* @return a {@link Card} object.
|
||||||
*/
|
*/
|
||||||
public static Card chooseCursedTarget(final CardCollectionView list, final String type, final int amount, final Player ai) {
|
public static Card chooseCursedTarget(final CardCollectionView list, final String type, final int amount, final Player ai) {
|
||||||
Card choice;
|
Card choice;
|
||||||
@@ -65,7 +65,7 @@ public abstract class CountersAi extends SpellAbilityAi {
|
|||||||
// try to kill the best killable creature, or reduce the best one
|
// try to kill the best killable creature, or reduce the best one
|
||||||
// but try not to target a Undying Creature
|
// but try not to target a Undying Creature
|
||||||
final List<Card> killable = CardLists.getNotKeyword(CardLists.filterToughness(list, amount), Keyword.UNDYING);
|
final List<Card> killable = CardLists.getNotKeyword(CardLists.filterToughness(list, amount), Keyword.UNDYING);
|
||||||
if (killable.size() > 0) {
|
if (!killable.isEmpty()) {
|
||||||
choice = ComputerUtilCard.getBestCreatureAI(killable);
|
choice = ComputerUtilCard.getBestCreatureAI(killable);
|
||||||
} else {
|
} else {
|
||||||
choice = ComputerUtilCard.getBestCreatureAI(list);
|
choice = ComputerUtilCard.getBestCreatureAI(list);
|
||||||
@@ -83,10 +83,10 @@ public abstract class CountersAi extends SpellAbilityAi {
|
|||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param list
|
* @param list
|
||||||
* a {@link forge.CardList} object.
|
* a {@link CardCollectionView} object.
|
||||||
* @param type
|
* @param type
|
||||||
* a {@link java.lang.String} object.
|
* a {@link String} object.
|
||||||
* @return a {@link forge.game.card.Card} object.
|
* @return a {@link Card} object.
|
||||||
*/
|
*/
|
||||||
public static Card chooseBoonTarget(final CardCollectionView list, final String type) {
|
public static Card chooseBoonTarget(final CardCollectionView list, final String type) {
|
||||||
Card choice = null;
|
Card choice = null;
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
import forge.ai.ComputerUtil;
|
import forge.ai.*;
|
||||||
import forge.ai.ComputerUtilCard;
|
|
||||||
import forge.ai.SpellAbilityAi;
|
|
||||||
import forge.game.Game;
|
import forge.game.Game;
|
||||||
import forge.game.ability.AbilityUtils;
|
import forge.game.ability.AbilityUtils;
|
||||||
import forge.game.card.*;
|
import forge.game.card.*;
|
||||||
@@ -21,19 +19,25 @@ import java.util.Map;
|
|||||||
|
|
||||||
public class CountersMoveAi extends SpellAbilityAi {
|
public class CountersMoveAi extends SpellAbilityAi {
|
||||||
@Override
|
@Override
|
||||||
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
|
protected AiAbilityDecision checkApiLogic(final Player ai, final SpellAbility sa) {
|
||||||
|
AiAbilityDecision decision = new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
if (sa.usesTargeting()) {
|
if (sa.usesTargeting()) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
if (!moveTgtAI(ai, sa)) {
|
decision = moveTgtAI(ai, sa);
|
||||||
return false;
|
if (!decision.willingToPlay()) {
|
||||||
|
return decision;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!playReusable(ai, sa)) {
|
if (!playReusable(ai, sa)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
return MyRandom.getRandom().nextFloat() < .8f; // random success
|
if (MyRandom.getRandom().nextFloat() < .8f) {
|
||||||
|
return decision;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -109,12 +113,13 @@ public class CountersMoveAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(final Player ai, SpellAbility sa, boolean mandatory) {
|
protected AiAbilityDecision doTriggerNoCost(final Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
if (sa.usesTargeting()) {
|
if (sa.usesTargeting()) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
|
|
||||||
if (!moveTgtAI(ai, sa) && !mandatory) {
|
AiAbilityDecision decision = moveTgtAI(ai, sa);
|
||||||
return false;
|
if (!decision.willingToPlay() && !mandatory) {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!sa.isTargetNumberValid() && mandatory) {
|
if (!sa.isTargetNumberValid() && mandatory) {
|
||||||
@@ -122,18 +127,18 @@ public class CountersMoveAi extends SpellAbilityAi {
|
|||||||
List<Card> tgtCards = CardLists.getTargetableCards(game.getCardsIn(ZoneType.Battlefield), sa);
|
List<Card> tgtCards = CardLists.getTargetableCards(game.getCardsIn(ZoneType.Battlefield), sa);
|
||||||
|
|
||||||
if (tgtCards.isEmpty()) {
|
if (tgtCards.isEmpty()) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
|
|
||||||
final Card card = ComputerUtilCard.getWorstAI(tgtCards);
|
final Card card = ComputerUtilCard.getWorstAI(tgtCards);
|
||||||
sa.getTargets().add(card);
|
sa.getTargets().add(card);
|
||||||
}
|
}
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
} else {
|
} else {
|
||||||
// no target Probably something like Graft
|
// no target Probably something like Graft
|
||||||
|
|
||||||
if (mandatory) {
|
if (mandatory) {
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
final Card host = sa.getHostCard();
|
final Card host = sa.getHostCard();
|
||||||
@@ -145,7 +150,7 @@ public class CountersMoveAi extends SpellAbilityAi {
|
|||||||
final List<Card> destCards = AbilityUtils.getDefinedCards(host, sa.getParam("Defined"), sa);
|
final List<Card> destCards = AbilityUtils.getDefinedCards(host, sa.getParam("Defined"), sa);
|
||||||
|
|
||||||
if (srcCards.isEmpty() || destCards.isEmpty()) {
|
if (srcCards.isEmpty() || destCards.isEmpty()) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.MissingNeededCards);
|
||||||
}
|
}
|
||||||
|
|
||||||
final Card src = srcCards.get(0);
|
final Card src = srcCards.get(0);
|
||||||
@@ -153,21 +158,21 @@ public class CountersMoveAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
// for such Trigger, do not move counter to another players creature
|
// for such Trigger, do not move counter to another players creature
|
||||||
if (!dest.getController().equals(ai)) {
|
if (!dest.getController().equals(ai)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
} else if (ComputerUtilCard.isUselessCreature(ai, dest)) {
|
} else if (ComputerUtilCard.isUselessCreature(ai, dest)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
} else if (dest.hasSVar("EndOfTurnLeavePlay")) {
|
} else if (dest.hasSVar("EndOfTurnLeavePlay")) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cType != null) {
|
if (cType != null) {
|
||||||
if (!dest.canReceiveCounters(cType)) {
|
if (!dest.canReceiveCounters(cType)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
final int amount = calcAmount(sa, cType);
|
final int amount = calcAmount(sa, cType);
|
||||||
int a = src.getCounters(cType);
|
int a = src.getCounters(cType);
|
||||||
if (a < amount) {
|
if (a < amount) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
final Card srcCopy = CardCopyService.getLKICopy(src);
|
final Card srcCopy = CardCopyService.getLKICopy(src);
|
||||||
@@ -181,27 +186,31 @@ public class CountersMoveAi extends SpellAbilityAi {
|
|||||||
int newEval = ComputerUtilCard.evaluateCreature(srcCopy) + ComputerUtilCard.evaluateCreature(destCopy);
|
int newEval = ComputerUtilCard.evaluateCreature(srcCopy) + ComputerUtilCard.evaluateCreature(destCopy);
|
||||||
|
|
||||||
if (newEval < oldEval) {
|
if (newEval < oldEval) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.MissingNeededCards);
|
||||||
}
|
}
|
||||||
|
|
||||||
// check for some specific AI preferences
|
// check for some specific AI preferences
|
||||||
if ("DontMoveCounterIfLethal".equals(sa.getParam("AILogic"))) {
|
if ("DontMoveCounterIfLethal".equals(sa.getParam("AILogic"))) {
|
||||||
return !cType.is(CounterEnumType.P1P1) || src.getNetToughness() - src.getTempToughnessBoost() - 1 > 0;
|
if (!cType.is(CounterEnumType.P1P1) || src.getNetToughness() - src.getTempToughnessBoost() - 1 > 0) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
} else {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// no target
|
// no target
|
||||||
return true;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
public AiAbilityDecision chkDrawback(SpellAbility sa, Player ai) {
|
||||||
if (sa.usesTargeting()) {
|
if (sa.usesTargeting()) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
return moveTgtAI(ai, sa);
|
return moveTgtAI(ai, sa);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int calcAmount(final SpellAbility sa, final CounterType cType) {
|
private static int calcAmount(final SpellAbility sa, final CounterType cType) {
|
||||||
@@ -226,7 +235,7 @@ public class CountersMoveAi extends SpellAbilityAi {
|
|||||||
return amount;
|
return amount;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean moveTgtAI(final Player ai, final SpellAbility sa) {
|
private AiAbilityDecision moveTgtAI(final Player ai, final SpellAbility sa) {
|
||||||
final Card host = sa.getHostCard();
|
final Card host = sa.getHostCard();
|
||||||
final Game game = ai.getGame();
|
final Game game = ai.getGame();
|
||||||
final String type = sa.getParam("CounterType");
|
final String type = sa.getParam("CounterType");
|
||||||
@@ -244,7 +253,7 @@ public class CountersMoveAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
if (destCards.isEmpty()) {
|
if (destCards.isEmpty()) {
|
||||||
// something went wrong
|
// something went wrong
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.MissingNeededCards);
|
||||||
}
|
}
|
||||||
|
|
||||||
final Card dest = destCards.get(0);
|
final Card dest = destCards.get(0);
|
||||||
@@ -253,7 +262,7 @@ public class CountersMoveAi extends SpellAbilityAi {
|
|||||||
tgtCards.remove(dest);
|
tgtCards.remove(dest);
|
||||||
|
|
||||||
if (cType != null && !dest.canReceiveCounters(cType)) {
|
if (cType != null && !dest.canReceiveCounters(cType)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
// prefered logic for this: try to steal counter
|
// prefered logic for this: try to steal counter
|
||||||
@@ -285,7 +294,7 @@ public class CountersMoveAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
if (card != null) {
|
if (card != null) {
|
||||||
sa.getTargets().add(card);
|
sa.getTargets().add(card);
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -329,14 +338,14 @@ public class CountersMoveAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
if (card != null) {
|
if (card != null) {
|
||||||
sa.getTargets().add(card);
|
sa.getTargets().add(card);
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
} else if (sa.getMaxTargets() == 2) {
|
} else if (sa.getMaxTargets() == 2) {
|
||||||
// TODO
|
// TODO
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
} else {
|
} else {
|
||||||
// SA uses target for Defined
|
// SA uses target for Defined
|
||||||
// Source => Targeted
|
// Source => Targeted
|
||||||
@@ -344,12 +353,12 @@ public class CountersMoveAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
if (srcCards.isEmpty()) {
|
if (srcCards.isEmpty()) {
|
||||||
// something went wrong
|
// something went wrong
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.MissingNeededCards);
|
||||||
}
|
}
|
||||||
|
|
||||||
final Card src = srcCards.get(0);
|
final Card src = srcCards.get(0);
|
||||||
if (cType != null && src.getCounters(cType) <= 0) {
|
if (cType != null && src.getCounters(cType) <= 0) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
Card lkiWithCounters = CardCopyService.getLKICopy(src);
|
Card lkiWithCounters = CardCopyService.getLKICopy(src);
|
||||||
@@ -402,14 +411,14 @@ public class CountersMoveAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
if (card != null) {
|
if (card != null) {
|
||||||
sa.getTargets().add(card);
|
sa.getTargets().add(card);
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
final boolean isMandatoryTrigger = (sa.isTrigger() && !sa.isOptionalTrigger())
|
final boolean isMandatoryTrigger = (sa.isTrigger() && !sa.isOptionalTrigger())
|
||||||
|| (sa.getRootAbility().isTrigger() && !sa.getRootAbility().isOptionalTrigger());
|
|| (sa.getRootAbility().isTrigger() && !sa.getRootAbility().isOptionalTrigger());
|
||||||
if (!isMandatoryTrigger) {
|
if (!isMandatoryTrigger) {
|
||||||
// no good target
|
// no good target
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -439,10 +448,10 @@ public class CountersMoveAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
if (card != null) {
|
if (card != null) {
|
||||||
sa.getTargets().add(card);
|
sa.getTargets().add(card);
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
import forge.ai.ComputerUtil;
|
import forge.ai.*;
|
||||||
import forge.ai.ComputerUtilCost;
|
|
||||||
import forge.ai.SpellAbilityAi;
|
|
||||||
import forge.game.Game;
|
import forge.game.Game;
|
||||||
import forge.game.ability.AbilityUtils;
|
import forge.game.ability.AbilityUtils;
|
||||||
import forge.game.card.*;
|
import forge.game.card.*;
|
||||||
@@ -21,7 +19,7 @@ import java.util.Map;
|
|||||||
public class CountersMultiplyAi extends SpellAbilityAi {
|
public class CountersMultiplyAi extends SpellAbilityAi {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean checkApiLogic(Player ai, SpellAbility sa) {
|
protected AiAbilityDecision checkApiLogic(Player ai, SpellAbility sa) {
|
||||||
final CounterType counterType = getCounterType(sa);
|
final CounterType counterType = getCounterType(sa);
|
||||||
|
|
||||||
if (!sa.usesTargeting()) {
|
if (!sa.usesTargeting()) {
|
||||||
@@ -53,7 +51,7 @@ public class CountersMultiplyAi extends SpellAbilityAi {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (list.isEmpty()) {
|
if (list.isEmpty()) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.MissingNeededCards);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return setTargets(ai, sa);
|
return setTargets(ai, sa);
|
||||||
@@ -85,24 +83,27 @@ public class CountersMultiplyAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
protected AiAbilityDecision doTriggerNoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
if (!sa.usesTargeting()) {
|
if (!sa.usesTargeting()) {
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
if (setTargets(ai, sa)) {
|
|
||||||
return true;
|
AiAbilityDecision decision = setTargets(ai, sa);
|
||||||
|
if (decision.willingToPlay()) {
|
||||||
|
return decision;
|
||||||
} else if (mandatory) {
|
} else if (mandatory) {
|
||||||
CardCollection list = CardLists.getTargetableCards(ai.getGame().getCardsIn(ZoneType.Battlefield), sa);
|
CardCollection list = CardLists.getTargetableCards(ai.getGame().getCardsIn(ZoneType.Battlefield), sa);
|
||||||
if (list.isEmpty()) {
|
if (list.isEmpty()) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
Card safeMatch = list.stream()
|
Card safeMatch = list.stream()
|
||||||
.filter(CardPredicates.hasCounters().negate())
|
.filter(CardPredicates.hasCounters().negate())
|
||||||
.findFirst().orElse(null);
|
.findFirst().orElse(null);
|
||||||
sa.getTargets().add(safeMatch == null ? list.getFirst() : safeMatch);
|
sa.getTargets().add(safeMatch == null ? list.getFirst() : safeMatch);
|
||||||
return true;
|
return new AiAbilityDecision(50, AiPlayDecision.MandatoryPlay);
|
||||||
}
|
}
|
||||||
return mandatory;
|
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
private CounterType getCounterType(SpellAbility sa) {
|
private CounterType getCounterType(SpellAbility sa) {
|
||||||
@@ -117,7 +118,7 @@ public class CountersMultiplyAi extends SpellAbilityAi {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean setTargets(Player ai, SpellAbility sa) {
|
private AiAbilityDecision setTargets(Player ai, SpellAbility sa) {
|
||||||
final CounterType counterType = getCounterType(sa);
|
final CounterType counterType = getCounterType(sa);
|
||||||
|
|
||||||
final Game game = ai.getGame();
|
final Game game = ai.getGame();
|
||||||
@@ -173,10 +174,10 @@ public class CountersMultiplyAi extends SpellAbilityAi {
|
|||||||
// targeting does failed
|
// targeting does failed
|
||||||
if (!sa.isTargetNumberValid() || sa.getTargets().size() == 0) {
|
if (!sa.isTargetNumberValid() || sa.getTargets().size() == 0) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addTargetsByCounterType(final Player ai, final SpellAbility sa, final CardCollection list,
|
private void addTargetsByCounterType(final Player ai, final SpellAbility sa, final CardCollection list,
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import java.util.Map;
|
|||||||
public class CountersProliferateAi extends SpellAbilityAi {
|
public class CountersProliferateAi extends SpellAbilityAi {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean checkApiLogic(Player ai, SpellAbility sa) {
|
protected AiAbilityDecision checkApiLogic(Player ai, SpellAbility sa) {
|
||||||
final List<Card> cperms = Lists.newArrayList();
|
final List<Card> cperms = Lists.newArrayList();
|
||||||
boolean allyExpOrEnergy = false;
|
boolean allyExpOrEnergy = false;
|
||||||
|
|
||||||
@@ -68,25 +68,34 @@ public class CountersProliferateAi extends SpellAbilityAi {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
return !cperms.isEmpty() || !hperms.isEmpty() || opponentPoison || allyExpOrEnergy;
|
if (!cperms.isEmpty() || !hperms.isEmpty() || opponentPoison || allyExpOrEnergy) {
|
||||||
|
// AI will play it if there are any counters to proliferate
|
||||||
|
// or if there are no counters, but AI has experience or energy counters
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
protected AiAbilityDecision doTriggerNoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
||||||
boolean chance = true;
|
boolean chance = true;
|
||||||
|
|
||||||
// TODO Make sure Human has poison counters or there are some counters
|
// TODO Make sure Human has poison counters or there are some counters
|
||||||
// we want to proliferate
|
// we want to proliferate
|
||||||
return chance;
|
return new AiAbilityDecision(
|
||||||
|
chance ? 100 : 0,
|
||||||
|
chance ? AiPlayDecision.WillPlay : AiPlayDecision.CantPlayAi
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* (non-Javadoc)
|
/* (non-Javadoc)
|
||||||
* @see forge.card.abilityfactory.SpellAiLogic#chkAIDrawback(java.util.Map, forge.card.spellability.SpellAbility, forge.game.player.Player)
|
* @see forge.card.abilityfactory.SpellAiLogic#chkAIDrawback(java.util.Map, forge.card.spellability.SpellAbility, forge.game.player.Player)
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
public AiAbilityDecision chkDrawback(SpellAbility sa, Player ai) {
|
||||||
if ("Always".equals(sa.getParam("AILogic"))) {
|
if ("Always".equals(sa.getParam("AILogic"))) {
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
return checkApiLogic(ai, sa);
|
return checkApiLogic(ai, sa);
|
||||||
|
|||||||
@@ -53,8 +53,7 @@ public class CountersPutAi extends CountersAi {
|
|||||||
|
|
||||||
// disable moving counters (unless a specialized AI logic supports it)
|
// disable moving counters (unless a specialized AI logic supports it)
|
||||||
for (final CostPart part : cost.getCostParts()) {
|
for (final CostPart part : cost.getCostParts()) {
|
||||||
if (part instanceof CostRemoveCounter) {
|
if (part instanceof CostRemoveCounter remCounter) {
|
||||||
final CostRemoveCounter remCounter = (CostRemoveCounter) part;
|
|
||||||
final CounterType counterType = remCounter.counter;
|
final CounterType counterType = remCounter.counter;
|
||||||
if (counterType.getName().equals(type) && !aiLogic.startsWith("MoveCounter")) {
|
if (counterType.getName().equals(type) && !aiLogic.startsWith("MoveCounter")) {
|
||||||
return false;
|
return false;
|
||||||
@@ -98,7 +97,7 @@ public class CountersPutAi extends CountersAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sa.hasParam("LevelUp")) {
|
if (sa.isKeyword(Keyword.LEVEL_UP)) {
|
||||||
// creatures enchanted by curse auras have low priority
|
// creatures enchanted by curse auras have low priority
|
||||||
if (ph.getPhase().isBefore(PhaseType.MAIN2)) {
|
if (ph.getPhase().isBefore(PhaseType.MAIN2)) {
|
||||||
for (Card aura : source.getEnchantedBy()) {
|
for (Card aura : source.getEnchantedBy()) {
|
||||||
@@ -119,7 +118,7 @@ public class CountersPutAi extends CountersAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean checkApiLogic(Player ai, final SpellAbility sa) {
|
protected AiAbilityDecision checkApiLogic(Player ai, final 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 Cost abCost = sa.getPayCosts();
|
final Cost abCost = sa.getPayCosts();
|
||||||
@@ -160,7 +159,7 @@ public class CountersPutAi extends CountersAi {
|
|||||||
PlayerCollection poisonList = oppList.filter(PlayerPredicates.hasCounter(CounterEnumType.POISON, 9));
|
PlayerCollection poisonList = oppList.filter(PlayerPredicates.hasCounter(CounterEnumType.POISON, 9));
|
||||||
if (!poisonList.isEmpty()) {
|
if (!poisonList.isEmpty()) {
|
||||||
sa.getTargets().add(poisonList.max(PlayerPredicates.compareByLife()));
|
sa.getTargets().add(poisonList.max(PlayerPredicates.compareByLife()));
|
||||||
return true;
|
return new AiAbilityDecision(1000, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -176,7 +175,7 @@ public class CountersPutAi extends CountersAi {
|
|||||||
Card best = ComputerUtilCard.getBestAI(oppCreatM1);
|
Card best = ComputerUtilCard.getBestAI(oppCreatM1);
|
||||||
if (best != null) {
|
if (best != null) {
|
||||||
sa.getTargets().add(best);
|
sa.getTargets().add(best);
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
CardCollection aiCreat = CardLists.getTargetableCards(ai.getCreaturesInPlay(), sa);
|
CardCollection aiCreat = CardLists.getTargetableCards(ai.getCreaturesInPlay(), sa);
|
||||||
@@ -196,7 +195,7 @@ public class CountersPutAi extends CountersAi {
|
|||||||
best = ComputerUtilCard.getBestAI(aiCreat);
|
best = ComputerUtilCard.getBestAI(aiCreat);
|
||||||
if (best != null) {
|
if (best != null) {
|
||||||
sa.getTargets().add(best);
|
sa.getTargets().add(best);
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -205,28 +204,22 @@ public class CountersPutAi extends CountersAi {
|
|||||||
if (!ai.getCounters().isEmpty()) {
|
if (!ai.getCounters().isEmpty()) {
|
||||||
if (!eachExisting || ai.getPoisonCounters() < 5) {
|
if (!eachExisting || ai.getPoisonCounters() < 5) {
|
||||||
sa.getTargets().add(ai);
|
sa.getTargets().add(ai);
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
if ("AlwaysWithNoTgt".equals(logic)) {
|
||||||
return false;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
|
||||||
|
|
||||||
if ("Never".equals(logic)) {
|
|
||||||
return false;
|
|
||||||
} else if ("AlwaysWithNoTgt".equals(logic)) {
|
|
||||||
return true;
|
|
||||||
} else if ("AristocratCounters".equals(logic)) {
|
} else if ("AristocratCounters".equals(logic)) {
|
||||||
return SpecialAiLogic.doAristocratWithCountersLogic(ai, sa);
|
return SpecialAiLogic.doAristocratWithCountersLogic(ai, sa);
|
||||||
} else if ("PayEnergy".equals(logic)) {
|
} else if ("PayEnergy".equals(logic)) {
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
} else if ("PayEnergyConservatively".equals(logic)) {
|
} else if ("PayEnergyConservatively".equals(logic)) {
|
||||||
boolean onlyInCombat = ai.getController().isAI()
|
boolean onlyInCombat = ai.getController().isAI()
|
||||||
&& ((PlayerControllerAi) ai.getController()).getAi().getBooleanProperty(AiProps.CONSERVATIVE_ENERGY_PAYMENT_ONLY_IN_COMBAT);
|
&& ((PlayerControllerAi) ai.getController()).getAi().getBooleanProperty(AiProps.CONSERVATIVE_ENERGY_PAYMENT_ONLY_IN_COMBAT);
|
||||||
@@ -235,10 +228,10 @@ public class CountersPutAi extends CountersAi {
|
|||||||
|
|
||||||
if (playAggro) {
|
if (playAggro) {
|
||||||
// aggro profiles ignore conservative play for this AI logic
|
// aggro profiles ignore conservative play for this AI logic
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
} else if (ph.inCombat() && source != null) {
|
} else if (ph.inCombat() && source != null) {
|
||||||
if (ai.getGame().getCombat().isAttacking(source) && !onlyDefensive) {
|
if (ai.getGame().getCombat().isAttacking(source) && !onlyDefensive) {
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.ImpactCombat);
|
||||||
} else if (ai.getGame().getCombat().isBlocking(source)) {
|
} else if (ai.getGame().getCombat().isBlocking(source)) {
|
||||||
// when blocking, consider this if it's possible to save the blocker and/or kill at least one attacker
|
// when blocking, consider this if it's possible to save the blocker and/or kill at least one attacker
|
||||||
CardCollection blocked = ai.getGame().getCombat().getAttackersBlockedBy(source);
|
CardCollection blocked = ai.getGame().getCombat().getAttackersBlockedBy(source);
|
||||||
@@ -248,28 +241,28 @@ public class CountersPutAi extends CountersAi {
|
|||||||
int numActivations = ai.getCounters(CounterEnumType.ENERGY) / sa.getPayCosts().getCostEnergy().convertAmount();
|
int numActivations = ai.getCounters(CounterEnumType.ENERGY) / sa.getPayCosts().getCostEnergy().convertAmount();
|
||||||
if (source.getNetToughness() + numActivations > totBlkPower
|
if (source.getNetToughness() + numActivations > totBlkPower
|
||||||
|| source.getNetPower() + numActivations >= totBlkToughness) {
|
|| source.getNetPower() + numActivations >= totBlkToughness) {
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.ImpactCombat);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (sa.getSubAbility() != null
|
} else if (sa.getSubAbility() != null
|
||||||
&& "Self".equals(sa.getSubAbility().getParam("Defined"))
|
&& "Self".equals(sa.getSubAbility().getParam("Defined"))
|
||||||
&& sa.getSubAbility().getParamOrDefault("KW", "").contains("Hexproof")
|
&& sa.getSubAbility().getParamOrDefault("KW", "").contains("Hexproof")
|
||||||
&& !AiCardMemory.isRememberedCard(ai, source, AiCardMemory.MemorySet.ANIMATED_THIS_TURN)) {
|
&& !AiCardMemory.isRememberedCard(ai, source, AiCardMemory.MemorySet.ACTIVATED_THIS_TURN)) {
|
||||||
// Bristling Hydra: save from death using a ping activation
|
// Bristling Hydra: save from death using a ping activation
|
||||||
if (ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), sa).contains(source)) {
|
if (ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), sa).contains(source)) {
|
||||||
AiCardMemory.rememberCard(ai, source, AiCardMemory.MemorySet.ACTIVATED_THIS_TURN);
|
AiCardMemory.rememberCard(ai, source, AiCardMemory.MemorySet.ACTIVATED_THIS_TURN);
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
} else if (ai.getCounters(CounterEnumType.ENERGY) > ComputerUtilCard.getMaxSAEnergyCostOnBattlefield(ai) + sa.getPayCosts().getCostEnergy().convertAmount()) {
|
} else if (ai.getCounters(CounterEnumType.ENERGY) > ComputerUtilCard.getMaxSAEnergyCostOnBattlefield(ai) + sa.getPayCosts().getCostEnergy().convertAmount()) {
|
||||||
// outside of combat, this logic only works if the relevant AI profile option is enabled
|
// outside of combat, this logic only works if the relevant AI profile option is enabled
|
||||||
// and if there is enough energy saved
|
// and if there is enough energy saved
|
||||||
if (!onlyInCombat) {
|
if (!onlyInCombat) {
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (logic.equals("MarkOppCreature")) {
|
} else if (logic.equals("MarkOppCreature")) {
|
||||||
if (!ph.is(PhaseType.END_OF_TURN)) {
|
if (!ph.is(PhaseType.END_OF_TURN)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.WaitForEndOfTurn);
|
||||||
}
|
}
|
||||||
|
|
||||||
Predicate<Card> predicate = CardPredicates.hasCounter(CounterType.getType(type));
|
Predicate<Card> predicate = CardPredicates.hasCounter(CounterType.getType(type));
|
||||||
@@ -281,12 +274,12 @@ public class CountersPutAi extends CountersAi {
|
|||||||
Card bestCreat = ComputerUtilCard.getBestCreatureAI(oppCreats);
|
Card bestCreat = ComputerUtilCard.getBestCreatureAI(oppCreats);
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
sa.getTargets().add(bestCreat);
|
sa.getTargets().add(bestCreat);
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
} else if (logic.equals("CheckDFC")) {
|
} else if (logic.equals("CheckDFC")) {
|
||||||
// for cards like Ludevic's Test Subject
|
// for cards like Ludevic's Test Subject
|
||||||
if (!source.canTransform(null)) {
|
if (!source.canTransform(null)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
} else if (logic.startsWith("MoveCounter")) {
|
} else if (logic.startsWith("MoveCounter")) {
|
||||||
return doMoveCounterLogic(ai, sa, ph);
|
return doMoveCounterLogic(ai, sa, ph);
|
||||||
@@ -295,8 +288,15 @@ public class CountersPutAi extends CountersAi {
|
|||||||
if (willActivate && ph.getPhase().isBefore(PhaseType.MAIN2)) {
|
if (willActivate && ph.getPhase().isBefore(PhaseType.MAIN2)) {
|
||||||
// don't use this for mana until after combat
|
// don't use this for mana until after combat
|
||||||
AiCardMemory.rememberCard(ai, source, AiCardMemory.MemorySet.HELD_MANA_SOURCES_FOR_MAIN2);
|
AiCardMemory.rememberCard(ai, source, AiCardMemory.MemorySet.HELD_MANA_SOURCES_FOR_MAIN2);
|
||||||
|
return new AiAbilityDecision(25, AiPlayDecision.WaitForMain2);
|
||||||
}
|
}
|
||||||
return willActivate;
|
|
||||||
|
if (willActivate) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
} else {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
|
}
|
||||||
|
|
||||||
} else if (logic.equals("ChargeToBestCMC")) {
|
} else if (logic.equals("ChargeToBestCMC")) {
|
||||||
return doChargeToCMCLogic(ai, sa);
|
return doChargeToCMCLogic(ai, sa);
|
||||||
} else if (logic.equals("ChargeToBestOppControlledCMC")) {
|
} else if (logic.equals("ChargeToBestOppControlledCMC")) {
|
||||||
@@ -305,15 +305,11 @@ public class CountersPutAi extends CountersAi {
|
|||||||
return SpecialCardAi.TheOneRing.consider(ai, sa);
|
return SpecialCardAi.TheOneRing.consider(ai, sa);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!sa.metConditions() && sa.getSubAbility() == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sourceName.equals("Feat of Resistance")) { // sub-ability should take precedence
|
if (sourceName.equals("Feat of Resistance")) { // sub-ability should take precedence
|
||||||
CardCollection prot = ProtectAi.getProtectCreatures(ai, sa.getSubAbility());
|
CardCollection prot = ProtectAi.getProtectCreatures(ai, sa.getSubAbility());
|
||||||
if (!prot.isEmpty()) {
|
if (!prot.isEmpty()) {
|
||||||
sa.getTargets().add(prot.get(0));
|
sa.getTargets().add(prot.get(0));
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.ImpactCombat);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -321,13 +317,13 @@ public class CountersPutAi extends CountersAi {
|
|||||||
CardCollection creatsYouCtrl = ai.getCreaturesInPlay();
|
CardCollection creatsYouCtrl = ai.getCreaturesInPlay();
|
||||||
List<Card> leastToughness = Aggregates.listWithMin(creatsYouCtrl, Card::getNetToughness);
|
List<Card> leastToughness = Aggregates.listWithMin(creatsYouCtrl, Card::getNetToughness);
|
||||||
if (leastToughness.isEmpty()) {
|
if (leastToughness.isEmpty()) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.MissingNeededCards);
|
||||||
}
|
}
|
||||||
// TODO If Creature that would be Bolstered for some reason is useless, also return False
|
// TODO If Creature that would be Bolstered for some reason is useless, also return False
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sa.hasParam("Monstrosity") && source.isMonstrous()) {
|
if (sa.hasParam("Monstrosity") && source.isMonstrous()) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO handle proper calculation of X values based on Cost
|
// TODO handle proper calculation of X values based on Cost
|
||||||
@@ -342,7 +338,7 @@ public class CountersPutAi extends CountersAi {
|
|||||||
Combat combat = game.getCombat();
|
Combat combat = game.getCombat();
|
||||||
|
|
||||||
if (!source.canReceiveCounters(CounterType.get(CounterEnumType.P1P1)) || source.getCounters(CounterEnumType.P1P1) > 0) {
|
if (!source.canReceiveCounters(CounterType.get(CounterEnumType.P1P1)) || source.getCounters(CounterEnumType.P1P1) > 0) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
} else if (combat != null && ph.is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
} else if (combat != null && ph.is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
||||||
return doCombatAdaptLogic(source, amount, combat);
|
return doCombatAdaptLogic(source, amount, combat);
|
||||||
}
|
}
|
||||||
@@ -369,12 +365,12 @@ public class CountersPutAi extends CountersAi {
|
|||||||
|
|
||||||
// This will "rewind" clockwork cards when they fall to 50% power or below, consider improving
|
// This will "rewind" clockwork cards when they fall to 50% power or below, consider improving
|
||||||
if (curCtrs > Math.ceil(maxCtrs / 2.0)) {
|
if (curCtrs > Math.ceil(maxCtrs / 2.0)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
amount = Math.min(amount, maxCtrs - curCtrs);
|
amount = Math.min(amount, maxCtrs - curCtrs);
|
||||||
if (amount <= 0) {
|
if (amount <= 0) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -386,14 +382,14 @@ public class CountersPutAi extends CountersAi {
|
|||||||
.mapToInt(Card::getCMC)
|
.mapToInt(Card::getCMC)
|
||||||
.max().orElse(0);
|
.max().orElse(0);
|
||||||
if (amount > 0 && ai.getGame().getPhaseHandler().is(PhaseType.END_OF_TURN)) {
|
if (amount > 0 && ai.getGame().getPhaseHandler().is(PhaseType.END_OF_TURN)) {
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// don't use it if no counters to add
|
// don't use it if no counters to add
|
||||||
if (amount <= 0) {
|
if (amount <= 0) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ("Polukranos".equals(logic)) {
|
if ("Polukranos".equals(logic)) {
|
||||||
@@ -420,20 +416,14 @@ public class CountersPutAi extends CountersAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!canSurvive) {
|
if (!canSurvive) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
found = true;
|
found = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (!found) {
|
if (!found) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ("AtOppEOT".equals(logic)) {
|
|
||||||
if (ph.is(PhaseType.END_OF_TURN) && ph.getNextTurn().equals(ai)) {
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -444,17 +434,19 @@ public class CountersPutAi extends CountersAi {
|
|||||||
if (!ai.getGame().getStack().isEmpty() && !isSorcerySpeed(sa, ai)) {
|
if (!ai.getGame().getStack().isEmpty() && !isSorcerySpeed(sa, ai)) {
|
||||||
// only evaluates case where all tokens are placed on a single target
|
// only evaluates case where all tokens are placed on a single target
|
||||||
if (sa.getMinTargets() < 2) {
|
if (sa.getMinTargets() < 2) {
|
||||||
if (ComputerUtilCard.canPumpAgainstRemoval(ai, sa)) {
|
AiAbilityDecision decision = ComputerUtilCard.canPumpAgainstRemoval(ai, sa);
|
||||||
|
if (decision.willingToPlay()) {
|
||||||
Card c = sa.getTargetCard();
|
Card c = sa.getTargetCard();
|
||||||
if (sa.getTargets().size() > 1) {
|
if (sa.getTargets().size() > 1) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
sa.getTargets().add(c);
|
sa.getTargets().add(c);
|
||||||
}
|
}
|
||||||
sa.addDividedAllocation(c, amount);
|
sa.addDividedAllocation(c, amount);
|
||||||
return true;
|
return decision;
|
||||||
} else {
|
} else {
|
||||||
if (!hasSacCost) { // for Sacrifice costs, evaluate further to see if it's worth using the ability before the card dies
|
if (!hasSacCost) {
|
||||||
return false;
|
// for Sacrifice costs, evaluate further to see if it's worth using the ability before the card dies
|
||||||
|
return decision;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -498,7 +490,7 @@ public class CountersPutAi extends CountersAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (list.size() < sa.getTargetRestrictions().getMinTargets(source, sa)) {
|
if (list.size() < sa.getTargetRestrictions().getMinTargets(source, sa)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Activate +Loyalty planeswalker abilities even if they have no target (e.g. Vivien of the Arkbow),
|
// Activate +Loyalty planeswalker abilities even if they have no target (e.g. Vivien of the Arkbow),
|
||||||
@@ -507,9 +499,9 @@ public class CountersPutAi extends CountersAi {
|
|||||||
&& sa.isPwAbility()
|
&& sa.isPwAbility()
|
||||||
&& sa.getPayCosts().hasOnlySpecificCostType(CostPutCounter.class)
|
&& sa.getPayCosts().hasOnlySpecificCostType(CostPutCounter.class)
|
||||||
&& sa.isTargetNumberValid()
|
&& sa.isTargetNumberValid()
|
||||||
&& sa.getTargets().size() == 0
|
&& sa.getTargets().isEmpty()
|
||||||
&& ai.getGame().getPhaseHandler().is(PhaseType.MAIN2, ai)) {
|
&& ai.getGame().getPhaseHandler().is(PhaseType.MAIN2, ai)) {
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sourceName.equals("Abzan Charm")) {
|
if (sourceName.equals("Abzan Charm")) {
|
||||||
@@ -531,11 +523,11 @@ public class CountersPutAi extends CountersAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (left == 0) {
|
if (left == 0) {
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
}
|
}
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
|
|
||||||
// target loop
|
// target loop
|
||||||
@@ -543,7 +535,7 @@ public class CountersPutAi extends CountersAi {
|
|||||||
if (list.isEmpty()) {
|
if (list.isEmpty()) {
|
||||||
if (!sa.isTargetNumberValid() || sa.getTargets().isEmpty()) {
|
if (!sa.isTargetNumberValid() || sa.getTargets().isEmpty()) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
} else {
|
} else {
|
||||||
// TODO is this good enough? for up to amounts?
|
// TODO is this good enough? for up to amounts?
|
||||||
break;
|
break;
|
||||||
@@ -575,10 +567,9 @@ public class CountersPutAi extends CountersAi {
|
|||||||
// check if other choice will already be played
|
// check if other choice will already be played
|
||||||
increasesCharmOutcome = !choices.get(0).getTargets().isEmpty();
|
increasesCharmOutcome = !choices.get(0).getTargets().isEmpty();
|
||||||
}
|
}
|
||||||
if (!source.isSpell() || increasesCharmOutcome // does not cost a card or can buff charm for no expense
|
if (source != null && !source.isSpell() || increasesCharmOutcome // does not cost a card or can buff charm for no expense
|
||||||
|| ph.getTurn() - source.getTurnInZone() >= source.getGame().getPlayers().size() * 2) {
|
|| ph.getTurn() - source.getTurnInZone() >= source.getGame().getPlayers().size() * 2) {
|
||||||
if (abCost == null || abCost == Cost.Zero
|
if (abCost == Cost.Zero || ph.is(PhaseType.END_OF_TURN) && ph.getPlayerTurn().isOpponentOf(ai)) {
|
||||||
|| (ph.is(PhaseType.END_OF_TURN) && ph.getPlayerTurn().isOpponentOf(ai))) {
|
|
||||||
// only use at opponent EOT unless it is free
|
// only use at opponent EOT unless it is free
|
||||||
choice = chooseBoonTarget(list, type);
|
choice = chooseBoonTarget(list, type);
|
||||||
}
|
}
|
||||||
@@ -592,7 +583,7 @@ public class CountersPutAi extends CountersAi {
|
|||||||
if (choice == null) { // can't find anything left
|
if (choice == null) { // can't find anything left
|
||||||
if (!sa.isTargetNumberValid() || sa.getTargets().isEmpty()) {
|
if (!sa.isTargetNumberValid() || sa.getTargets().isEmpty()) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
} else {
|
} else {
|
||||||
// TODO is this good enough? for up to amounts?
|
// TODO is this good enough? for up to amounts?
|
||||||
break;
|
break;
|
||||||
@@ -608,14 +599,14 @@ public class CountersPutAi extends CountersAi {
|
|||||||
choice = null;
|
choice = null;
|
||||||
}
|
}
|
||||||
if (sa.getTargets().isEmpty()) {
|
if (sa.getTargets().isEmpty()) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
final List<Card> cards = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa);
|
final List<Card> cards = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa);
|
||||||
// Don't activate Curse abilities on my cards and non-curse abilities
|
// Don't activate Curse abilities on my cards and non-curse abilities
|
||||||
// on my opponents
|
// on my opponents
|
||||||
if (cards.isEmpty() || (cards.get(0).getController().isOpponentOf(ai) && !sa.isCurse())) {
|
if (cards.isEmpty() || (cards.get(0).getController().isOpponentOf(ai) && !sa.isCurse())) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.MissingNeededCards);
|
||||||
}
|
}
|
||||||
|
|
||||||
final int currCounters = cards.get(0).getCounters(CounterType.get(type));
|
final int currCounters = cards.get(0).getCounters(CounterType.get(type));
|
||||||
@@ -623,51 +614,50 @@ public class CountersPutAi extends CountersAi {
|
|||||||
// activating this ability.
|
// activating this ability.
|
||||||
|
|
||||||
if (!(type.equals("P1P1") || type.equals("M1M1") || type.equals("ICE")) && (MyRandom.getRandom().nextFloat() < (.1 * currCounters))) {
|
if (!(type.equals("P1P1") || type.equals("M1M1") || type.equals("ICE")) && (MyRandom.getRandom().nextFloat() < (.1 * currCounters))) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
// Instant +1/+1
|
// Instant +1/+1
|
||||||
if (type.equals("P1P1") && !isSorcerySpeed(sa, ai)) {
|
if (type.equals("P1P1") && !isSorcerySpeed(sa, ai)) {
|
||||||
if (!hasSacCost && !(ph.getNextTurn() == ai && ph.is(PhaseType.END_OF_TURN) && abCost.isReusuableResource())) {
|
if (!hasSacCost && !(ph.getNextTurn() == ai && ph.is(PhaseType.END_OF_TURN) && abCost.isReusuableResource())) {
|
||||||
return false; // only if next turn and cost is reusable
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Useless since the card already has the keyword (or for another reason)
|
// Useless since the card already has the keyword (or for another reason)
|
||||||
if (ComputerUtil.isUselessCounter(CounterType.get(type), cards.get(0))) {
|
if (ComputerUtil.isUselessCounter(CounterType.get(type), cards.get(0))) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean immediately = ComputerUtil.playImmediately(ai, sa);
|
boolean immediately = ComputerUtil.playImmediately(ai, sa);
|
||||||
|
|
||||||
if (abCost != null && !ComputerUtilCost.checkSacrificeCost(ai, abCost, source, sa, immediately)) {
|
if (!ComputerUtilCost.checkSacrificeCost(ai, abCost, source, sa, immediately)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantAfford);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (immediately) {
|
if (immediately) {
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!type.equals("P1P1") && !type.equals("M1M1") && !sa.hasParam("ActivationPhases")) {
|
if (!type.equals("P1P1") && !type.equals("M1M1") && !sa.hasParam("ActivationPhases")) {
|
||||||
// Don't use non P1P1/M1M1 counters before main 2 if possible
|
// Don't use non P1P1/M1M1 counters before main 2 if possible
|
||||||
if (ph.getPhase().isBefore(PhaseType.MAIN2) && !ComputerUtil.castSpellInMain1(ai, sa)) {
|
if (ph.getPhase().isBefore(PhaseType.MAIN2) && !ComputerUtil.castSpellInMain1(ai, sa)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.WaitForMain2);
|
||||||
}
|
}
|
||||||
if (ph.isPlayerTurn(ai) && !isSorcerySpeed(sa, ai)) {
|
if (ph.isPlayerTurn(ai) && !isSorcerySpeed(sa, ai)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.AnotherTime);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ComputerUtil.waitForBlocking(sa)) {
|
if (ComputerUtil.waitForBlocking(sa)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.WaitForCombat);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean chkAIDrawback(final SpellAbility sa, Player ai) {
|
public AiAbilityDecision chkDrawback(final SpellAbility sa, Player ai) {
|
||||||
boolean chance = true;
|
|
||||||
final Game game = ai.getGame();
|
final Game game = ai.getGame();
|
||||||
Card choice = null;
|
Card choice = null;
|
||||||
final String type = sa.getParam("CounterType");
|
final String type = sa.getParam("CounterType");
|
||||||
@@ -701,9 +691,9 @@ public class CountersPutAi extends CountersAi {
|
|||||||
while (sa.canAddMoreTarget()) {
|
while (sa.canAddMoreTarget()) {
|
||||||
if (list.isEmpty()) {
|
if (list.isEmpty()) {
|
||||||
if (!sa.isTargetNumberValid()
|
if (!sa.isTargetNumberValid()
|
||||||
|| sa.getTargets().size() == 0) {
|
|| sa.getTargets().isEmpty()) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
} else {
|
} else {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -724,9 +714,9 @@ public class CountersPutAi extends CountersAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (choice == null) { // can't find anything left
|
if (choice == null) { // can't find anything left
|
||||||
if ((!sa.isTargetNumberValid()) || (sa.getTargets().size() == 0)) {
|
if ((!sa.isTargetNumberValid()) || (sa.getTargets().isEmpty())) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
} else {
|
} else {
|
||||||
// TODO is this good enough? for up to amounts?
|
// TODO is this good enough? for up to amounts?
|
||||||
break;
|
break;
|
||||||
@@ -741,11 +731,11 @@ public class CountersPutAi extends CountersAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return chance;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
protected AiAbilityDecision doTriggerNoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
final SpellAbility root = sa.getRootAbility();
|
final SpellAbility root = sa.getRootAbility();
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
final String aiLogic = sa.getParamOrDefault("AILogic", "");
|
final String aiLogic = sa.getParamOrDefault("AILogic", "");
|
||||||
@@ -770,9 +760,28 @@ public class CountersPutAi extends CountersAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ("ChargeToBestCMC".equals(aiLogic)) {
|
if ("ChargeToBestCMC".equals(aiLogic)) {
|
||||||
return doChargeToCMCLogic(ai, sa) || mandatory;
|
AiAbilityDecision decision = doChargeToCMCLogic(ai, sa);
|
||||||
|
if (decision.willingToPlay()) {
|
||||||
|
// If the AI logic is to charge to best CMC, we can return true
|
||||||
|
// if the logic was successfully applied or if it's mandatory.
|
||||||
|
return decision;
|
||||||
|
} else if (mandatory) {
|
||||||
|
// If the logic was not applied and it's mandatory, we return false.
|
||||||
|
return new AiAbilityDecision(50, AiPlayDecision.MandatoryPlay);
|
||||||
|
} else {
|
||||||
|
// If the logic was not applied and it's not mandatory, we return false.
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
|
}
|
||||||
} else if ("ChargeToBestOppControlledCMC".equals(aiLogic)) {
|
} else if ("ChargeToBestOppControlledCMC".equals(aiLogic)) {
|
||||||
return doChargeToOppCtrlCMCLogic(ai, sa) || mandatory;
|
AiAbilityDecision decision = doChargeToOppCtrlCMCLogic(ai, sa);
|
||||||
|
if (decision.willingToPlay()) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
} else if (mandatory) {
|
||||||
|
// If the logic was not applied and it's mandatory, we return false.
|
||||||
|
return new AiAbilityDecision(50, AiPlayDecision.MandatoryPlay);
|
||||||
|
} else {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!sa.usesTargeting()) {
|
if (!sa.usesTargeting()) {
|
||||||
@@ -801,7 +810,7 @@ public class CountersPutAi extends CountersAi {
|
|||||||
sa.getTargetRestrictions().getAllCandidates(sa, true, true), Player.class));
|
sa.getTargetRestrictions().getAllCandidates(sa, true, true), Player.class));
|
||||||
|
|
||||||
if (playerList.isEmpty()) {
|
if (playerList.isEmpty()) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
|
|
||||||
// try to choose player with less creatures
|
// try to choose player with less creatures
|
||||||
@@ -817,8 +826,9 @@ public class CountersPutAi extends CountersAi {
|
|||||||
if (type.equals("P1P1")) {
|
if (type.equals("P1P1")) {
|
||||||
nPump = amount;
|
nPump = amount;
|
||||||
}
|
}
|
||||||
if (FightAi.canFightAi(ai, sa, nPump, nPump)) {
|
AiAbilityDecision decision = FightAi.canFightAi(ai, sa, nPump, nPump);
|
||||||
return true;
|
if (decision.willingToPlay()) {
|
||||||
|
return decision;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -839,7 +849,7 @@ public class CountersPutAi extends CountersAi {
|
|||||||
if (mandatory) {
|
if (mandatory) {
|
||||||
// When things are mandatory, gotta handle a little differently
|
// When things are mandatory, gotta handle a little differently
|
||||||
if ((list.isEmpty() || !preferred) && sa.isTargetNumberValid()) {
|
if ((list.isEmpty() || !preferred) && sa.isTargetNumberValid()) {
|
||||||
return true;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (list.isEmpty() && preferred) {
|
if (list.isEmpty() && preferred) {
|
||||||
@@ -859,7 +869,9 @@ public class CountersPutAi extends CountersAi {
|
|||||||
if (list.isEmpty()) {
|
if (list.isEmpty()) {
|
||||||
// Not mandatory, or the the list was regenerated and is still empty,
|
// Not mandatory, or the the list was regenerated and is still empty,
|
||||||
// so return whether or not we found enough targets
|
// so return whether or not we found enough targets
|
||||||
return sa.isTargetNumberValid();
|
if (sa.isTargetNumberValid()) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Card choice = null;
|
Card choice = null;
|
||||||
@@ -912,7 +924,7 @@ public class CountersPutAi extends CountersAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -1120,7 +1132,7 @@ public class CountersPutAi extends CountersAi {
|
|||||||
return Iterables.getFirst(options, null);
|
return Iterables.getFirst(options, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean doMoveCounterLogic(final Player ai, SpellAbility sa, PhaseHandler ph) {
|
private AiAbilityDecision doMoveCounterLogic(final Player ai, SpellAbility sa, PhaseHandler ph) {
|
||||||
// Spikes (Tempest)
|
// Spikes (Tempest)
|
||||||
|
|
||||||
// Try not to do it unless at the end of opponent's turn or the creature is threatened
|
// Try not to do it unless at the end of opponent's turn or the creature is threatened
|
||||||
@@ -1133,7 +1145,7 @@ public class CountersPutAi extends CountersAi {
|
|||||||
|| (combat.isBlocking(source) && ComputerUtilCombat.blockerWouldBeDestroyed(ai, source, combat) && !ComputerUtilCombat.willKillAtLeastOne(ai, source, combat))));
|
|| (combat.isBlocking(source) && ComputerUtilCombat.blockerWouldBeDestroyed(ai, source, combat) && !ComputerUtilCombat.willKillAtLeastOne(ai, source, combat))));
|
||||||
|
|
||||||
if (!(threatened || (ph.is(PhaseType.END_OF_TURN) && ph.getNextTurn() == ai))) {
|
if (!(threatened || (ph.is(PhaseType.END_OF_TURN) && ph.getNextTurn() == ai))) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.AnotherTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
CardCollection targets = CardLists.getTargetableCards(ai.getCreaturesInPlay(), sa);
|
CardCollection targets = CardLists.getTargetableCards(ai.getCreaturesInPlay(), sa);
|
||||||
@@ -1151,45 +1163,45 @@ public class CountersPutAi extends CountersAi {
|
|||||||
|
|
||||||
if (bestTgt != null) {
|
if (bestTgt != null) {
|
||||||
sa.getTargets().add(bestTgt);
|
sa.getTargets().add(bestTgt);
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean doCombatAdaptLogic(Card source, int amount, Combat combat) {
|
private AiAbilityDecision doCombatAdaptLogic(Card source, int amount, Combat combat) {
|
||||||
if (combat.isAttacking(source)) {
|
if (combat.isAttacking(source)) {
|
||||||
if (!combat.isBlocked(source)) {
|
if (!combat.isBlocked(source)) {
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
} else {
|
} else {
|
||||||
for (Card blockedBy : combat.getBlockers(source)) {
|
for (Card blockedBy : combat.getBlockers(source)) {
|
||||||
if (blockedBy.getNetToughness() > source.getNetPower()
|
if (blockedBy.getNetToughness() > source.getNetPower()
|
||||||
&& blockedBy.getNetToughness() <= source.getNetPower() + amount) {
|
&& blockedBy.getNetToughness() <= source.getNetPower() + amount) {
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int totBlkPower = Aggregates.sum(combat.getBlockers(source), Card::getNetPower);
|
int totBlkPower = Aggregates.sum(combat.getBlockers(source), Card::getNetPower);
|
||||||
if (source.getNetToughness() <= totBlkPower
|
if (source.getNetToughness() <= totBlkPower
|
||||||
&& source.getNetToughness() + amount > totBlkPower) {
|
&& source.getNetToughness() + amount > totBlkPower) {
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.ImpactCombat);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (combat.isBlocking(source)) {
|
} else if (combat.isBlocking(source)) {
|
||||||
for (Card blocked : combat.getAttackersBlockedBy(source)) {
|
for (Card blocked : combat.getAttackersBlockedBy(source)) {
|
||||||
if (blocked.getNetToughness() > source.getNetPower()
|
if (blocked.getNetToughness() > source.getNetPower()
|
||||||
&& blocked.getNetToughness() <= source.getNetPower() + amount) {
|
&& blocked.getNetToughness() <= source.getNetPower() + amount) {
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.ImpactCombat);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int totAtkPower = Aggregates.sum(combat.getAttackersBlockedBy(source), Card::getNetPower);
|
int totAtkPower = Aggregates.sum(combat.getAttackersBlockedBy(source), Card::getNetPower);
|
||||||
if (source.getNetToughness() <= totAtkPower
|
if (source.getNetToughness() <= totAtkPower
|
||||||
&& source.getNetToughness() + amount > totAtkPower) {
|
&& source.getNetToughness() + amount > totAtkPower) {
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -1200,7 +1212,7 @@ public class CountersPutAi extends CountersAi {
|
|||||||
return max;
|
return max;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean doChargeToCMCLogic(Player ai, SpellAbility sa) {
|
private AiAbilityDecision doChargeToCMCLogic(Player ai, SpellAbility sa) {
|
||||||
Card source = sa.getHostCard();
|
Card source = sa.getHostCard();
|
||||||
CardCollectionView ownLib = CardLists.filter(ai.getCardsIn(ZoneType.Library), CardPredicates.CREATURES);
|
CardCollectionView ownLib = CardLists.filter(ai.getCardsIn(ZoneType.Library), CardPredicates.CREATURES);
|
||||||
int numCtrs = source.getCounters(CounterEnumType.CHARGE);
|
int numCtrs = source.getCounters(CounterEnumType.CHARGE);
|
||||||
@@ -1215,10 +1227,16 @@ public class CountersPutAi extends CountersAi {
|
|||||||
optimalCMC = cmc;
|
optimalCMC = cmc;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return numCtrs < optimalCMC;
|
if (numCtrs < optimalCMC) {
|
||||||
|
// If the AI has less counters than the optimal CMC, it should play the ability.
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
} else {
|
||||||
|
// If the AI has enough counters or more than the optimal CMC, it should not play the ability.
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean doChargeToOppCtrlCMCLogic(Player ai, SpellAbility sa) {
|
private AiAbilityDecision doChargeToOppCtrlCMCLogic(Player ai, SpellAbility sa) {
|
||||||
Card source = sa.getHostCard();
|
Card source = sa.getHostCard();
|
||||||
CardCollectionView oppInPlay = CardLists.filter(ai.getOpponents().getCardsIn(ZoneType.Battlefield), CardPredicates.NONLAND_PERMANENTS);
|
CardCollectionView oppInPlay = CardLists.filter(ai.getOpponents().getCardsIn(ZoneType.Battlefield), CardPredicates.NONLAND_PERMANENTS);
|
||||||
int numCtrs = source.getCounters(CounterEnumType.CHARGE);
|
int numCtrs = source.getCounters(CounterEnumType.CHARGE);
|
||||||
@@ -1232,6 +1250,12 @@ public class CountersPutAi extends CountersAi {
|
|||||||
optimalCMC = cmc;
|
optimalCMC = cmc;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return numCtrs < optimalCMC;
|
if (numCtrs < optimalCMC) {
|
||||||
|
// If the AI has less counters than the optimal CMC, it should play the ability.
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
} else {
|
||||||
|
// If the AI has enough counters or more than the optimal CMC, it should not play the ability.
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
|
import forge.ai.AiAbilityDecision;
|
||||||
|
import forge.ai.AiPlayDecision;
|
||||||
import forge.ai.ComputerUtilCost;
|
import forge.ai.ComputerUtilCost;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.ability.AbilityUtils;
|
import forge.game.ability.AbilityUtils;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
import forge.game.card.CardLists;
|
import forge.game.card.CardLists;
|
||||||
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;
|
||||||
@@ -15,17 +16,15 @@ import forge.game.spellability.AbilitySub;
|
|||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.spellability.TargetRestrictions;
|
import forge.game.spellability.TargetRestrictions;
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
import forge.util.MyRandom;
|
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
public class CountersPutAllAi extends SpellAbilityAi {
|
public class CountersPutAllAi extends SpellAbilityAi {
|
||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
protected AiAbilityDecision checkApiLogic(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 Cost abCost = sa.getPayCosts();
|
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
List<Card> hList;
|
List<Card> hList;
|
||||||
List<Card> cList;
|
List<Card> cList;
|
||||||
@@ -44,28 +43,9 @@ public class CountersPutAllAi extends SpellAbilityAi {
|
|||||||
cList = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), valid, source.getController(), source, sa);
|
cList = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), valid, source.getController(), source, sa);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (abCost != null) {
|
|
||||||
// AI currently disabled for these costs
|
|
||||||
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 8, sa)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!ComputerUtilCost.checkDiscardCost(ai, abCost, source, sa)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!ComputerUtilCost.checkSacrificeCost(ai, abCost, source, sa)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (logic.equals("AtEOTOrBlock")) {
|
if (logic.equals("AtEOTOrBlock")) {
|
||||||
if (!ai.getGame().getPhaseHandler().is(PhaseType.END_OF_TURN) && !ai.getGame().getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
if (!ai.getGame().getPhaseHandler().is(PhaseType.END_OF_TURN) && !ai.getGame().getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.AnotherTime);
|
||||||
}
|
|
||||||
} else if (logic.equals("AtOppEOT")) {
|
|
||||||
if (!(ai.getGame().getPhaseHandler().is(PhaseType.END_OF_TURN) && ai.getGame().getPhaseHandler().getNextTurn() == ai)) {
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,26 +68,23 @@ public class CountersPutAllAi extends SpellAbilityAi {
|
|||||||
amount = AbilityUtils.calculateAmount(source, amountStr, sa);
|
amount = AbilityUtils.calculateAmount(source, amountStr, sa);
|
||||||
}
|
}
|
||||||
|
|
||||||
// prevent run-away activations - first time will always return true
|
|
||||||
boolean chance = MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
|
|
||||||
|
|
||||||
if (curse) {
|
if (curse) {
|
||||||
if (type.equals("M1M1")) {
|
if (type.equals("M1M1")) {
|
||||||
final List<Card> killable = CardLists.filter(hList, c -> c.getNetToughness() <= amount);
|
final List<Card> killable = CardLists.filter(hList, c -> c.getNetToughness() <= amount);
|
||||||
if (!(killable.size() > 2)) {
|
if (killable.size() <= 2) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// make sure compy doesn't harm his stuff more than human's
|
// make sure compy doesn't harm his stuff more than human's
|
||||||
// stuff
|
// stuff
|
||||||
if (cList.size() > hList.size()) {
|
if (cList.size() > hList.size()) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// human has more things that will benefit, don't play
|
// human has more things that will benefit, don't play
|
||||||
if (hList.size() >= cList.size()) {
|
if (hList.size() >= cList.size()) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
//Check for cards that could profit from the ability
|
//Check for cards that could profit from the ability
|
||||||
@@ -125,21 +102,21 @@ public class CountersPutAllAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!combatants) {
|
if (!combatants) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (playReusable(ai, sa)) {
|
if (playReusable(ai, sa)) {
|
||||||
return chance;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
return ((MyRandom.getRandom().nextFloat() < .6667) && chance);
|
return super.checkApiLogic(ai, sa);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
public AiAbilityDecision chkDrawback(SpellAbility sa, Player ai) {
|
||||||
return canPlayAI(ai, sa);
|
return canPlay(ai, sa);
|
||||||
}
|
}
|
||||||
/* (non-Javadoc)
|
/* (non-Javadoc)
|
||||||
* @see forge.card.ability.SpellAbilityAi#confirmAction(forge.game.player.Player, forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String)
|
* @see forge.card.ability.SpellAbilityAi#confirmAction(forge.game.player.Player, forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String)
|
||||||
@@ -150,7 +127,7 @@ public class CountersPutAllAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(final Player aiPlayer, final SpellAbility sa, final boolean mandatory) {
|
protected AiAbilityDecision doTriggerNoCost(final Player aiPlayer, final SpellAbility sa, final boolean mandatory) {
|
||||||
if (sa.usesTargeting()) {
|
if (sa.usesTargeting()) {
|
||||||
List<Player> players = Lists.newArrayList();
|
List<Player> players = Lists.newArrayList();
|
||||||
if (!sa.isCurse()) {
|
if (!sa.isCurse()) {
|
||||||
@@ -168,11 +145,23 @@ public class CountersPutAllAi extends SpellAbilityAi {
|
|||||||
preferred = (sa.isCurse() && p.isOpponentOf(aiPlayer)) || (!sa.isCurse() && p == aiPlayer);
|
preferred = (sa.isCurse() && p.isOpponentOf(aiPlayer)) || (!sa.isCurse() && p == aiPlayer);
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
sa.getTargets().add(p);
|
sa.getTargets().add(p);
|
||||||
return preferred || mandatory;
|
if (preferred) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mandatory) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
} else {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return mandatory || canPlayAI(aiPlayer, sa);
|
if (mandatory) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
}
|
||||||
|
|
||||||
|
return canPlay(aiPlayer, sa);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,9 +18,7 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
import forge.ai.ComputerUtil;
|
import forge.ai.*;
|
||||||
import forge.ai.ComputerUtilCard;
|
|
||||||
import forge.ai.SpellAbilityAi;
|
|
||||||
import forge.game.Game;
|
import forge.game.Game;
|
||||||
import forge.game.ability.AbilityUtils;
|
import forge.game.ability.AbilityUtils;
|
||||||
import forge.game.card.*;
|
import forge.game.card.*;
|
||||||
@@ -52,9 +50,13 @@ public class CountersPutOrRemoveAi extends SpellAbilityAi {
|
|||||||
* forge.game.spellability.SpellAbility)
|
* forge.game.spellability.SpellAbility)
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected boolean checkApiLogic(Player ai, SpellAbility sa) {
|
protected AiAbilityDecision checkApiLogic(Player ai, SpellAbility sa) {
|
||||||
if (sa.usesTargeting()) {
|
if (sa.usesTargeting()) {
|
||||||
return doTgt(ai, sa, false);
|
if (doTgt(ai, sa, false)) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
} else {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return super.checkApiLogic(ai, sa);
|
return super.checkApiLogic(ai, sa);
|
||||||
}
|
}
|
||||||
@@ -180,11 +182,27 @@ public class CountersPutOrRemoveAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
protected AiAbilityDecision doTriggerNoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
if (sa.usesTargeting()) {
|
if (sa.usesTargeting()) {
|
||||||
return doTgt(ai, sa, mandatory);
|
if (doTgt(ai, sa, mandatory)) {
|
||||||
|
// if we can target, then we can play it
|
||||||
|
if (sa.isTargetNumberValid()) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
} else {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// if we can't target, then we can't play it
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (mandatory) {
|
||||||
|
// if mandatory, just play it
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
} else {
|
||||||
|
// if not mandatory, check if we can play it
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
return mandatory;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
|
import forge.ai.AiAbilityDecision;
|
||||||
|
import forge.ai.AiPlayDecision;
|
||||||
import forge.ai.ComputerUtil;
|
import forge.ai.ComputerUtil;
|
||||||
import forge.ai.ComputerUtilCard;
|
import forge.ai.ComputerUtilCard;
|
||||||
import forge.ai.ComputerUtilCost;
|
import forge.ai.ComputerUtilCost;
|
||||||
@@ -23,14 +25,6 @@ import java.util.function.Predicate;
|
|||||||
|
|
||||||
public class CountersRemoveAi extends SpellAbilityAi {
|
public class CountersRemoveAi extends SpellAbilityAi {
|
||||||
|
|
||||||
@Override
|
|
||||||
protected boolean canPlayWithoutRestrict(final Player ai, final SpellAbility sa) {
|
|
||||||
if ("Always".equals(sa.getParam("AILogic"))) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return super.canPlayWithoutRestrict(ai, sa);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* (non-Javadoc)
|
* (non-Javadoc)
|
||||||
*
|
*
|
||||||
@@ -48,24 +42,6 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
|||||||
return super.checkPhaseRestrictions(ai, sa, ph);
|
return super.checkPhaseRestrictions(ai, sa, ph);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* (non-Javadoc)
|
|
||||||
*
|
|
||||||
* @see
|
|
||||||
* forge.ai.SpellAbilityAi#checkPhaseRestrictions(forge.game.player.Player,
|
|
||||||
* forge.game.spellability.SpellAbility, forge.game.phase.PhaseHandler,
|
|
||||||
* java.lang.String)
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
protected boolean checkPhaseRestrictions(Player ai, SpellAbility sa, PhaseHandler ph, String logic) {
|
|
||||||
if ("EndOfOpponentsTurn".equals(logic)) {
|
|
||||||
if (!ph.is(PhaseType.END_OF_TURN) || !ph.getNextTurn().equals(ai)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return super.checkPhaseRestrictions(ai, sa, ph, logic);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* (non-Javadoc)
|
* (non-Javadoc)
|
||||||
*
|
*
|
||||||
@@ -73,7 +49,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
|||||||
* forge.game.spellability.SpellAbility)
|
* forge.game.spellability.SpellAbility)
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected boolean checkApiLogic(Player ai, SpellAbility sa) {
|
protected AiAbilityDecision checkApiLogic(Player ai, SpellAbility sa) {
|
||||||
final String type = sa.getParam("CounterType");
|
final String type = sa.getParam("CounterType");
|
||||||
|
|
||||||
if (sa.usesTargeting()) {
|
if (sa.usesTargeting()) {
|
||||||
@@ -83,14 +59,14 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
|||||||
if (!type.matches("Any") && !type.matches("All")) {
|
if (!type.matches("Any") && !type.matches("All")) {
|
||||||
final int currCounters = sa.getHostCard().getCounters(CounterType.getType(type));
|
final int currCounters = sa.getHostCard().getCounters(CounterType.getType(type));
|
||||||
if (currCounters < 1) {
|
if (currCounters < 1) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return super.checkApiLogic(ai, sa);
|
return super.checkApiLogic(ai, sa);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean doTgt(Player ai, SpellAbility sa, boolean mandatory) {
|
private AiAbilityDecision doTgt(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
final Game game = ai.getGame();
|
final Game game = ai.getGame();
|
||||||
|
|
||||||
@@ -103,7 +79,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
|||||||
CardCollection list = CardLists.getTargetableCards(game.getCardsIn(tgt.getZone()), sa);
|
CardCollection list = CardLists.getTargetableCards(game.getCardsIn(tgt.getZone()), sa);
|
||||||
|
|
||||||
if (list.isEmpty()) {
|
if (list.isEmpty()) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filter AI-specific targets if provided
|
// Filter AI-specific targets if provided
|
||||||
@@ -121,7 +97,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
|||||||
CardPredicates.hasCounter(CounterEnumType.ICE, 3));
|
CardPredicates.hasCounter(CounterEnumType.ICE, 3));
|
||||||
if (!depthsList.isEmpty()) {
|
if (!depthsList.isEmpty()) {
|
||||||
sa.getTargets().add(depthsList.getFirst());
|
sa.getTargets().add(depthsList.getFirst());
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -134,7 +110,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
if (!planeswalkerList.isEmpty()) {
|
if (!planeswalkerList.isEmpty()) {
|
||||||
sa.getTargets().add(ComputerUtilCard.getBestPlaneswalkerAI(planeswalkerList));
|
sa.getTargets().add(ComputerUtilCard.getBestPlaneswalkerAI(planeswalkerList));
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
} else if (type.matches("Any")) {
|
} else if (type.matches("Any")) {
|
||||||
// variable amount for Hex Parasite
|
// variable amount for Hex Parasite
|
||||||
@@ -144,7 +120,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
|||||||
final int manaLeft = ComputerUtilCost.getMaxXValue(sa, ai, sa.isTrigger());
|
final int manaLeft = ComputerUtilCost.getMaxXValue(sa, ai, sa.isTrigger());
|
||||||
|
|
||||||
if (manaLeft == 0) {
|
if (manaLeft == 0) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantAffordX);
|
||||||
}
|
}
|
||||||
amount = manaLeft;
|
amount = manaLeft;
|
||||||
xPay = true;
|
xPay = true;
|
||||||
@@ -166,7 +142,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
|||||||
if (xPay) {
|
if (xPay) {
|
||||||
sa.setXManaCostPaid(ice);
|
sa.setXManaCostPaid(ice);
|
||||||
}
|
}
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -185,7 +161,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
|||||||
if (xPay) {
|
if (xPay) {
|
||||||
sa.setXManaCostPaid(best.getCurrentLoyalty());
|
sa.setXManaCostPaid(best.getCurrentLoyalty());
|
||||||
}
|
}
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
// some rules only for amount = 1
|
// some rules only for amount = 1
|
||||||
@@ -202,7 +178,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
if (!aiM1M1List.isEmpty()) {
|
if (!aiM1M1List.isEmpty()) {
|
||||||
sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(aiM1M1List));
|
sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(aiM1M1List));
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
// do as P1P1 part
|
// do as P1P1 part
|
||||||
@@ -211,7 +187,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
if (!aiUndyingList.isEmpty()) {
|
if (!aiUndyingList.isEmpty()) {
|
||||||
sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(aiUndyingList));
|
sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(aiUndyingList));
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO stun counters with canRemoveCounters check
|
// TODO stun counters with canRemoveCounters check
|
||||||
@@ -222,7 +198,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
|||||||
CardPredicates.hasCounter(CounterEnumType.P1P1));
|
CardPredicates.hasCounter(CounterEnumType.P1P1));
|
||||||
if (!oppP1P1List.isEmpty()) {
|
if (!oppP1P1List.isEmpty()) {
|
||||||
sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(oppP1P1List));
|
sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(oppP1P1List));
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
// fallback to remove any counter from opponent
|
// fallback to remove any counter from opponent
|
||||||
@@ -234,7 +210,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
|||||||
for (final CounterType aType : best.getCounters().keySet()) {
|
for (final CounterType aType : best.getCounters().keySet()) {
|
||||||
if (!ComputerUtil.isNegativeCounter(aType, best)) {
|
if (!ComputerUtil.isNegativeCounter(aType, best)) {
|
||||||
sa.getTargets().add(best);
|
sa.getTargets().add(best);
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -255,7 +231,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
if (!aiList.isEmpty()) {
|
if (!aiList.isEmpty()) {
|
||||||
sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(aiList));
|
sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(aiList));
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
} else if (type.equals("P1P1")) {
|
} else if (type.equals("P1P1")) {
|
||||||
// no special amount for that one yet
|
// no special amount for that one yet
|
||||||
@@ -273,7 +249,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
if (!aiList.isEmpty()) {
|
if (!aiList.isEmpty()) {
|
||||||
sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(aiList));
|
sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(aiList));
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -287,7 +263,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
if (!oppList.isEmpty()) {
|
if (!oppList.isEmpty()) {
|
||||||
sa.getTargets().add(ComputerUtilCard.getWorstCreatureAI(oppList));
|
sa.getTargets().add(ComputerUtilCard.getWorstCreatureAI(oppList));
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (type.equals("TIME")) {
|
} else if (type.equals("TIME")) {
|
||||||
@@ -298,7 +274,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
|||||||
final int manaLeft = ComputerUtilCost.getMaxXValue(sa, ai, sa.isTrigger());
|
final int manaLeft = ComputerUtilCost.getMaxXValue(sa, ai, sa.isTrigger());
|
||||||
|
|
||||||
if (manaLeft == 0) {
|
if (manaLeft == 0) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantAffordX);
|
||||||
}
|
}
|
||||||
amount = manaLeft;
|
amount = manaLeft;
|
||||||
xPay = true;
|
xPay = true;
|
||||||
@@ -316,7 +292,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
|||||||
if (xPay) {
|
if (xPay) {
|
||||||
sa.setXManaCostPaid(timeCount);
|
sa.setXManaCostPaid(timeCount);
|
||||||
}
|
}
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (mandatory) {
|
if (mandatory) {
|
||||||
@@ -325,7 +301,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
|||||||
CardCollection adaptCreats = CardLists.filter(list, CardPredicates.hasKeyword(Keyword.ADAPT));
|
CardCollection adaptCreats = CardLists.filter(list, CardPredicates.hasKeyword(Keyword.ADAPT));
|
||||||
if (!adaptCreats.isEmpty()) {
|
if (!adaptCreats.isEmpty()) {
|
||||||
sa.getTargets().add(ComputerUtilCard.getWorstAI(adaptCreats));
|
sa.getTargets().add(ComputerUtilCard.getWorstAI(adaptCreats));
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Outlast nice target
|
// Outlast nice target
|
||||||
@@ -336,26 +312,27 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
if (!betterTargets.isEmpty()) {
|
if (!betterTargets.isEmpty()) {
|
||||||
sa.getTargets().add(ComputerUtilCard.getWorstAI(betterTargets));
|
sa.getTargets().add(ComputerUtilCard.getWorstAI(betterTargets));
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
sa.getTargets().add(ComputerUtilCard.getWorstAI(outlastCreats));
|
sa.getTargets().add(ComputerUtilCard.getWorstAI(outlastCreats));
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sa.getTargets().add(ComputerUtilCard.getWorstAI(list));
|
sa.getTargets().add(ComputerUtilCard.getWorstAI(list));
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
protected AiAbilityDecision doTriggerNoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
||||||
if (sa.usesTargeting()) {
|
if (sa.usesTargeting()) {
|
||||||
return doTgt(aiPlayer, sa, mandatory);
|
return doTgt(aiPlayer, sa, mandatory);
|
||||||
}
|
}
|
||||||
return mandatory;
|
return mandatory ? new AiAbilityDecision(100, AiPlayDecision.MandatoryPlay)
|
||||||
|
: new AiAbilityDecision(0, AiPlayDecision.CantPlaySa);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -369,8 +346,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
|||||||
GameEntity target = (GameEntity) params.get("Target");
|
GameEntity target = (GameEntity) params.get("Target");
|
||||||
CounterType type = (CounterType) params.get("CounterType");
|
CounterType type = (CounterType) params.get("CounterType");
|
||||||
|
|
||||||
if (target instanceof Card) {
|
if (target instanceof Card targetCard) {
|
||||||
Card targetCard = (Card) target;
|
|
||||||
if (targetCard.getController().isOpponentOf(player)) {
|
if (targetCard.getController().isOpponentOf(player)) {
|
||||||
return !ComputerUtil.isNegativeCounter(type, targetCard) ? max : min;
|
return !ComputerUtil.isNegativeCounter(type, targetCard) ? max : min;
|
||||||
} else {
|
} else {
|
||||||
@@ -381,8 +357,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
return ComputerUtil.isNegativeCounter(type, targetCard) ? max : min;
|
return ComputerUtil.isNegativeCounter(type, targetCard) ? max : min;
|
||||||
}
|
}
|
||||||
} else if (target instanceof Player) {
|
} else if (target instanceof Player targetPlayer) {
|
||||||
Player targetPlayer = (Player) target;
|
|
||||||
if (targetPlayer.isOpponentOf(player)) {
|
if (targetPlayer.isOpponentOf(player)) {
|
||||||
return !type.is(CounterEnumType.POISON) ? max : min;
|
return !type.is(CounterEnumType.POISON) ? max : min;
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -11,32 +11,27 @@ import forge.game.phase.PhaseType;
|
|||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
import forge.util.MyRandom;
|
|
||||||
|
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
public class DamageAllAi extends SpellAbilityAi {
|
public class DamageAllAi extends SpellAbilityAi {
|
||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
protected AiAbilityDecision checkApiLogic(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 Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
|
|
||||||
// prevent run-away activations - first time will always return true
|
|
||||||
if (MyRandom.getRandom().nextFloat() > Math.pow(.9, sa.getActivationsThisTurn())) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// abCost stuff that should probably be centralized...
|
// abCost stuff that should probably be centralized...
|
||||||
final Cost abCost = sa.getPayCosts();
|
final Cost abCost = sa.getPayCosts();
|
||||||
if (abCost != null) {
|
if (abCost != null) {
|
||||||
// AI currently disabled for some costs
|
// AI currently disabled for some costs
|
||||||
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, sa)) {
|
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, sa)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantAfford);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// wait until stack is empty (prevents duplicate kills)
|
// wait until stack is empty (prevents duplicate kills)
|
||||||
if (!ai.getGame().getStack().isEmpty()) {
|
if (!ai.getGame().getStack().isEmpty()) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.StackNotEmpty);
|
||||||
}
|
}
|
||||||
|
|
||||||
int x = -1;
|
int x = -1;
|
||||||
@@ -51,11 +46,15 @@ public class DamageAllAi extends SpellAbilityAi {
|
|||||||
if (x == -1) {
|
if (x == -1) {
|
||||||
if (determineOppToKill(ai, sa, source, dmg) != null) {
|
if (determineOppToKill(ai, sa, source, dmg) != null) {
|
||||||
// we already know we can kill a player, so go for it
|
// we already know we can kill a player, so go for it
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
// look for other value in this (damaging creatures or
|
// look for other value in this (damaging creatures or
|
||||||
// creatures + player, e.g. Pestilence, etc.)
|
// creatures + player, e.g. Pestilence, etc.)
|
||||||
return evaluateDamageAll(ai, sa, source, dmg) > 0;
|
if (evaluateDamageAll(ai, sa, source, dmg) > 0) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
} else {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
int best = -1, best_x = -1;
|
int best = -1, best_x = -1;
|
||||||
Player bestOpp = determineOppToKill(ai, sa, source, x);
|
Player bestOpp = determineOppToKill(ai, sa, source, x);
|
||||||
@@ -81,9 +80,9 @@ public class DamageAllAi extends SpellAbilityAi {
|
|||||||
if (sa.getSVar(damage).equals("Count$xPaid")) {
|
if (sa.getSVar(damage).equals("Count$xPaid")) {
|
||||||
sa.setXManaCostPaid(best_x);
|
sa.setXManaCostPaid(best_x);
|
||||||
}
|
}
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -185,7 +184,7 @@ public class DamageAllAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
public AiAbilityDecision chkDrawback(SpellAbility sa, Player ai) {
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
final String validP = sa.getParamOrDefault("ValidPlayers", "");
|
final String validP = sa.getParamOrDefault("ValidPlayers", "");
|
||||||
|
|
||||||
@@ -211,21 +210,21 @@ public class DamageAllAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
// Don't get yourself killed
|
// Don't get yourself killed
|
||||||
if (validP.equals("Player") && (ai.getLife() <= ComputerUtilCombat.predictDamageTo(ai, dmg, source, false))) {
|
if (validP.equals("Player") && (ai.getLife() <= ComputerUtilCombat.predictDamageTo(ai, dmg, source, false))) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
// if we can kill human, do it
|
// if we can kill human, do it
|
||||||
if ((validP.equals("Player") || validP.equals("Opponent") || validP.contains("Targeted"))
|
if ((validP.equals("Player") || validP.equals("Opponent") || validP.contains("Targeted"))
|
||||||
&& (enemy.getLife() <= ComputerUtilCombat.predictDamageTo(enemy, dmg, source, false))) {
|
&& (enemy.getLife() <= ComputerUtilCombat.predictDamageTo(enemy, dmg, source, false))) {
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!computerList.isEmpty() && ComputerUtilCard.evaluateCreatureList(computerList) > ComputerUtilCard
|
if (!computerList.isEmpty() && ComputerUtilCard.evaluateCreatureList(computerList) > ComputerUtilCard
|
||||||
.evaluateCreatureList(humanList)) {
|
.evaluateCreatureList(humanList)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -258,7 +257,7 @@ public class DamageAllAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
protected AiAbilityDecision doTriggerNoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
final String validP = sa.getParamOrDefault("ValidPlayers", "");
|
final String validP = sa.getParamOrDefault("ValidPlayers", "");
|
||||||
|
|
||||||
@@ -287,24 +286,24 @@ public class DamageAllAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
// If it's not mandatory check a few things
|
// If it's not mandatory check a few things
|
||||||
if (mandatory) {
|
if (mandatory) {
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
// Don't get yourself killed
|
// Don't get yourself killed
|
||||||
if (validP.equals("Player") && (ai.getLife() <= ComputerUtilCombat.predictDamageTo(ai, dmg, source, false))) {
|
if (validP.equals("Player") && (ai.getLife() <= ComputerUtilCombat.predictDamageTo(ai, dmg, source, false))) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
// if we can kill human, do it
|
// if we can kill human, do it
|
||||||
if ((validP.equals("Player") || validP.contains("Opponent") || validP.contains("Targeted"))
|
if ((validP.equals("Player") || validP.contains("Opponent") || validP.contains("Targeted"))
|
||||||
&& (enemy.getLife() <= ComputerUtilCombat.predictDamageTo(enemy, dmg, source, false))) {
|
&& (enemy.getLife() <= ComputerUtilCombat.predictDamageTo(enemy, dmg, source, false))) {
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!computerList.isEmpty() && ComputerUtilCard.evaluateCreatureList(computerList) + 50 >= ComputerUtilCard
|
if (!computerList.isEmpty() && ComputerUtilCard.evaluateCreatureList(computerList) + 50 >= ComputerUtilCard
|
||||||
.evaluateCreatureList(humanList)) {
|
.evaluateCreatureList(humanList)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ import java.util.Map;
|
|||||||
|
|
||||||
public class DamageDealAi extends DamageAiBase {
|
public class DamageDealAi extends DamageAiBase {
|
||||||
@Override
|
@Override
|
||||||
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
public AiAbilityDecision chkDrawback(SpellAbility sa, Player ai) {
|
||||||
final SpellAbility root = sa.getRootAbility();
|
final SpellAbility root = sa.getRootAbility();
|
||||||
final String damage = sa.getParam("NumDmg");
|
final String damage = sa.getParam("NumDmg");
|
||||||
Card source = sa.getHostCard();
|
Card source = sa.getHostCard();
|
||||||
@@ -65,15 +65,19 @@ public class DamageDealAi extends DamageAiBase {
|
|||||||
continue; // in case the calculation gets messed up somewhere
|
continue; // in case the calculation gets messed up somewhere
|
||||||
}
|
}
|
||||||
root.setSVar("EnergyToPay", "Number$" + dmg);
|
root.setSVar("EnergyToPay", "Number$" + dmg);
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
if (sa.getSVar(damage).equals("Count$xPaid")) {
|
if (sa.getSVar(damage).equals("Count$xPaid")) {
|
||||||
// Life Drain
|
// Life Drain
|
||||||
if ("XLifeDrain".equals(logic)) {
|
if ("XLifeDrain".equals(logic)) {
|
||||||
return doXLifeDrainLogic(ai, sa);
|
if (doXLifeDrainLogic(ai, sa)) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
} else {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set PayX here to maximum value.
|
// Set PayX here to maximum value.
|
||||||
@@ -83,11 +87,15 @@ public class DamageDealAi extends DamageAiBase {
|
|||||||
dmg--; // the card will be spent casting the spell, so actual damage is 1 less
|
dmg--; // the card will be spent casting the spell, so actual damage is 1 less
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return damageTargetAI(ai, sa, dmg, true);
|
if (damageTargetAI(ai, sa, dmg, true)) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
} else {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
protected AiAbilityDecision canPlay(Player ai, SpellAbility sa) {
|
||||||
final Cost abCost = sa.getPayCosts();
|
final Cost abCost = sa.getPayCosts();
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
|
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
|
||||||
@@ -108,7 +116,7 @@ public class DamageDealAi extends DamageAiBase {
|
|||||||
boolean inDanger = ComputerUtil.aiLifeInDanger(ai, false, 0);
|
boolean inDanger = ComputerUtil.aiLifeInDanger(ai, false, 0);
|
||||||
boolean isLethal = sa.usesTargeting() && sa.getTargetRestrictions().canTgtPlayer() && dmg >= ai.getWeakestOpponent().getLife() && !ai.getWeakestOpponent().cantLoseForZeroOrLessLife();
|
boolean isLethal = sa.usesTargeting() && sa.getTargetRestrictions().canTgtPlayer() && dmg >= ai.getWeakestOpponent().getLife() && !ai.getWeakestOpponent().cantLoseForZeroOrLessLife();
|
||||||
if (dmg < threshold && ai.getGame().getPhaseHandler().getTurn() / 2 < threshold && !inDanger && !isLethal) {
|
if (dmg < threshold && ai.getGame().getPhaseHandler().getTurn() / 2 < threshold && !inDanger && !isLethal) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -134,10 +142,10 @@ public class DamageDealAi extends DamageAiBase {
|
|||||||
if (shouldTgtP(ai, sa, maxDmg, false)) {
|
if (shouldTgtP(ai, sa, maxDmg, false)) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
sa.getTargets().add(maxDamaged);
|
sa.getTargets().add(maxDamaged);
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -154,7 +162,7 @@ public class DamageDealAi extends DamageAiBase {
|
|||||||
if (ai.getGame().getPhaseHandler().isPlayerTurn(ai) && ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
if (ai.getGame().getPhaseHandler().isPlayerTurn(ai) && ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
||||||
for (Card potentialAtkr : ai.getCreaturesInPlay()) {
|
for (Card potentialAtkr : ai.getCreaturesInPlay()) {
|
||||||
if (ComputerUtilCard.doesCreatureAttackAI(ai, potentialAtkr)) {
|
if (ComputerUtilCard.doesCreatureAttackAI(ai, potentialAtkr)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -175,16 +183,24 @@ public class DamageDealAi extends DamageAiBase {
|
|||||||
* Mostly used to ping the player with remaining counters. The issue with
|
* Mostly used to ping the player with remaining counters. The issue with
|
||||||
* stacked effects might appear here.
|
* stacked effects might appear here.
|
||||||
*/
|
*/
|
||||||
return damageTargetAI(ai, sa, n, true);
|
if (damageTargetAI(ai, sa, n, true)) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
} else {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
/*
|
/*
|
||||||
* Only ping when stack is clear to avoid hassle of evaluating stacked effects
|
* Only ping when stack is clear to avoid hassle of evaluating stacked effects
|
||||||
* like protection/pumps or over-killing target.
|
* like protection/pumps or over-killing target.
|
||||||
*/
|
*/
|
||||||
return ai.getGame().getStack().isEmpty() && damageTargetAI(ai, sa, n, false);
|
if (ai.getGame().getStack().isEmpty() && damageTargetAI(ai, sa, n, false)) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
} else {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.StackNotEmpty);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
} else if ("NinThePainArtist".equals(logic)) {
|
} else if ("NinThePainArtist".equals(logic)) {
|
||||||
// Make sure not to mana lock ourselves + make the opponent draw cards into an immediate discard
|
// Make sure not to mana lock ourselves + make the opponent draw cards into an immediate discard
|
||||||
@@ -193,11 +209,15 @@ public class DamageDealAi extends DamageAiBase {
|
|||||||
if (doTarget) {
|
if (doTarget) {
|
||||||
Card tgt = sa.getTargetCard();
|
Card tgt = sa.getTargetCard();
|
||||||
if (tgt != null) {
|
if (tgt != null) {
|
||||||
return ai.getGame().getPhaseHandler().getPlayerTurn() == tgt.getController();
|
if (ai.getGame().getPhaseHandler().getPlayerTurn() == tgt.getController()) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
} else {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.WaitForEndOfTurn);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.WaitForEndOfTurn);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sourceName.equals("Sorin, Grim Nemesis")) {
|
if (sourceName.equals("Sorin, Grim Nemesis")) {
|
||||||
@@ -209,35 +229,35 @@ public class DamageDealAi extends DamageAiBase {
|
|||||||
continue; // in case the calculation gets messed up somewhere
|
continue; // in case the calculation gets messed up somewhere
|
||||||
}
|
}
|
||||||
sa.setXManaCostPaid(dmg);
|
sa.setXManaCostPaid(dmg);
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dmg <= 0) {
|
if (dmg <= 0) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
// temporarily disabled until better AI
|
// temporarily disabled until better AI
|
||||||
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, sa)) {
|
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, sa)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantAfford);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ComputerUtilCost.checkSacrificeCost(ai, abCost, source, sa)) {
|
if (!ComputerUtilCost.checkSacrificeCost(ai, abCost, source, sa)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantAfford);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ComputerUtilCost.checkRemoveCounterCost(abCost, source, sa)) {
|
if (!ComputerUtilCost.checkRemoveCounterCost(abCost, source, sa)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantAfford);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ("DiscardLands".equals(sa.getParam("AILogic")) && !ComputerUtilCost.checkDiscardCost(ai, abCost, source, sa)) {
|
if ("DiscardLands".equals(sa.getParam("AILogic")) && !ComputerUtilCost.checkDiscardCost(ai, abCost, source, sa)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantAfford);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to chain damage/debuff effects
|
// Try to chain damage/debuff effects
|
||||||
@@ -248,13 +268,13 @@ public class DamageDealAi extends DamageAiBase {
|
|||||||
int extraDmg = chainDmg.getValue();
|
int extraDmg = chainDmg.getValue();
|
||||||
boolean willTargetIfChained = damageTargetAI(ai, sa, dmg + extraDmg, false);
|
boolean willTargetIfChained = damageTargetAI(ai, sa, dmg + extraDmg, false);
|
||||||
if (!willTargetIfChained) {
|
if (!willTargetIfChained) {
|
||||||
return false; // won't play it even in chain
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed); // won't play it even in chain
|
||||||
} else if (willTargetIfChained && chainDmg.getKey().getApi() == ApiType.Pump && sa.getTargets().isTargetingAnyPlayer()) {
|
} else if (willTargetIfChained && chainDmg.getKey().getApi() == ApiType.Pump && sa.getTargets().isTargetingAnyPlayer()) {
|
||||||
// we're trying to chain a pump spell to a damage spell targeting a player, that won't work
|
// we're trying to chain a pump spell to a damage spell targeting a player, that won't work
|
||||||
// so run an additional check to ensure that we want to cast the current spell separately
|
// so run an additional check to ensure that we want to cast the current spell separately
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
if (!damageTargetAI(ai, sa, dmg, false)) {
|
if (!damageTargetAI(ai, sa, dmg, false)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// we are about to decide to play this damage spell; if there's something chained to it, reserve mana for
|
// we are about to decide to play this damage spell; if there's something chained to it, reserve mana for
|
||||||
@@ -264,7 +284,7 @@ public class DamageDealAi extends DamageAiBase {
|
|||||||
}
|
}
|
||||||
} else if (!damageTargetAI(ai, sa, dmg, false)) {
|
} else if (!damageTargetAI(ai, sa, dmg, false)) {
|
||||||
// simple targeting when there is no spell chaining plan
|
// simple targeting when there is no spell chaining plan
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((damage.equals("X") && sa.getSVar(damage).equals("Count$xPaid")) ||
|
if ((damage.equals("X") && sa.getSVar(damage).equals("Count$xPaid")) ||
|
||||||
@@ -288,10 +308,12 @@ public class DamageDealAi extends DamageAiBase {
|
|||||||
|
|
||||||
if ("DiscardCMCX".equals(sa.getParam("AILogic"))) {
|
if ("DiscardCMCX".equals(sa.getParam("AILogic"))) {
|
||||||
final int cmc = sa.getXManaCostPaid();
|
final int cmc = sa.getXManaCostPaid();
|
||||||
return ai.getZone(ZoneType.Hand).contains(CardPredicates.hasCMC(cmc));
|
if (!ai.getZone(ZoneType.Hand).contains(CardPredicates.hasCMC(cmc))) {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -932,14 +954,14 @@ public class DamageDealAi extends DamageAiBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
protected AiAbilityDecision doTriggerNoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
final String damage = sa.getParam("NumDmg");
|
final String damage = sa.getParam("NumDmg");
|
||||||
int dmg = calculateDamageAmount(sa, source, damage);
|
int dmg = calculateDamageAmount(sa, source, damage);
|
||||||
|
|
||||||
// Remove all damage
|
// Remove all damage
|
||||||
if (sa.hasParam("Remove")) {
|
if (sa.hasParam("Remove")) {
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (damage.equals("X") && sa.getSVar(damage).equals("Count$xPaid")) {
|
if (damage.equals("X") && sa.getSVar(damage).equals("Count$xPaid")) {
|
||||||
@@ -950,10 +972,18 @@ public class DamageDealAi extends DamageAiBase {
|
|||||||
|
|
||||||
if (!sa.usesTargeting()) {
|
if (!sa.usesTargeting()) {
|
||||||
// If it's not mandatory check a few things
|
// If it's not mandatory check a few things
|
||||||
return mandatory || damageChooseNontargeted(ai, sa, dmg);
|
if (mandatory) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (damageChooseNontargeted(ai, sa, dmg)) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
} else {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if (!damageChoosingTargets(ai, sa, sa.getTargetRestrictions(), dmg, mandatory, true) && !mandatory) {
|
if (!damageChoosingTargets(ai, sa, sa.getTargetRestrictions(), dmg, mandatory, true) && !mandatory) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (damage.equals("X") && sa.getSVar(damage).equals("Count$xPaid") && !sa.isDividedAsYouChoose()) {
|
if (damage.equals("X") && sa.getSVar(damage).equals("Count$xPaid") && !sa.isDividedAsYouChoose()) {
|
||||||
@@ -976,7 +1006,7 @@ public class DamageDealAi extends DamageAiBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int calculateDamageAmount(SpellAbility sa, Card source, String damage) {
|
private static int calculateDamageAmount(SpellAbility sa, Card source, String damage) {
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
|
||||||
|
import forge.ai.AiAbilityDecision;
|
||||||
|
import forge.ai.AiPlayDecision;
|
||||||
import forge.ai.SpecialCardAi;
|
import forge.ai.SpecialCardAi;
|
||||||
import forge.game.ability.AbilityUtils;
|
import forge.game.ability.AbilityUtils;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
@@ -14,7 +16,7 @@ public class DamageEachAi extends DamageAiBase {
|
|||||||
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
|
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
protected AiAbilityDecision canPlay(Player ai, SpellAbility sa) {
|
||||||
final String logic = sa.getParam("AILogic");
|
final String logic = sa.getParam("AILogic");
|
||||||
|
|
||||||
PlayerCollection targetableOpps = ai.getOpponents().filter(PlayerPredicates.isTargetableBy(sa));
|
PlayerCollection targetableOpps = ai.getOpponents().filter(PlayerPredicates.isTargetableBy(sa));
|
||||||
@@ -22,30 +24,41 @@ public class DamageEachAi extends DamageAiBase {
|
|||||||
|
|
||||||
if (sa.usesTargeting() && weakestOpp != null) {
|
if (sa.usesTargeting() && weakestOpp != null) {
|
||||||
if ("MadSarkhanUltimate".equals(logic) && !SpecialCardAi.SarkhanTheMad.considerUltimate(ai, sa, weakestOpp)) {
|
if ("MadSarkhanUltimate".equals(logic) && !SpecialCardAi.SarkhanTheMad.considerUltimate(ai, sa, weakestOpp)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
sa.getTargets().add(weakestOpp);
|
if (weakestOpp.canLoseLife() && !weakestOpp.cantLoseForZeroOrLessLife()) {
|
||||||
return weakestOpp.canLoseLife() && !weakestOpp.cantLoseForZeroOrLessLife();
|
sa.getTargets().add(weakestOpp);
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final String damage = sa.getParam("NumDmg");
|
final String damage = sa.getParam("NumDmg");
|
||||||
final int iDmg = AbilityUtils.calculateAmount(sa.getHostCard(), damage, sa);
|
final int iDmg = AbilityUtils.calculateAmount(sa.getHostCard(), damage, sa);
|
||||||
return shouldTgtP(ai, sa, iDmg, false);
|
|
||||||
|
if (shouldTgtP(ai, sa, iDmg, false)) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
|
public AiAbilityDecision chkDrawback(SpellAbility sa, Player aiPlayer) {
|
||||||
// check AI life before playing this drawback?
|
// check AI life before playing this drawback?
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* (non-Javadoc)
|
/* (non-Javadoc)
|
||||||
* @see forge.card.abilityfactory.SpellAiLogic#doTriggerAINoCost(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility, boolean)
|
* @see forge.card.abilityfactory.SpellAiLogic#doTriggerAINoCost(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility, boolean)
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
protected AiAbilityDecision doTriggerNoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
return mandatory || canPlayAI(ai, sa);
|
if (mandatory) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
}
|
||||||
|
|
||||||
|
return canPlay(ai, sa);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,11 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
import forge.ai.ComputerUtil;
|
import forge.ai.*;
|
||||||
import forge.ai.ComputerUtilCard;
|
|
||||||
import forge.ai.ComputerUtilCombat;
|
|
||||||
import forge.ai.SpellAbilityAi;
|
|
||||||
import forge.game.Game;
|
import forge.game.Game;
|
||||||
import forge.game.GameObject;
|
import forge.game.GameObject;
|
||||||
import forge.game.ability.AbilityUtils;
|
import forge.game.ability.AbilityUtils;
|
||||||
import forge.game.card.*;
|
import forge.game.card.*;
|
||||||
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;
|
||||||
@@ -24,18 +20,12 @@ import java.util.List;
|
|||||||
public class DamagePreventAi extends SpellAbilityAi {
|
public class DamagePreventAi extends SpellAbilityAi {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
protected AiAbilityDecision checkApiLogic(Player ai, SpellAbility sa) {
|
||||||
final Card hostCard = sa.getHostCard();
|
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;
|
||||||
|
|
||||||
final Cost cost = sa.getPayCosts();
|
|
||||||
|
|
||||||
if (!willPayCosts(ai, sa, cost, hostCard)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
if (tgt == null) {
|
if (tgt == null) {
|
||||||
// As far as I can tell these Defined Cards will only have one of them
|
// As far as I can tell these Defined Cards will only have one of them
|
||||||
@@ -70,7 +60,7 @@ public class DamagePreventAi extends SpellAbilityAi {
|
|||||||
chance = flag;
|
chance = flag;
|
||||||
} else { // if nothing on the stack, and it's not declare
|
} else { // if nothing on the stack, and it's not declare
|
||||||
// blockers. no need to prevent
|
// blockers. no need to prevent
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} // non-targeted
|
} // non-targeted
|
||||||
@@ -120,7 +110,7 @@ public class DamagePreventAi extends SpellAbilityAi {
|
|||||||
targetables = CardLists.getTargetableCards(targetables, sa);
|
targetables = CardLists.getTargetableCards(targetables, sa);
|
||||||
|
|
||||||
if (targetables.isEmpty()) {
|
if (targetables.isEmpty()) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
final CardCollection combatants = CardLists.filter(targetables, CardPredicates.CREATURES);
|
final CardCollection combatants = CardLists.filter(targetables, CardPredicates.CREATURES);
|
||||||
ComputerUtilCard.sortByEvaluateCreature(combatants);
|
ComputerUtilCard.sortByEvaluateCreature(combatants);
|
||||||
@@ -137,11 +127,15 @@ public class DamagePreventAi extends SpellAbilityAi {
|
|||||||
sa.addDividedAllocation(sa.getTargets().get(0), AbilityUtils.calculateAmount(hostCard, sa.getParam("Amount"), sa));
|
sa.addDividedAllocation(sa.getTargets().get(0), AbilityUtils.calculateAmount(hostCard, sa.getParam("Amount"), sa));
|
||||||
}
|
}
|
||||||
|
|
||||||
return chance;
|
if (chance) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
} else {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
protected AiAbilityDecision doTriggerNoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
boolean chance = false;
|
boolean chance = false;
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
if (tgt == null) {
|
if (tgt == null) {
|
||||||
@@ -151,7 +145,11 @@ public class DamagePreventAi extends SpellAbilityAi {
|
|||||||
chance = preventDamageMandatoryTarget(ai, sa, mandatory);
|
chance = preventDamageMandatoryTarget(ai, sa, mandatory);
|
||||||
}
|
}
|
||||||
|
|
||||||
return chance;
|
if (chance) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
} else {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.StopRunawayActivations);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
import forge.ai.AiAbilityDecision;
|
||||||
|
import forge.ai.AiPlayDecision;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.phase.PhaseHandler;
|
import forge.game.phase.PhaseHandler;
|
||||||
import forge.game.phase.PhaseType;
|
import forge.game.phase.PhaseType;
|
||||||
@@ -11,24 +13,34 @@ import java.util.Map;
|
|||||||
|
|
||||||
public class DayTimeAi extends SpellAbilityAi {
|
public class DayTimeAi extends SpellAbilityAi {
|
||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
protected AiAbilityDecision canPlay(Player aiPlayer, SpellAbility sa) {
|
||||||
PhaseHandler ph = aiPlayer.getGame().getPhaseHandler();
|
PhaseHandler ph = aiPlayer.getGame().getPhaseHandler();
|
||||||
|
|
||||||
if ((sa.getHostCard().isCreature() && sa.getPayCosts().hasTapCost()) || sa.getPayCosts().hasManaCost()) {
|
if ((sa.getHostCard().isCreature() && sa.getPayCosts().hasTapCost()) || sa.getPayCosts().hasManaCost()) {
|
||||||
// If it involves a cost that may put us at a disadvantage, better activate before own turn if possible
|
// If it involves a cost that may put us at a disadvantage, better activate before own turn if possible
|
||||||
if (!isSorcerySpeed(sa, aiPlayer)) {
|
if (!isSorcerySpeed(sa, aiPlayer)) {
|
||||||
return ph.is(PhaseType.END_OF_TURN) && ph.getNextTurn() == aiPlayer;
|
if (ph.is(PhaseType.END_OF_TURN) && ph.getNextTurn() == aiPlayer) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
} else {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.AnotherTime);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return ph.is(PhaseType.MAIN2, aiPlayer); // Give other things a chance to be cast (e.g. Celestus)
|
if (ph.is(PhaseType.MAIN2, aiPlayer)) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
} else {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
protected AiAbilityDecision doTriggerNoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
||||||
return true; // TODO: more logic if it's ever a bad idea to trigger this (when non-mandatory)
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -1,10 +1,7 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
import forge.ai.AiAttackController;
|
import forge.ai.*;
|
||||||
import forge.ai.ComputerUtilCard;
|
|
||||||
import forge.ai.ComputerUtilCost;
|
|
||||||
import forge.ai.SpellAbilityAi;
|
|
||||||
import forge.game.Game;
|
import forge.game.Game;
|
||||||
import forge.game.ability.AbilityUtils;
|
import forge.game.ability.AbilityUtils;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
@@ -26,27 +23,27 @@ import java.util.List;
|
|||||||
public class DebuffAi extends SpellAbilityAi {
|
public class DebuffAi extends SpellAbilityAi {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(final Player ai, final SpellAbility sa) {
|
protected AiAbilityDecision canPlay(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.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
final Game game = ai.getGame();
|
final Game game = ai.getGame();
|
||||||
if (!sa.usesTargeting() && !source.isInPlay()) {
|
if (!sa.usesTargeting() && !source.isInPlay()) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
final Cost cost = sa.getPayCosts();
|
final Cost cost = sa.getPayCosts();
|
||||||
|
|
||||||
// temporarily disabled until AI is improved
|
// temporarily disabled until AI is improved
|
||||||
if (!ComputerUtilCost.checkCreatureSacrificeCost(ai, cost, source, sa)) {
|
if (!ComputerUtilCost.checkCreatureSacrificeCost(ai, cost, source, sa)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantAfford);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ComputerUtilCost.checkLifeCost(ai, cost, source, 40, sa)) {
|
if (!ComputerUtilCost.checkLifeCost(ai, cost, source, 40, sa)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantAfford);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ComputerUtilCost.checkRemoveCounterCost(cost, source, sa)) {
|
if (!ComputerUtilCost.checkRemoveCounterCost(cost, source, sa)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantAfford);
|
||||||
}
|
}
|
||||||
|
|
||||||
final PhaseHandler ph = game.getPhaseHandler();
|
final PhaseHandler ph = game.getPhaseHandler();
|
||||||
@@ -58,7 +55,7 @@ public class DebuffAi extends SpellAbilityAi {
|
|||||||
// Instant-speed pumps should not be cast outside of combat when the
|
// Instant-speed pumps should not be cast outside of combat when the
|
||||||
// stack is empty, unless there are specific activation phase requirements
|
// stack is empty, unless there are specific activation phase requirements
|
||||||
if (!isSorcerySpeed(sa, ai) && !sa.hasParam("ActivationPhases")) {
|
if (!isSorcerySpeed(sa, ai) && !sa.hasParam("ActivationPhases")) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.AnotherTime);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,7 +63,7 @@ public class DebuffAi extends SpellAbilityAi {
|
|||||||
List<Card> cards = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa);
|
List<Card> cards = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa);
|
||||||
|
|
||||||
final Combat combat = game.getCombat();
|
final Combat combat = game.getCombat();
|
||||||
return cards.stream().anyMatch(c -> {
|
if (cards.stream().anyMatch(c -> {
|
||||||
if (c.getController().equals(sa.getActivatingPlayer()) || combat == null)
|
if (c.getController().equals(sa.getActivatingPlayer()) || combat == null)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
@@ -75,21 +72,34 @@ public class DebuffAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
// don't add duplicate negative keywords
|
// don't add duplicate negative keywords
|
||||||
return sa.hasParam("Keywords") && c.hasAnyKeyword(Arrays.asList(sa.getParam("Keywords").split(" & ")));
|
return sa.hasParam("Keywords") && c.hasAnyKeyword(Arrays.asList(sa.getParam("Keywords").split(" & ")));
|
||||||
});
|
})) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
} else {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return debuffTgtAI(ai, sa, sa.hasParam("Keywords") ? Arrays.asList(sa.getParam("Keywords").split(" & ")) : null, false);
|
if (debuffTgtAI(ai, sa, sa.hasParam("Keywords") ? Arrays.asList(sa.getParam("Keywords").split(" & ")) : null, false)) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
} else {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
public AiAbilityDecision chkDrawback(SpellAbility sa, Player ai) {
|
||||||
if (!sa.usesTargeting()) {
|
if (!sa.usesTargeting()) {
|
||||||
// TODO - copied from AF_Pump.pumpDrawbackAI() - what should be here?
|
// TODO - copied from AF_Pump.pumpDrawbackAI() - what should be here?
|
||||||
} else {
|
} else {
|
||||||
return debuffTgtAI(ai, sa, sa.hasParam("Keywords") ? Arrays.asList(sa.getParam("Keywords").split(" & ")) : null, false);
|
if (debuffTgtAI(ai, sa, sa.hasParam("Keywords") ? Arrays.asList(sa.getParam("Keywords").split(" & ")) : null, false)) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
} else {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
|
||||||
} // debuffDrawbackAI()
|
} // debuffDrawbackAI()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -234,18 +244,24 @@ public class DebuffAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
protected AiAbilityDecision doTriggerNoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
final List<String> kws = sa.hasParam("Keywords") ? Arrays.asList(sa.getParam("Keywords").split(" & ")) : new ArrayList<>();
|
final List<String> kws = sa.hasParam("Keywords") ? Arrays.asList(sa.getParam("Keywords").split(" & ")) : new ArrayList<>();
|
||||||
|
|
||||||
if (!sa.usesTargeting()) {
|
if (!sa.usesTargeting()) {
|
||||||
if (mandatory) {
|
if (mandatory) {
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return debuffTgtAI(ai, sa, kws, mandatory);
|
if (debuffTgtAI(ai, sa, kws, mandatory)) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
} else {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,43 +15,56 @@ import forge.game.zone.ZoneType;
|
|||||||
public class DelayedTriggerAi extends SpellAbilityAi {
|
public class DelayedTriggerAi extends SpellAbilityAi {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
public AiAbilityDecision chkDrawback(SpellAbility sa, Player ai) {
|
||||||
if ("Always".equals(sa.getParam("AILogic"))) {
|
if ("Always".equals(sa.getParam("AILogic"))) {
|
||||||
// TODO: improve ai
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
SpellAbility trigsa = sa.getAdditionalAbility("Execute");
|
SpellAbility trigsa = sa.getAdditionalAbility("Execute");
|
||||||
if (trigsa == null) {
|
if (trigsa == null) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
trigsa.setActivatingPlayer(ai);
|
trigsa.setActivatingPlayer(ai);
|
||||||
|
|
||||||
if (trigsa instanceof AbilitySub) {
|
if (trigsa instanceof AbilitySub) {
|
||||||
return SpellApiToAi.Converter.get(trigsa).chkDrawbackWithSubs(ai, (AbilitySub)trigsa);
|
return SpellApiToAi.Converter.get(trigsa).chkDrawbackWithSubs(ai, (AbilitySub)trigsa);
|
||||||
} else {
|
} else {
|
||||||
return AiPlayDecision.WillPlay == ((PlayerControllerAi)ai.getController()).getAi().canPlaySa(trigsa);
|
AiPlayDecision decision = ((PlayerControllerAi)ai.getController()).getAi().canPlaySa(trigsa);
|
||||||
|
if (decision == AiPlayDecision.WillPlay) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
} else {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
protected AiAbilityDecision doTriggerNoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
SpellAbility trigsa = sa.getAdditionalAbility("Execute");
|
SpellAbility trigsa = sa.getAdditionalAbility("Execute");
|
||||||
if (trigsa == null) {
|
if (trigsa == null) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
AiController aic = ((PlayerControllerAi)ai.getController()).getAi();
|
AiController aic = ((PlayerControllerAi)ai.getController()).getAi();
|
||||||
trigsa.setActivatingPlayer(ai);
|
trigsa.setActivatingPlayer(ai);
|
||||||
|
|
||||||
if (!sa.hasParam("OptionalDecider")) {
|
if (!sa.hasParam("OptionalDecider")) {
|
||||||
return aic.doTrigger(trigsa, true);
|
if (aic.doTrigger(trigsa, true)) {
|
||||||
|
// If the trigger is mandatory, we can play it
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
} else {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return aic.doTrigger(trigsa, !sa.getParam("OptionalDecider").equals("You"));
|
if (aic.doTrigger(trigsa, !sa.getParam("OptionalDecider").equals("You"))) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
} else {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
protected AiAbilityDecision canPlay(Player ai, SpellAbility sa) {
|
||||||
// Card-specific logic
|
// Card-specific logic
|
||||||
String logic = sa.getParamOrDefault("AILogic", "");
|
String logic = sa.getParamOrDefault("AILogic", "");
|
||||||
if (logic.equals("SpellCopy")) {
|
if (logic.equals("SpellCopy")) {
|
||||||
@@ -90,9 +103,9 @@ public class DelayedTriggerAi extends SpellAbilityAi {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (count == 0) {
|
if (count == 0) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
} else if (logic.equals("NarsetRebound")) {
|
} else if (logic.equals("NarsetRebound")) {
|
||||||
// should be done in Main2, but it might broke for other cards
|
// should be done in Main2, but it might broke for other cards
|
||||||
//if (phase.getPhase().isBefore(PhaseType.MAIN2)) {
|
//if (phase.getPhase().isBefore(PhaseType.MAIN2)) {
|
||||||
@@ -125,10 +138,10 @@ public class DelayedTriggerAi extends SpellAbilityAi {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (count == 0) {
|
if (count == 0) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
} else if (logic.equals("SaveCreature")) {
|
} else if (logic.equals("SaveCreature")) {
|
||||||
CardCollection ownCreatures = ai.getCreaturesInPlay();
|
CardCollection ownCreatures = ai.getCreaturesInPlay();
|
||||||
|
|
||||||
@@ -142,19 +155,25 @@ public class DelayedTriggerAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
if (!ownCreatures.isEmpty()) {
|
if (!ownCreatures.isEmpty()) {
|
||||||
sa.getTargets().add(ComputerUtilCard.getBestAI(ownCreatures));
|
sa.getTargets().add(ComputerUtilCard.getBestAI(ownCreatures));
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generic logic
|
// Generic logic
|
||||||
SpellAbility trigsa = sa.getAdditionalAbility("Execute");
|
SpellAbility trigsa = sa.getAdditionalAbility("Execute");
|
||||||
if (trigsa == null) {
|
if (trigsa == null) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
trigsa.setActivatingPlayer(ai);
|
trigsa.setActivatingPlayer(ai);
|
||||||
return AiPlayDecision.WillPlay == ((PlayerControllerAi)ai.getController()).getAi().canPlaySa(trigsa);
|
|
||||||
|
AiPlayDecision decision = ((PlayerControllerAi)ai.getController()).getAi().canPlaySa(trigsa);
|
||||||
|
if (decision == AiPlayDecision.WillPlay) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
} else {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import forge.util.collect.FCollectionView;
|
|||||||
|
|
||||||
public class DestroyAi extends SpellAbilityAi {
|
public class DestroyAi extends SpellAbilityAi {
|
||||||
@Override
|
@Override
|
||||||
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
public AiAbilityDecision chkDrawback(SpellAbility sa, Player ai) {
|
||||||
return checkApiLogic(ai, sa);
|
return checkApiLogic(ai, sa);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,17 +103,13 @@ public class DestroyAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
|
protected AiAbilityDecision checkApiLogic(final Player ai, final SpellAbility sa) {
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
final boolean noRegen = sa.hasParam("NoRegen");
|
final boolean noRegen = sa.hasParam("NoRegen");
|
||||||
final String logic = sa.getParam("AILogic");
|
final String logic = sa.getParam("AILogic");
|
||||||
|
|
||||||
CardCollection list;
|
CardCollection list;
|
||||||
|
|
||||||
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Targeting
|
// Targeting
|
||||||
if (sa.usesTargeting()) {
|
if (sa.usesTargeting()) {
|
||||||
// If there's X in payment costs and it's tied to targeting, make sure we set the XManaCostPaid first
|
// If there's X in payment costs and it's tied to targeting, make sure we set the XManaCostPaid first
|
||||||
@@ -125,7 +121,7 @@ public class DestroyAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
// Assume there where already enough targets chosen by AI Logic Above
|
// Assume there where already enough targets chosen by AI Logic Above
|
||||||
if (sa.hasParam("AILogic") && !sa.canAddMoreTarget() && sa.isTargetNumberValid()) {
|
if (sa.hasParam("AILogic") && !sa.canAddMoreTarget() && sa.isTargetNumberValid()) {
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
// reset targets before AI Logic part
|
// reset targets before AI Logic part
|
||||||
@@ -145,23 +141,22 @@ public class DestroyAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
if (maxTargets == 0) {
|
if (maxTargets == 0) {
|
||||||
// can't afford X or otherwise target anything
|
// can't afford X or otherwise target anything
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantAffordX);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sa.hasParam("TargetingPlayer")) {
|
if (sa.hasParam("TargetingPlayer")) {
|
||||||
Player targetingPlayer = AbilityUtils.getDefinedPlayers(source, sa.getParam("TargetingPlayer"), sa).get(0);
|
Player targetingPlayer = AbilityUtils.getDefinedPlayers(source, sa.getParam("TargetingPlayer"), sa).get(0);
|
||||||
sa.setTargetingPlayer(targetingPlayer);
|
sa.setTargetingPlayer(targetingPlayer);
|
||||||
return targetingPlayer.getController().chooseTargetsFor(sa);
|
if (targetingPlayer.getController().chooseTargetsFor(sa)) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
} else {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// AI doesn't destroy own cards if it isn't defined in AI logic
|
// AI doesn't destroy own cards if it isn't defined in AI logic
|
||||||
list = CardLists.getTargetableCards(ai.getOpponents().getCardsIn(ZoneType.Battlefield), sa);
|
list = CardLists.getTargetableCards(ai.getOpponents().getCardsIn(ZoneType.Battlefield), sa);
|
||||||
if ("FatalPush".equals(logic)) {
|
|
||||||
final int cmcMax = ai.hasRevolt() ? 4 : 2;
|
|
||||||
list = CardLists.filter(list, CardPredicates.lessCMC(cmcMax));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filter AI-specific targets if provided
|
|
||||||
list = ComputerUtil.filterAITgts(sa, ai, list, true);
|
list = ComputerUtil.filterAITgts(sa, ai, list, true);
|
||||||
|
|
||||||
list = CardLists.getNotKeyword(list, Keyword.INDESTRUCTIBLE);
|
list = CardLists.getNotKeyword(list, Keyword.INDESTRUCTIBLE);
|
||||||
@@ -206,7 +201,7 @@ public class DestroyAi extends SpellAbilityAi {
|
|||||||
// Try to avoid targeting creatures that are dead on board
|
// Try to avoid targeting creatures that are dead on board
|
||||||
list = ComputerUtil.filterCreaturesThatWillDieThisTurn(ai, list, sa);
|
list = ComputerUtil.filterCreaturesThatWillDieThisTurn(ai, list, sa);
|
||||||
if (list.isEmpty()) {
|
if (list.isEmpty()) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
|
|
||||||
// target loop
|
// target loop
|
||||||
@@ -221,7 +216,7 @@ public class DestroyAi extends SpellAbilityAi {
|
|||||||
if (list.isEmpty()) {
|
if (list.isEmpty()) {
|
||||||
if (!sa.isMinTargetChosen() || sa.isZeroTargets()) {
|
if (!sa.isMinTargetChosen() || sa.isZeroTargets()) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
} else {
|
} else {
|
||||||
// TODO is this good enough? for up to amounts?
|
// TODO is this good enough? for up to amounts?
|
||||||
break;
|
break;
|
||||||
@@ -235,7 +230,7 @@ public class DestroyAi extends SpellAbilityAi {
|
|||||||
if ("OppDestroyYours".equals(logic)) {
|
if ("OppDestroyYours".equals(logic)) {
|
||||||
Card aiBest = ComputerUtilCard.getBestCreatureAI(ai.getCreaturesInPlay());
|
Card aiBest = ComputerUtilCard.getBestCreatureAI(ai.getCreaturesInPlay());
|
||||||
if (ComputerUtilCard.evaluateCreature(aiBest) > ComputerUtilCard.evaluateCreature(choice) - 40) {
|
if (ComputerUtilCard.evaluateCreature(aiBest) > ComputerUtilCard.evaluateCreature(choice) - 40) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (CardLists.getNotType(list, "Land").isEmpty()) {
|
} else if (CardLists.getNotType(list, "Land").isEmpty()) {
|
||||||
@@ -244,7 +239,7 @@ public class DestroyAi extends SpellAbilityAi {
|
|||||||
if ("LandForLand".equals(logic) || "GhostQuarter".equals(logic)) {
|
if ("LandForLand".equals(logic) || "GhostQuarter".equals(logic)) {
|
||||||
// Strip Mine, Wasteland - cut short if the relevant logic fails
|
// Strip Mine, Wasteland - cut short if the relevant logic fails
|
||||||
if (!doLandForLandRemovalLogic(sa, ai, choice, logic)) {
|
if (!doLandForLandRemovalLogic(sa, ai, choice, logic)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -254,14 +249,14 @@ public class DestroyAi extends SpellAbilityAi {
|
|||||||
//option to hold removal instead only applies for single targeted removal
|
//option to hold removal instead only applies for single targeted removal
|
||||||
if (!sa.isTrigger() && sa.getMaxTargets() == 1) {
|
if (!sa.isTrigger() && sa.getMaxTargets() == 1) {
|
||||||
if (choice == null || !ComputerUtilCard.useRemovalNow(sa, choice, 0, ZoneType.Graveyard)) {
|
if (choice == null || !ComputerUtilCard.useRemovalNow(sa, choice, 0, ZoneType.Graveyard)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (choice == null) { // can't find anything left
|
if (choice == null) { // can't find anything left
|
||||||
if (!sa.isMinTargetChosen() || sa.isZeroTargets()) {
|
if (!sa.isMinTargetChosen() || sa.isZeroTargets()) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
} else {
|
} else {
|
||||||
// TODO is this good enough? for up to amounts?
|
// TODO is this good enough? for up to amounts?
|
||||||
break;
|
break;
|
||||||
@@ -298,22 +293,22 @@ public class DestroyAi extends SpellAbilityAi {
|
|||||||
|| !source.getGame().getPhaseHandler().isPlayerTurn(ai)
|
|| !source.getGame().getPhaseHandler().isPlayerTurn(ai)
|
||||||
|| ai.getLife() <= 5)) {
|
|| ai.getLife() <= 5)) {
|
||||||
// Basic ai logic for Lethal Vapors
|
// Basic ai logic for Lethal Vapors
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
} else if ("Always".equals(logic)) {
|
} else if ("Always".equals(logic)) {
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (list.isEmpty()
|
if (list.isEmpty()
|
||||||
|| !CardLists.filterControlledBy(list, ai).isEmpty()
|
|| !CardLists.filterControlledBy(list, ai).isEmpty()
|
||||||
|| CardLists.getNotKeyword(list, Keyword.INDESTRUCTIBLE).isEmpty()) {
|
|| CardLists.getNotKeyword(list, Keyword.INDESTRUCTIBLE).isEmpty()) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
protected AiAbilityDecision doTriggerNoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
final boolean noRegen = sa.hasParam("NoRegen");
|
final boolean noRegen = sa.hasParam("NoRegen");
|
||||||
if (sa.usesTargeting()) {
|
if (sa.usesTargeting()) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
@@ -321,7 +316,7 @@ public class DestroyAi extends SpellAbilityAi {
|
|||||||
CardCollection list = CardLists.getTargetableCards(ai.getGame().getCardsIn(ZoneType.Battlefield), sa);
|
CardCollection list = CardLists.getTargetableCards(ai.getGame().getCardsIn(ZoneType.Battlefield), sa);
|
||||||
|
|
||||||
if (list.isEmpty() || list.size() < sa.getMinTargets()) {
|
if (list.isEmpty() || list.size() < sa.getMinTargets()) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to avoid targeting creatures that are dead on board
|
// Try to avoid targeting creatures that are dead on board
|
||||||
@@ -349,7 +344,7 @@ public class DestroyAi extends SpellAbilityAi {
|
|||||||
list.removeAll(preferred);
|
list.removeAll(preferred);
|
||||||
|
|
||||||
if (preferred.isEmpty() && !mandatory) {
|
if (preferred.isEmpty() && !mandatory) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
while (sa.canAddMoreTarget()) {
|
while (sa.canAddMoreTarget()) {
|
||||||
@@ -357,12 +352,12 @@ public class DestroyAi extends SpellAbilityAi {
|
|||||||
if (!sa.isMinTargetChosen()) {
|
if (!sa.isMinTargetChosen()) {
|
||||||
if (!mandatory) {
|
if (!mandatory) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
} else {
|
} else {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Card c = ComputerUtilCard.getBestAI(preferred);
|
Card c = ComputerUtilCard.getBestAI(preferred);
|
||||||
@@ -397,9 +392,18 @@ public class DestroyAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return sa.isTargetNumberValid();
|
if (sa.isTargetNumberValid()) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
} else {
|
||||||
|
sa.resetTargets();
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return mandatory;
|
if (mandatory) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
} else {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,38 +23,23 @@ public class DestroyAllAi extends SpellAbilityAi {
|
|||||||
* @see forge.card.abilityfactory.SpellAiLogic#doTriggerAINoCost(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility, boolean)
|
* @see forge.card.abilityfactory.SpellAiLogic#doTriggerAINoCost(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility, boolean)
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
protected AiAbilityDecision doTriggerNoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
if (mandatory) {
|
if (mandatory) {
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
return doMassRemovalLogic(ai, sa);
|
return doMassRemovalLogic(ai, sa);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
|
public AiAbilityDecision chkDrawback(SpellAbility sa, Player aiPlayer) {
|
||||||
return doMassRemovalLogic(aiPlayer, sa);
|
return doMassRemovalLogic(aiPlayer, sa);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(final Player ai, SpellAbility sa) {
|
protected AiAbilityDecision checkApiLogic(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 Cost abCost = sa.getPayCosts();
|
|
||||||
final Card source = sa.getHostCard();
|
|
||||||
|
|
||||||
if (abCost != null) {
|
|
||||||
// AI currently disabled for some costs
|
|
||||||
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, sa)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// prevent run-away activations - first time will always return true
|
|
||||||
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
final String aiLogic = sa.getParamOrDefault("AILogic", "");
|
final String aiLogic = sa.getParamOrDefault("AILogic", "");
|
||||||
|
|
||||||
if ("FellTheMighty".equals(aiLogic)) {
|
if ("FellTheMighty".equals(aiLogic)) {
|
||||||
@@ -64,7 +49,7 @@ public class DestroyAllAi extends SpellAbilityAi {
|
|||||||
return doMassRemovalLogic(ai, sa);
|
return doMassRemovalLogic(ai, sa);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean doMassRemovalLogic(Player ai, SpellAbility sa) {
|
public static AiAbilityDecision doMassRemovalLogic(Player ai, SpellAbility sa) {
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
final String logic = sa.getParamOrDefault("AILogic", "");
|
final String logic = sa.getParamOrDefault("AILogic", "");
|
||||||
|
|
||||||
@@ -72,7 +57,7 @@ public class DestroyAllAi extends SpellAbilityAi {
|
|||||||
final int CREATURE_EVAL_THRESHOLD = 200 / (!sa.usesTargeting() ? ai.getOpponents().size() : 1);
|
final int CREATURE_EVAL_THRESHOLD = 200 / (!sa.usesTargeting() ? ai.getOpponents().size() : 1);
|
||||||
|
|
||||||
if (logic.equals("Always")) {
|
if (logic.equals("Always")) {
|
||||||
return true; // e.g. Tetzimoc, Primal Death, where we want to cast the permanent even if the removal trigger does nothing
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
String valid = sa.getParamOrDefault("ValidCards", "");
|
String valid = sa.getParamOrDefault("ValidCards", "");
|
||||||
@@ -92,7 +77,7 @@ public class DestroyAllAi extends SpellAbilityAi {
|
|||||||
opplist = CardLists.filter(opplist, predicate);
|
opplist = CardLists.filter(opplist, predicate);
|
||||||
ailist = CardLists.filter(ailist, predicate);
|
ailist = CardLists.filter(ailist, predicate);
|
||||||
if (opplist.isEmpty()) {
|
if (opplist.isEmpty()) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sa.usesTargeting()) {
|
if (sa.usesTargeting()) {
|
||||||
@@ -101,7 +86,7 @@ public class DestroyAllAi extends SpellAbilityAi {
|
|||||||
sa.getTargets().add(opponent);
|
sa.getTargets().add(opponent);
|
||||||
ailist.clear();
|
ailist.clear();
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,30 +95,35 @@ public class DestroyAllAi extends SpellAbilityAi {
|
|||||||
int numAiCanSave = Math.min(CardLists.count(ai.getCreaturesInPlay(), CardPredicates.isColor(MagicColor.WHITE).and(CardPredicates.UNTAPPED)) * 2, ailist.size());
|
int numAiCanSave = Math.min(CardLists.count(ai.getCreaturesInPlay(), CardPredicates.isColor(MagicColor.WHITE).and(CardPredicates.UNTAPPED)) * 2, ailist.size());
|
||||||
int numOppsCanSave = Math.min(CardLists.count(ai.getOpponents().getCreaturesInPlay(), CardPredicates.isColor(MagicColor.WHITE).and(CardPredicates.UNTAPPED)) * 2, opplist.size());
|
int numOppsCanSave = Math.min(CardLists.count(ai.getOpponents().getCreaturesInPlay(), CardPredicates.isColor(MagicColor.WHITE).and(CardPredicates.UNTAPPED)) * 2, opplist.size());
|
||||||
|
|
||||||
return numOppsCanSave < opplist.size() && (ailist.size() - numAiCanSave < opplist.size() - numOppsCanSave);
|
if (numOppsCanSave < opplist.size() && (ailist.size() - numAiCanSave < opplist.size() - numOppsCanSave)) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
} else if (numAiCanSave < ailist.size() && (opplist.size() - numOppsCanSave < ailist.size() - numAiCanSave)) {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If effect is destroying creatures and AI is about to lose, activate effect anyway no matter what!
|
// If effect is destroying creatures and AI is about to lose, activate effect anyway no matter what!
|
||||||
if ((!CardLists.getType(opplist, "Creature").isEmpty()) && (ai.getGame().getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS))
|
if ((!CardLists.getType(opplist, "Creature").isEmpty()) && (ai.getGame().getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS))
|
||||||
&& (ai.getGame().getCombat() != null && ComputerUtilCombat.lifeInSeriousDanger(ai, ai.getGame().getCombat()))) {
|
&& (ai.getGame().getCombat() != null && ComputerUtilCombat.lifeInSeriousDanger(ai, ai.getGame().getCombat()))) {
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If effect is destroying creatures and AI is about to get low on life, activate effect anyway if difference in lost permanents not very much
|
// If effect is destroying creatures and AI is about to get low on life, activate effect anyway if difference in lost permanents not very much
|
||||||
if ((!CardLists.getType(opplist, "Creature").isEmpty()) && (ai.getGame().getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS))
|
if ((!CardLists.getType(opplist, "Creature").isEmpty()) && (ai.getGame().getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS))
|
||||||
&& (ai.getGame().getCombat() != null && ComputerUtilCombat.lifeInDanger(ai, ai.getGame().getCombat()))
|
&& (ai.getGame().getCombat() != null && ComputerUtilCombat.lifeInDanger(ai, ai.getGame().getCombat()))
|
||||||
&& ((ComputerUtilCard.evaluatePermanentList(ailist) - 6) >= ComputerUtilCard.evaluatePermanentList(opplist))) {
|
&& ((ComputerUtilCard.evaluatePermanentList(ailist) - 6) >= ComputerUtilCard.evaluatePermanentList(opplist))) {
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
// if only creatures are affected evaluate both lists and pass only if human creatures are more valuable
|
// if only creatures are affected evaluate both lists and pass only if human creatures are more valuable
|
||||||
if (CardLists.getNotType(opplist, "Creature").isEmpty() && CardLists.getNotType(ailist, "Creature").isEmpty()) {
|
if (CardLists.getNotType(opplist, "Creature").isEmpty() && CardLists.getNotType(ailist, "Creature").isEmpty()) {
|
||||||
if (ComputerUtilCard.evaluateCreatureList(ailist) + CREATURE_EVAL_THRESHOLD < ComputerUtilCard.evaluateCreatureList(opplist)) {
|
if (ComputerUtilCard.evaluateCreatureList(ailist) + CREATURE_EVAL_THRESHOLD < ComputerUtilCard.evaluateCreatureList(opplist)) {
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2)) {
|
if (ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.WaitForMain2);
|
||||||
}
|
}
|
||||||
|
|
||||||
// test whether the human can kill the ai next turn
|
// test whether the human can kill the ai next turn
|
||||||
@@ -146,39 +136,42 @@ public class DestroyAllAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!containsAttacker) {
|
if (!containsAttacker) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
AiBlockController block = new AiBlockController(ai, false);
|
AiBlockController block = new AiBlockController(ai, false);
|
||||||
block.assignBlockersForCombat(combat);
|
block.assignBlockersForCombat(combat);
|
||||||
|
|
||||||
if (ComputerUtilCombat.lifeInSeriousDanger(ai, combat)) {
|
if (ComputerUtilCombat.lifeInSeriousDanger(ai, combat)) {
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
} // only lands involved
|
} // only lands involved
|
||||||
else if (CardLists.getNotType(opplist, "Land").isEmpty() && CardLists.getNotType(ailist, "Land").isEmpty()) {
|
else if (CardLists.getNotType(opplist, "Land").isEmpty() && CardLists.getNotType(ailist, "Land").isEmpty()) {
|
||||||
if (ai.isCardInPlay("Crucible of Worlds") && !opponent.isCardInPlay("Crucible of Worlds")) {
|
if (ai.isCardInPlay("Crucible of Worlds") && !opponent.isCardInPlay("Crucible of Worlds")) {
|
||||||
return true;
|
// TODO Should care about any land recursion, not just Crucible of Worlds
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
|
||||||
}
|
}
|
||||||
// evaluate the situation with creatures on the battlefield separately, as that's where the AI typically makes mistakes
|
// evaluate the situation with creatures on the battlefield separately, as that's where the AI typically makes mistakes
|
||||||
CardCollection aiCreatures = ai.getCreaturesInPlay();
|
CardCollection aiCreatures = ai.getCreaturesInPlay();
|
||||||
CardCollection oppCreatures = opponent.getCreaturesInPlay();
|
CardCollection oppCreatures = opponent.getCreaturesInPlay();
|
||||||
if (!oppCreatures.isEmpty()) {
|
if (!oppCreatures.isEmpty()) {
|
||||||
if (ComputerUtilCard.evaluateCreatureList(aiCreatures) < ComputerUtilCard.evaluateCreatureList(oppCreatures) + CREATURE_EVAL_THRESHOLD) {
|
if (ComputerUtilCard.evaluateCreatureList(aiCreatures) < ComputerUtilCard.evaluateCreatureList(oppCreatures) + CREATURE_EVAL_THRESHOLD) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// check if the AI would lose more lands than the opponent would
|
// check if the AI would lose more lands than the opponent would
|
||||||
if (ComputerUtilCard.evaluatePermanentList(ailist) > ComputerUtilCard.evaluatePermanentList(opplist) + 1) {
|
if (ComputerUtilCard.evaluatePermanentList(ailist) > ComputerUtilCard.evaluatePermanentList(opplist) + 1) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
} // otherwise evaluate both lists by CMC and pass only if human permanents are more valuable
|
} // otherwise evaluate both lists by CMC and pass only if human permanents are more valuable
|
||||||
else if ((ComputerUtilCard.evaluatePermanentList(ailist) + 3) >= ComputerUtilCard.evaluatePermanentList(opplist)) {
|
else if ((ComputerUtilCard.evaluatePermanentList(ailist) + 3) >= ComputerUtilCard.evaluatePermanentList(opplist)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
|
||||||
}
|
}
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
119
forge-ai/src/main/java/forge/ai/ability/DetainAi.java
Normal file
119
forge-ai/src/main/java/forge/ai/ability/DetainAi.java
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
|
import forge.ai.AiAttackController;
|
||||||
|
import forge.ai.ComputerUtil;
|
||||||
|
import forge.ai.ComputerUtilCard;
|
||||||
|
import forge.ai.SpellAbilityAi;
|
||||||
|
import forge.game.Game;
|
||||||
|
import forge.game.card.Card;
|
||||||
|
import forge.game.card.CardCollection;
|
||||||
|
import forge.game.card.CardLists;
|
||||||
|
import forge.game.card.CardPredicates;
|
||||||
|
import forge.game.combat.CombatUtil;
|
||||||
|
import forge.game.phase.PhaseHandler;
|
||||||
|
import forge.game.phase.PhaseType;
|
||||||
|
import forge.game.player.Player;
|
||||||
|
import forge.game.spellability.SpellAbility;
|
||||||
|
import forge.game.zone.ZoneType;
|
||||||
|
|
||||||
|
public class DetainAi extends SpellAbilityAi {
|
||||||
|
|
||||||
|
Predicate<Card> CREATURE_OR_TAP_ABILITY = c -> {
|
||||||
|
if (c.isCreature()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (final SpellAbility sa : c.getSpellAbilities()) {
|
||||||
|
if (sa.isAbility() && sa.getPayCosts().hasTapCost()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
protected boolean prefTargeting(final Player ai, final Card source, final SpellAbility sa, final boolean mandatory) {
|
||||||
|
final Game game = ai.getGame();
|
||||||
|
CardCollection list = CardLists.getTargetableCards(ai.getOpponents().getCardsIn(ZoneType.Battlefield), sa);
|
||||||
|
list = CardLists.filter(list, CREATURE_OR_TAP_ABILITY);
|
||||||
|
|
||||||
|
// Filter AI-specific targets if provided
|
||||||
|
list = ComputerUtil.filterAITgts(sa, ai, list, true);
|
||||||
|
|
||||||
|
|
||||||
|
if (list.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (sa.canAddMoreTarget()) {
|
||||||
|
Card choice = null;
|
||||||
|
if (list.isEmpty()) {
|
||||||
|
if (!sa.isMinTargetChosen() || sa.isZeroTargets()) {
|
||||||
|
if (!mandatory) {
|
||||||
|
sa.resetTargets();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PhaseHandler phase = game.getPhaseHandler();
|
||||||
|
final Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
|
||||||
|
Card primeTarget = ComputerUtil.getKilledByTargeting(sa, list);
|
||||||
|
if (primeTarget != null) {
|
||||||
|
choice = primeTarget;
|
||||||
|
} else if (phase.isPlayerTurn(ai) && phase.getPhase().isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
||||||
|
// Tap creatures possible blockers before combat during AI's turn.
|
||||||
|
List<Card> attackers;
|
||||||
|
if (phase.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)) {
|
||||||
|
//Combat has already started
|
||||||
|
attackers = game.getCombat().getAttackers();
|
||||||
|
} else {
|
||||||
|
attackers = CardLists.filter(ai.getCreaturesInPlay(), c -> CombatUtil.canAttack(c, opp));
|
||||||
|
attackers.remove(source);
|
||||||
|
}
|
||||||
|
List<Card> creatureList = CardLists.filter(list, CardPredicates.possibleBlockerForAtLeastOne(attackers));
|
||||||
|
|
||||||
|
// TODO check if own creature would be forced to attack and we want to keep it alive
|
||||||
|
|
||||||
|
if (!attackers.isEmpty() && !creatureList.isEmpty()) {
|
||||||
|
choice = ComputerUtilCard.getBestCreatureAI(creatureList);
|
||||||
|
} else if (sa.isTrigger() || ComputerUtil.castSpellInMain1(ai, sa)) {
|
||||||
|
choice = ComputerUtilCard.getMostExpensivePermanentAI(list);
|
||||||
|
}
|
||||||
|
} else if (phase.isPlayerTurn(opp)
|
||||||
|
&& phase.getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)) {
|
||||||
|
// Tap creatures possible blockers before combat during AI's turn.
|
||||||
|
if (list.anyMatch(CardPredicates.CREATURES)) {
|
||||||
|
List<Card> creatureList = CardLists.filter(list, c -> c.isCreature() && CombatUtil.canAttack(c, opp));
|
||||||
|
choice = ComputerUtilCard.getBestCreatureAI(creatureList);
|
||||||
|
} else { // no creatures available
|
||||||
|
choice = ComputerUtilCard.getMostExpensivePermanentAI(list);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
choice = ComputerUtilCard.getMostExpensivePermanentAI(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (choice == null) { // can't find anything left
|
||||||
|
if (!sa.isMinTargetChosen() || sa.isZeroTargets()) {
|
||||||
|
if (!mandatory) {
|
||||||
|
sa.resetTargets();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
if (!ComputerUtil.shouldCastLessThanMax(ai, source)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
list.remove(choice);
|
||||||
|
sa.getTargets().add(choice);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -20,26 +20,21 @@ import forge.util.TextUtil;
|
|||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
|
||||||
public class DigAi extends SpellAbilityAi {
|
public class DigAi extends SpellAbilityAi {
|
||||||
/* (non-Javadoc)
|
/* (non-Javadoc)
|
||||||
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
|
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
protected AiAbilityDecision checkApiLogic(Player ai, SpellAbility sa) {
|
||||||
final Game game = ai.getGame();
|
final Game game = ai.getGame();
|
||||||
Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
|
Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
|
||||||
final Card host = sa.getHostCard();
|
final Card host = sa.getHostCard();
|
||||||
Player libraryOwner = ai;
|
Player libraryOwner = ai;
|
||||||
|
|
||||||
if (!willPayCosts(ai, sa, sa.getPayCosts(), host)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sa.usesTargeting()) {
|
if (sa.usesTargeting()) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
if (!sa.canTarget(opp)) {
|
if (!sa.canTarget(opp)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
sa.getTargets().add(opp);
|
sa.getTargets().add(opp);
|
||||||
libraryOwner = opp;
|
libraryOwner = opp;
|
||||||
@@ -47,29 +42,21 @@ public class DigAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
// return false if nothing to dig into
|
// return false if nothing to dig into
|
||||||
if (libraryOwner.getCardsIn(ZoneType.Library).isEmpty()) {
|
if (libraryOwner.getCardsIn(ZoneType.Library).isEmpty()) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
|
||||||
|
|
||||||
if ("Never".equals(sa.getParam("AILogic"))) {
|
|
||||||
return false;
|
|
||||||
} else if ("AtOppEOT".equals(sa.getParam("AILogic"))) {
|
|
||||||
if (!(game.getPhaseHandler().getNextTurn() == ai && game.getPhaseHandler().is(PhaseType.END_OF_TURN))) {
|
|
||||||
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);
|
||||||
if (libraryOwner == ai && ai.getCardsIn(ZoneType.Library).size() <= numToDig + 2) {
|
if (libraryOwner == ai && ai.getCardsIn(ZoneType.Library).size() <= numToDig + 2) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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) && !sa.hasParam("ActivationPhases")
|
if (game.getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2) && !sa.hasParam("ActivationPhases")
|
||||||
&& !sa.hasParam("DestinationZone") && !ComputerUtil.castSpellInMain1(ai, sa)) {
|
&& !sa.hasParam("DestinationZone") && !ComputerUtil.castSpellInMain1(ai, sa)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
final String num = sa.getParam("DigNum");
|
final String num = sa.getParam("DigNum");
|
||||||
@@ -87,14 +74,14 @@ public class DigAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
int numCards = ComputerUtilCost.getMaxXValue(sa, ai, sa.isTrigger()) - manaToSave;
|
int numCards = ComputerUtilCost.getMaxXValue(sa, ai, sa.isTrigger()) - manaToSave;
|
||||||
if (numCards <= 0) {
|
if (numCards <= 0) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
root.setXManaCostPaid(numCards);
|
root.setXManaCostPaid(numCards);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (playReusable(ai, sa)) {
|
if (playReusable(ai, sa)) {
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((!game.getPhaseHandler().getNextTurn().equals(ai)
|
if ((!game.getPhaseHandler().getNextTurn().equals(ai)
|
||||||
@@ -102,24 +89,24 @@ public class DigAi extends SpellAbilityAi {
|
|||||||
&& !sa.hasParam("PlayerTurn") && !isSorcerySpeed(sa, ai)
|
&& !sa.hasParam("PlayerTurn") && !isSorcerySpeed(sa, ai)
|
||||||
&& (ai.getCardsIn(ZoneType.Hand).size() > 1 || game.getPhaseHandler().getPhase().isBefore(PhaseType.DRAW))
|
&& (ai.getCardsIn(ZoneType.Hand).size() > 1 || game.getPhaseHandler().getPhase().isBefore(PhaseType.DRAW))
|
||||||
&& !ComputerUtil.activateForCost(sa, ai)) {
|
&& !ComputerUtil.activateForCost(sa, ai)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ("MadSarkhanDigDmg".equals(sa.getParam("AILogic"))) {
|
if ("MadSarkhanDigDmg".equals(sa.getParam("AILogic"))) {
|
||||||
return SpecialCardAi.SarkhanTheMad.considerDig(ai, sa);
|
return SpecialCardAi.SarkhanTheMad.considerDig(ai, sa);
|
||||||
}
|
}
|
||||||
|
|
||||||
return !ComputerUtil.preventRunAwayActivations(sa);
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
|
public AiAbilityDecision chkDrawback(SpellAbility sa, Player aiPlayer) {
|
||||||
// TODO: improve this check in ways that may be specific to a subability
|
// TODO: improve this check in ways that may be specific to a subability
|
||||||
return canPlayAI(aiPlayer, sa);
|
return canPlay(aiPlayer, sa);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
protected AiAbilityDecision doTriggerNoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
final SpellAbility root = sa.getRootAbility();
|
final SpellAbility root = sa.getRootAbility();
|
||||||
PlayerCollection targetableOpps = ai.getOpponents().filter(PlayerPredicates.isTargetableBy(sa));
|
PlayerCollection targetableOpps = ai.getOpponents().filter(PlayerPredicates.isTargetableBy(sa));
|
||||||
Player opp = targetableOpps.min(PlayerPredicates.compareByLife());
|
Player opp = targetableOpps.min(PlayerPredicates.compareByLife());
|
||||||
@@ -137,12 +124,16 @@ public class DigAi extends SpellAbilityAi {
|
|||||||
int manaToSave = Integer.parseInt(TextUtil.split(sa.getParam("AILogic"), '.')[1]);
|
int manaToSave = Integer.parseInt(TextUtil.split(sa.getParam("AILogic"), '.')[1]);
|
||||||
int numCards = ComputerUtilCost.getMaxXValue(sa, ai, true) - manaToSave;
|
int numCards = ComputerUtilCost.getMaxXValue(sa, ai, true) - manaToSave;
|
||||||
if (numCards <= 0) {
|
if (numCards <= 0) {
|
||||||
return mandatory;
|
if (mandatory) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
} else {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.CantPlayAi);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
root.setXManaCostPaid(numCards);
|
root.setXManaCostPaid(numCards);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -203,15 +194,12 @@ public class DigAi extends SpellAbilityAi {
|
|||||||
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message, Map<String, Object> params) {
|
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message, Map<String, Object> params) {
|
||||||
Card topc = player.getZone(ZoneType.Library).get(0);
|
Card topc = player.getZone(ZoneType.Library).get(0);
|
||||||
|
|
||||||
// AI actions for individual cards (until this AI can be generalized)
|
if (ComputerUtilAbility.getAbilitySourceName(sa).equals("Explorer's Scope")) {
|
||||||
if (sa.getHostCard() != null) {
|
// for Explorer's Scope, always put a land on the battlefield tapped
|
||||||
if (ComputerUtilAbility.getAbilitySourceName(sa).equals("Explorer's Scope")) {
|
// (TODO: might not always be a good idea, e.g. when a land ETBing can have detrimental effects)
|
||||||
// for Explorer's Scope, always put a land on the battlefield tapped
|
return true;
|
||||||
// (TODO: might not always be a good idea, e.g. when a land ETBing can have detrimental effects)
|
} else if ("AlwaysConfirm".equals(sa.getParam("AILogic"))) {
|
||||||
return true;
|
return true;
|
||||||
} else if ("AlwaysConfirm".equals(sa.getParam("AILogic"))) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// looks like perfect code for Delver of Secrets, but what about other cards?
|
// looks like perfect code for Delver of Secrets, but what about other cards?
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
import forge.ai.AiAttackController;
|
import forge.ai.*;
|
||||||
import forge.ai.ComputerUtil;
|
|
||||||
import forge.ai.SpellAbilityAi;
|
|
||||||
import forge.game.Game;
|
import forge.game.Game;
|
||||||
import forge.game.ability.AbilityUtils;
|
import forge.game.ability.AbilityUtils;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
@@ -14,13 +12,12 @@ import forge.game.zone.ZoneType;
|
|||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
|
||||||
public class DigMultipleAi extends SpellAbilityAi {
|
public class DigMultipleAi extends SpellAbilityAi {
|
||||||
/* (non-Javadoc)
|
/* (non-Javadoc)
|
||||||
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
|
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
protected AiAbilityDecision checkApiLogic(Player ai, SpellAbility sa) {
|
||||||
final Game game = ai.getGame();
|
final Game game = ai.getGame();
|
||||||
Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
|
Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
|
||||||
final Card host = sa.getHostCard();
|
final Card host = sa.getHostCard();
|
||||||
@@ -29,7 +26,7 @@ public class DigMultipleAi extends SpellAbilityAi {
|
|||||||
if (sa.usesTargeting()) {
|
if (sa.usesTargeting()) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
if (!opp.canBeTargetedBy(sa)) {
|
if (!opp.canBeTargetedBy(sa)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
sa.getTargets().add(opp);
|
sa.getTargets().add(opp);
|
||||||
libraryOwner = opp;
|
libraryOwner = opp;
|
||||||
@@ -37,33 +34,29 @@ public class DigMultipleAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
// return false if nothing to dig into
|
// return false if nothing to dig into
|
||||||
if (libraryOwner.getCardsIn(ZoneType.Library).isEmpty()) {
|
if (libraryOwner.getCardsIn(ZoneType.Library).isEmpty()) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ("Never".equals(sa.getParam("AILogic"))) {
|
if ("Never".equals(sa.getParam("AILogic"))) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
} else if ("AtOppEOT".equals(sa.getParam("AILogic"))) {
|
|
||||||
if (!(game.getPhaseHandler().getNextTurn() == ai && game.getPhaseHandler().is(PhaseType.END_OF_TURN))) {
|
|
||||||
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);
|
||||||
if (libraryOwner == ai && ai.getCardsIn(ZoneType.Library).size() <= numToDig + 2) {
|
if (libraryOwner == ai && ai.getCardsIn(ZoneType.Library).size() <= numToDig + 2) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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) && !sa.hasParam("ActivationPhases")
|
if (game.getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2) && !sa.hasParam("ActivationPhases")
|
||||||
&& !sa.hasParam("DestinationZone") && !ComputerUtil.castSpellInMain1(ai, sa)) {
|
&& !sa.hasParam("DestinationZone") && !ComputerUtil.castSpellInMain1(ai, sa)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (playReusable(ai, sa)) {
|
if (playReusable(ai, sa)) {
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((!game.getPhaseHandler().getNextTurn().equals(ai)
|
if ((!game.getPhaseHandler().getNextTurn().equals(ai)
|
||||||
@@ -71,14 +64,14 @@ public class DigMultipleAi extends SpellAbilityAi {
|
|||||||
&& !sa.hasParam("PlayerTurn") && !isSorcerySpeed(sa, ai)
|
&& !sa.hasParam("PlayerTurn") && !isSorcerySpeed(sa, ai)
|
||||||
&& (ai.getCardsIn(ZoneType.Hand).size() > 1 || game.getPhaseHandler().getPhase().isBefore(PhaseType.DRAW))
|
&& (ai.getCardsIn(ZoneType.Hand).size() > 1 || game.getPhaseHandler().getPhase().isBefore(PhaseType.DRAW))
|
||||||
&& !ComputerUtil.activateForCost(sa, ai)) {
|
&& !ComputerUtil.activateForCost(sa, ai)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
return !ComputerUtil.preventRunAwayActivations(sa);
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
protected AiAbilityDecision doTriggerNoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
final Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
|
final Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
|
||||||
if (sa.usesTargeting()) {
|
if (sa.usesTargeting()) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
@@ -89,7 +82,7 @@ public class DigMultipleAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* (non-Javadoc)
|
/* (non-Javadoc)
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
import forge.ai.AiAttackController;
|
import forge.ai.*;
|
||||||
import forge.ai.ComputerUtilCost;
|
|
||||||
import forge.ai.SpellAbilityAi;
|
|
||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
import forge.game.card.CardLists;
|
import forge.game.card.CardLists;
|
||||||
import forge.game.card.CardPredicates;
|
import forge.game.card.CardPredicates;
|
||||||
@@ -11,7 +9,6 @@ import forge.game.player.Player;
|
|||||||
import forge.game.player.PlayerActionConfirmMode;
|
import forge.game.player.PlayerActionConfirmMode;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
import forge.util.MyRandom;
|
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -19,7 +16,7 @@ import java.util.Map;
|
|||||||
public class DigUntilAi extends SpellAbilityAi {
|
public class DigUntilAi extends SpellAbilityAi {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
protected AiAbilityDecision checkApiLogic(Player ai, SpellAbility sa) {
|
||||||
Card source = sa.getHostCard();
|
Card source = sa.getHostCard();
|
||||||
final String logic = sa.getParamOrDefault("AILogic", "");
|
final String logic = sa.getParamOrDefault("AILogic", "");
|
||||||
double chance = .4; // 40 percent chance with instant speed stuff
|
double chance = .4; // 40 percent chance with instant speed stuff
|
||||||
@@ -42,7 +39,7 @@ public class DigUntilAi extends SpellAbilityAi {
|
|||||||
// material in the library after using it several times.
|
// material in the library after using it several times.
|
||||||
// TODO: maybe this should happen for any DigUntil SA with RevealedDestination$ Graveyard?
|
// TODO: maybe this should happen for any DigUntil SA with RevealedDestination$ Graveyard?
|
||||||
if (ai.getCardsIn(ZoneType.Library).size() < 20) {
|
if (ai.getCardsIn(ZoneType.Library).size() < 20) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
if ("Land.Basic".equals(sa.getParam("Valid"))
|
if ("Land.Basic".equals(sa.getParam("Valid"))
|
||||||
&& ai.getZone(ZoneType.Hand).contains(CardPredicates.LANDS_PRODUCING_MANA)) {
|
&& ai.getZone(ZoneType.Hand).contains(CardPredicates.LANDS_PRODUCING_MANA)) {
|
||||||
@@ -52,7 +49,7 @@ public class DigUntilAi extends SpellAbilityAi {
|
|||||||
// This is important for Replenish/Living Death type decks
|
// This is important for Replenish/Living Death type decks
|
||||||
if (!ai.getGame().getPhaseHandler().is(PhaseType.END_OF_TURN)
|
if (!ai.getGame().getPhaseHandler().is(PhaseType.END_OF_TURN)
|
||||||
&& !ai.getGame().getPhaseHandler().isPlayerTurn(ai)) {
|
&& !ai.getGame().getPhaseHandler().isPlayerTurn(ai)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -60,7 +57,7 @@ public class DigUntilAi extends SpellAbilityAi {
|
|||||||
if (sa.usesTargeting()) {
|
if (sa.usesTargeting()) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
if (!sa.canTarget(opp)) {
|
if (!sa.canTarget(opp)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
sa.getTargets().add(opp);
|
sa.getTargets().add(opp);
|
||||||
libraryOwner = opp;
|
libraryOwner = opp;
|
||||||
@@ -68,7 +65,7 @@ public class DigUntilAi extends SpellAbilityAi {
|
|||||||
if (sa.hasParam("Valid")) {
|
if (sa.hasParam("Valid")) {
|
||||||
final String valid = sa.getParam("Valid");
|
final String valid = sa.getParam("Valid");
|
||||||
if (CardLists.getValidCards(ai.getCardsIn(ZoneType.Library), valid, source.getController(), source, sa).isEmpty()) {
|
if (CardLists.getValidCards(ai.getCardsIn(ZoneType.Library), valid, source.getController(), source, sa).isEmpty()) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -80,7 +77,7 @@ public class DigUntilAi extends SpellAbilityAi {
|
|||||||
if (root.getXManaCostPaid() == null) {
|
if (root.getXManaCostPaid() == null) {
|
||||||
int numCards = ComputerUtilCost.getMaxXValue(sa, ai, sa.isTrigger());
|
int numCards = ComputerUtilCost.getMaxXValue(sa, ai, sa.isTrigger());
|
||||||
if (numCards <= 0) {
|
if (numCards <= 0) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
root.setXManaCostPaid(numCards);
|
root.setXManaCostPaid(numCards);
|
||||||
}
|
}
|
||||||
@@ -88,15 +85,14 @@ public class DigUntilAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
// return false if nothing to dig into
|
// return false if nothing to dig into
|
||||||
if (libraryOwner.getCardsIn(ZoneType.Library).isEmpty()) {
|
if (libraryOwner.getCardsIn(ZoneType.Library).isEmpty()) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
final boolean randomReturn = MyRandom.getRandom().nextFloat() <= Math.pow(chance, sa.getActivationsThisTurn() + 1);
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
return randomReturn;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
protected AiAbilityDecision doTriggerNoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
if (sa.usesTargeting()) {
|
if (sa.usesTargeting()) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
if (sa.isCurse()) {
|
if (sa.isCurse()) {
|
||||||
@@ -116,7 +112,7 @@ public class DigUntilAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* (non-Javadoc)
|
/* (non-Javadoc)
|
||||||
|
|||||||
@@ -26,31 +26,29 @@ import forge.util.collect.FCollectionView;
|
|||||||
public class DiscardAi extends SpellAbilityAi {
|
public class DiscardAi extends SpellAbilityAi {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
protected AiAbilityDecision checkApiLogic(Player ai, SpellAbility sa) {
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
|
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
|
||||||
final Cost abCost = sa.getPayCosts();
|
|
||||||
final String aiLogic = sa.getParamOrDefault("AILogic", "");
|
final String aiLogic = sa.getParamOrDefault("AILogic", "");
|
||||||
|
|
||||||
// temporarily disabled until better AI
|
|
||||||
if (!willPayCosts(ai, sa, abCost, source)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ("Chandra, Flamecaller".equals(sourceName)) {
|
if ("Chandra, Flamecaller".equals(sourceName)) {
|
||||||
final int hand = ai.getCardsIn(ZoneType.Hand).size();
|
final int hand = ai.getCardsIn(ZoneType.Hand).size();
|
||||||
return MyRandom.getRandom().nextFloat() < (1.0 / (1 + hand));
|
if (MyRandom.getRandom().nextFloat() < (1.0 / (1 + hand))) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
} else {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (aiLogic.equals("VolrathsShapeshifter")) {
|
if (aiLogic.equals("VolrathsShapeshifter")) {
|
||||||
return SpecialCardAi.VolrathsShapeshifter.consider(ai, sa);
|
return SpecialCardAi.VolrathsShapeshifter.consider(ai, sa);
|
||||||
}
|
}
|
||||||
|
|
||||||
final boolean humanHasHand = ai.getWeakestOpponent().getCardsIn(ZoneType.Hand).size() > 0;
|
final boolean humanHasHand = !ai.getWeakestOpponent().getCardsIn(ZoneType.Hand).isEmpty();
|
||||||
|
|
||||||
if (sa.usesTargeting()) {
|
if (sa.usesTargeting()) {
|
||||||
if (!discardTargetAI(ai, sa)) {
|
if (!discardTargetAI(ai, sa)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// TODO: Add appropriate restrictions
|
// TODO: Add appropriate restrictions
|
||||||
@@ -64,7 +62,7 @@ public class DiscardAi extends SpellAbilityAi {
|
|||||||
} else {
|
} else {
|
||||||
// defined to the human, so that's fine as long the human has cards
|
// defined to the human, so that's fine as long the human has cards
|
||||||
if (!humanHasHand) {
|
if (!humanHasHand) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -78,12 +76,12 @@ public class DiscardAi extends SpellAbilityAi {
|
|||||||
final int cardsToDiscard = Math.min(ComputerUtilCost.getMaxXValue(sa, ai, sa.isTrigger()), ai.getWeakestOpponent()
|
final int cardsToDiscard = Math.min(ComputerUtilCost.getMaxXValue(sa, ai, sa.isTrigger()), ai.getWeakestOpponent()
|
||||||
.getCardsIn(ZoneType.Hand).size());
|
.getCardsIn(ZoneType.Hand).size());
|
||||||
if (cardsToDiscard < 1) {
|
if (cardsToDiscard < 1) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
sa.setXManaCostPaid(cardsToDiscard);
|
sa.setXManaCostPaid(cardsToDiscard);
|
||||||
} else {
|
} else {
|
||||||
if (AbilityUtils.calculateAmount(source, sa.getParam("NumCards"), sa) < 1) {
|
if (AbilityUtils.calculateAmount(source, sa.getParam("NumCards"), sa) < 1) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -113,7 +111,7 @@ public class DiscardAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (numDiscard == 0) {
|
if (numDiscard == 0) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -121,27 +119,25 @@ public class DiscardAi extends SpellAbilityAi {
|
|||||||
// Don't use discard abilities before main 2 if possible
|
// Don't use discard abilities before main 2 if possible
|
||||||
if (ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2)
|
if (ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2)
|
||||||
&& !sa.hasParam("ActivationPhases") && !aiLogic.startsWith("AnyPhase")) {
|
&& !sa.hasParam("ActivationPhases") && !aiLogic.startsWith("AnyPhase")) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (aiLogic.equals("AnyPhaseIfFavored")) {
|
if (aiLogic.equals("AnyPhaseIfFavored")) {
|
||||||
if (ai.getGame().getCombat() != null) {
|
if (ai.getGame().getCombat() != null) {
|
||||||
if (ai.getCardsIn(ZoneType.Hand).size() < ai.getGame().getCombat().getDefenderPlayerByAttacker(source).getCardsIn(ZoneType.Hand).size()) {
|
if (ai.getCardsIn(ZoneType.Hand).size() < ai.getGame().getCombat().getDefenderPlayerByAttacker(source).getCardsIn(ZoneType.Hand).size()) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean randomReturn = MyRandom.getRandom().nextFloat() <= Math.pow(0.9, sa.getActivationsThisTurn());
|
|
||||||
|
|
||||||
// some other variables here, like handsize vs. maxHandSize
|
// some other variables here, like handsize vs. maxHandSize
|
||||||
|
|
||||||
return randomReturn;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean discardTargetAI(final Player ai, final SpellAbility sa) {
|
private boolean discardTargetAI(final Player ai, final SpellAbility sa) {
|
||||||
@@ -166,7 +162,7 @@ public class DiscardAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
protected AiAbilityDecision doTriggerNoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
if (sa.usesTargeting()) {
|
if (sa.usesTargeting()) {
|
||||||
PlayerCollection targetableOpps = ai.getOpponents().filter(PlayerPredicates.isTargetableBy(sa));
|
PlayerCollection targetableOpps = ai.getOpponents().filter(PlayerPredicates.isTargetableBy(sa));
|
||||||
Player opp = targetableOpps.min(PlayerPredicates.compareByLife());
|
Player opp = targetableOpps.min(PlayerPredicates.compareByLife());
|
||||||
@@ -176,7 +172,7 @@ public class DiscardAi extends SpellAbilityAi {
|
|||||||
} else if (mandatory && sa.canTarget(ai)) {
|
} else if (mandatory && sa.canTarget(ai)) {
|
||||||
sa.getTargets().add(ai);
|
sa.getTargets().add(ai);
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -184,7 +180,7 @@ public class DiscardAi extends SpellAbilityAi {
|
|||||||
if ("AtLeast2".equals(sa.getParam("AILogic"))) {
|
if ("AtLeast2".equals(sa.getParam("AILogic"))) {
|
||||||
final List<Player> players = AbilityUtils.getDefinedPlayers(sa.getHostCard(), sa.getParam("Defined"), sa);
|
final List<Player> players = AbilityUtils.getDefinedPlayers(sa.getHostCard(), sa.getParam("Defined"), sa);
|
||||||
if (players.isEmpty() || players.get(0).getCardsIn(ZoneType.Hand).size() < 2) {
|
if (players.isEmpty() || players.get(0).getCardsIn(ZoneType.Hand).size() < 2) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -196,18 +192,22 @@ public class DiscardAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
public AiAbilityDecision chkDrawback(SpellAbility sa, Player ai) {
|
||||||
// Drawback AI improvements
|
// Drawback AI improvements
|
||||||
// if parent draws cards, make sure cards in hand + cards drawn > 0
|
// if parent draws cards, make sure cards in hand + cards drawn > 0
|
||||||
if (sa.usesTargeting()) {
|
if (sa.usesTargeting()) {
|
||||||
return discardTargetAI(ai, sa);
|
if (discardTargetAI(ai, sa)) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
} else {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// TODO: check for some extra things
|
// TODO: check for some extra things
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message, Map<String, Object> params) {
|
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message, Map<String, Object> params) {
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
import forge.ai.AiPlayDecision;
|
import forge.ai.*;
|
||||||
import forge.ai.ComputerUtil;
|
|
||||||
import forge.ai.PlayerControllerAi;
|
|
||||||
import forge.ai.SpellAbilityAi;
|
|
||||||
import forge.game.ability.AbilityUtils;
|
import forge.game.ability.AbilityUtils;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
@@ -16,12 +13,8 @@ import java.util.Map;
|
|||||||
public class DiscoverAi extends SpellAbilityAi {
|
public class DiscoverAi extends SpellAbilityAi {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
|
protected AiAbilityDecision checkApiLogic(final Player ai, final SpellAbility sa) {
|
||||||
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
return false; // prevent infinite loop
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -36,8 +29,12 @@ public class DiscoverAi extends SpellAbilityAi {
|
|||||||
* @return a boolean.
|
* @return a boolean.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(final Player ai, final SpellAbility sa, final boolean mandatory) {
|
protected AiAbilityDecision doTriggerNoCost(final Player ai, final SpellAbility sa, final boolean mandatory) {
|
||||||
return mandatory || checkApiLogic(ai, sa);
|
if (mandatory) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
}
|
||||||
|
|
||||||
|
return checkApiLogic(ai, sa);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -1,23 +1,23 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
import forge.ai.AiAbilityDecision;
|
||||||
|
import forge.ai.AiPlayDecision;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.ability.AbilityUtils;
|
import forge.game.ability.AbilityUtils;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.util.MyRandom;
|
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class DrainManaAi extends SpellAbilityAi {
|
public class DrainManaAi extends SpellAbilityAi {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
protected AiAbilityDecision checkApiLogic(Player ai, SpellAbility sa) {
|
||||||
// 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 Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
final Player opp = ai.getWeakestOpponent();
|
final Player opp = ai.getWeakestOpponent();
|
||||||
boolean randomReturn = MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
|
|
||||||
|
|
||||||
if (!sa.usesTargeting()) {
|
if (!sa.usesTargeting()) {
|
||||||
// assume we are looking to tap human's stuff
|
// assume we are looking to tap human's stuff
|
||||||
@@ -25,56 +25,58 @@ public class DrainManaAi extends SpellAbilityAi {
|
|||||||
final List<Player> defined = AbilityUtils.getDefinedPlayers(source, sa.getParam("Defined"), sa);
|
final List<Player> defined = AbilityUtils.getDefinedPlayers(source, sa.getParam("Defined"), sa);
|
||||||
|
|
||||||
if (!defined.contains(opp)) {
|
if (!defined.contains(opp)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
sa.getTargets().add(opp);
|
sa.getTargets().add(opp);
|
||||||
}
|
}
|
||||||
|
|
||||||
return randomReturn;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
protected AiAbilityDecision doTriggerNoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
final Player opp = ai.getWeakestOpponent();
|
final Player opp = ai.getWeakestOpponent();
|
||||||
|
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
|
|
||||||
if (!sa.usesTargeting()) {
|
if (!sa.usesTargeting()) {
|
||||||
if (mandatory) {
|
if (mandatory) {
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
} else {
|
} else {
|
||||||
final List<Player> defined = AbilityUtils.getDefinedPlayers(source, sa.getParam("Defined"), sa);
|
final List<Player> defined = AbilityUtils.getDefinedPlayers(source, sa.getParam("Defined"), sa);
|
||||||
|
|
||||||
return defined.contains(opp);
|
if (defined.contains(opp)) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
} else {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
sa.getTargets().add(opp);
|
sa.getTargets().add(opp);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
public AiAbilityDecision chkDrawback(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 Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
|
|
||||||
boolean randomReturn = true;
|
|
||||||
|
|
||||||
if (!sa.usesTargeting()) {
|
if (!sa.usesTargeting()) {
|
||||||
final List<Player> defined = AbilityUtils.getDefinedPlayers(source, sa.getParam("Defined"), sa);
|
final List<Player> defined = AbilityUtils.getDefinedPlayers(source, sa.getParam("Defined"), sa);
|
||||||
|
|
||||||
if (defined.contains(ai)) {
|
if (defined.contains(ai)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
sa.getTargets().add(ai.getWeakestOpponent());
|
sa.getTargets().add(ai.getWeakestOpponent());
|
||||||
}
|
}
|
||||||
|
|
||||||
return randomReturn;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,43 +42,41 @@ public class DrawAi extends SpellAbilityAi {
|
|||||||
* @see forge.ai.SpellAbilityAi#checkApiLogic(forge.game.player.Player, forge.game.spellability.SpellAbility)
|
* @see forge.ai.SpellAbilityAi#checkApiLogic(forge.game.player.Player, forge.game.spellability.SpellAbility)
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected boolean checkApiLogic(Player ai, SpellAbility sa) {
|
protected AiAbilityDecision checkApiLogic(Player ai, SpellAbility sa) {
|
||||||
if (!targetAI(ai, sa, false)) {
|
if (!targetAI(ai, sa, false)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sa.usesTargeting()) {
|
if (sa.usesTargeting()) {
|
||||||
final Player player = sa.getTargets().getFirstTargetedPlayer();
|
final Player player = sa.getTargets().getFirstTargetedPlayer();
|
||||||
if (player != null && player.isOpponentOf(ai)) {
|
if (player != null && player.isOpponentOf(ai)) {
|
||||||
return true;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// prevent run-away activations - first time will always return true
|
|
||||||
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ComputerUtil.playImmediately(ai, sa)) {
|
if (ComputerUtil.playImmediately(ai, sa)) {
|
||||||
return true;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.WaitForCombat);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!canLoot(ai, sa)) {
|
if (!canLoot(ai, sa)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ComputerUtilCost.isSacrificeSelfCost(sa.getPayCosts())) {
|
if (ComputerUtilCost.isSacrificeSelfCost(sa.getPayCosts())) {
|
||||||
// Canopy lands and other cards that sacrifice themselves to draw cards
|
// Canopy lands and other cards that sacrifice themselves to draw cards
|
||||||
return ai.getCardsIn(ZoneType.Hand).isEmpty()
|
if (ai.getCardsIn(ZoneType.Hand).isEmpty()
|
||||||
|| (sa.getHostCard().isLand() && ai.getLandsInPlay().size() >= 5); // TODO: make this configurable in the AI profile
|
|| (sa.getHostCard().isLand() && ai.getLandsInPlay().size() >= 5)) {
|
||||||
|
// TODO: make this configurable in the AI profile
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -161,8 +159,6 @@ public class DrawAi extends SpellAbilityAi {
|
|||||||
// LifeLessThan logic presupposes activation as soon as possible in an
|
// LifeLessThan logic presupposes activation as soon as possible in an
|
||||||
// attempt to save the AI from dying
|
// attempt to save the AI from dying
|
||||||
return true;
|
return true;
|
||||||
} else if (logic.equals("AtOppEOT")) {
|
|
||||||
return ph.is(PhaseType.END_OF_TURN) && ph.getNextTurn().equals(ai);
|
|
||||||
} else if (logic.equals("RespondToOwnActivation")) {
|
} else if (logic.equals("RespondToOwnActivation")) {
|
||||||
return !ai.getGame().getStack().isEmpty() && ai.getGame().getStack().peekAbility().getHostCard().equals(sa.getHostCard());
|
return !ai.getGame().getStack().isEmpty() && ai.getGame().getStack().peekAbility().getHostCard().equals(sa.getHostCard());
|
||||||
} else if ((!ph.getNextTurn().equals(ai) || ph.getPhase().isBefore(PhaseType.END_OF_TURN))
|
} else if ((!ph.getNextTurn().equals(ai) || ph.getPhase().isBefore(PhaseType.END_OF_TURN))
|
||||||
@@ -175,8 +171,12 @@ public class DrawAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
public AiAbilityDecision chkDrawback(SpellAbility sa, Player ai) {
|
||||||
return targetAI(ai, sa, sa.isTrigger() && sa.getHostCard().isInPlay());
|
if (targetAI(ai, sa, sa.isTrigger() && sa.getHostCard().isInPlay())) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
} else {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -534,12 +534,16 @@ public class DrawAi extends SpellAbilityAi {
|
|||||||
} // drawTargetAI()
|
} // drawTargetAI()
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
protected AiAbilityDecision doTriggerNoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
if (!mandatory && !willPayCosts(ai, sa, sa.getPayCosts(), sa.getHostCard())) {
|
if (!mandatory && !willPayCosts(ai, sa, sa.getPayCosts(), sa.getHostCard())) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
return targetAI(ai, sa, mandatory);
|
if (targetAI(ai, sa, mandatory)) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
} else {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* (non-Javadoc)
|
/* (non-Javadoc)
|
||||||
|
|||||||
@@ -23,8 +23,10 @@ import forge.game.replacement.ReplacementType;
|
|||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.spellability.SpellAbilityStackInstance;
|
import forge.game.spellability.SpellAbilityStackInstance;
|
||||||
import forge.game.spellability.TargetRestrictions;
|
import forge.game.spellability.TargetRestrictions;
|
||||||
|
import forge.game.staticability.StaticAbilityMode;
|
||||||
import forge.game.zone.MagicStack;
|
import forge.game.zone.MagicStack;
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
|
import forge.util.FileSection;
|
||||||
import forge.util.MyRandom;
|
import forge.util.MyRandom;
|
||||||
import forge.util.TextUtil;
|
import forge.util.TextUtil;
|
||||||
import forge.util.collect.FCollectionView;
|
import forge.util.collect.FCollectionView;
|
||||||
@@ -35,22 +37,22 @@ import java.util.Map;
|
|||||||
|
|
||||||
public class EffectAi extends SpellAbilityAi {
|
public class EffectAi extends SpellAbilityAi {
|
||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(final Player ai,final SpellAbility sa) {
|
protected AiAbilityDecision checkApiLogic(final Player ai, final SpellAbility sa) {
|
||||||
final Game game = ai.getGame();
|
final Game game = ai.getGame();
|
||||||
|
final PhaseHandler phase = game.getPhaseHandler();
|
||||||
boolean randomReturn = MyRandom.getRandom().nextFloat() <= .6667;
|
boolean randomReturn = MyRandom.getRandom().nextFloat() <= .6667;
|
||||||
String logic = "";
|
String logic = "";
|
||||||
|
|
||||||
if (sa.hasParam("AILogic")) {
|
if (sa.hasParam("AILogic")) {
|
||||||
logic = sa.getParam("AILogic");
|
logic = sa.getParam("AILogic");
|
||||||
final PhaseHandler phase = game.getPhaseHandler();
|
|
||||||
if (logic.equals("BeginningOfOppTurn")) {
|
if (logic.equals("BeginningOfOppTurn")) {
|
||||||
if (!phase.getPlayerTurn().isOpponentOf(ai) || phase.getPhase().isAfter(PhaseType.DRAW)) {
|
if (!phase.getPlayerTurn().isOpponentOf(ai) || phase.getPhase().isAfter(PhaseType.DRAW)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
randomReturn = true;
|
randomReturn = true;
|
||||||
} else if (logic.equals("EndOfOppTurn")) {
|
} else if (logic.equals("EndOfOppTurn")) {
|
||||||
if (!phase.getPlayerTurn().isOpponentOf(ai) || phase.getPhase().isBefore(PhaseType.END_OF_TURN)) {
|
if (!phase.getPlayerTurn().isOpponentOf(ai) || phase.getPhase().isBefore(PhaseType.END_OF_TURN)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
randomReturn = true;
|
randomReturn = true;
|
||||||
} else if (logic.equals("KeepOppCreatsLandsTapped")) {
|
} else if (logic.equals("KeepOppCreatsLandsTapped")) {
|
||||||
@@ -64,20 +66,20 @@ public class EffectAi extends SpellAbilityAi {
|
|||||||
worthHolding = true;
|
worthHolding = true;
|
||||||
}
|
}
|
||||||
if (!worthHolding) {
|
if (!worthHolding) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
randomReturn = true;
|
randomReturn = true;
|
||||||
}
|
}
|
||||||
} else if (logic.equals("RestrictBlocking")) {
|
} else if (logic.equals("RestrictBlocking")) {
|
||||||
if (!phase.isPlayerTurn(ai) || phase.getPhase().isBefore(PhaseType.COMBAT_BEGIN)
|
if (!phase.isPlayerTurn(ai) || phase.getPhase().isBefore(PhaseType.COMBAT_BEGIN)
|
||||||
|| phase.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)) {
|
|| phase.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sa.getPayCosts().getTotalMana().countX() > 0 && sa.getHostCard().getSVar("X").equals("Count$xPaid")) {
|
if (sa.getPayCosts().getTotalMana().countX() > 0 && sa.getHostCard().getSVar("X").equals("Count$xPaid")) {
|
||||||
// Set PayX here to half the remaining mana to allow for Main 2 and other combat shenanigans.
|
// Set PayX here to half the remaining mana to allow for Main 2 and other combat shenanigans.
|
||||||
final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai, sa.isTrigger()) / 2;
|
final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai, sa.isTrigger()) / 2;
|
||||||
if (xPay == 0) { return false; }
|
if (xPay == 0) { return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi); }
|
||||||
sa.setXManaCostPaid(xPay);
|
sa.setXManaCostPaid(xPay);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,23 +92,27 @@ public class EffectAi extends SpellAbilityAi {
|
|||||||
int potentialDmg = 0;
|
int potentialDmg = 0;
|
||||||
List<Card> currentAttackers = new ArrayList<>();
|
List<Card> currentAttackers = new ArrayList<>();
|
||||||
|
|
||||||
if (possibleBlockers.isEmpty()) { return false; }
|
if (possibleBlockers.isEmpty()) { return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi); }
|
||||||
|
|
||||||
for (final Card creat : possibleAttackers) {
|
for (final Card creat : possibleAttackers) {
|
||||||
if (CombatUtil.canAttack(creat, opp) && possibleBlockers.size() > 1) {
|
if (CombatUtil.canAttack(creat, opp) && possibleBlockers.size() > 1) {
|
||||||
potentialDmg += creat.getCurrentPower();
|
potentialDmg += creat.getCurrentPower();
|
||||||
if (potentialDmg >= oppLife) { return true; }
|
if (potentialDmg >= oppLife) { return new AiAbilityDecision(100, AiPlayDecision.WillPlay); }
|
||||||
}
|
}
|
||||||
if (combat != null && combat.isAttacking(creat)) {
|
if (combat != null && combat.isAttacking(creat)) {
|
||||||
currentAttackers.add(creat);
|
currentAttackers.add(creat);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return currentAttackers.size() > possibleBlockers.size();
|
if (currentAttackers.size() > possibleBlockers.size()) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
} else {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
|
}
|
||||||
} else if (logic.equals("Fog")) {
|
} else if (logic.equals("Fog")) {
|
||||||
FogAi fogAi = new FogAi();
|
FogAi fogAi = new FogAi();
|
||||||
if (!fogAi.canPlayAI(ai, sa)) {
|
if (!fogAi.canPlay(ai, sa).willingToPlay()) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
@@ -124,14 +130,14 @@ public class EffectAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!canTgt) {
|
if (!canTgt) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
List<Card> list = game.getCombat().getAttackers();
|
List<Card> list = game.getCombat().getAttackers();
|
||||||
list = CardLists.getTargetableCards(list, sa);
|
list = CardLists.getTargetableCards(list, sa);
|
||||||
Card target = ComputerUtilCard.getBestCreatureAI(list);
|
Card target = ComputerUtilCard.getBestCreatureAI(list);
|
||||||
if (target == null) {
|
if (target == null) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
sa.getTargets().add(target);
|
sa.getTargets().add(target);
|
||||||
}
|
}
|
||||||
@@ -139,7 +145,7 @@ public class EffectAi extends SpellAbilityAi {
|
|||||||
randomReturn = true;
|
randomReturn = true;
|
||||||
} else if (logic.equals("ChainVeil")) {
|
} else if (logic.equals("ChainVeil")) {
|
||||||
if (!phase.isPlayerTurn(ai) || !phase.getPhase().equals(PhaseType.MAIN2) || ai.getPlaneswalkersInPlay().isEmpty()) {
|
if (!phase.isPlayerTurn(ai) || !phase.getPhase().equals(PhaseType.MAIN2) || ai.getPlaneswalkersInPlay().isEmpty()) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
randomReturn = true;
|
randomReturn = true;
|
||||||
} else if (logic.equals("WillCastCreature") && ai.isAI()) {
|
} else if (logic.equals("WillCastCreature") && ai.isAI()) {
|
||||||
@@ -150,17 +156,17 @@ public class EffectAi extends SpellAbilityAi {
|
|||||||
randomReturn = true;
|
randomReturn = true;
|
||||||
} else if (logic.equals("Main1")) {
|
} else if (logic.equals("Main1")) {
|
||||||
if (phase.getPhase().isBefore(PhaseType.MAIN1)) {
|
if (phase.getPhase().isBefore(PhaseType.MAIN1)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
randomReturn = true;
|
randomReturn = true;
|
||||||
} else if (logic.equals("Main2")) {
|
} else if (logic.equals("Main2")) {
|
||||||
if (phase.getPhase().isBefore(PhaseType.MAIN2)) {
|
if (phase.getPhase().isBefore(PhaseType.MAIN2)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
randomReturn = true;
|
randomReturn = true;
|
||||||
} else if (logic.equals("Evasion")) {
|
} else if (logic.equals("Evasion")) {
|
||||||
if (!phase.isPlayerTurn(ai)) {
|
if (!phase.isPlayerTurn(ai)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean shouldPlay = false;
|
boolean shouldPlay = false;
|
||||||
@@ -185,10 +191,10 @@ public class EffectAi extends SpellAbilityAi {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return shouldPlay;
|
return shouldPlay ? new AiAbilityDecision(100, AiPlayDecision.WillPlay) : new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
} else if (logic.equals("RedirectSpellDamageFromPlayer")) {
|
} else if (logic.equals("RedirectSpellDamageFromPlayer")) {
|
||||||
if (game.getStack().isEmpty()) {
|
if (game.getStack().isEmpty()) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
boolean threatened = false;
|
boolean threatened = false;
|
||||||
for (final SpellAbilityStackInstance stackInst : game.getStack()) {
|
for (final SpellAbilityStackInstance stackInst : game.getStack()) {
|
||||||
@@ -204,7 +210,7 @@ public class EffectAi extends SpellAbilityAi {
|
|||||||
randomReturn = threatened;
|
randomReturn = threatened;
|
||||||
} else if (logic.equals("Prevent")) { // prevent burn spell from opponent
|
} else if (logic.equals("Prevent")) { // prevent burn spell from opponent
|
||||||
if (game.getStack().isEmpty()) {
|
if (game.getStack().isEmpty()) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
final SpellAbility saTop = game.getStack().peekAbility();
|
final SpellAbility saTop = game.getStack().peekAbility();
|
||||||
final Card host = saTop.getHostCard();
|
final Card host = saTop.getHostCard();
|
||||||
@@ -215,10 +221,10 @@ public class EffectAi extends SpellAbilityAi {
|
|||||||
final ApiType type = saTop.getApi();
|
final ApiType type = saTop.getApi();
|
||||||
if (type == ApiType.DealDamage || type == ApiType.DamageAll) { // burn spell
|
if (type == ApiType.DealDamage || type == ApiType.DamageAll) { // burn spell
|
||||||
sa.getTargets().add(saTop);
|
sa.getTargets().add(saTop);
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
} else if (logic.equals("NoGain")) {
|
} else if (logic.equals("NoGain")) {
|
||||||
// basic logic to cancel GainLife on stack
|
// basic logic to cancel GainLife on stack
|
||||||
if (!game.getStack().isEmpty()) {
|
if (!game.getStack().isEmpty()) {
|
||||||
@@ -228,14 +234,14 @@ public class EffectAi extends SpellAbilityAi {
|
|||||||
while (topStack != null) {
|
while (topStack != null) {
|
||||||
if (topStack.getApi() == ApiType.GainLife) {
|
if (topStack.getApi() == ApiType.GainLife) {
|
||||||
if ("You".equals(topStack.getParam("Defined")) || topStack.isTargeting(activator) || (!topStack.usesTargeting() && !topStack.hasParam("Defined"))) {
|
if ("You".equals(topStack.getParam("Defined")) || topStack.isTargeting(activator) || (!topStack.usesTargeting() && !topStack.hasParam("Defined"))) {
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
} else if (topStack.getApi() == ApiType.DealDamage && topStack.getHostCard().hasKeyword(Keyword.LIFELINK)) {
|
} else if (topStack.getApi() == ApiType.DealDamage && topStack.getHostCard().hasKeyword(Keyword.LIFELINK)) {
|
||||||
Card host = topStack.getHostCard();
|
Card host = topStack.getHostCard();
|
||||||
for (GameEntity target : topStack.getTargets().getTargetEntities()) {
|
for (GameEntity target : topStack.getTargets().getTargetEntities()) {
|
||||||
if (ComputerUtilCombat.predictDamageTo(target,
|
if (ComputerUtilCombat.predictDamageTo(target,
|
||||||
AbilityUtils.calculateAmount(host, topStack.getParam("NumDmg"), topStack), host, false) > 0) {
|
AbilityUtils.calculateAmount(host, topStack.getParam("NumDmg"), topStack), host, false) > 0) {
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -249,11 +255,11 @@ public class EffectAi extends SpellAbilityAi {
|
|||||||
final Player attackingPlayer = combat.getAttackingPlayer();
|
final Player attackingPlayer = combat.getAttackingPlayer();
|
||||||
if (attackingPlayer.isOpponentOf(ai) && attackingPlayer.canGainLife()) {
|
if (attackingPlayer.isOpponentOf(ai) && attackingPlayer.canGainLife()) {
|
||||||
if (ComputerUtilCombat.checkAttackerLifelinkDamage(combat) > 0) {
|
if (ComputerUtilCombat.checkAttackerLifelinkDamage(combat) > 0) {
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
} else if (logic.equals("NonCastCreature")) {
|
} else if (logic.equals("NonCastCreature")) {
|
||||||
// TODO: add support for more cases with more convoluted API setups
|
// TODO: add support for more cases with more convoluted API setups
|
||||||
if (!game.getStack().isEmpty()) {
|
if (!game.getStack().isEmpty()) {
|
||||||
@@ -265,13 +271,13 @@ public class EffectAi extends SpellAbilityAi {
|
|||||||
boolean reanimator = "true".equalsIgnoreCase(topStack.getSVar("IsReanimatorCard"));
|
boolean reanimator = "true".equalsIgnoreCase(topStack.getSVar("IsReanimatorCard"));
|
||||||
if (changeZone && (toBattlefield || reanimator)) {
|
if (changeZone && (toBattlefield || reanimator)) {
|
||||||
if ("Creature".equals(topStack.getParam("ChangeType")) || topStack.getParamOrDefault("Defined", "").contains("Creature"))
|
if ("Creature".equals(topStack.getParam("ChangeType")) || topStack.getParamOrDefault("Defined", "").contains("Creature"))
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
} else if (logic.equals("Fight")) {
|
} else if (logic.equals("Fight")) {
|
||||||
return FightAi.canFightAi(ai, sa, 0, 0);
|
return FightAi.canFightAi(ai, sa, 0,0);
|
||||||
} else if (logic.equals("Pump")) {
|
} else if (logic.equals("Pump")) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
List<Card> options = CardUtil.getValidCardsToTarget(sa);
|
List<Card> options = CardUtil.getValidCardsToTarget(sa);
|
||||||
@@ -281,45 +287,44 @@ public class EffectAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
if (!options.isEmpty() && phase.isPlayerTurn(ai) && phase.getPhase().isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
if (!options.isEmpty() && phase.isPlayerTurn(ai) && phase.getPhase().isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
||||||
sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(options));
|
sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(options));
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
} else if (logic.equals("Burn")) {
|
} else if (logic.equals("Burn")) {
|
||||||
// for DamageDeal sub-abilities (eg. Wild Slash, Skullcrack)
|
|
||||||
SpellAbility burn = sa.getSubAbility();
|
SpellAbility burn = sa.getSubAbility();
|
||||||
return SpellApiToAi.Converter.get(burn).canPlayAIWithSubs(ai, burn);
|
return SpellApiToAi.Converter.get(burn).canPlayWithSubs(ai, burn).willingToPlay() ? new AiAbilityDecision(100, AiPlayDecision.WillPlay) : new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
} else if (logic.equals("YawgmothsWill")) {
|
} else if (logic.equals("YawgmothsWill")) {
|
||||||
return SpecialCardAi.YawgmothsWill.consider(ai, sa);
|
return SpecialCardAi.YawgmothsWill.consider(ai, sa) ? new AiAbilityDecision(100, AiPlayDecision.WillPlay) : new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
} else if (logic.startsWith("NeedCreatures")) {
|
} else if (logic.startsWith("NeedCreatures")) {
|
||||||
if (ai.getCreaturesInPlay().isEmpty()) {
|
if (ai.getCreaturesInPlay().isEmpty()) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
if (logic.contains(":")) {
|
if (logic.contains(":")) {
|
||||||
String[] k = logic.split(":");
|
String[] k = logic.split(":");
|
||||||
int i = Integer.parseInt(k[1]);
|
int i = Integer.parseInt(k[1]);
|
||||||
return ai.getCreaturesInPlay().size() >= i;
|
return ai.getCreaturesInPlay().size() >= i ? new AiAbilityDecision(100, AiPlayDecision.WillPlay) : new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
} else if (logic.equals("ReplaySpell")) {
|
} else if (logic.equals("ReplaySpell")) {
|
||||||
CardCollection list = CardLists.getValidCards(game.getCardsIn(ZoneType.Graveyard), sa.getTargetRestrictions().getValidTgts(), ai, sa.getHostCard(), sa);
|
CardCollection list = CardLists.getValidCards(game.getCardsIn(ZoneType.Graveyard), sa.getTargetRestrictions().getValidTgts(), ai, sa.getHostCard(), sa);
|
||||||
if (!ComputerUtil.targetPlayableSpellCard(ai, list, sa, false, false)) {
|
if (!ComputerUtil.targetPlayableSpellCard(ai, list, sa, false, false)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
} else if (logic.equals("PeaceTalks")) {
|
} else if (logic.equals("PeaceTalks")) {
|
||||||
Player nextPlayer = game.getNextPlayerAfter(ai);
|
Player nextPlayer = game.getNextPlayerAfter(ai);
|
||||||
|
|
||||||
// If opponent doesn't have creatures, preventing attacks don't mean as much
|
// If opponent doesn't have creatures, preventing attacks don't mean as much
|
||||||
if (nextPlayer.getCreaturesInPlay().isEmpty()) {
|
if (nextPlayer.getCreaturesInPlay().isEmpty()) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only cast Peace Talks after you attack just in case you have creatures
|
// Only cast Peace Talks after you attack just in case you have creatures
|
||||||
if (!phase.is(PhaseType.MAIN2)) {
|
if (!phase.is(PhaseType.MAIN2)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a pseudo combat and see if my life is in danger
|
// Create a pseudo combat and see if my life is in danger
|
||||||
return randomReturn;
|
return randomReturn ? new AiAbilityDecision(100, AiPlayDecision.WillPlay) : new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
} else if (logic.equals("Bribe")) {
|
} else if (logic.equals("Bribe")) {
|
||||||
Card host = sa.getHostCard();
|
Card host = sa.getHostCard();
|
||||||
Combat combat = game.getCombat();
|
Combat combat = game.getCombat();
|
||||||
@@ -327,9 +332,9 @@ public class EffectAi extends SpellAbilityAi {
|
|||||||
&& phase.is(PhaseType.COMBAT_DECLARE_BLOCKERS)
|
&& phase.is(PhaseType.COMBAT_DECLARE_BLOCKERS)
|
||||||
&& !AiCardMemory.isRememberedCard(ai, host, AiCardMemory.MemorySet.ACTIVATED_THIS_TURN)) {
|
&& !AiCardMemory.isRememberedCard(ai, host, AiCardMemory.MemorySet.ACTIVATED_THIS_TURN)) {
|
||||||
AiCardMemory.rememberCard(ai, host, AiCardMemory.MemorySet.ACTIVATED_THIS_TURN); // ideally needs once per combat or something
|
AiCardMemory.rememberCard(ai, host, AiCardMemory.MemorySet.ACTIVATED_THIS_TURN); // ideally needs once per combat or something
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
} else if (logic.equals("CantRegenerate")) {
|
} else if (logic.equals("CantRegenerate")) {
|
||||||
if (sa.usesTargeting()) {
|
if (sa.usesTargeting()) {
|
||||||
CardCollection list = CardLists.getTargetableCards(ai.getOpponents().getCardsIn(ZoneType.Battlefield), sa);
|
CardCollection list = CardLists.getTargetableCards(ai.getOpponents().getCardsIn(ZoneType.Battlefield), sa);
|
||||||
@@ -350,19 +355,19 @@ public class EffectAi extends SpellAbilityAi {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (list.isEmpty()) {
|
if (list.isEmpty()) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
// TODO check Stack for Effects that would destroy the selected card?
|
// TODO check Stack for Effects that would destroy the selected card?
|
||||||
sa.getTargets().add(ComputerUtilCard.getBestAI(list));
|
sa.getTargets().add(ComputerUtilCard.getBestAI(list));
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
} else if (sa.getParent() != null) {
|
} else if (sa.getParent() != null) {
|
||||||
// sub ability should be okay
|
// sub ability should be okay
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
} else if ("Self".equals(sa.getParam("RememberObjects"))) {
|
} else if ("Self".equals(sa.getParam("RememberObjects"))) {
|
||||||
// the ones affecting itself are Nimbus cards, were opponent can activate this effect
|
// the ones affecting itself are Nimbus cards, were opponent can activate this effect
|
||||||
Card host = sa.getHostCard();
|
Card host = sa.getHostCard();
|
||||||
if (!host.canBeDestroyed()) {
|
if (!host.canBeDestroyed()) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<AbilityKey, Object> runParams = AbilityKey.mapFromAffected(sa.getHostCard());
|
Map<AbilityKey, Object> runParams = AbilityKey.mapFromAffected(sa.getHostCard());
|
||||||
@@ -370,18 +375,130 @@ public class EffectAi extends SpellAbilityAi {
|
|||||||
List<ReplacementEffect> repDestroyList = game.getReplacementHandler().getReplacementList(ReplacementType.Destroy, runParams, ReplacementLayer.Other);
|
List<ReplacementEffect> repDestroyList = game.getReplacementHandler().getReplacementList(ReplacementType.Destroy, runParams, ReplacementLayer.Other);
|
||||||
// no Destroy Replacement, or one non-Regeneration one like Totem-Armor
|
// no Destroy Replacement, or one non-Regeneration one like Totem-Armor
|
||||||
if (repDestroyList.isEmpty() || repDestroyList.stream().anyMatch(CardTraitPredicates.hasParam("Regeneration").negate())) {
|
if (repDestroyList.isEmpty() || repDestroyList.stream().anyMatch(CardTraitPredicates.hasParam("Regeneration").negate())) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cantRegenerateCheckCombat(host) || cantRegenerateCheckStack(host)) {
|
if (cantRegenerateCheckCombat(host) || cantRegenerateCheckStack(host)) {
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if (sa.hasParam("RememberObjects")) { //generic
|
||||||
|
boolean cantAttack = false;
|
||||||
|
boolean cantBlock = false;
|
||||||
|
boolean cantActivate = false;
|
||||||
|
|
||||||
|
String duraction = sa.getParam("Duration");
|
||||||
|
|
||||||
|
String matchStr = "Card.IsRemembered";
|
||||||
|
|
||||||
|
for (String st : sa.getParam("StaticAbilities").split(",")) {
|
||||||
|
Map<String, String> params = FileSection.parseToMap(sa.getSVar(st), FileSection.DOLLAR_SIGN_KV_SEPARATOR);
|
||||||
|
List<StaticAbilityMode> modes = StaticAbilityMode.listValueOf(params.get("Mode"));
|
||||||
|
|
||||||
|
if (modes.contains(StaticAbilityMode.CantAttack) && matchStr.equals(params.get("ValidCard"))) {
|
||||||
|
cantAttack = true;
|
||||||
|
}
|
||||||
|
if (modes.contains(StaticAbilityMode.CantBlock) && matchStr.equals(params.get("ValidCard"))) {
|
||||||
|
cantBlock = true;
|
||||||
|
}
|
||||||
|
if (modes.contains(StaticAbilityMode.CantBlockBy) && matchStr.equals(params.get("ValidBlocker"))) {
|
||||||
|
cantBlock = true;
|
||||||
|
}
|
||||||
|
if (modes.contains(StaticAbilityMode.CantBeActivated) && matchStr.equals(params.get("ValidCard"))) {
|
||||||
|
cantActivate = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO add more cases later
|
||||||
|
if (!cantAttack && !cantBlock && !cantActivate) {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cantBlock && duraction == null && phase.isPlayerTurn(ai) && !phase.getPhase().isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sa.usesTargeting()) {
|
||||||
|
final Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
|
||||||
|
|
||||||
|
CardCollection list = new CardCollection(CardUtil.getValidCardsToTarget(sa));
|
||||||
|
|
||||||
|
list = ComputerUtil.filterAITgts(sa, ai, list, true);
|
||||||
|
|
||||||
|
if (list.isEmpty()) {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Card> oppCreatures = CardLists.filterAsList(list, c -> {
|
||||||
|
return c.isCreature() && c.getController().isOpponentOf(ai);
|
||||||
|
});
|
||||||
|
|
||||||
|
List<Card> oppWithAbilities = CardLists.filterAsList(list, c -> {
|
||||||
|
return !c.isCreature() && c.getController().isOpponentOf(ai) && c.getSpellAbilities().anyMatch(SpellAbility::isActivatedAbility);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (cantAttack || cantBlock) {
|
||||||
|
if (oppCreatures.isEmpty()) {
|
||||||
|
if (!cantActivate || oppWithAbilities.isEmpty()) {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
while (sa.canAddMoreTarget()) {
|
||||||
|
Card choice = null;
|
||||||
|
if (cantAttack && cantBlock && !oppCreatures.isEmpty()) {
|
||||||
|
Card primeTarget = ComputerUtil.getKilledByTargeting(sa, oppCreatures);
|
||||||
|
if (primeTarget != null) {
|
||||||
|
choice = primeTarget;
|
||||||
|
} else if (phase.isPlayerTurn(ai) && phase.getPhase().isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
||||||
|
// Tap creatures possible blockers before combat during AI's turn.
|
||||||
|
List<Card> attackers;
|
||||||
|
if (phase.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)) {
|
||||||
|
//Combat has already started
|
||||||
|
attackers = game.getCombat().getAttackers();
|
||||||
|
} else {
|
||||||
|
attackers = CardLists.filter(ai.getCreaturesInPlay(), c -> CombatUtil.canAttack(c, opp));
|
||||||
|
}
|
||||||
|
List<Card> creatureList = CardLists.filter(list, CardPredicates.possibleBlockerForAtLeastOne(attackers));
|
||||||
|
|
||||||
|
// TODO check if own creature would be forced to attack and we want to keep it alive
|
||||||
|
|
||||||
|
if (!attackers.isEmpty() && !creatureList.isEmpty()) {
|
||||||
|
choice = ComputerUtilCard.getBestCreatureAI(creatureList);
|
||||||
|
} else if (sa.isTrigger() || ComputerUtil.castSpellInMain1(ai, sa)) {
|
||||||
|
choice = ComputerUtilCard.getMostExpensivePermanentAI(list);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // TODO add logic to tap non creatures with activated abilities if cantActivate is true
|
||||||
|
|
||||||
|
if (choice == null) { // can't find anything left
|
||||||
|
if (!sa.isMinTargetChosen() || sa.isZeroTargets()) {
|
||||||
|
sa.resetTargets();
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
|
} else {
|
||||||
|
if (!ComputerUtil.shouldCastLessThanMax(ai, sa.getHostCard())) {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
list.remove(choice);
|
||||||
|
oppCreatures.remove(choice);
|
||||||
|
sa.getTargets().add(choice);
|
||||||
|
}
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
} else { //no AILogic
|
} else { //no AILogic
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ("False".equals(sa.getParam("Stackable"))) {
|
if ("False".equals(sa.getParam("Stackable"))) {
|
||||||
@@ -390,7 +507,7 @@ public class EffectAi extends SpellAbilityAi {
|
|||||||
name = sa.getHostCard().getName() + "'s Effect";
|
name = sa.getHostCard().getName() + "'s Effect";
|
||||||
}
|
}
|
||||||
if (sa.getActivatingPlayer().isCardInCommand(name)) {
|
if (sa.getActivatingPlayer().isCardInCommand(name)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -406,20 +523,20 @@ public class EffectAi extends SpellAbilityAi {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return canTgt;
|
return canTgt ? new AiAbilityDecision(100, AiPlayDecision.WillPlay) : new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
} else {
|
} else {
|
||||||
sa.getTargets().add(ai);
|
sa.getTargets().add(ai);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return randomReturn;
|
return randomReturn ? new AiAbilityDecision(100, AiPlayDecision.WillPlay) : new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(final Player aiPlayer, final SpellAbility sa, final boolean mandatory) {
|
protected AiAbilityDecision doTriggerNoCost(final Player aiPlayer, final SpellAbility sa, final boolean mandatory) {
|
||||||
if (sa.hasParam("AILogic")) {
|
if (sa.hasParam("AILogic")) {
|
||||||
if (canPlayAI(aiPlayer, sa)) {
|
if (canPlay(aiPlayer, sa).willingToPlay()) {
|
||||||
return true; // if false, fall through further to do the mandatory stuff
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -431,7 +548,7 @@ public class EffectAi extends SpellAbilityAi {
|
|||||||
if (!oppPerms.isEmpty()) {
|
if (!oppPerms.isEmpty()) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
sa.getTargets().add(ComputerUtilCard.getBestAI(oppPerms));
|
sa.getTargets().add(ComputerUtilCard.getBestAI(oppPerms));
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mandatory) {
|
if (mandatory) {
|
||||||
@@ -441,14 +558,14 @@ public class EffectAi extends SpellAbilityAi {
|
|||||||
if (!aiPerms.isEmpty()) {
|
if (!aiPerms.isEmpty()) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
sa.getTargets().add(ComputerUtilCard.getWorstAI(aiPerms));
|
sa.getTargets().add(ComputerUtilCard.getWorstAI(aiPerms));
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
return super.doTriggerAINoCost(aiPlayer, sa, mandatory);
|
return super.doTriggerNoCost(aiPlayer, sa, mandatory);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected boolean cantRegenerateCheckCombat(Card host) {
|
protected boolean cantRegenerateCheckCombat(Card host) {
|
||||||
|
|||||||
@@ -17,9 +17,7 @@
|
|||||||
*/
|
*/
|
||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
import forge.ai.ComputerUtilCard;
|
import forge.ai.*;
|
||||||
import forge.ai.ComputerUtilCombat;
|
|
||||||
import forge.ai.SpellAbilityAi;
|
|
||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
import forge.game.card.CardLists;
|
import forge.game.card.CardLists;
|
||||||
import forge.game.combat.CombatUtil;
|
import forge.game.combat.CombatUtil;
|
||||||
@@ -45,19 +43,17 @@ public final class EncodeAi extends SpellAbilityAi {
|
|||||||
* </p>
|
* </p>
|
||||||
* @param sa
|
* @param sa
|
||||||
* a {@link forge.game.spellability.SpellAbility} object.
|
* a {@link forge.game.spellability.SpellAbility} object.
|
||||||
* @param af
|
|
||||||
* a {@link forge.game.ability.AbilityFactory} object.
|
|
||||||
*
|
*
|
||||||
* @return a boolean.
|
* @return a boolean.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
protected AiAbilityDecision canPlay(Player aiPlayer, SpellAbility sa) {
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
public AiAbilityDecision chkDrawback(SpellAbility sa, Player ai) {
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
|
||||||
|
import forge.ai.AiAbilityDecision;
|
||||||
|
import forge.ai.AiPlayDecision;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
@@ -12,18 +14,22 @@ import forge.game.spellability.SpellAbility;
|
|||||||
public class EndTurnAi extends SpellAbilityAi {
|
public class EndTurnAi extends SpellAbilityAi {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
protected AiAbilityDecision doTriggerNoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
||||||
return mandatory;
|
if (mandatory) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
} else {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) { return false; }
|
public AiAbilityDecision chkDrawback(SpellAbility sa, Player aiPlayer) { return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi); }
|
||||||
|
|
||||||
/* (non-Javadoc)
|
/* (non-Javadoc)
|
||||||
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
|
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
protected AiAbilityDecision canPlay(Player aiPlayer, SpellAbility sa) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ package forge.ai.ability;
|
|||||||
|
|
||||||
import com.google.common.collect.Sets;
|
import com.google.common.collect.Sets;
|
||||||
|
|
||||||
|
import forge.ai.AiAbilityDecision;
|
||||||
|
import forge.ai.AiPlayDecision;
|
||||||
import forge.ai.ComputerUtilCard;
|
import forge.ai.ComputerUtilCard;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.ability.AbilityUtils;
|
import forge.game.ability.AbilityUtils;
|
||||||
@@ -22,19 +24,19 @@ public class EndureAi extends SpellAbilityAi {
|
|||||||
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
|
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
protected AiAbilityDecision canPlay(Player aiPlayer, SpellAbility sa) {
|
||||||
// Support for possible targeted Endure (e.g. target creature endures X)
|
// Support for possible targeted Endure (e.g. target creature endures X)
|
||||||
if (sa.usesTargeting()) {
|
if (sa.usesTargeting()) {
|
||||||
Card bestCreature = ComputerUtilCard.getBestCreatureAI(aiPlayer.getCardsIn(ZoneType.Battlefield));
|
Card bestCreature = ComputerUtilCard.getBestCreatureAI(aiPlayer.getCardsIn(ZoneType.Battlefield));
|
||||||
if (bestCreature == null) {
|
if (bestCreature == null) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
sa.getTargets().add(bestCreature);
|
sa.getTargets().add(bestCreature);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean shouldPutCounters(Player ai, SpellAbility sa) {
|
public static boolean shouldPutCounters(Player ai, SpellAbility sa) {
|
||||||
@@ -121,7 +123,7 @@ public class EndureAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
protected AiAbilityDecision doTriggerNoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
||||||
// Support for possible targeted Endure (e.g. target creature endures X)
|
// Support for possible targeted Endure (e.g. target creature endures X)
|
||||||
if (sa.usesTargeting()) {
|
if (sa.usesTargeting()) {
|
||||||
CardCollection list = CardLists.getValidCards(aiPlayer.getGame().getCardsIn(ZoneType.Battlefield),
|
CardCollection list = CardLists.getValidCards(aiPlayer.getGame().getCardsIn(ZoneType.Battlefield),
|
||||||
@@ -129,12 +131,16 @@ public class EndureAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
if (!list.isEmpty()) {
|
if (!list.isEmpty()) {
|
||||||
sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(list));
|
sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(list));
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
return canPlayAI(aiPlayer, sa) || mandatory;
|
if (mandatory) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
}
|
||||||
|
|
||||||
|
return canPlay(aiPlayer, sa);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,19 +15,19 @@ public class ExploreAi extends SpellAbilityAi {
|
|||||||
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
|
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
protected AiAbilityDecision canPlay(Player aiPlayer, SpellAbility sa) {
|
||||||
// Explore with a target (e.g. Enter the Unknown)
|
// Explore with a target (e.g. Enter the Unknown)
|
||||||
if (sa.usesTargeting()) {
|
if (sa.usesTargeting()) {
|
||||||
Card bestCreature = ComputerUtilCard.getBestCreatureAI(aiPlayer.getCardsIn(ZoneType.Battlefield));
|
Card bestCreature = ComputerUtilCard.getBestCreatureAI(aiPlayer.getCardsIn(ZoneType.Battlefield));
|
||||||
if (bestCreature == null) {
|
if (bestCreature == null) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
sa.getTargets().add(bestCreature);
|
sa.getTargets().add(bestCreature);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean shouldPutInGraveyard(Card topCard, Player ai) {
|
public static boolean shouldPutInGraveyard(Card topCard, Player ai) {
|
||||||
@@ -64,19 +64,23 @@ public class ExploreAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
protected AiAbilityDecision doTriggerNoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
||||||
if (sa.usesTargeting()) {
|
if (sa.usesTargeting()) {
|
||||||
CardCollection list = CardLists.getValidCards(aiPlayer.getGame().getCardsIn(ZoneType.Battlefield),
|
CardCollection list = CardLists.getValidCards(aiPlayer.getGame().getCardsIn(ZoneType.Battlefield),
|
||||||
sa.getTargetRestrictions().getValidTgts(), aiPlayer, sa.getHostCard(), sa);
|
sa.getTargetRestrictions().getValidTgts(), aiPlayer, sa.getHostCard(), sa);
|
||||||
|
|
||||||
if (!list.isEmpty()) {
|
if (!list.isEmpty()) {
|
||||||
sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(list));
|
sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(list));
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
|
|
||||||
return canPlayAI(aiPlayer, sa) || mandatory;
|
if (mandatory) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
}
|
||||||
|
|
||||||
|
return canPlay(aiPlayer, sa);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,13 +24,13 @@ public class FightAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
|
protected AiAbilityDecision checkApiLogic(final Player ai, final SpellAbility sa) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
|
|
||||||
// everything is defined or targeted above, can't do anything there unless a specific logic is set
|
// everything is defined or targeted above, can't do anything there unless a specific logic is set
|
||||||
if (sa.hasParam("Defined") && !sa.usesTargeting()) {
|
if (sa.hasParam("Defined") && !sa.usesTargeting()) {
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get creature lists
|
// Get creature lists
|
||||||
@@ -42,8 +42,10 @@ public class FightAi extends SpellAbilityAi {
|
|||||||
// Filter MustTarget requirements
|
// Filter MustTarget requirements
|
||||||
StaticAbilityMustTarget.filterMustTargetCards(ai, humCreatures, sa);
|
StaticAbilityMustTarget.filterMustTargetCards(ai, humCreatures, sa);
|
||||||
|
|
||||||
if (humCreatures.isEmpty())
|
//prevent IndexOutOfBoundsException on MOJHOSTO variant
|
||||||
return false; //prevent IndexOutOfBoundsException on MOJHOSTO variant
|
if (humCreatures.isEmpty()) {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.MissingNeededCards);
|
||||||
|
}
|
||||||
|
|
||||||
// assumes the triggered card belongs to the ai
|
// assumes the triggered card belongs to the ai
|
||||||
if (sa.hasParam("Defined")) {
|
if (sa.hasParam("Defined")) {
|
||||||
@@ -54,7 +56,7 @@ public class FightAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (fighter1List.isEmpty()) {
|
if (fighter1List.isEmpty()) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.MissingNeededCards);
|
||||||
}
|
}
|
||||||
Card fighter1 = fighter1List.get(0);
|
Card fighter1 = fighter1List.get(0);
|
||||||
for (Card humanCreature : humCreatures) {
|
for (Card humanCreature : humCreatures) {
|
||||||
@@ -62,10 +64,11 @@ public class FightAi extends SpellAbilityAi {
|
|||||||
&& !canKill(humanCreature, fighter1, 0)) {
|
&& !canKill(humanCreature, fighter1, 0)) {
|
||||||
// todo: check min/max targets; see if we picked the best matchup
|
// todo: check min/max targets; see if we picked the best matchup
|
||||||
sa.getTargets().add(humanCreature);
|
sa.getTargets().add(humanCreature);
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false; // bail at this point, otherwise the AI will overtarget and waste the activation
|
// bail at this point, otherwise the AI will overtarget and waste the activation
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sa.hasParam("TargetsFromDifferentZone")) {
|
if (sa.hasParam("TargetsFromDifferentZone")) {
|
||||||
@@ -77,12 +80,12 @@ public class FightAi extends SpellAbilityAi {
|
|||||||
// todo: check min/max targets; see if we picked the best matchup
|
// todo: check min/max targets; see if we picked the best matchup
|
||||||
sa.getTargets().add(humanCreature);
|
sa.getTargets().add(humanCreature);
|
||||||
sa.getTargets().add(aiCreature);
|
sa.getTargets().add(aiCreature);
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
for (Card creature1 : humCreatures) {
|
for (Card creature1 : humCreatures) {
|
||||||
for (Card creature2 : humCreatures) {
|
for (Card creature2 : humCreatures) {
|
||||||
@@ -97,42 +100,52 @@ public class FightAi extends SpellAbilityAi {
|
|||||||
// todo: check min/max targets; see if we picked the best matchup
|
// todo: check min/max targets; see if we picked the best matchup
|
||||||
sa.getTargets().add(creature1);
|
sa.getTargets().add(creature1);
|
||||||
sa.getTargets().add(creature2);
|
sa.getTargets().add(creature2);
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean chkAIDrawback(final SpellAbility sa, final Player aiPlayer) {
|
public AiAbilityDecision chkDrawback(final SpellAbility sa, final Player aiPlayer) {
|
||||||
if ("Always".equals(sa.getParam("AILogic"))) {
|
if ("Always".equals(sa.getParam("AILogic"))) {
|
||||||
return true; // e.g. Hunt the Weak, the AI logic was already checked through canFightAi
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay); // e.g. Hunt the Weak, the AI logic was already checked through canFightAi
|
||||||
}
|
}
|
||||||
|
|
||||||
return checkApiLogic(aiPlayer, sa);
|
return checkApiLogic(aiPlayer, sa);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
protected AiAbilityDecision doTriggerNoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
final String aiLogic = sa.getParamOrDefault("AILogic", "");
|
final String aiLogic = sa.getParamOrDefault("AILogic", "");
|
||||||
if (aiLogic.equals("Grothama")) {
|
if (aiLogic.equals("Grothama")) {
|
||||||
return mandatory ? true : SpecialCardAi.GrothamaAllDevouring.consider(ai, sa);
|
if (mandatory) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (SpecialCardAi.GrothamaAllDevouring.consider(ai, sa)) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
} else {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (checkApiLogic(ai, sa)) {
|
AiAbilityDecision decision = checkApiLogic(ai, sa);
|
||||||
return true;
|
if (decision.willingToPlay()) {
|
||||||
|
return decision;
|
||||||
}
|
}
|
||||||
if (!mandatory) {
|
if (!mandatory) {
|
||||||
return false;
|
return decision;
|
||||||
}
|
}
|
||||||
|
// if mandatory, we have to play it, so we will try to make a good trade or no trade
|
||||||
|
|
||||||
//try to make a good trade or no trade
|
//try to make a good trade or no trade
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
List<Card> humCreatures = ai.getOpponents().getCreaturesInPlay();
|
List<Card> humCreatures = ai.getOpponents().getCreaturesInPlay();
|
||||||
humCreatures = CardLists.getTargetableCards(humCreatures, sa);
|
humCreatures = CardLists.getTargetableCards(humCreatures, sa);
|
||||||
if (humCreatures.isEmpty()) {
|
if (humCreatures.isEmpty()) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
//assumes the triggered card belongs to the ai
|
//assumes the triggered card belongs to the ai
|
||||||
if (sa.hasParam("Defined")) {
|
if (sa.hasParam("Defined")) {
|
||||||
@@ -141,19 +154,19 @@ public class FightAi extends SpellAbilityAi {
|
|||||||
if (canKill(aiCreature, humanCreature, 0)
|
if (canKill(aiCreature, humanCreature, 0)
|
||||||
&& ComputerUtilCard.evaluateCreature(humanCreature) > ComputerUtilCard.evaluateCreature(aiCreature)) {
|
&& ComputerUtilCard.evaluateCreature(humanCreature) > ComputerUtilCard.evaluateCreature(aiCreature)) {
|
||||||
sa.getTargets().add(humanCreature);
|
sa.getTargets().add(humanCreature);
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.MandatoryPlay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (Card humanCreature : humCreatures) {
|
for (Card humanCreature : humCreatures) {
|
||||||
if (!canKill(humanCreature, aiCreature, 0)) {
|
if (!canKill(humanCreature, aiCreature, 0)) {
|
||||||
sa.getTargets().add(humanCreature);
|
sa.getTargets().add(humanCreature);
|
||||||
return true;
|
return new AiAbilityDecision(50, AiPlayDecision.MandatoryPlay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sa.getTargets().add(humCreatures.get(0));
|
sa.getTargets().add(humCreatures.get(0));
|
||||||
return true;
|
return new AiAbilityDecision(50, AiPlayDecision.MandatoryPlay);
|
||||||
}
|
}
|
||||||
return true;
|
return new AiAbilityDecision(50, AiPlayDecision.MandatoryPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -164,7 +177,7 @@ public class FightAi extends SpellAbilityAi {
|
|||||||
* @param power bonus to power
|
* @param power bonus to power
|
||||||
* @return true if fight effect should be played, false otherwise
|
* @return true if fight effect should be played, false otherwise
|
||||||
*/
|
*/
|
||||||
public static boolean canFightAi(final Player ai, final SpellAbility sa, int power, int toughness) {
|
public static AiAbilityDecision canFightAi(final Player ai, final SpellAbility sa, int power, int toughness) {
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
|
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
|
||||||
AbilitySub tgtFight = sa.getSubAbility();
|
AbilitySub tgtFight = sa.getSubAbility();
|
||||||
@@ -196,7 +209,7 @@ public class FightAi extends SpellAbilityAi {
|
|||||||
ComputerUtilCard.sortByEvaluateCreature(aiCreatures);
|
ComputerUtilCard.sortByEvaluateCreature(aiCreatures);
|
||||||
ComputerUtilCard.sortByEvaluateCreature(humCreatures);
|
ComputerUtilCard.sortByEvaluateCreature(humCreatures);
|
||||||
if (humCreatures.isEmpty() || aiCreatures.isEmpty()) {
|
if (humCreatures.isEmpty() || aiCreatures.isEmpty()) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.MissingNeededCards);
|
||||||
}
|
}
|
||||||
// Evaluate creature pairs
|
// Evaluate creature pairs
|
||||||
for (Card humanCreature : humCreatures) {
|
for (Card humanCreature : humCreatures) {
|
||||||
@@ -226,7 +239,7 @@ public class FightAi extends SpellAbilityAi {
|
|||||||
tgtFight.resetTargets();
|
tgtFight.resetTargets();
|
||||||
tgtFight.getTargets().add(humanCreature);
|
tgtFight.getTargets().add(humanCreature);
|
||||||
}
|
}
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Other cards that use AILogic PowerDmg and a single target
|
// Other cards that use AILogic PowerDmg and a single target
|
||||||
@@ -236,7 +249,7 @@ public class FightAi extends SpellAbilityAi {
|
|||||||
tgtFight.resetTargets();
|
tgtFight.resetTargets();
|
||||||
tgtFight.getTargets().add(humanCreature);
|
tgtFight.getTargets().add(humanCreature);
|
||||||
}
|
}
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -249,12 +262,12 @@ public class FightAi extends SpellAbilityAi {
|
|||||||
sa.getTargets().add(aiCreature);
|
sa.getTargets().add(aiCreature);
|
||||||
tgtFight.resetTargets();
|
tgtFight.resetTargets();
|
||||||
tgtFight.getTargets().add(humanCreature);
|
tgtFight.getTargets().add(humanCreature);
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
import forge.ai.AiAbilityDecision;
|
||||||
|
import forge.ai.AiPlayDecision;
|
||||||
import forge.ai.ComputerUtil;
|
import forge.ai.ComputerUtil;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
@@ -13,52 +15,56 @@ public class FlipACoinAi extends SpellAbilityAi {
|
|||||||
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
|
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
protected AiAbilityDecision canPlay(Player ai, SpellAbility sa) {
|
||||||
if (sa.hasParam("AILogic")) {
|
if (sa.hasParam("AILogic")) {
|
||||||
String ailogic = sa.getParam("AILogic");
|
String ailogic = sa.getParam("AILogic");
|
||||||
if (ailogic.equals("Never")) {
|
if (ailogic.equals("Never")) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
} else if (ailogic.equals("PhaseOut")) {
|
} else if (ailogic.equals("PhaseOut")) {
|
||||||
if (!ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), sa).contains(sa.getHostCard())) {
|
if (!ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), sa).contains(sa.getHostCard())) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
} else if (ailogic.equals("Bangchuckers")) {
|
} else if (ailogic.equals("Bangchuckers")) {
|
||||||
if (ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.END_OF_TURN) ) {
|
if (ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.END_OF_TURN) ) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
for (Player o : ai.getOpponents()) {
|
for (Player o : ai.getOpponents()) {
|
||||||
if (sa.canTarget(o) && o.canLoseLife() && !o.cantLoseForZeroOrLessLife()) {
|
if (sa.canTarget(o) && o.canLoseLife() && !o.cantLoseForZeroOrLessLife()) {
|
||||||
sa.getTargets().add(o);
|
sa.getTargets().add(o);
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (Card c : ai.getOpponents().getCreaturesInPlay()) {
|
for (Card c : ai.getOpponents().getCreaturesInPlay()) {
|
||||||
if (sa.canTarget(c)) {
|
if (sa.canTarget(c)) {
|
||||||
sa.getTargets().add(c);
|
sa.getTargets().add(c);
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
} else if (ailogic.equals("KillOrcs")) {
|
} else if (ailogic.equals("KillOrcs")) {
|
||||||
if (ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.END_OF_TURN) ) {
|
if (ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.END_OF_TURN) ) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
for (Card c : ai.getOpponents().getCreaturesInPlay()) {
|
for (Card c : ai.getOpponents().getCreaturesInPlay()) {
|
||||||
if (sa.canTarget(c)) {
|
if (sa.canTarget(c)) {
|
||||||
sa.getTargets().add(c);
|
sa.getTargets().add(c);
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return sa.isTargetNumberValid();
|
if (sa.isTargetNumberValid()) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
} else {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
public AiAbilityDecision chkDrawback(SpellAbility sa, Player ai) {
|
||||||
return canPlayAI(ai, sa);
|
return canPlay(ai, sa);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
import forge.ai.AiAbilityDecision;
|
||||||
|
import forge.ai.AiPlayDecision;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.card.CardCollectionView;
|
import forge.game.card.CardCollectionView;
|
||||||
import forge.game.card.CardLists;
|
import forge.game.card.CardLists;
|
||||||
@@ -14,26 +16,43 @@ import java.util.Map;
|
|||||||
|
|
||||||
public class FlipOntoBattlefieldAi extends SpellAbilityAi {
|
public class FlipOntoBattlefieldAi extends SpellAbilityAi {
|
||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
protected AiAbilityDecision canPlay(Player aiPlayer, SpellAbility sa) {
|
||||||
PhaseHandler ph = sa.getHostCard().getGame().getPhaseHandler();
|
PhaseHandler ph = sa.getHostCard().getGame().getPhaseHandler();
|
||||||
String logic = sa.getParamOrDefault("AILogic", "");
|
String logic = sa.getParamOrDefault("AILogic", "");
|
||||||
|
|
||||||
if (!isSorcerySpeed(sa, aiPlayer) && sa.getPayCosts().hasManaCost()) {
|
if (!isSorcerySpeed(sa, aiPlayer) && sa.getPayCosts().hasManaCost()) {
|
||||||
return ph.is(PhaseType.END_OF_TURN);
|
if (ph.is(PhaseType.END_OF_TURN)) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
} else {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.WaitForEndOfTurn);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ("DamageCreatures".equals(logic)) {
|
if ("DamageCreatures".equals(logic)) {
|
||||||
int maxToughness = Integer.parseInt(sa.getSubAbility().getParam("NumDmg"));
|
int maxToughness = Integer.parseInt(sa.getSubAbility().getParam("NumDmg"));
|
||||||
CardCollectionView rightToughness = CardLists.filter(aiPlayer.getOpponents().getCreaturesInPlay(), card -> card.getNetToughness() <= maxToughness && card.canBeDestroyed());
|
CardCollectionView rightToughness = CardLists.filter(aiPlayer.getOpponents().getCreaturesInPlay(), card -> card.getNetToughness() <= maxToughness && card.canBeDestroyed());
|
||||||
return !rightToughness.isEmpty();
|
|
||||||
|
if (rightToughness.isEmpty()) {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.MissingNeededCards);
|
||||||
|
} else {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return !aiPlayer.getOpponents().getCardsIn(ZoneType.Battlefield).isEmpty();
|
if (!aiPlayer.getOpponents().getCardsIn(ZoneType.Battlefield).isEmpty()) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
} else {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
protected AiAbilityDecision doTriggerNoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
||||||
return canPlayAI(aiPlayer, sa) || mandatory;
|
if (mandatory) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
}
|
||||||
|
|
||||||
|
return canPlay(aiPlayer, sa);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -22,36 +22,36 @@ public class FogAi extends SpellAbilityAi {
|
|||||||
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
|
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
protected AiAbilityDecision canPlay(Player ai, SpellAbility sa) {
|
||||||
final Game game = ai.getGame();
|
final Game game = ai.getGame();
|
||||||
final Card hostCard = sa.getHostCard();
|
final Card hostCard = sa.getHostCard();
|
||||||
final Combat combat = game.getCombat();
|
final Combat combat = game.getCombat();
|
||||||
|
|
||||||
// Don't cast it, if the effect is already in place
|
// Don't cast it, if the effect is already in place
|
||||||
if (game.getReplacementHandler().isPreventCombatDamageThisTurn()) {
|
if (game.getReplacementHandler().isPreventCombatDamageThisTurn()) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO Test if we can even Fog successfully
|
// TODO Test if we can even Fog successfully
|
||||||
if (handleMemoryCheck(ai, sa)) {
|
if (handleMemoryCheck(ai, sa)) {
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only cast when Stack is empty, so Human uses spells/abilities first
|
// Only cast when Stack is empty, so Human uses spells/abilities first
|
||||||
if (!game.getStack().isEmpty()) {
|
if (!game.getStack().isEmpty()) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO Only cast outside of combat if I won't be able to cast inside of combat
|
// TODO Only cast outside of combat if I won't be able to cast inside of combat
|
||||||
if (combat == null) {
|
if (combat == null) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
// AI should only activate this during Opponents Declare Blockers phase
|
// AI should only activate this during Opponents Declare Blockers phase
|
||||||
if (!game.getPhaseHandler().getPlayerTurn().isOpponentOf(ai) ||
|
if (!game.getPhaseHandler().getPlayerTurn().isOpponentOf(ai) ||
|
||||||
!game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
!game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
||||||
// TODO Be careful of effects that don't let you cast spells during combat
|
// TODO Be careful of effects that don't let you cast spells during combat
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
int remainingLife = ComputerUtilCombat.lifeThatWouldRemain(ai, combat);
|
int remainingLife = ComputerUtilCombat.lifeThatWouldRemain(ai, combat);
|
||||||
@@ -61,28 +61,32 @@ public class FogAi extends SpellAbilityAi {
|
|||||||
int fogs = countAvailableFogs(ai);
|
int fogs = countAvailableFogs(ai);
|
||||||
if (fogs > 2 && dmg > 2) {
|
if (fogs > 2 && dmg > 2) {
|
||||||
// Playing a fog deck. If you got them play them.
|
// Playing a fog deck. If you got them play them.
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.Tempo);
|
||||||
}
|
}
|
||||||
if (dmg > 2 &&
|
if (dmg > 2 &&
|
||||||
hostCard.hasKeyword(Keyword.BUYBACK) &&
|
hostCard.hasKeyword(Keyword.BUYBACK) &&
|
||||||
CardLists.count(ai.getCardsIn(ZoneType.Battlefield), Card::isLand) > 3) {
|
CardLists.count(ai.getCardsIn(ZoneType.Battlefield), Card::isLand) > 3) {
|
||||||
// Constant mists sacrifices a land to buyback. But if AI is running it, they are probably ok sacrificing some lands
|
// Constant mists sacrifices a land to buyback. But if AI is running it, they are probably ok sacrificing some lands
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.Tempo);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ("SeriousDamage".equals(sa.getParam("AILogic"))) {
|
if ("SeriousDamage".equals(sa.getParam("AILogic"))) {
|
||||||
if (dmg > ai.getLife() / 4) {
|
if (dmg > ai.getLife() / 4) {
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.Tempo);
|
||||||
} else if (dmg >= 5) {
|
} else if (dmg >= 5) {
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.Tempo);
|
||||||
} else if (ai.getLife() < ai.getStartingLife() / 3) {
|
} else if (ai.getLife() < ai.getStartingLife() / 3) {
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.Tempo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// TODO Compare to poison counters?
|
// TODO Compare to poison counters?
|
||||||
|
|
||||||
// Cast it if life is in danger
|
// Cast it if life is in danger
|
||||||
return ComputerUtilCombat.lifeInDanger(ai, game.getCombat());
|
if (ComputerUtilCombat.lifeInDanger(ai, game.getCombat())) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.Tempo);
|
||||||
|
} else {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean handleMemoryCheck(Player ai, SpellAbility sa) {
|
private boolean handleMemoryCheck(Player ai, SpellAbility sa) {
|
||||||
@@ -137,7 +141,7 @@ public class FogAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
public AiAbilityDecision chkDrawback(SpellAbility sa, Player ai) {
|
||||||
// AI should only activate this during Human's turn
|
// AI should only activate this during Human's turn
|
||||||
boolean chance;
|
boolean chance;
|
||||||
final Game game = ai.getGame();
|
final Game game = ai.getGame();
|
||||||
@@ -149,11 +153,15 @@ public class FogAi extends SpellAbilityAi {
|
|||||||
chance = game.getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DAMAGE);
|
chance = game.getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DAMAGE);
|
||||||
}
|
}
|
||||||
|
|
||||||
return chance;
|
if (chance) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
protected AiAbilityDecision doTriggerNoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
||||||
final Game game = aiPlayer.getGame();
|
final Game game = aiPlayer.getGame();
|
||||||
boolean chance;
|
boolean chance;
|
||||||
if (game.getPhaseHandler().isPlayerTurn(sa.getActivatingPlayer().getWeakestOpponent())) {
|
if (game.getPhaseHandler().isPlayerTurn(sa.getActivatingPlayer().getWeakestOpponent())) {
|
||||||
@@ -162,6 +170,10 @@ public class FogAi extends SpellAbilityAi {
|
|||||||
chance = game.getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DAMAGE);
|
chance = game.getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DAMAGE);
|
||||||
}
|
}
|
||||||
|
|
||||||
return chance || mandatory;
|
if (mandatory || chance) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,17 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
import forge.ai.AiAbilityDecision;
|
||||||
|
import forge.ai.AiPlayDecision;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
|
|
||||||
public class GameLossAi extends SpellAbilityAi {
|
public class GameLossAi extends SpellAbilityAi {
|
||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
protected AiAbilityDecision canPlay(Player ai, SpellAbility sa) {
|
||||||
final Player opp = ai.getStrongestOpponent();
|
final Player opp = ai.getStrongestOpponent();
|
||||||
if (opp.cantLose()) {
|
if (opp.cantLose()) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only one SA Lose the Game card right now, which is Door to Nothingness
|
// Only one SA Lose the Game card right now, which is Door to Nothingness
|
||||||
@@ -17,14 +19,14 @@ public class GameLossAi extends SpellAbilityAi {
|
|||||||
if (sa.usesTargeting() && sa.canTarget(opp)) {
|
if (sa.usesTargeting() && sa.canTarget(opp)) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
sa.getTargets().add(opp);
|
sa.getTargets().add(opp);
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
protected AiAbilityDecision doTriggerNoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
Player loser = ai;
|
Player loser = ai;
|
||||||
|
|
||||||
// Phage the Untouchable
|
// Phage the Untouchable
|
||||||
@@ -33,7 +35,7 @@ public class GameLossAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!mandatory && (loser == ai || loser.cantLose())) {
|
if (!mandatory && (loser == ai || loser.cantLose())) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sa.usesTargeting() && sa.canTarget(loser)) {
|
if (sa.usesTargeting() && sa.canTarget(loser)) {
|
||||||
@@ -41,6 +43,6 @@ public class GameLossAi extends SpellAbilityAi {
|
|||||||
sa.getTargets().add(loser);
|
sa.getTargets().add(loser);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
|
||||||
|
import forge.ai.AiAbilityDecision;
|
||||||
|
import forge.ai.AiPlayDecision;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
@@ -10,20 +12,25 @@ public class GameWinAi extends SpellAbilityAi {
|
|||||||
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
|
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
protected AiAbilityDecision canPlay(Player ai, SpellAbility sa) {
|
||||||
return !ai.cantWin();
|
if (ai.cantWin()) {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
|
}
|
||||||
|
// If the AI can win the game, it should play this ability.
|
||||||
|
// This is a special case where the AI should always play the ability if it can win.
|
||||||
|
|
||||||
// TODO Check conditions are met on card (e.g. Coalition Victory)
|
// TODO Check conditions are met on card (e.g. Coalition Victory)
|
||||||
|
|
||||||
// TODO Consider likelihood of SA getting countered
|
// TODO Consider likelihood of SA getting countered
|
||||||
|
|
||||||
|
return new AiAbilityDecision(10000, AiPlayDecision.WillPlay);
|
||||||
// In general, don't return true.
|
// In general, don't return true.
|
||||||
// But this card wins the game, I can make an exception for that
|
// But this card wins the game, I can make an exception for that
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
protected AiAbilityDecision doTriggerNoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
import forge.ai.ComputerUtilCard;
|
import forge.ai.*;
|
||||||
import forge.ai.ComputerUtilCombat;
|
|
||||||
import forge.ai.SpellAbilityAi;
|
|
||||||
import forge.game.Game;
|
import forge.game.Game;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
import forge.game.card.CardCollection;
|
import forge.game.card.CardCollection;
|
||||||
@@ -16,7 +14,7 @@ import java.util.List;
|
|||||||
public class GoadAi extends SpellAbilityAi {
|
public class GoadAi extends SpellAbilityAi {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
|
protected AiAbilityDecision checkApiLogic(final Player ai, final SpellAbility sa) {
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
final Game game = source.getGame();
|
final Game game = source.getGame();
|
||||||
|
|
||||||
@@ -26,7 +24,7 @@ public class GoadAi extends SpellAbilityAi {
|
|||||||
List<Card> list = CardLists.getTargetableCards(game.getCardsIn(ZoneType.Battlefield), sa);
|
List<Card> list = CardLists.getTargetableCards(game.getCardsIn(ZoneType.Battlefield), sa);
|
||||||
|
|
||||||
if (list.isEmpty())
|
if (list.isEmpty())
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
|
|
||||||
if (game.getPlayers().size() > 2) {
|
if (game.getPlayers().size() > 2) {
|
||||||
// use this part only in multiplayer
|
// use this part only in multiplayer
|
||||||
@@ -52,7 +50,7 @@ public class GoadAi extends SpellAbilityAi {
|
|||||||
if (!betterList.isEmpty()) {
|
if (!betterList.isEmpty()) {
|
||||||
list = betterList;
|
list = betterList;
|
||||||
sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(list));
|
sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(list));
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// single Player, goaded creature would attack ai
|
// single Player, goaded creature would attack ai
|
||||||
@@ -73,49 +71,52 @@ public class GoadAi extends SpellAbilityAi {
|
|||||||
if (!betterList.isEmpty()) {
|
if (!betterList.isEmpty()) {
|
||||||
list = betterList;
|
list = betterList;
|
||||||
sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(list));
|
sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(list));
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// AI does not find a good creature to goad.
|
// AI does not find a good creature to goad.
|
||||||
// because if it would goad a creature it would attack AI.
|
// because if it would goad a creature it would attack AI.
|
||||||
// AI might not have enough information to block it
|
// AI might not have enough information to block it
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
return true;
|
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
protected AiAbilityDecision doTriggerNoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
if (checkApiLogic(ai, sa)) {
|
AiAbilityDecision decision = checkApiLogic(ai, sa);
|
||||||
return true;
|
if (decision.willingToPlay()) {
|
||||||
|
return decision;
|
||||||
}
|
}
|
||||||
if (!mandatory) {
|
if (!mandatory) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
// mandatory play, so we have to play it
|
||||||
if (sa.usesTargeting()) {
|
if (sa.usesTargeting()) {
|
||||||
if (sa.getTargetRestrictions().canTgtPlayer()) {
|
if (sa.getTargetRestrictions().canTgtPlayer()) {
|
||||||
for (Player opp : ai.getOpponents()) {
|
for (Player opp : ai.getOpponents()) {
|
||||||
if (sa.canTarget(opp)) {
|
if (sa.canTarget(opp)) {
|
||||||
sa.getTargets().add(opp);
|
sa.getTargets().add(opp);
|
||||||
return true;
|
return new AiAbilityDecision(50, AiPlayDecision.MandatoryPlay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (sa.canTarget(ai)) {
|
if (sa.canTarget(ai)) {
|
||||||
sa.getTargets().add(ai);
|
sa.getTargets().add(ai);
|
||||||
return true;
|
return new AiAbilityDecision(50, AiPlayDecision.MandatoryPlay);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
List<Card> list = CardLists.getTargetableCards(ai.getGame().getCardsIn(ZoneType.Battlefield), sa);
|
List<Card> list = CardLists.getTargetableCards(ai.getGame().getCardsIn(ZoneType.Battlefield), sa);
|
||||||
|
|
||||||
if (list.isEmpty())
|
if (list.isEmpty())
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
|
|
||||||
sa.getTargets().add(ComputerUtilCard.getWorstCreatureAI(list));
|
sa.getTargets().add(ComputerUtilCard.getWorstCreatureAI(list));
|
||||||
return true;
|
return new AiAbilityDecision(30, AiPlayDecision.MandatoryPlay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
import forge.ai.AiAbilityDecision;
|
||||||
|
import forge.ai.AiPlayDecision;
|
||||||
import forge.ai.ComputerUtilCard;
|
import forge.ai.ComputerUtilCard;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.Game;
|
import forge.game.Game;
|
||||||
@@ -15,7 +17,7 @@ import java.util.List;
|
|||||||
public class HauntAi extends SpellAbilityAi {
|
public class HauntAi extends SpellAbilityAi {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
protected AiAbilityDecision doTriggerNoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
final Card card = sa.getHostCard();
|
final Card card = sa.getHostCard();
|
||||||
final Game game = ai.getGame();
|
final Game game = ai.getGame();
|
||||||
if (sa.usesTargeting() && !card.isToken()) {
|
if (sa.usesTargeting() && !card.isToken()) {
|
||||||
@@ -24,12 +26,12 @@ public class HauntAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
// nothing to haunt
|
// nothing to haunt
|
||||||
if (creats.isEmpty()) {
|
if (creats.isEmpty()) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
final List<Card> oppCreats = CardLists.filterControlledBy(creats, ai.getOpponents());
|
final List<Card> oppCreats = CardLists.filterControlledBy(creats, ai.getOpponents());
|
||||||
sa.getTargets().add(ComputerUtilCard.getWorstCreatureAI(oppCreats.isEmpty() ? creats : oppCreats));
|
sa.getTargets().add(ComputerUtilCard.getWorstCreatureAI(oppCreats.isEmpty() ? creats : oppCreats));
|
||||||
}
|
}
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -10,60 +10,65 @@ public class ImmediateTriggerAi extends SpellAbilityAi {
|
|||||||
// TODO: this class is largely reused from DelayedTriggerAi, consider updating
|
// TODO: this class is largely reused from DelayedTriggerAi, consider updating
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
public AiAbilityDecision chkDrawback(SpellAbility sa, Player ai) {
|
||||||
String logic = sa.getParamOrDefault("AILogic", "");
|
String logic = sa.getParamOrDefault("AILogic", "");
|
||||||
if (logic.equals("Always")) {
|
if (logic.equals("Always")) {
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
SpellAbility trigsa = sa.getAdditionalAbility("Execute");
|
SpellAbility trigsa = sa.getAdditionalAbility("Execute");
|
||||||
if (trigsa == null) {
|
if (trigsa == null) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
trigsa.setActivatingPlayer(ai);
|
trigsa.setActivatingPlayer(ai);
|
||||||
|
|
||||||
if (trigsa instanceof AbilitySub) {
|
if (trigsa instanceof AbilitySub) {
|
||||||
return SpellApiToAi.Converter.get(trigsa).chkDrawbackWithSubs(ai, (AbilitySub)trigsa);
|
return SpellApiToAi.Converter.get(trigsa).chkDrawbackWithSubs(ai, (AbilitySub)trigsa);
|
||||||
} else {
|
|
||||||
return AiPlayDecision.WillPlay == ((PlayerControllerAi)ai.getController()).getAi().canPlaySa(trigsa);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AiPlayDecision decision = ((PlayerControllerAi)ai.getController()).getAi().canPlaySa(trigsa);
|
||||||
|
if (decision == AiPlayDecision.WillPlay) {
|
||||||
|
return new AiAbilityDecision(100, decision);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
protected AiAbilityDecision doTriggerNoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
// always add to stack, targeting happens after payment
|
// always add to stack, targeting happens after payment
|
||||||
if (mandatory) {
|
if (mandatory) {
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
SpellAbility trigsa = sa.getAdditionalAbility("Execute");
|
SpellAbility trigsa = sa.getAdditionalAbility("Execute");
|
||||||
if (trigsa == null) {
|
if (trigsa == null) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
AiController aic = ((PlayerControllerAi)ai.getController()).getAi();
|
AiController aic = ((PlayerControllerAi)ai.getController()).getAi();
|
||||||
trigsa.setActivatingPlayer(ai);
|
trigsa.setActivatingPlayer(ai);
|
||||||
|
|
||||||
return aic.doTrigger(trigsa, !"You".equals(sa.getParamOrDefault("OptionalDecider", "You")));
|
return aic.doTrigger(trigsa, !"You".equals(sa.getParamOrDefault("OptionalDecider", "You"))) ? new AiAbilityDecision(100, AiPlayDecision.WillPlay) : new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
protected AiAbilityDecision canPlay(Player ai, SpellAbility sa) {
|
||||||
String logic = sa.getParamOrDefault("AILogic", "");
|
String logic = sa.getParamOrDefault("AILogic", "");
|
||||||
if (logic.equals("Always")) {
|
if (logic.equals("Always")) {
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
SpellAbility trigsa = sa.getAdditionalAbility("Execute");
|
SpellAbility trigsa = sa.getAdditionalAbility("Execute");
|
||||||
if (trigsa == null) {
|
if (trigsa == null) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (logic.equals("WeakerCreature")) {
|
if (logic.equals("WeakerCreature")) {
|
||||||
Card ownCreature = ComputerUtilCard.getWorstCreatureAI(ai.getCreaturesInPlay());
|
Card ownCreature = ComputerUtilCard.getWorstCreatureAI(ai.getCreaturesInPlay());
|
||||||
if (ownCreature == null) {
|
if (ownCreature == null) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
int eval = ComputerUtilCard.evaluateCreature(ownCreature);
|
int eval = ComputerUtilCard.evaluateCreature(ownCreature);
|
||||||
@@ -75,12 +80,12 @@ public class ImmediateTriggerAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!foundWorse) {
|
if (!foundWorse) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
trigsa.setActivatingPlayer(ai);
|
trigsa.setActivatingPlayer(ai);
|
||||||
return AiPlayDecision.WillPlay == ((PlayerControllerAi)ai.getController()).getAi().canPlaySa(trigsa);
|
return ((PlayerControllerAi)ai.getController()).getAi().canPlaySa(trigsa) == AiPlayDecision.WillPlay ? new AiAbilityDecision(100, AiPlayDecision.WillPlay) : new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user